davanstrien HF Staff commited on
Commit
03c6ad0
·
verified ·
0 Parent(s):

Squashing commit

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +70 -0
  2. AI-Design-Patterns-for-GLAM.pdf +3 -0
  3. README.md +10 -0
  4. assets/vllm-structured-generation/indexes/DSC00168.JPG +3 -0
  5. assets/vllm-structured-generation/indexes/DSC00169.JPG +3 -0
  6. assets/vllm-structured-generation/indexes/DSC00170.JPG +3 -0
  7. assets/vllm-structured-generation/indexes/DSC00171.JPG +3 -0
  8. assets/vllm-structured-generation/indexes/DSC00172.JPG +3 -0
  9. assets/vllm-structured-generation/indexes/DSC00173.JPG +3 -0
  10. assets/workshop-slides/quickfix.jpg +0 -0
  11. index.html +679 -0
  12. patterns/structured-generation/advisor-index-cards.html +0 -0
  13. patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-1.png +3 -0
  14. patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-2.png +3 -0
  15. patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-3.png +3 -0
  16. patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-4.png +3 -0
  17. patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-5.png +3 -0
  18. patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-6.png +3 -0
  19. patterns/structured-generation/advisor-index-cards_files/figure-html/cell-16-output-2.png +3 -0
  20. patterns/structured-generation/advisor-index-cards_files/figure-html/cell-2-output-1.png +3 -0
  21. patterns/structured-generation/advisor-index-cards_files/figure-html/cell-9-output-1.png +3 -0
  22. patterns/structured-generation/intro.html +647 -0
  23. patterns/structured-generation/vlm-structured-generation.html +0 -0
  24. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-12-output-1.png +3 -0
  25. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-13-output-1.png +3 -0
  26. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-14-output-1.png +3 -0
  27. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-15-output-1.png +3 -0
  28. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-16-output-1.png +3 -0
  29. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-17-output-1.png +3 -0
  30. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-18-output-1.png +3 -0
  31. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-19-output-1.png +3 -0
  32. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-20-output-1.png +3 -0
  33. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-21-output-1.png +3 -0
  34. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-22-output-1.png +3 -0
  35. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-24-output-1.png +3 -0
  36. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-25-output-1.png +3 -0
  37. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-26-output-1.png +3 -0
  38. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-27-output-1.png +3 -0
  39. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-3-output-1.png +3 -0
  40. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-4-output-1.png +3 -0
  41. patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-5-output-1.png +3 -0
  42. patterns/what-is-an-ai-pattern.html +620 -0
  43. search.json +263 -0
  44. site_libs/bootstrap/bootstrap-27c261d06b905028a18691de25d09dde.min.css +0 -0
  45. site_libs/bootstrap/bootstrap-icons.css +0 -0
  46. site_libs/bootstrap/bootstrap-icons.woff +3 -0
  47. site_libs/bootstrap/bootstrap.min.js +7 -0
  48. site_libs/clipboard/clipboard.min.js +7 -0
  49. site_libs/quarto-html/anchor.min.js +9 -0
  50. site_libs/quarto-html/axe/axe-check.js +145 -0
.gitattributes ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ AI-Design-Patterns-for-GLAM.pdf filter=lfs diff=lfs merge=lfs -text
37
+ patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-1.png filter=lfs diff=lfs merge=lfs -text
38
+ patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-2.png filter=lfs diff=lfs merge=lfs -text
39
+ patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-3.png filter=lfs diff=lfs merge=lfs -text
40
+ patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-4.png filter=lfs diff=lfs merge=lfs -text
41
+ patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-5.png filter=lfs diff=lfs merge=lfs -text
42
+ patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-6.png filter=lfs diff=lfs merge=lfs -text
43
+ patterns/structured-generation/advisor-index-cards_files/figure-html/cell-16-output-2.png filter=lfs diff=lfs merge=lfs -text
44
+ patterns/structured-generation/advisor-index-cards_files/figure-html/cell-2-output-1.png filter=lfs diff=lfs merge=lfs -text
45
+ patterns/structured-generation/advisor-index-cards_files/figure-html/cell-9-output-1.png filter=lfs diff=lfs merge=lfs -text
46
+ site_libs/bootstrap/bootstrap-icons.woff filter=lfs diff=lfs merge=lfs -text
47
+ assets/vllm-structured-generation/indexes/DSC00168.JPG filter=lfs diff=lfs merge=lfs -text
48
+ assets/vllm-structured-generation/indexes/DSC00169.JPG filter=lfs diff=lfs merge=lfs -text
49
+ assets/vllm-structured-generation/indexes/DSC00170.JPG filter=lfs diff=lfs merge=lfs -text
50
+ assets/vllm-structured-generation/indexes/DSC00171.JPG filter=lfs diff=lfs merge=lfs -text
51
+ assets/vllm-structured-generation/indexes/DSC00172.JPG filter=lfs diff=lfs merge=lfs -text
52
+ assets/vllm-structured-generation/indexes/DSC00173.JPG filter=lfs diff=lfs merge=lfs -text
53
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-12-output-1.png filter=lfs diff=lfs merge=lfs -text
54
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-13-output-1.png filter=lfs diff=lfs merge=lfs -text
55
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-14-output-1.png filter=lfs diff=lfs merge=lfs -text
56
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-15-output-1.png filter=lfs diff=lfs merge=lfs -text
57
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-16-output-1.png filter=lfs diff=lfs merge=lfs -text
58
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-17-output-1.png filter=lfs diff=lfs merge=lfs -text
59
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-18-output-1.png filter=lfs diff=lfs merge=lfs -text
60
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-19-output-1.png filter=lfs diff=lfs merge=lfs -text
61
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-20-output-1.png filter=lfs diff=lfs merge=lfs -text
62
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-21-output-1.png filter=lfs diff=lfs merge=lfs -text
63
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-22-output-1.png filter=lfs diff=lfs merge=lfs -text
64
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-24-output-1.png filter=lfs diff=lfs merge=lfs -text
65
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-25-output-1.png filter=lfs diff=lfs merge=lfs -text
66
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-26-output-1.png filter=lfs diff=lfs merge=lfs -text
67
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-27-output-1.png filter=lfs diff=lfs merge=lfs -text
68
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-3-output-1.png filter=lfs diff=lfs merge=lfs -text
69
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-4-output-1.png filter=lfs diff=lfs merge=lfs -text
70
+ patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-5-output-1.png filter=lfs diff=lfs merge=lfs -text
AI-Design-Patterns-for-GLAM.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f965a7eaa1cbdebbaf82e3375edb6f3f3b2c8e9c9e4f6895d4b25f0775bf91ca
3
+ size 30037725
README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Ai Patterns For Glam
3
+ emoji: 🌍
4
+ colorFrom: gray
5
+ colorTo: purple
6
+ sdk: static
7
+ pinned: false
8
+ ---
9
+
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
assets/vllm-structured-generation/indexes/DSC00168.JPG ADDED

Git LFS Details

  • SHA256: 6b67ba5fcdb404ff96cbafe3c9c80116acb1a37169bd4662fa3fb9b3772894af
  • Pointer size: 132 Bytes
  • Size of remote file: 5.44 MB
assets/vllm-structured-generation/indexes/DSC00169.JPG ADDED

Git LFS Details

  • SHA256: cf9524fba435cd302f0964f938987a531d53b87824b8bc3df49d9287f6a62d6e
  • Pointer size: 132 Bytes
  • Size of remote file: 5.39 MB
assets/vllm-structured-generation/indexes/DSC00170.JPG ADDED

Git LFS Details

  • SHA256: 3eb8696f091bb4af5fe1b479f4ac137d9b135ad146775e5e35e0307580c32d46
  • Pointer size: 132 Bytes
  • Size of remote file: 5.44 MB
assets/vllm-structured-generation/indexes/DSC00171.JPG ADDED

Git LFS Details

  • SHA256: bd65e3cbbf1d652eae2e050dd529f2d8044766c7c1332cc6871166ab65784a8c
  • Pointer size: 132 Bytes
  • Size of remote file: 5.44 MB
assets/vllm-structured-generation/indexes/DSC00172.JPG ADDED

Git LFS Details

  • SHA256: 964a292b92c851addddd1c837a5a9ed78ff131863c09903b25edc7c0efd50ffa
  • Pointer size: 131 Bytes
  • Size of remote file: 164 kB
assets/vllm-structured-generation/indexes/DSC00173.JPG ADDED

Git LFS Details

  • SHA256: 5a611f14635fcde06f703ddf2d30a52a8d069fa7cb9f0bb6412cf7e31964e5de
  • Pointer size: 132 Bytes
  • Size of remote file: 5.42 MB
assets/workshop-slides/quickfix.jpg ADDED
index.html ADDED
@@ -0,0 +1,679 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>
3
+
4
+ <meta charset="utf-8">
5
+ <meta name="generator" content="quarto-1.8.25">
6
+
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
8
+
9
+ <meta name="author" content="Daniel van Strien">
10
+ <meta name="dcterms.date" content="2025-10-27">
11
+
12
+ <title>AI Design Patterns for GLAM</title>
13
+ <style>
14
+ code{white-space: pre-wrap;}
15
+ span.smallcaps{font-variant: small-caps;}
16
+ div.columns{display: flex; gap: min(4vw, 1.5em);}
17
+ div.column{flex: auto; overflow-x: auto;}
18
+ div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
19
+ ul.task-list{list-style: none;}
20
+ ul.task-list li input[type="checkbox"] {
21
+ width: 0.8em;
22
+ margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */
23
+ vertical-align: middle;
24
+ }
25
+ </style>
26
+
27
+
28
+ <script src="site_libs/quarto-nav/quarto-nav.js"></script>
29
+ <script src="site_libs/quarto-nav/headroom.min.js"></script>
30
+ <script src="site_libs/clipboard/clipboard.min.js"></script>
31
+ <script src="site_libs/quarto-search/autocomplete.umd.js"></script>
32
+ <script src="site_libs/quarto-search/fuse.min.js"></script>
33
+ <script src="site_libs/quarto-search/quarto-search.js"></script>
34
+ <meta name="quarto:offset" content="./">
35
+ <link href="./patterns/what-is-an-ai-pattern.html" rel="next">
36
+ <script src="site_libs/quarto-html/quarto.js" type="module"></script>
37
+ <script src="site_libs/quarto-html/tabsets/tabsets.js" type="module"></script>
38
+ <script src="site_libs/quarto-html/axe/axe-check.js" type="module"></script>
39
+ <script src="site_libs/quarto-html/popper.min.js"></script>
40
+ <script src="site_libs/quarto-html/tippy.umd.min.js"></script>
41
+ <script src="site_libs/quarto-html/anchor.min.js"></script>
42
+ <link href="site_libs/quarto-html/tippy.css" rel="stylesheet">
43
+ <link href="site_libs/quarto-html/quarto-syntax-highlighting-7b89279ff1a6dce999919e0e67d4d9ec.css" rel="stylesheet" id="quarto-text-highlighting-styles">
44
+ <script src="site_libs/bootstrap/bootstrap.min.js"></script>
45
+ <link href="site_libs/bootstrap/bootstrap-icons.css" rel="stylesheet">
46
+ <link href="site_libs/bootstrap/bootstrap-27c261d06b905028a18691de25d09dde.min.css" rel="stylesheet" append-hash="true" id="quarto-bootstrap" data-mode="light">
47
+ <script id="quarto-search-options" type="application/json">{
48
+ "location": "sidebar",
49
+ "copy-button": false,
50
+ "collapse-after": 3,
51
+ "panel-placement": "start",
52
+ "type": "textbox",
53
+ "limit": 50,
54
+ "keyboard-shortcut": [
55
+ "f",
56
+ "/",
57
+ "s"
58
+ ],
59
+ "show-item-context": false,
60
+ "language": {
61
+ "search-no-results-text": "No results",
62
+ "search-matching-documents-text": "matching documents",
63
+ "search-copy-link-title": "Copy link to search",
64
+ "search-hide-matches-text": "Hide additional matches",
65
+ "search-more-match-text": "more match in this document",
66
+ "search-more-matches-text": "more matches in this document",
67
+ "search-clear-button-title": "Clear",
68
+ "search-text-placeholder": "",
69
+ "search-detached-cancel-button-title": "Cancel",
70
+ "search-submit-button-title": "Submit",
71
+ "search-label": "Search"
72
+ }
73
+ }</script>
74
+
75
+
76
+ </head>
77
+
78
+ <body class="nav-sidebar floating quarto-light">
79
+
80
+ <div id="quarto-search-results"></div>
81
+ <header id="quarto-header" class="headroom fixed-top">
82
+ <nav class="quarto-secondary-nav">
83
+ <div class="container-fluid d-flex">
84
+ <button type="button" class="quarto-btn-toggle btn" data-bs-toggle="collapse" role="button" data-bs-target=".quarto-sidebar-collapse-item" aria-controls="quarto-sidebar" aria-expanded="false" aria-label="Toggle sidebar navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
85
+ <i class="bi bi-layout-text-sidebar-reverse"></i>
86
+ </button>
87
+ <nav class="quarto-page-breadcrumbs" aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="./index.html">Welcome</a></li></ol></nav>
88
+ <a class="flex-grow-1" role="navigation" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item" aria-controls="quarto-sidebar" aria-expanded="false" aria-label="Toggle sidebar navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
89
+ </a>
90
+ <button type="button" class="btn quarto-search-button" aria-label="Search" onclick="window.quartoOpenSearch();">
91
+ <i class="bi bi-search"></i>
92
+ </button>
93
+ </div>
94
+ </nav>
95
+ </header>
96
+ <!-- content -->
97
+ <div id="quarto-content" class="quarto-container page-columns page-rows-contents page-layout-article">
98
+ <!-- sidebar -->
99
+ <nav id="quarto-sidebar" class="sidebar collapse collapse-horizontal quarto-sidebar-collapse-item sidebar-navigation floating overflow-auto">
100
+ <div class="pt-lg-2 mt-2 text-left sidebar-header sidebar-header-stacked">
101
+ <a href="./index.html" class="sidebar-logo-link">
102
+ </a>
103
+ <div class="sidebar-title mb-0 py-0">
104
+ <a href="./">AI Design Patterns for GLAM</a>
105
+ </div>
106
+ </div>
107
+ <div class="mt-2 flex-shrink-0 align-items-center">
108
+ <div class="sidebar-search">
109
+ <div id="quarto-search" class="" title="Search"></div>
110
+ </div>
111
+ </div>
112
+ <div class="sidebar-menu-container">
113
+ <ul class="list-unstyled mt-1">
114
+ <li class="sidebar-item">
115
+ <div class="sidebar-item-container">
116
+ <a href="./index.html" class="sidebar-item-text sidebar-link active">
117
+ <span class="menu-text">Welcome</span></a>
118
+ </div>
119
+ </li>
120
+ <li class="sidebar-item sidebar-item-section">
121
+ <span class="sidebar-item-text sidebar-link text-start">
122
+ <span class="menu-text">Beyond Chat Interfaces to Collections?</span></span>
123
+ </li>
124
+ <li class="sidebar-item sidebar-item-section">
125
+ <div class="sidebar-item-container">
126
+ <a class="sidebar-item-text sidebar-link text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-2" role="navigation" aria-expanded="true">
127
+ <span class="menu-text">Design Patterns</span></a>
128
+ <a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-2" role="navigation" aria-expanded="true" aria-label="Toggle section">
129
+ <i class="bi bi-chevron-right ms-2"></i>
130
+ </a>
131
+ </div>
132
+ <ul id="quarto-sidebar-section-2" class="collapse list-unstyled sidebar-section depth1 show">
133
+ <li class="sidebar-item">
134
+ <div class="sidebar-item-container">
135
+ <a href="./patterns/what-is-an-ai-pattern.html" class="sidebar-item-text sidebar-link">
136
+ <span class="menu-text"><span class="chapter-number">1</span>&nbsp; <span class="chapter-title">What is an AI Pattern?</span></span></a>
137
+ </div>
138
+ </li>
139
+ </ul>
140
+ </li>
141
+ <li class="sidebar-item sidebar-item-section">
142
+ <div class="sidebar-item-container">
143
+ <a class="sidebar-item-text sidebar-link text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-3" role="navigation" aria-expanded="true">
144
+ <span class="menu-text">Structured Information Extraction</span></a>
145
+ <a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-3" role="navigation" aria-expanded="true" aria-label="Toggle section">
146
+ <i class="bi bi-chevron-right ms-2"></i>
147
+ </a>
148
+ </div>
149
+ <ul id="quarto-sidebar-section-3" class="collapse list-unstyled sidebar-section depth1 show">
150
+ <li class="sidebar-item">
151
+ <div class="sidebar-item-container">
152
+ <a href="./patterns/structured-generation/intro.html" class="sidebar-item-text sidebar-link">
153
+ <span class="menu-text"><span class="chapter-number">2</span>&nbsp; <span class="chapter-title">Example Pattern: Structured Document Processing</span></span></a>
154
+ </div>
155
+ </li>
156
+ <li class="sidebar-item">
157
+ <div class="sidebar-item-container">
158
+ <a href="./patterns/structured-generation/vlm-structured-generation.html" class="sidebar-item-text sidebar-link">
159
+ <span class="menu-text"><span class="chapter-number">3</span>&nbsp; <span class="chapter-title">Structured Information Extraction with Vision Language Models</span></span></a>
160
+ </div>
161
+ </li>
162
+ <li class="sidebar-item">
163
+ <div class="sidebar-item-container">
164
+ <a href="./patterns/structured-generation/advisor-index-cards.html" class="sidebar-item-text sidebar-link">
165
+ <span class="menu-text"><span class="chapter-number">4</span>&nbsp; <span class="chapter-title">Practical Application: Advisor Index Card Extraction</span></span></a>
166
+ </div>
167
+ </li>
168
+ </ul>
169
+ </li>
170
+ </ul>
171
+ </div>
172
+ </nav>
173
+ <div id="quarto-sidebar-glass" class="quarto-sidebar-collapse-item" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item"></div>
174
+ <!-- margin-sidebar -->
175
+ <div id="quarto-margin-sidebar" class="sidebar margin-sidebar">
176
+ <nav id="TOC" role="doc-toc" class="toc-active">
177
+ <h2 id="toc-title">Table of contents</h2>
178
+
179
+ <ul>
180
+ <li><a href="#welcome" id="toc-welcome" class="nav-link active" data-scroll-target="#welcome">Welcome</a>
181
+ <ul class="collapse">
182
+ <li><a href="#about-this-book" id="toc-about-this-book" class="nav-link" data-scroll-target="#about-this-book">About This Book</a>
183
+ <ul class="collapse">
184
+ <li><a href="#goal-of-this-book" id="toc-goal-of-this-book" class="nav-link" data-scroll-target="#goal-of-this-book">Goal of this book</a></li>
185
+ </ul></li>
186
+ <li><a href="#background-to-this-work" id="toc-background-to-this-work" class="nav-link" data-scroll-target="#background-to-this-work">Background to this work</a>
187
+ <ul class="collapse">
188
+ <li><a href="#about-the-author" id="toc-about-the-author" class="nav-link" data-scroll-target="#about-the-author">About the Author</a></li>
189
+ </ul></li>
190
+ </ul></li>
191
+ </ul>
192
+ </nav>
193
+ </div>
194
+ <!-- main -->
195
+ <main class="content" id="quarto-document-content">
196
+
197
+ <header id="title-block-header" class="quarto-title-block default">
198
+ <div class="quarto-title">
199
+ <h1 class="title">AI Design Patterns for GLAM</h1>
200
+ </div>
201
+
202
+
203
+
204
+ <div class="quarto-title-meta">
205
+
206
+ <div>
207
+ <div class="quarto-title-meta-heading">Author</div>
208
+ <div class="quarto-title-meta-contents">
209
+ <p>Daniel van Strien </p>
210
+ </div>
211
+ </div>
212
+
213
+ <div>
214
+ <div class="quarto-title-meta-heading">Published</div>
215
+ <div class="quarto-title-meta-contents">
216
+ <p class="date">October 27, 2025</p>
217
+ </div>
218
+ </div>
219
+
220
+
221
+ </div>
222
+
223
+
224
+
225
+ </header>
226
+
227
+
228
+ <section id="welcome" class="level1 unnumbered">
229
+ <h1 class="unnumbered">Welcome</h1>
230
+ <section id="about-this-book" class="level2">
231
+ <h2 class="anchored" data-anchor-id="about-this-book">About This Book</h2>
232
+ <div class="callout callout-style-default callout-note callout-titled">
233
+ <div class="callout-header d-flex align-content-center">
234
+ <div class="callout-icon-container">
235
+ <i class="callout-icon"></i>
236
+ </div>
237
+ <div class="callout-title-container flex-fill">
238
+ Note
239
+ </div>
240
+ </div>
241
+ <div class="callout-body-container callout-body">
242
+ <p>This is a work in progress. Content will be added and updated over time. This is an early draft!</p>
243
+ </div>
244
+ </div>
245
+ <p>This book presents practical AI design patterns for Galleries, Libraries, Archives, and Museums (GLAM). Rather than focusing on specific technologies that will inevitably change, we document <strong>patterns</strong> which aim to capture reusable solutions to common challenges faced when implementing AI in GLAM contexts.</p>
246
+ <section id="goal-of-this-book" class="level3">
247
+ <h3 class="anchored" data-anchor-id="goal-of-this-book">Goal of this book</h3>
248
+ <p>There are many exciting possibilities for applying AI in GLAM contexts, but also significant challenges. This book aims to provide a practical guide to help practitioners navigate this rapidly evolving landscape.</p>
249
+ <p>Whilst there is a lot of hype around the adoption of AI in the GLAM sector there isn’t always clear guidance on how to approach projects in a structured way. This book aims to help fill that gap by providing a set of patterns that can be adapted and applied to a wide range of use cases.</p>
250
+ </section>
251
+ </section>
252
+ <section id="background-to-this-work" class="level2">
253
+ <h2 class="anchored" data-anchor-id="background-to-this-work">Background to this work</h2>
254
+ <p>This documentation emerged from work with the National Library of Scotland, but the patterns and approaches apply broadly across the GLAM sector.</p>
255
+ <section id="about-the-author" class="level3">
256
+ <h3 class="anchored" data-anchor-id="about-the-author">About the Author</h3>
257
+ <p>Daniel van Strien</p>
258
+
259
+
260
+ </section>
261
+ </section>
262
+ </section>
263
+
264
+ </main> <!-- /main -->
265
+ <script id="quarto-html-after-body" type="application/javascript">
266
+ window.document.addEventListener("DOMContentLoaded", function (event) {
267
+ const icon = "";
268
+ const anchorJS = new window.AnchorJS();
269
+ anchorJS.options = {
270
+ placement: 'right',
271
+ icon: icon
272
+ };
273
+ anchorJS.add('.anchored');
274
+ const isCodeAnnotation = (el) => {
275
+ for (const clz of el.classList) {
276
+ if (clz.startsWith('code-annotation-')) {
277
+ return true;
278
+ }
279
+ }
280
+ return false;
281
+ }
282
+ const onCopySuccess = function(e) {
283
+ // button target
284
+ const button = e.trigger;
285
+ // don't keep focus
286
+ button.blur();
287
+ // flash "checked"
288
+ button.classList.add('code-copy-button-checked');
289
+ var currentTitle = button.getAttribute("title");
290
+ button.setAttribute("title", "Copied!");
291
+ let tooltip;
292
+ if (window.bootstrap) {
293
+ button.setAttribute("data-bs-toggle", "tooltip");
294
+ button.setAttribute("data-bs-placement", "left");
295
+ button.setAttribute("data-bs-title", "Copied!");
296
+ tooltip = new bootstrap.Tooltip(button,
297
+ { trigger: "manual",
298
+ customClass: "code-copy-button-tooltip",
299
+ offset: [0, -8]});
300
+ tooltip.show();
301
+ }
302
+ setTimeout(function() {
303
+ if (tooltip) {
304
+ tooltip.hide();
305
+ button.removeAttribute("data-bs-title");
306
+ button.removeAttribute("data-bs-toggle");
307
+ button.removeAttribute("data-bs-placement");
308
+ }
309
+ button.setAttribute("title", currentTitle);
310
+ button.classList.remove('code-copy-button-checked');
311
+ }, 1000);
312
+ // clear code selection
313
+ e.clearSelection();
314
+ }
315
+ const getTextToCopy = function(trigger) {
316
+ const outerScaffold = trigger.parentElement.cloneNode(true);
317
+ const codeEl = outerScaffold.querySelector('code');
318
+ for (const childEl of codeEl.children) {
319
+ if (isCodeAnnotation(childEl)) {
320
+ childEl.remove();
321
+ }
322
+ }
323
+ return codeEl.innerText;
324
+ }
325
+ const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', {
326
+ text: getTextToCopy
327
+ });
328
+ clipboard.on('success', onCopySuccess);
329
+ if (window.document.getElementById('quarto-embedded-source-code-modal')) {
330
+ const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', {
331
+ text: getTextToCopy,
332
+ container: window.document.getElementById('quarto-embedded-source-code-modal')
333
+ });
334
+ clipboardModal.on('success', onCopySuccess);
335
+ }
336
+ var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//);
337
+ var mailtoRegex = new RegExp(/^mailto:/);
338
+ var filterRegex = new RegExp('/' + window.location.host + '/');
339
+ var isInternal = (href) => {
340
+ return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href);
341
+ }
342
+ // Inspect non-navigation links and adorn them if external
343
+ var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)');
344
+ for (var i=0; i<links.length; i++) {
345
+ const link = links[i];
346
+ if (!isInternal(link.href)) {
347
+ // undo the damage that might have been done by quarto-nav.js in the case of
348
+ // links that we want to consider external
349
+ if (link.dataset.originalHref !== undefined) {
350
+ link.href = link.dataset.originalHref;
351
+ }
352
+ }
353
+ }
354
+ function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) {
355
+ const config = {
356
+ allowHTML: true,
357
+ maxWidth: 500,
358
+ delay: 100,
359
+ arrow: false,
360
+ appendTo: function(el) {
361
+ return el.parentElement;
362
+ },
363
+ interactive: true,
364
+ interactiveBorder: 10,
365
+ theme: 'quarto',
366
+ placement: 'bottom-start',
367
+ };
368
+ if (contentFn) {
369
+ config.content = contentFn;
370
+ }
371
+ if (onTriggerFn) {
372
+ config.onTrigger = onTriggerFn;
373
+ }
374
+ if (onUntriggerFn) {
375
+ config.onUntrigger = onUntriggerFn;
376
+ }
377
+ window.tippy(el, config);
378
+ }
379
+ const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]');
380
+ for (var i=0; i<noterefs.length; i++) {
381
+ const ref = noterefs[i];
382
+ tippyHover(ref, function() {
383
+ // use id or data attribute instead here
384
+ let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href');
385
+ try { href = new URL(href).hash; } catch {}
386
+ const id = href.replace(/^#\/?/, "");
387
+ const note = window.document.getElementById(id);
388
+ if (note) {
389
+ return note.innerHTML;
390
+ } else {
391
+ return "";
392
+ }
393
+ });
394
+ }
395
+ const xrefs = window.document.querySelectorAll('a.quarto-xref');
396
+ const processXRef = (id, note) => {
397
+ // Strip column container classes
398
+ const stripColumnClz = (el) => {
399
+ el.classList.remove("page-full", "page-columns");
400
+ if (el.children) {
401
+ for (const child of el.children) {
402
+ stripColumnClz(child);
403
+ }
404
+ }
405
+ }
406
+ stripColumnClz(note)
407
+ if (id === null || id.startsWith('sec-')) {
408
+ // Special case sections, only their first couple elements
409
+ const container = document.createElement("div");
410
+ if (note.children && note.children.length > 2) {
411
+ container.appendChild(note.children[0].cloneNode(true));
412
+ for (let i = 1; i < note.children.length; i++) {
413
+ const child = note.children[i];
414
+ if (child.tagName === "P" && child.innerText === "") {
415
+ continue;
416
+ } else {
417
+ container.appendChild(child.cloneNode(true));
418
+ break;
419
+ }
420
+ }
421
+ if (window.Quarto?.typesetMath) {
422
+ window.Quarto.typesetMath(container);
423
+ }
424
+ return container.innerHTML
425
+ } else {
426
+ if (window.Quarto?.typesetMath) {
427
+ window.Quarto.typesetMath(note);
428
+ }
429
+ return note.innerHTML;
430
+ }
431
+ } else {
432
+ // Remove any anchor links if they are present
433
+ const anchorLink = note.querySelector('a.anchorjs-link');
434
+ if (anchorLink) {
435
+ anchorLink.remove();
436
+ }
437
+ if (window.Quarto?.typesetMath) {
438
+ window.Quarto.typesetMath(note);
439
+ }
440
+ if (note.classList.contains("callout")) {
441
+ return note.outerHTML;
442
+ } else {
443
+ return note.innerHTML;
444
+ }
445
+ }
446
+ }
447
+ for (var i=0; i<xrefs.length; i++) {
448
+ const xref = xrefs[i];
449
+ tippyHover(xref, undefined, function(instance) {
450
+ instance.disable();
451
+ let url = xref.getAttribute('href');
452
+ let hash = undefined;
453
+ if (url.startsWith('#')) {
454
+ hash = url;
455
+ } else {
456
+ try { hash = new URL(url).hash; } catch {}
457
+ }
458
+ if (hash) {
459
+ const id = hash.replace(/^#\/?/, "");
460
+ const note = window.document.getElementById(id);
461
+ if (note !== null) {
462
+ try {
463
+ const html = processXRef(id, note.cloneNode(true));
464
+ instance.setContent(html);
465
+ } finally {
466
+ instance.enable();
467
+ instance.show();
468
+ }
469
+ } else {
470
+ // See if we can fetch this
471
+ fetch(url.split('#')[0])
472
+ .then(res => res.text())
473
+ .then(html => {
474
+ const parser = new DOMParser();
475
+ const htmlDoc = parser.parseFromString(html, "text/html");
476
+ const note = htmlDoc.getElementById(id);
477
+ if (note !== null) {
478
+ const html = processXRef(id, note);
479
+ instance.setContent(html);
480
+ }
481
+ }).finally(() => {
482
+ instance.enable();
483
+ instance.show();
484
+ });
485
+ }
486
+ } else {
487
+ // See if we can fetch a full url (with no hash to target)
488
+ // This is a special case and we should probably do some content thinning / targeting
489
+ fetch(url)
490
+ .then(res => res.text())
491
+ .then(html => {
492
+ const parser = new DOMParser();
493
+ const htmlDoc = parser.parseFromString(html, "text/html");
494
+ const note = htmlDoc.querySelector('main.content');
495
+ if (note !== null) {
496
+ // This should only happen for chapter cross references
497
+ // (since there is no id in the URL)
498
+ // remove the first header
499
+ if (note.children.length > 0 && note.children[0].tagName === "HEADER") {
500
+ note.children[0].remove();
501
+ }
502
+ const html = processXRef(null, note);
503
+ instance.setContent(html);
504
+ }
505
+ }).finally(() => {
506
+ instance.enable();
507
+ instance.show();
508
+ });
509
+ }
510
+ }, function(instance) {
511
+ });
512
+ }
513
+ let selectedAnnoteEl;
514
+ const selectorForAnnotation = ( cell, annotation) => {
515
+ let cellAttr = 'data-code-cell="' + cell + '"';
516
+ let lineAttr = 'data-code-annotation="' + annotation + '"';
517
+ const selector = 'span[' + cellAttr + '][' + lineAttr + ']';
518
+ return selector;
519
+ }
520
+ const selectCodeLines = (annoteEl) => {
521
+ const doc = window.document;
522
+ const targetCell = annoteEl.getAttribute("data-target-cell");
523
+ const targetAnnotation = annoteEl.getAttribute("data-target-annotation");
524
+ const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation));
525
+ const lines = annoteSpan.getAttribute("data-code-lines").split(",");
526
+ const lineIds = lines.map((line) => {
527
+ return targetCell + "-" + line;
528
+ })
529
+ let top = null;
530
+ let height = null;
531
+ let parent = null;
532
+ if (lineIds.length > 0) {
533
+ //compute the position of the single el (top and bottom and make a div)
534
+ const el = window.document.getElementById(lineIds[0]);
535
+ top = el.offsetTop;
536
+ height = el.offsetHeight;
537
+ parent = el.parentElement.parentElement;
538
+ if (lineIds.length > 1) {
539
+ const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]);
540
+ const bottom = lastEl.offsetTop + lastEl.offsetHeight;
541
+ height = bottom - top;
542
+ }
543
+ if (top !== null && height !== null && parent !== null) {
544
+ // cook up a div (if necessary) and position it
545
+ let div = window.document.getElementById("code-annotation-line-highlight");
546
+ if (div === null) {
547
+ div = window.document.createElement("div");
548
+ div.setAttribute("id", "code-annotation-line-highlight");
549
+ div.style.position = 'absolute';
550
+ parent.appendChild(div);
551
+ }
552
+ div.style.top = top - 2 + "px";
553
+ div.style.height = height + 4 + "px";
554
+ div.style.left = 0;
555
+ let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter");
556
+ if (gutterDiv === null) {
557
+ gutterDiv = window.document.createElement("div");
558
+ gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter");
559
+ gutterDiv.style.position = 'absolute';
560
+ const codeCell = window.document.getElementById(targetCell);
561
+ const gutter = codeCell.querySelector('.code-annotation-gutter');
562
+ gutter.appendChild(gutterDiv);
563
+ }
564
+ gutterDiv.style.top = top - 2 + "px";
565
+ gutterDiv.style.height = height + 4 + "px";
566
+ }
567
+ selectedAnnoteEl = annoteEl;
568
+ }
569
+ };
570
+ const unselectCodeLines = () => {
571
+ const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"];
572
+ elementsIds.forEach((elId) => {
573
+ const div = window.document.getElementById(elId);
574
+ if (div) {
575
+ div.remove();
576
+ }
577
+ });
578
+ selectedAnnoteEl = undefined;
579
+ };
580
+ // Handle positioning of the toggle
581
+ window.addEventListener(
582
+ "resize",
583
+ throttle(() => {
584
+ elRect = undefined;
585
+ if (selectedAnnoteEl) {
586
+ selectCodeLines(selectedAnnoteEl);
587
+ }
588
+ }, 10)
589
+ );
590
+ function throttle(fn, ms) {
591
+ let throttle = false;
592
+ let timer;
593
+ return (...args) => {
594
+ if(!throttle) { // first call gets through
595
+ fn.apply(this, args);
596
+ throttle = true;
597
+ } else { // all the others get throttled
598
+ if(timer) clearTimeout(timer); // cancel #2
599
+ timer = setTimeout(() => {
600
+ fn.apply(this, args);
601
+ timer = throttle = false;
602
+ }, ms);
603
+ }
604
+ };
605
+ }
606
+ // Attach click handler to the DT
607
+ const annoteDls = window.document.querySelectorAll('dt[data-target-cell]');
608
+ for (const annoteDlNode of annoteDls) {
609
+ annoteDlNode.addEventListener('click', (event) => {
610
+ const clickedEl = event.target;
611
+ if (clickedEl !== selectedAnnoteEl) {
612
+ unselectCodeLines();
613
+ const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active');
614
+ if (activeEl) {
615
+ activeEl.classList.remove('code-annotation-active');
616
+ }
617
+ selectCodeLines(clickedEl);
618
+ clickedEl.classList.add('code-annotation-active');
619
+ } else {
620
+ // Unselect the line
621
+ unselectCodeLines();
622
+ clickedEl.classList.remove('code-annotation-active');
623
+ }
624
+ });
625
+ }
626
+ const findCites = (el) => {
627
+ const parentEl = el.parentElement;
628
+ if (parentEl) {
629
+ const cites = parentEl.dataset.cites;
630
+ if (cites) {
631
+ return {
632
+ el,
633
+ cites: cites.split(' ')
634
+ };
635
+ } else {
636
+ return findCites(el.parentElement)
637
+ }
638
+ } else {
639
+ return undefined;
640
+ }
641
+ };
642
+ var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]');
643
+ for (var i=0; i<bibliorefs.length; i++) {
644
+ const ref = bibliorefs[i];
645
+ const citeInfo = findCites(ref);
646
+ if (citeInfo) {
647
+ tippyHover(citeInfo.el, function() {
648
+ var popup = window.document.createElement('div');
649
+ citeInfo.cites.forEach(function(cite) {
650
+ var citeDiv = window.document.createElement('div');
651
+ citeDiv.classList.add('hanging-indent');
652
+ citeDiv.classList.add('csl-entry');
653
+ var biblioDiv = window.document.getElementById('ref-' + cite);
654
+ if (biblioDiv) {
655
+ citeDiv.innerHTML = biblioDiv.innerHTML;
656
+ }
657
+ popup.appendChild(citeDiv);
658
+ });
659
+ return popup.innerHTML;
660
+ });
661
+ }
662
+ }
663
+ });
664
+ </script>
665
+ <nav class="page-navigation">
666
+ <div class="nav-page nav-page-previous">
667
+ </div>
668
+ <div class="nav-page nav-page-next">
669
+ <a href="./patterns/what-is-an-ai-pattern.html" class="pagination-link" aria-label="What is an AI Pattern?">
670
+ <span class="nav-page-text"><span class="chapter-number">1</span>&nbsp; <span class="chapter-title">What is an AI Pattern?</span></span> <i class="bi bi-arrow-right-short"></i>
671
+ </a>
672
+ </div>
673
+ </nav>
674
+ </div> <!-- /content -->
675
+
676
+
677
+
678
+
679
+ </body></html>
patterns/structured-generation/advisor-index-cards.html ADDED
The diff for this file is too large to render. See raw diff
 
patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-1.png ADDED

Git LFS Details

  • SHA256: 734f0890505f7fb82ce46b32680f0eecb7bebac297ab1380158f7cb3dbdf563a
  • Pointer size: 131 Bytes
  • Size of remote file: 533 kB
patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-2.png ADDED

Git LFS Details

  • SHA256: ee463f550113684c918c9eb30758063addbff0bd9b314db8bcf8b9bee2dd51fb
  • Pointer size: 131 Bytes
  • Size of remote file: 586 kB
patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-3.png ADDED

Git LFS Details

  • SHA256: 61618970287b02b162f746e0c6a39e8a6aed57a44b9047a9a4473ba1fa784662
  • Pointer size: 131 Bytes
  • Size of remote file: 608 kB
patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-4.png ADDED

Git LFS Details

  • SHA256: 0e81b4d746b0f00a5d219db971173a6ad155d4f97587a09def000af5e308c5b4
  • Pointer size: 131 Bytes
  • Size of remote file: 589 kB
patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-5.png ADDED

Git LFS Details

  • SHA256: 5db9554464928e9ec226ee88cdd9f539c7ad4f9f6b3ddaa8ffea2120d92f0c70
  • Pointer size: 131 Bytes
  • Size of remote file: 613 kB
patterns/structured-generation/advisor-index-cards_files/figure-html/cell-13-output-6.png ADDED

Git LFS Details

  • SHA256: b401e93bdd832932407aa5856f6110c398d66d88641fe8d578d307d44acdd23e
  • Pointer size: 131 Bytes
  • Size of remote file: 610 kB
patterns/structured-generation/advisor-index-cards_files/figure-html/cell-16-output-2.png ADDED

Git LFS Details

  • SHA256: b541f0b9a4cd1c9d69771f7e3e4f1cc4ca5a1ee93e790d5d527e9129ab918152
  • Pointer size: 131 Bytes
  • Size of remote file: 275 kB
patterns/structured-generation/advisor-index-cards_files/figure-html/cell-2-output-1.png ADDED

Git LFS Details

  • SHA256: fa5a849361e72ee80b73c4d701321f2ddbd7a61cc8bbd3321679d15916d152d8
  • Pointer size: 132 Bytes
  • Size of remote file: 1.22 MB
patterns/structured-generation/advisor-index-cards_files/figure-html/cell-9-output-1.png ADDED

Git LFS Details

  • SHA256: d94deab584be1fe7f807d9a37d651f34802c5e41ff57b30567a97af8a7993c60
  • Pointer size: 132 Bytes
  • Size of remote file: 1.09 MB
patterns/structured-generation/intro.html ADDED
@@ -0,0 +1,647 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>
3
+
4
+ <meta charset="utf-8">
5
+ <meta name="generator" content="quarto-1.8.25">
6
+
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
8
+
9
+
10
+ <title>2&nbsp; Example Pattern: Structured Document Processing – AI Design Patterns for GLAM</title>
11
+ <style>
12
+ code{white-space: pre-wrap;}
13
+ span.smallcaps{font-variant: small-caps;}
14
+ div.columns{display: flex; gap: min(4vw, 1.5em);}
15
+ div.column{flex: auto; overflow-x: auto;}
16
+ div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
17
+ ul.task-list{list-style: none;}
18
+ ul.task-list li input[type="checkbox"] {
19
+ width: 0.8em;
20
+ margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */
21
+ vertical-align: middle;
22
+ }
23
+ </style>
24
+
25
+
26
+ <script src="../../site_libs/quarto-nav/quarto-nav.js"></script>
27
+ <script src="../../site_libs/quarto-nav/headroom.min.js"></script>
28
+ <script src="../../site_libs/clipboard/clipboard.min.js"></script>
29
+ <script src="../../site_libs/quarto-search/autocomplete.umd.js"></script>
30
+ <script src="../../site_libs/quarto-search/fuse.min.js"></script>
31
+ <script src="../../site_libs/quarto-search/quarto-search.js"></script>
32
+ <meta name="quarto:offset" content="../../">
33
+ <link href="../../patterns/structured-generation/vlm-structured-generation.html" rel="next">
34
+ <link href="../../patterns/what-is-an-ai-pattern.html" rel="prev">
35
+ <script src="../../site_libs/quarto-html/quarto.js" type="module"></script>
36
+ <script src="../../site_libs/quarto-html/tabsets/tabsets.js" type="module"></script>
37
+ <script src="../../site_libs/quarto-html/axe/axe-check.js" type="module"></script>
38
+ <script src="../../site_libs/quarto-html/popper.min.js"></script>
39
+ <script src="../../site_libs/quarto-html/tippy.umd.min.js"></script>
40
+ <script src="../../site_libs/quarto-html/anchor.min.js"></script>
41
+ <link href="../../site_libs/quarto-html/tippy.css" rel="stylesheet">
42
+ <link href="../../site_libs/quarto-html/quarto-syntax-highlighting-7b89279ff1a6dce999919e0e67d4d9ec.css" rel="stylesheet" id="quarto-text-highlighting-styles">
43
+ <script src="../../site_libs/bootstrap/bootstrap.min.js"></script>
44
+ <link href="../../site_libs/bootstrap/bootstrap-icons.css" rel="stylesheet">
45
+ <link href="../../site_libs/bootstrap/bootstrap-27c261d06b905028a18691de25d09dde.min.css" rel="stylesheet" append-hash="true" id="quarto-bootstrap" data-mode="light">
46
+ <script id="quarto-search-options" type="application/json">{
47
+ "location": "sidebar",
48
+ "copy-button": false,
49
+ "collapse-after": 3,
50
+ "panel-placement": "start",
51
+ "type": "textbox",
52
+ "limit": 50,
53
+ "keyboard-shortcut": [
54
+ "f",
55
+ "/",
56
+ "s"
57
+ ],
58
+ "show-item-context": false,
59
+ "language": {
60
+ "search-no-results-text": "No results",
61
+ "search-matching-documents-text": "matching documents",
62
+ "search-copy-link-title": "Copy link to search",
63
+ "search-hide-matches-text": "Hide additional matches",
64
+ "search-more-match-text": "more match in this document",
65
+ "search-more-matches-text": "more matches in this document",
66
+ "search-clear-button-title": "Clear",
67
+ "search-text-placeholder": "",
68
+ "search-detached-cancel-button-title": "Cancel",
69
+ "search-submit-button-title": "Submit",
70
+ "search-label": "Search"
71
+ }
72
+ }</script>
73
+
74
+
75
+ </head>
76
+
77
+ <body class="nav-sidebar floating quarto-light">
78
+
79
+ <div id="quarto-search-results"></div>
80
+ <header id="quarto-header" class="headroom fixed-top">
81
+ <nav class="quarto-secondary-nav">
82
+ <div class="container-fluid d-flex">
83
+ <button type="button" class="quarto-btn-toggle btn" data-bs-toggle="collapse" role="button" data-bs-target=".quarto-sidebar-collapse-item" aria-controls="quarto-sidebar" aria-expanded="false" aria-label="Toggle sidebar navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
84
+ <i class="bi bi-layout-text-sidebar-reverse"></i>
85
+ </button>
86
+ <nav class="quarto-page-breadcrumbs" aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="../../patterns/structured-generation/intro.html">Structured Information Extraction</a></li><li class="breadcrumb-item"><a href="../../patterns/structured-generation/intro.html"><span class="chapter-number">2</span>&nbsp; <span class="chapter-title">Example Pattern: Structured Document Processing</span></a></li></ol></nav>
87
+ <a class="flex-grow-1" role="navigation" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item" aria-controls="quarto-sidebar" aria-expanded="false" aria-label="Toggle sidebar navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
88
+ </a>
89
+ <button type="button" class="btn quarto-search-button" aria-label="Search" onclick="window.quartoOpenSearch();">
90
+ <i class="bi bi-search"></i>
91
+ </button>
92
+ </div>
93
+ </nav>
94
+ </header>
95
+ <!-- content -->
96
+ <div id="quarto-content" class="quarto-container page-columns page-rows-contents page-layout-article">
97
+ <!-- sidebar -->
98
+ <nav id="quarto-sidebar" class="sidebar collapse collapse-horizontal quarto-sidebar-collapse-item sidebar-navigation floating overflow-auto">
99
+ <div class="pt-lg-2 mt-2 text-left sidebar-header sidebar-header-stacked">
100
+ <a href="../../index.html" class="sidebar-logo-link">
101
+ </a>
102
+ <div class="sidebar-title mb-0 py-0">
103
+ <a href="../../">AI Design Patterns for GLAM</a>
104
+ </div>
105
+ </div>
106
+ <div class="mt-2 flex-shrink-0 align-items-center">
107
+ <div class="sidebar-search">
108
+ <div id="quarto-search" class="" title="Search"></div>
109
+ </div>
110
+ </div>
111
+ <div class="sidebar-menu-container">
112
+ <ul class="list-unstyled mt-1">
113
+ <li class="sidebar-item">
114
+ <div class="sidebar-item-container">
115
+ <a href="../../index.html" class="sidebar-item-text sidebar-link">
116
+ <span class="menu-text">Welcome</span></a>
117
+ </div>
118
+ </li>
119
+ <li class="sidebar-item sidebar-item-section">
120
+ <span class="sidebar-item-text sidebar-link text-start">
121
+ <span class="menu-text">Beyond Chat Interfaces to Collections?</span></span>
122
+ </li>
123
+ <li class="sidebar-item sidebar-item-section">
124
+ <div class="sidebar-item-container">
125
+ <a class="sidebar-item-text sidebar-link text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-2" role="navigation" aria-expanded="true">
126
+ <span class="menu-text">Design Patterns</span></a>
127
+ <a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-2" role="navigation" aria-expanded="true" aria-label="Toggle section">
128
+ <i class="bi bi-chevron-right ms-2"></i>
129
+ </a>
130
+ </div>
131
+ <ul id="quarto-sidebar-section-2" class="collapse list-unstyled sidebar-section depth1 show">
132
+ <li class="sidebar-item">
133
+ <div class="sidebar-item-container">
134
+ <a href="../../patterns/what-is-an-ai-pattern.html" class="sidebar-item-text sidebar-link">
135
+ <span class="menu-text"><span class="chapter-number">1</span>&nbsp; <span class="chapter-title">What is an AI Pattern?</span></span></a>
136
+ </div>
137
+ </li>
138
+ </ul>
139
+ </li>
140
+ <li class="sidebar-item sidebar-item-section">
141
+ <div class="sidebar-item-container">
142
+ <a class="sidebar-item-text sidebar-link text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-3" role="navigation" aria-expanded="true">
143
+ <span class="menu-text">Structured Information Extraction</span></a>
144
+ <a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-3" role="navigation" aria-expanded="true" aria-label="Toggle section">
145
+ <i class="bi bi-chevron-right ms-2"></i>
146
+ </a>
147
+ </div>
148
+ <ul id="quarto-sidebar-section-3" class="collapse list-unstyled sidebar-section depth1 show">
149
+ <li class="sidebar-item">
150
+ <div class="sidebar-item-container">
151
+ <a href="../../patterns/structured-generation/intro.html" class="sidebar-item-text sidebar-link active">
152
+ <span class="menu-text"><span class="chapter-number">2</span>&nbsp; <span class="chapter-title">Example Pattern: Structured Document Processing</span></span></a>
153
+ </div>
154
+ </li>
155
+ <li class="sidebar-item">
156
+ <div class="sidebar-item-container">
157
+ <a href="../../patterns/structured-generation/vlm-structured-generation.html" class="sidebar-item-text sidebar-link">
158
+ <span class="menu-text"><span class="chapter-number">3</span>&nbsp; <span class="chapter-title">Structured Information Extraction with Vision Language Models</span></span></a>
159
+ </div>
160
+ </li>
161
+ <li class="sidebar-item">
162
+ <div class="sidebar-item-container">
163
+ <a href="../../patterns/structured-generation/advisor-index-cards.html" class="sidebar-item-text sidebar-link">
164
+ <span class="menu-text"><span class="chapter-number">4</span>&nbsp; <span class="chapter-title">Practical Application: Advisor Index Card Extraction</span></span></a>
165
+ </div>
166
+ </li>
167
+ </ul>
168
+ </li>
169
+ </ul>
170
+ </div>
171
+ </nav>
172
+ <div id="quarto-sidebar-glass" class="quarto-sidebar-collapse-item" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item"></div>
173
+ <!-- margin-sidebar -->
174
+ <div id="quarto-margin-sidebar" class="sidebar margin-sidebar">
175
+ <nav id="TOC" role="doc-toc" class="toc-active">
176
+ <h2 id="toc-title">Table of contents</h2>
177
+
178
+ <ul>
179
+ <li><a href="#the-challenge" id="toc-the-challenge" class="nav-link active" data-scroll-target="#the-challenge"><span class="header-section-number">2.1</span> The Challenge</a></li>
180
+ <li><a href="#dont-we-just-need-ocr" id="toc-dont-we-just-need-ocr" class="nav-link" data-scroll-target="#dont-we-just-need-ocr"><span class="header-section-number">2.2</span> Don’t we just need OCR?</a></li>
181
+ <li><a href="#solution-overview" id="toc-solution-overview" class="nav-link" data-scroll-target="#solution-overview"><span class="header-section-number">2.3</span> Solution Overview</a>
182
+ <ul class="collapse">
183
+ <li><a href="#what-this-pattern-looks-like" id="toc-what-this-pattern-looks-like" class="nav-link" data-scroll-target="#what-this-pattern-looks-like"><span class="header-section-number">2.3.1</span> What this pattern looks like?</a></li>
184
+ </ul></li>
185
+ </ul>
186
+ </nav>
187
+ </div>
188
+ <!-- main -->
189
+ <main class="content" id="quarto-document-content">
190
+
191
+ <header id="title-block-header" class="quarto-title-block default"><nav class="quarto-page-breadcrumbs quarto-title-breadcrumbs d-none d-lg-block" aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="../../patterns/structured-generation/intro.html">Structured Information Extraction</a></li><li class="breadcrumb-item"><a href="../../patterns/structured-generation/intro.html"><span class="chapter-number">2</span>&nbsp; <span class="chapter-title">Example Pattern: Structured Document Processing</span></a></li></ol></nav>
192
+ <div class="quarto-title">
193
+ <h1 class="title"><span class="chapter-number">2</span>&nbsp; <span class="chapter-title">Example Pattern: Structured Document Processing</span></h1>
194
+ </div>
195
+
196
+
197
+
198
+ <div class="quarto-title-meta">
199
+
200
+
201
+
202
+
203
+ </div>
204
+
205
+
206
+
207
+ </header>
208
+
209
+
210
+ <section id="the-challenge" class="level2" data-number="2.1">
211
+ <h2 data-number="2.1" class="anchored" data-anchor-id="the-challenge"><span class="header-section-number">2.1</span> The Challenge</h2>
212
+ <p>Many GLAM institutions have vast collections of structured documents - index cards, forms, registers - containing valuable information locked in physical or image formats. Manual transcription doesn’t scale, but the structured nature of these documents makes them ideal candidates for AI-powered processing.</p>
213
+ </section>
214
+ <section id="dont-we-just-need-ocr" class="level2" data-number="2.2">
215
+ <h2 data-number="2.2" class="anchored" data-anchor-id="dont-we-just-need-ocr"><span class="header-section-number">2.2</span> Don’t we just need OCR?</h2>
216
+ <!-- WHY OCR might not be sufficient -->
217
+ </section>
218
+ <section id="solution-overview" class="level2" data-number="2.3">
219
+ <h2 data-number="2.3" class="anchored" data-anchor-id="solution-overview"><span class="header-section-number">2.3</span> Solution Overview</h2>
220
+ <p>This pattern uses Visual Language Models (VLMs) combined with structured output generation to automatically extract information from document images while preserving the original structure and relationships.</p>
221
+ <section id="what-this-pattern-looks-like" class="level3" data-number="2.3.1">
222
+ <h3 data-number="2.3.1" class="anchored" data-anchor-id="what-this-pattern-looks-like"><span class="header-section-number">2.3.1</span> What this pattern looks like?</h3>
223
+ <!-- Example diagram or illustration of the pattern -->
224
+
225
+
226
+ </section>
227
+ </section>
228
+
229
+ </main> <!-- /main -->
230
+ <script id="quarto-html-after-body" type="application/javascript">
231
+ window.document.addEventListener("DOMContentLoaded", function (event) {
232
+ const icon = "";
233
+ const anchorJS = new window.AnchorJS();
234
+ anchorJS.options = {
235
+ placement: 'right',
236
+ icon: icon
237
+ };
238
+ anchorJS.add('.anchored');
239
+ const isCodeAnnotation = (el) => {
240
+ for (const clz of el.classList) {
241
+ if (clz.startsWith('code-annotation-')) {
242
+ return true;
243
+ }
244
+ }
245
+ return false;
246
+ }
247
+ const onCopySuccess = function(e) {
248
+ // button target
249
+ const button = e.trigger;
250
+ // don't keep focus
251
+ button.blur();
252
+ // flash "checked"
253
+ button.classList.add('code-copy-button-checked');
254
+ var currentTitle = button.getAttribute("title");
255
+ button.setAttribute("title", "Copied!");
256
+ let tooltip;
257
+ if (window.bootstrap) {
258
+ button.setAttribute("data-bs-toggle", "tooltip");
259
+ button.setAttribute("data-bs-placement", "left");
260
+ button.setAttribute("data-bs-title", "Copied!");
261
+ tooltip = new bootstrap.Tooltip(button,
262
+ { trigger: "manual",
263
+ customClass: "code-copy-button-tooltip",
264
+ offset: [0, -8]});
265
+ tooltip.show();
266
+ }
267
+ setTimeout(function() {
268
+ if (tooltip) {
269
+ tooltip.hide();
270
+ button.removeAttribute("data-bs-title");
271
+ button.removeAttribute("data-bs-toggle");
272
+ button.removeAttribute("data-bs-placement");
273
+ }
274
+ button.setAttribute("title", currentTitle);
275
+ button.classList.remove('code-copy-button-checked');
276
+ }, 1000);
277
+ // clear code selection
278
+ e.clearSelection();
279
+ }
280
+ const getTextToCopy = function(trigger) {
281
+ const outerScaffold = trigger.parentElement.cloneNode(true);
282
+ const codeEl = outerScaffold.querySelector('code');
283
+ for (const childEl of codeEl.children) {
284
+ if (isCodeAnnotation(childEl)) {
285
+ childEl.remove();
286
+ }
287
+ }
288
+ return codeEl.innerText;
289
+ }
290
+ const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', {
291
+ text: getTextToCopy
292
+ });
293
+ clipboard.on('success', onCopySuccess);
294
+ if (window.document.getElementById('quarto-embedded-source-code-modal')) {
295
+ const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', {
296
+ text: getTextToCopy,
297
+ container: window.document.getElementById('quarto-embedded-source-code-modal')
298
+ });
299
+ clipboardModal.on('success', onCopySuccess);
300
+ }
301
+ var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//);
302
+ var mailtoRegex = new RegExp(/^mailto:/);
303
+ var filterRegex = new RegExp('/' + window.location.host + '/');
304
+ var isInternal = (href) => {
305
+ return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href);
306
+ }
307
+ // Inspect non-navigation links and adorn them if external
308
+ var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)');
309
+ for (var i=0; i<links.length; i++) {
310
+ const link = links[i];
311
+ if (!isInternal(link.href)) {
312
+ // undo the damage that might have been done by quarto-nav.js in the case of
313
+ // links that we want to consider external
314
+ if (link.dataset.originalHref !== undefined) {
315
+ link.href = link.dataset.originalHref;
316
+ }
317
+ }
318
+ }
319
+ function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) {
320
+ const config = {
321
+ allowHTML: true,
322
+ maxWidth: 500,
323
+ delay: 100,
324
+ arrow: false,
325
+ appendTo: function(el) {
326
+ return el.parentElement;
327
+ },
328
+ interactive: true,
329
+ interactiveBorder: 10,
330
+ theme: 'quarto',
331
+ placement: 'bottom-start',
332
+ };
333
+ if (contentFn) {
334
+ config.content = contentFn;
335
+ }
336
+ if (onTriggerFn) {
337
+ config.onTrigger = onTriggerFn;
338
+ }
339
+ if (onUntriggerFn) {
340
+ config.onUntrigger = onUntriggerFn;
341
+ }
342
+ window.tippy(el, config);
343
+ }
344
+ const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]');
345
+ for (var i=0; i<noterefs.length; i++) {
346
+ const ref = noterefs[i];
347
+ tippyHover(ref, function() {
348
+ // use id or data attribute instead here
349
+ let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href');
350
+ try { href = new URL(href).hash; } catch {}
351
+ const id = href.replace(/^#\/?/, "");
352
+ const note = window.document.getElementById(id);
353
+ if (note) {
354
+ return note.innerHTML;
355
+ } else {
356
+ return "";
357
+ }
358
+ });
359
+ }
360
+ const xrefs = window.document.querySelectorAll('a.quarto-xref');
361
+ const processXRef = (id, note) => {
362
+ // Strip column container classes
363
+ const stripColumnClz = (el) => {
364
+ el.classList.remove("page-full", "page-columns");
365
+ if (el.children) {
366
+ for (const child of el.children) {
367
+ stripColumnClz(child);
368
+ }
369
+ }
370
+ }
371
+ stripColumnClz(note)
372
+ if (id === null || id.startsWith('sec-')) {
373
+ // Special case sections, only their first couple elements
374
+ const container = document.createElement("div");
375
+ if (note.children && note.children.length > 2) {
376
+ container.appendChild(note.children[0].cloneNode(true));
377
+ for (let i = 1; i < note.children.length; i++) {
378
+ const child = note.children[i];
379
+ if (child.tagName === "P" && child.innerText === "") {
380
+ continue;
381
+ } else {
382
+ container.appendChild(child.cloneNode(true));
383
+ break;
384
+ }
385
+ }
386
+ if (window.Quarto?.typesetMath) {
387
+ window.Quarto.typesetMath(container);
388
+ }
389
+ return container.innerHTML
390
+ } else {
391
+ if (window.Quarto?.typesetMath) {
392
+ window.Quarto.typesetMath(note);
393
+ }
394
+ return note.innerHTML;
395
+ }
396
+ } else {
397
+ // Remove any anchor links if they are present
398
+ const anchorLink = note.querySelector('a.anchorjs-link');
399
+ if (anchorLink) {
400
+ anchorLink.remove();
401
+ }
402
+ if (window.Quarto?.typesetMath) {
403
+ window.Quarto.typesetMath(note);
404
+ }
405
+ if (note.classList.contains("callout")) {
406
+ return note.outerHTML;
407
+ } else {
408
+ return note.innerHTML;
409
+ }
410
+ }
411
+ }
412
+ for (var i=0; i<xrefs.length; i++) {
413
+ const xref = xrefs[i];
414
+ tippyHover(xref, undefined, function(instance) {
415
+ instance.disable();
416
+ let url = xref.getAttribute('href');
417
+ let hash = undefined;
418
+ if (url.startsWith('#')) {
419
+ hash = url;
420
+ } else {
421
+ try { hash = new URL(url).hash; } catch {}
422
+ }
423
+ if (hash) {
424
+ const id = hash.replace(/^#\/?/, "");
425
+ const note = window.document.getElementById(id);
426
+ if (note !== null) {
427
+ try {
428
+ const html = processXRef(id, note.cloneNode(true));
429
+ instance.setContent(html);
430
+ } finally {
431
+ instance.enable();
432
+ instance.show();
433
+ }
434
+ } else {
435
+ // See if we can fetch this
436
+ fetch(url.split('#')[0])
437
+ .then(res => res.text())
438
+ .then(html => {
439
+ const parser = new DOMParser();
440
+ const htmlDoc = parser.parseFromString(html, "text/html");
441
+ const note = htmlDoc.getElementById(id);
442
+ if (note !== null) {
443
+ const html = processXRef(id, note);
444
+ instance.setContent(html);
445
+ }
446
+ }).finally(() => {
447
+ instance.enable();
448
+ instance.show();
449
+ });
450
+ }
451
+ } else {
452
+ // See if we can fetch a full url (with no hash to target)
453
+ // This is a special case and we should probably do some content thinning / targeting
454
+ fetch(url)
455
+ .then(res => res.text())
456
+ .then(html => {
457
+ const parser = new DOMParser();
458
+ const htmlDoc = parser.parseFromString(html, "text/html");
459
+ const note = htmlDoc.querySelector('main.content');
460
+ if (note !== null) {
461
+ // This should only happen for chapter cross references
462
+ // (since there is no id in the URL)
463
+ // remove the first header
464
+ if (note.children.length > 0 && note.children[0].tagName === "HEADER") {
465
+ note.children[0].remove();
466
+ }
467
+ const html = processXRef(null, note);
468
+ instance.setContent(html);
469
+ }
470
+ }).finally(() => {
471
+ instance.enable();
472
+ instance.show();
473
+ });
474
+ }
475
+ }, function(instance) {
476
+ });
477
+ }
478
+ let selectedAnnoteEl;
479
+ const selectorForAnnotation = ( cell, annotation) => {
480
+ let cellAttr = 'data-code-cell="' + cell + '"';
481
+ let lineAttr = 'data-code-annotation="' + annotation + '"';
482
+ const selector = 'span[' + cellAttr + '][' + lineAttr + ']';
483
+ return selector;
484
+ }
485
+ const selectCodeLines = (annoteEl) => {
486
+ const doc = window.document;
487
+ const targetCell = annoteEl.getAttribute("data-target-cell");
488
+ const targetAnnotation = annoteEl.getAttribute("data-target-annotation");
489
+ const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation));
490
+ const lines = annoteSpan.getAttribute("data-code-lines").split(",");
491
+ const lineIds = lines.map((line) => {
492
+ return targetCell + "-" + line;
493
+ })
494
+ let top = null;
495
+ let height = null;
496
+ let parent = null;
497
+ if (lineIds.length > 0) {
498
+ //compute the position of the single el (top and bottom and make a div)
499
+ const el = window.document.getElementById(lineIds[0]);
500
+ top = el.offsetTop;
501
+ height = el.offsetHeight;
502
+ parent = el.parentElement.parentElement;
503
+ if (lineIds.length > 1) {
504
+ const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]);
505
+ const bottom = lastEl.offsetTop + lastEl.offsetHeight;
506
+ height = bottom - top;
507
+ }
508
+ if (top !== null && height !== null && parent !== null) {
509
+ // cook up a div (if necessary) and position it
510
+ let div = window.document.getElementById("code-annotation-line-highlight");
511
+ if (div === null) {
512
+ div = window.document.createElement("div");
513
+ div.setAttribute("id", "code-annotation-line-highlight");
514
+ div.style.position = 'absolute';
515
+ parent.appendChild(div);
516
+ }
517
+ div.style.top = top - 2 + "px";
518
+ div.style.height = height + 4 + "px";
519
+ div.style.left = 0;
520
+ let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter");
521
+ if (gutterDiv === null) {
522
+ gutterDiv = window.document.createElement("div");
523
+ gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter");
524
+ gutterDiv.style.position = 'absolute';
525
+ const codeCell = window.document.getElementById(targetCell);
526
+ const gutter = codeCell.querySelector('.code-annotation-gutter');
527
+ gutter.appendChild(gutterDiv);
528
+ }
529
+ gutterDiv.style.top = top - 2 + "px";
530
+ gutterDiv.style.height = height + 4 + "px";
531
+ }
532
+ selectedAnnoteEl = annoteEl;
533
+ }
534
+ };
535
+ const unselectCodeLines = () => {
536
+ const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"];
537
+ elementsIds.forEach((elId) => {
538
+ const div = window.document.getElementById(elId);
539
+ if (div) {
540
+ div.remove();
541
+ }
542
+ });
543
+ selectedAnnoteEl = undefined;
544
+ };
545
+ // Handle positioning of the toggle
546
+ window.addEventListener(
547
+ "resize",
548
+ throttle(() => {
549
+ elRect = undefined;
550
+ if (selectedAnnoteEl) {
551
+ selectCodeLines(selectedAnnoteEl);
552
+ }
553
+ }, 10)
554
+ );
555
+ function throttle(fn, ms) {
556
+ let throttle = false;
557
+ let timer;
558
+ return (...args) => {
559
+ if(!throttle) { // first call gets through
560
+ fn.apply(this, args);
561
+ throttle = true;
562
+ } else { // all the others get throttled
563
+ if(timer) clearTimeout(timer); // cancel #2
564
+ timer = setTimeout(() => {
565
+ fn.apply(this, args);
566
+ timer = throttle = false;
567
+ }, ms);
568
+ }
569
+ };
570
+ }
571
+ // Attach click handler to the DT
572
+ const annoteDls = window.document.querySelectorAll('dt[data-target-cell]');
573
+ for (const annoteDlNode of annoteDls) {
574
+ annoteDlNode.addEventListener('click', (event) => {
575
+ const clickedEl = event.target;
576
+ if (clickedEl !== selectedAnnoteEl) {
577
+ unselectCodeLines();
578
+ const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active');
579
+ if (activeEl) {
580
+ activeEl.classList.remove('code-annotation-active');
581
+ }
582
+ selectCodeLines(clickedEl);
583
+ clickedEl.classList.add('code-annotation-active');
584
+ } else {
585
+ // Unselect the line
586
+ unselectCodeLines();
587
+ clickedEl.classList.remove('code-annotation-active');
588
+ }
589
+ });
590
+ }
591
+ const findCites = (el) => {
592
+ const parentEl = el.parentElement;
593
+ if (parentEl) {
594
+ const cites = parentEl.dataset.cites;
595
+ if (cites) {
596
+ return {
597
+ el,
598
+ cites: cites.split(' ')
599
+ };
600
+ } else {
601
+ return findCites(el.parentElement)
602
+ }
603
+ } else {
604
+ return undefined;
605
+ }
606
+ };
607
+ var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]');
608
+ for (var i=0; i<bibliorefs.length; i++) {
609
+ const ref = bibliorefs[i];
610
+ const citeInfo = findCites(ref);
611
+ if (citeInfo) {
612
+ tippyHover(citeInfo.el, function() {
613
+ var popup = window.document.createElement('div');
614
+ citeInfo.cites.forEach(function(cite) {
615
+ var citeDiv = window.document.createElement('div');
616
+ citeDiv.classList.add('hanging-indent');
617
+ citeDiv.classList.add('csl-entry');
618
+ var biblioDiv = window.document.getElementById('ref-' + cite);
619
+ if (biblioDiv) {
620
+ citeDiv.innerHTML = biblioDiv.innerHTML;
621
+ }
622
+ popup.appendChild(citeDiv);
623
+ });
624
+ return popup.innerHTML;
625
+ });
626
+ }
627
+ }
628
+ });
629
+ </script>
630
+ <nav class="page-navigation">
631
+ <div class="nav-page nav-page-previous">
632
+ <a href="../../patterns/what-is-an-ai-pattern.html" class="pagination-link" aria-label="What is an AI Pattern?">
633
+ <i class="bi bi-arrow-left-short"></i> <span class="nav-page-text"><span class="chapter-number">1</span>&nbsp; <span class="chapter-title">What is an AI Pattern?</span></span>
634
+ </a>
635
+ </div>
636
+ <div class="nav-page nav-page-next">
637
+ <a href="../../patterns/structured-generation/vlm-structured-generation.html" class="pagination-link" aria-label="Structured Information Extraction with Vision Language Models">
638
+ <span class="nav-page-text"><span class="chapter-number">3</span>&nbsp; <span class="chapter-title">Structured Information Extraction with Vision Language Models</span></span> <i class="bi bi-arrow-right-short"></i>
639
+ </a>
640
+ </div>
641
+ </nav>
642
+ </div> <!-- /content -->
643
+
644
+
645
+
646
+
647
+ </body></html>
patterns/structured-generation/vlm-structured-generation.html ADDED
The diff for this file is too large to render. See raw diff
 
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-12-output-1.png ADDED

Git LFS Details

  • SHA256: 9f56c71ffd546d3ede3e0f65c7486598b98a35ee8eac5d002c76ccf0c719c927
  • Pointer size: 132 Bytes
  • Size of remote file: 4.22 MB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-13-output-1.png ADDED

Git LFS Details

  • SHA256: 9f56c71ffd546d3ede3e0f65c7486598b98a35ee8eac5d002c76ccf0c719c927
  • Pointer size: 132 Bytes
  • Size of remote file: 4.22 MB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-14-output-1.png ADDED

Git LFS Details

  • SHA256: 9f56c71ffd546d3ede3e0f65c7486598b98a35ee8eac5d002c76ccf0c719c927
  • Pointer size: 132 Bytes
  • Size of remote file: 4.22 MB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-15-output-1.png ADDED

Git LFS Details

  • SHA256: c72fa7e4865d47f87f9326269e1cd3dccbef9679c74fd3b6ca321819f829a283
  • Pointer size: 131 Bytes
  • Size of remote file: 546 kB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-16-output-1.png ADDED

Git LFS Details

  • SHA256: 9c0db64207ae3dce59fa480cd9db3896c76cfec393fffcd03c99da0543946531
  • Pointer size: 131 Bytes
  • Size of remote file: 475 kB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-17-output-1.png ADDED

Git LFS Details

  • SHA256: 9f56c71ffd546d3ede3e0f65c7486598b98a35ee8eac5d002c76ccf0c719c927
  • Pointer size: 132 Bytes
  • Size of remote file: 4.22 MB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-18-output-1.png ADDED

Git LFS Details

  • SHA256: 2bc344a05af6f7362fdac90e6a00366cca6912d6e1c38e658fa86a26cd395ad0
  • Pointer size: 132 Bytes
  • Size of remote file: 3.11 MB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-19-output-1.png ADDED

Git LFS Details

  • SHA256: 2bc344a05af6f7362fdac90e6a00366cca6912d6e1c38e658fa86a26cd395ad0
  • Pointer size: 132 Bytes
  • Size of remote file: 3.11 MB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-20-output-1.png ADDED

Git LFS Details

  • SHA256: 2bc344a05af6f7362fdac90e6a00366cca6912d6e1c38e658fa86a26cd395ad0
  • Pointer size: 132 Bytes
  • Size of remote file: 3.11 MB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-21-output-1.png ADDED

Git LFS Details

  • SHA256: 5327f58e8912e8d95519934165a054ea5f3a465048197529aa816ed95893d3f5
  • Pointer size: 131 Bytes
  • Size of remote file: 400 kB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-22-output-1.png ADDED

Git LFS Details

  • SHA256: 9f56c71ffd546d3ede3e0f65c7486598b98a35ee8eac5d002c76ccf0c719c927
  • Pointer size: 132 Bytes
  • Size of remote file: 4.22 MB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-24-output-1.png ADDED

Git LFS Details

  • SHA256: 9f56c71ffd546d3ede3e0f65c7486598b98a35ee8eac5d002c76ccf0c719c927
  • Pointer size: 132 Bytes
  • Size of remote file: 4.22 MB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-25-output-1.png ADDED

Git LFS Details

  • SHA256: 9f56c71ffd546d3ede3e0f65c7486598b98a35ee8eac5d002c76ccf0c719c927
  • Pointer size: 132 Bytes
  • Size of remote file: 4.22 MB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-26-output-1.png ADDED

Git LFS Details

  • SHA256: 9f56c71ffd546d3ede3e0f65c7486598b98a35ee8eac5d002c76ccf0c719c927
  • Pointer size: 132 Bytes
  • Size of remote file: 4.22 MB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-27-output-1.png ADDED

Git LFS Details

  • SHA256: 9f56c71ffd546d3ede3e0f65c7486598b98a35ee8eac5d002c76ccf0c719c927
  • Pointer size: 132 Bytes
  • Size of remote file: 4.22 MB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-3-output-1.png ADDED

Git LFS Details

  • SHA256: 9f56c71ffd546d3ede3e0f65c7486598b98a35ee8eac5d002c76ccf0c719c927
  • Pointer size: 132 Bytes
  • Size of remote file: 4.22 MB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-4-output-1.png ADDED

Git LFS Details

  • SHA256: 8111b71a269399eccfea83dc6776665acf59e542e95432d5fb456a2adc856150
  • Pointer size: 132 Bytes
  • Size of remote file: 1.75 MB
patterns/structured-generation/vlm-structured-generation_files/figure-html/cell-5-output-1.png ADDED

Git LFS Details

  • SHA256: 7bdbb92b670774648b5ba6079362a9c082c082c28cc9996d53d2356ef9eb51ba
  • Pointer size: 132 Bytes
  • Size of remote file: 3.49 MB
patterns/what-is-an-ai-pattern.html ADDED
@@ -0,0 +1,620 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>
3
+
4
+ <meta charset="utf-8">
5
+ <meta name="generator" content="quarto-1.8.25">
6
+
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
8
+
9
+
10
+ <title>1&nbsp; What is an AI Pattern? – AI Design Patterns for GLAM</title>
11
+ <style>
12
+ code{white-space: pre-wrap;}
13
+ span.smallcaps{font-variant: small-caps;}
14
+ div.columns{display: flex; gap: min(4vw, 1.5em);}
15
+ div.column{flex: auto; overflow-x: auto;}
16
+ div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
17
+ ul.task-list{list-style: none;}
18
+ ul.task-list li input[type="checkbox"] {
19
+ width: 0.8em;
20
+ margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */
21
+ vertical-align: middle;
22
+ }
23
+ </style>
24
+
25
+
26
+ <script src="../site_libs/quarto-nav/quarto-nav.js"></script>
27
+ <script src="../site_libs/quarto-nav/headroom.min.js"></script>
28
+ <script src="../site_libs/clipboard/clipboard.min.js"></script>
29
+ <script src="../site_libs/quarto-search/autocomplete.umd.js"></script>
30
+ <script src="../site_libs/quarto-search/fuse.min.js"></script>
31
+ <script src="../site_libs/quarto-search/quarto-search.js"></script>
32
+ <meta name="quarto:offset" content="../">
33
+ <link href="../patterns/structured-generation/intro.html" rel="next">
34
+ <link href="../index.html" rel="prev">
35
+ <script src="../site_libs/quarto-html/quarto.js" type="module"></script>
36
+ <script src="../site_libs/quarto-html/tabsets/tabsets.js" type="module"></script>
37
+ <script src="../site_libs/quarto-html/axe/axe-check.js" type="module"></script>
38
+ <script src="../site_libs/quarto-html/popper.min.js"></script>
39
+ <script src="../site_libs/quarto-html/tippy.umd.min.js"></script>
40
+ <script src="../site_libs/quarto-html/anchor.min.js"></script>
41
+ <link href="../site_libs/quarto-html/tippy.css" rel="stylesheet">
42
+ <link href="../site_libs/quarto-html/quarto-syntax-highlighting-7b89279ff1a6dce999919e0e67d4d9ec.css" rel="stylesheet" id="quarto-text-highlighting-styles">
43
+ <script src="../site_libs/bootstrap/bootstrap.min.js"></script>
44
+ <link href="../site_libs/bootstrap/bootstrap-icons.css" rel="stylesheet">
45
+ <link href="../site_libs/bootstrap/bootstrap-27c261d06b905028a18691de25d09dde.min.css" rel="stylesheet" append-hash="true" id="quarto-bootstrap" data-mode="light">
46
+ <script id="quarto-search-options" type="application/json">{
47
+ "location": "sidebar",
48
+ "copy-button": false,
49
+ "collapse-after": 3,
50
+ "panel-placement": "start",
51
+ "type": "textbox",
52
+ "limit": 50,
53
+ "keyboard-shortcut": [
54
+ "f",
55
+ "/",
56
+ "s"
57
+ ],
58
+ "show-item-context": false,
59
+ "language": {
60
+ "search-no-results-text": "No results",
61
+ "search-matching-documents-text": "matching documents",
62
+ "search-copy-link-title": "Copy link to search",
63
+ "search-hide-matches-text": "Hide additional matches",
64
+ "search-more-match-text": "more match in this document",
65
+ "search-more-matches-text": "more matches in this document",
66
+ "search-clear-button-title": "Clear",
67
+ "search-text-placeholder": "",
68
+ "search-detached-cancel-button-title": "Cancel",
69
+ "search-submit-button-title": "Submit",
70
+ "search-label": "Search"
71
+ }
72
+ }</script>
73
+
74
+
75
+ </head>
76
+
77
+ <body class="nav-sidebar floating quarto-light">
78
+
79
+ <div id="quarto-search-results"></div>
80
+ <header id="quarto-header" class="headroom fixed-top">
81
+ <nav class="quarto-secondary-nav">
82
+ <div class="container-fluid d-flex">
83
+ <button type="button" class="quarto-btn-toggle btn" data-bs-toggle="collapse" role="button" data-bs-target=".quarto-sidebar-collapse-item" aria-controls="quarto-sidebar" aria-expanded="false" aria-label="Toggle sidebar navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
84
+ <i class="bi bi-layout-text-sidebar-reverse"></i>
85
+ </button>
86
+ <nav class="quarto-page-breadcrumbs" aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="../patterns/what-is-an-ai-pattern.html">Design Patterns</a></li><li class="breadcrumb-item"><a href="../patterns/what-is-an-ai-pattern.html"><span class="chapter-number">1</span>&nbsp; <span class="chapter-title">What is an AI Pattern?</span></a></li></ol></nav>
87
+ <a class="flex-grow-1" role="navigation" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item" aria-controls="quarto-sidebar" aria-expanded="false" aria-label="Toggle sidebar navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
88
+ </a>
89
+ <button type="button" class="btn quarto-search-button" aria-label="Search" onclick="window.quartoOpenSearch();">
90
+ <i class="bi bi-search"></i>
91
+ </button>
92
+ </div>
93
+ </nav>
94
+ </header>
95
+ <!-- content -->
96
+ <div id="quarto-content" class="quarto-container page-columns page-rows-contents page-layout-article">
97
+ <!-- sidebar -->
98
+ <nav id="quarto-sidebar" class="sidebar collapse collapse-horizontal quarto-sidebar-collapse-item sidebar-navigation floating overflow-auto">
99
+ <div class="pt-lg-2 mt-2 text-left sidebar-header sidebar-header-stacked">
100
+ <a href="../index.html" class="sidebar-logo-link">
101
+ </a>
102
+ <div class="sidebar-title mb-0 py-0">
103
+ <a href="../">AI Design Patterns for GLAM</a>
104
+ </div>
105
+ </div>
106
+ <div class="mt-2 flex-shrink-0 align-items-center">
107
+ <div class="sidebar-search">
108
+ <div id="quarto-search" class="" title="Search"></div>
109
+ </div>
110
+ </div>
111
+ <div class="sidebar-menu-container">
112
+ <ul class="list-unstyled mt-1">
113
+ <li class="sidebar-item">
114
+ <div class="sidebar-item-container">
115
+ <a href="../index.html" class="sidebar-item-text sidebar-link">
116
+ <span class="menu-text">Welcome</span></a>
117
+ </div>
118
+ </li>
119
+ <li class="sidebar-item sidebar-item-section">
120
+ <span class="sidebar-item-text sidebar-link text-start">
121
+ <span class="menu-text">Beyond Chat Interfaces to Collections?</span></span>
122
+ </li>
123
+ <li class="sidebar-item sidebar-item-section">
124
+ <div class="sidebar-item-container">
125
+ <a class="sidebar-item-text sidebar-link text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-2" role="navigation" aria-expanded="true">
126
+ <span class="menu-text">Design Patterns</span></a>
127
+ <a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-2" role="navigation" aria-expanded="true" aria-label="Toggle section">
128
+ <i class="bi bi-chevron-right ms-2"></i>
129
+ </a>
130
+ </div>
131
+ <ul id="quarto-sidebar-section-2" class="collapse list-unstyled sidebar-section depth1 show">
132
+ <li class="sidebar-item">
133
+ <div class="sidebar-item-container">
134
+ <a href="../patterns/what-is-an-ai-pattern.html" class="sidebar-item-text sidebar-link active">
135
+ <span class="menu-text"><span class="chapter-number">1</span>&nbsp; <span class="chapter-title">What is an AI Pattern?</span></span></a>
136
+ </div>
137
+ </li>
138
+ </ul>
139
+ </li>
140
+ <li class="sidebar-item sidebar-item-section">
141
+ <div class="sidebar-item-container">
142
+ <a class="sidebar-item-text sidebar-link text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-3" role="navigation" aria-expanded="true">
143
+ <span class="menu-text">Structured Information Extraction</span></a>
144
+ <a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-3" role="navigation" aria-expanded="true" aria-label="Toggle section">
145
+ <i class="bi bi-chevron-right ms-2"></i>
146
+ </a>
147
+ </div>
148
+ <ul id="quarto-sidebar-section-3" class="collapse list-unstyled sidebar-section depth1 show">
149
+ <li class="sidebar-item">
150
+ <div class="sidebar-item-container">
151
+ <a href="../patterns/structured-generation/intro.html" class="sidebar-item-text sidebar-link">
152
+ <span class="menu-text"><span class="chapter-number">2</span>&nbsp; <span class="chapter-title">Example Pattern: Structured Document Processing</span></span></a>
153
+ </div>
154
+ </li>
155
+ <li class="sidebar-item">
156
+ <div class="sidebar-item-container">
157
+ <a href="../patterns/structured-generation/vlm-structured-generation.html" class="sidebar-item-text sidebar-link">
158
+ <span class="menu-text"><span class="chapter-number">3</span>&nbsp; <span class="chapter-title">Structured Information Extraction with Vision Language Models</span></span></a>
159
+ </div>
160
+ </li>
161
+ <li class="sidebar-item">
162
+ <div class="sidebar-item-container">
163
+ <a href="../patterns/structured-generation/advisor-index-cards.html" class="sidebar-item-text sidebar-link">
164
+ <span class="menu-text"><span class="chapter-number">4</span>&nbsp; <span class="chapter-title">Practical Application: Advisor Index Card Extraction</span></span></a>
165
+ </div>
166
+ </li>
167
+ </ul>
168
+ </li>
169
+ </ul>
170
+ </div>
171
+ </nav>
172
+ <div id="quarto-sidebar-glass" class="quarto-sidebar-collapse-item" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item"></div>
173
+ <!-- margin-sidebar -->
174
+ <div id="quarto-margin-sidebar" class="sidebar margin-sidebar zindex-bottom">
175
+
176
+ </div>
177
+ <!-- main -->
178
+ <main class="content" id="quarto-document-content">
179
+
180
+ <header id="title-block-header" class="quarto-title-block default"><nav class="quarto-page-breadcrumbs quarto-title-breadcrumbs d-none d-lg-block" aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="../patterns/what-is-an-ai-pattern.html">Design Patterns</a></li><li class="breadcrumb-item"><a href="../patterns/what-is-an-ai-pattern.html"><span class="chapter-number">1</span>&nbsp; <span class="chapter-title">What is an AI Pattern?</span></a></li></ol></nav>
181
+ <div class="quarto-title">
182
+ <h1 class="title"><span class="chapter-number">1</span>&nbsp; <span class="chapter-title">What is an AI Pattern?</span></h1>
183
+ </div>
184
+
185
+
186
+
187
+ <div class="quarto-title-meta">
188
+
189
+
190
+
191
+
192
+ </div>
193
+
194
+
195
+
196
+ </header>
197
+
198
+
199
+
200
+
201
+
202
+ </main> <!-- /main -->
203
+ <script id="quarto-html-after-body" type="application/javascript">
204
+ window.document.addEventListener("DOMContentLoaded", function (event) {
205
+ const icon = "";
206
+ const anchorJS = new window.AnchorJS();
207
+ anchorJS.options = {
208
+ placement: 'right',
209
+ icon: icon
210
+ };
211
+ anchorJS.add('.anchored');
212
+ const isCodeAnnotation = (el) => {
213
+ for (const clz of el.classList) {
214
+ if (clz.startsWith('code-annotation-')) {
215
+ return true;
216
+ }
217
+ }
218
+ return false;
219
+ }
220
+ const onCopySuccess = function(e) {
221
+ // button target
222
+ const button = e.trigger;
223
+ // don't keep focus
224
+ button.blur();
225
+ // flash "checked"
226
+ button.classList.add('code-copy-button-checked');
227
+ var currentTitle = button.getAttribute("title");
228
+ button.setAttribute("title", "Copied!");
229
+ let tooltip;
230
+ if (window.bootstrap) {
231
+ button.setAttribute("data-bs-toggle", "tooltip");
232
+ button.setAttribute("data-bs-placement", "left");
233
+ button.setAttribute("data-bs-title", "Copied!");
234
+ tooltip = new bootstrap.Tooltip(button,
235
+ { trigger: "manual",
236
+ customClass: "code-copy-button-tooltip",
237
+ offset: [0, -8]});
238
+ tooltip.show();
239
+ }
240
+ setTimeout(function() {
241
+ if (tooltip) {
242
+ tooltip.hide();
243
+ button.removeAttribute("data-bs-title");
244
+ button.removeAttribute("data-bs-toggle");
245
+ button.removeAttribute("data-bs-placement");
246
+ }
247
+ button.setAttribute("title", currentTitle);
248
+ button.classList.remove('code-copy-button-checked');
249
+ }, 1000);
250
+ // clear code selection
251
+ e.clearSelection();
252
+ }
253
+ const getTextToCopy = function(trigger) {
254
+ const outerScaffold = trigger.parentElement.cloneNode(true);
255
+ const codeEl = outerScaffold.querySelector('code');
256
+ for (const childEl of codeEl.children) {
257
+ if (isCodeAnnotation(childEl)) {
258
+ childEl.remove();
259
+ }
260
+ }
261
+ return codeEl.innerText;
262
+ }
263
+ const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', {
264
+ text: getTextToCopy
265
+ });
266
+ clipboard.on('success', onCopySuccess);
267
+ if (window.document.getElementById('quarto-embedded-source-code-modal')) {
268
+ const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', {
269
+ text: getTextToCopy,
270
+ container: window.document.getElementById('quarto-embedded-source-code-modal')
271
+ });
272
+ clipboardModal.on('success', onCopySuccess);
273
+ }
274
+ var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//);
275
+ var mailtoRegex = new RegExp(/^mailto:/);
276
+ var filterRegex = new RegExp('/' + window.location.host + '/');
277
+ var isInternal = (href) => {
278
+ return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href);
279
+ }
280
+ // Inspect non-navigation links and adorn them if external
281
+ var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)');
282
+ for (var i=0; i<links.length; i++) {
283
+ const link = links[i];
284
+ if (!isInternal(link.href)) {
285
+ // undo the damage that might have been done by quarto-nav.js in the case of
286
+ // links that we want to consider external
287
+ if (link.dataset.originalHref !== undefined) {
288
+ link.href = link.dataset.originalHref;
289
+ }
290
+ }
291
+ }
292
+ function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) {
293
+ const config = {
294
+ allowHTML: true,
295
+ maxWidth: 500,
296
+ delay: 100,
297
+ arrow: false,
298
+ appendTo: function(el) {
299
+ return el.parentElement;
300
+ },
301
+ interactive: true,
302
+ interactiveBorder: 10,
303
+ theme: 'quarto',
304
+ placement: 'bottom-start',
305
+ };
306
+ if (contentFn) {
307
+ config.content = contentFn;
308
+ }
309
+ if (onTriggerFn) {
310
+ config.onTrigger = onTriggerFn;
311
+ }
312
+ if (onUntriggerFn) {
313
+ config.onUntrigger = onUntriggerFn;
314
+ }
315
+ window.tippy(el, config);
316
+ }
317
+ const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]');
318
+ for (var i=0; i<noterefs.length; i++) {
319
+ const ref = noterefs[i];
320
+ tippyHover(ref, function() {
321
+ // use id or data attribute instead here
322
+ let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href');
323
+ try { href = new URL(href).hash; } catch {}
324
+ const id = href.replace(/^#\/?/, "");
325
+ const note = window.document.getElementById(id);
326
+ if (note) {
327
+ return note.innerHTML;
328
+ } else {
329
+ return "";
330
+ }
331
+ });
332
+ }
333
+ const xrefs = window.document.querySelectorAll('a.quarto-xref');
334
+ const processXRef = (id, note) => {
335
+ // Strip column container classes
336
+ const stripColumnClz = (el) => {
337
+ el.classList.remove("page-full", "page-columns");
338
+ if (el.children) {
339
+ for (const child of el.children) {
340
+ stripColumnClz(child);
341
+ }
342
+ }
343
+ }
344
+ stripColumnClz(note)
345
+ if (id === null || id.startsWith('sec-')) {
346
+ // Special case sections, only their first couple elements
347
+ const container = document.createElement("div");
348
+ if (note.children && note.children.length > 2) {
349
+ container.appendChild(note.children[0].cloneNode(true));
350
+ for (let i = 1; i < note.children.length; i++) {
351
+ const child = note.children[i];
352
+ if (child.tagName === "P" && child.innerText === "") {
353
+ continue;
354
+ } else {
355
+ container.appendChild(child.cloneNode(true));
356
+ break;
357
+ }
358
+ }
359
+ if (window.Quarto?.typesetMath) {
360
+ window.Quarto.typesetMath(container);
361
+ }
362
+ return container.innerHTML
363
+ } else {
364
+ if (window.Quarto?.typesetMath) {
365
+ window.Quarto.typesetMath(note);
366
+ }
367
+ return note.innerHTML;
368
+ }
369
+ } else {
370
+ // Remove any anchor links if they are present
371
+ const anchorLink = note.querySelector('a.anchorjs-link');
372
+ if (anchorLink) {
373
+ anchorLink.remove();
374
+ }
375
+ if (window.Quarto?.typesetMath) {
376
+ window.Quarto.typesetMath(note);
377
+ }
378
+ if (note.classList.contains("callout")) {
379
+ return note.outerHTML;
380
+ } else {
381
+ return note.innerHTML;
382
+ }
383
+ }
384
+ }
385
+ for (var i=0; i<xrefs.length; i++) {
386
+ const xref = xrefs[i];
387
+ tippyHover(xref, undefined, function(instance) {
388
+ instance.disable();
389
+ let url = xref.getAttribute('href');
390
+ let hash = undefined;
391
+ if (url.startsWith('#')) {
392
+ hash = url;
393
+ } else {
394
+ try { hash = new URL(url).hash; } catch {}
395
+ }
396
+ if (hash) {
397
+ const id = hash.replace(/^#\/?/, "");
398
+ const note = window.document.getElementById(id);
399
+ if (note !== null) {
400
+ try {
401
+ const html = processXRef(id, note.cloneNode(true));
402
+ instance.setContent(html);
403
+ } finally {
404
+ instance.enable();
405
+ instance.show();
406
+ }
407
+ } else {
408
+ // See if we can fetch this
409
+ fetch(url.split('#')[0])
410
+ .then(res => res.text())
411
+ .then(html => {
412
+ const parser = new DOMParser();
413
+ const htmlDoc = parser.parseFromString(html, "text/html");
414
+ const note = htmlDoc.getElementById(id);
415
+ if (note !== null) {
416
+ const html = processXRef(id, note);
417
+ instance.setContent(html);
418
+ }
419
+ }).finally(() => {
420
+ instance.enable();
421
+ instance.show();
422
+ });
423
+ }
424
+ } else {
425
+ // See if we can fetch a full url (with no hash to target)
426
+ // This is a special case and we should probably do some content thinning / targeting
427
+ fetch(url)
428
+ .then(res => res.text())
429
+ .then(html => {
430
+ const parser = new DOMParser();
431
+ const htmlDoc = parser.parseFromString(html, "text/html");
432
+ const note = htmlDoc.querySelector('main.content');
433
+ if (note !== null) {
434
+ // This should only happen for chapter cross references
435
+ // (since there is no id in the URL)
436
+ // remove the first header
437
+ if (note.children.length > 0 && note.children[0].tagName === "HEADER") {
438
+ note.children[0].remove();
439
+ }
440
+ const html = processXRef(null, note);
441
+ instance.setContent(html);
442
+ }
443
+ }).finally(() => {
444
+ instance.enable();
445
+ instance.show();
446
+ });
447
+ }
448
+ }, function(instance) {
449
+ });
450
+ }
451
+ let selectedAnnoteEl;
452
+ const selectorForAnnotation = ( cell, annotation) => {
453
+ let cellAttr = 'data-code-cell="' + cell + '"';
454
+ let lineAttr = 'data-code-annotation="' + annotation + '"';
455
+ const selector = 'span[' + cellAttr + '][' + lineAttr + ']';
456
+ return selector;
457
+ }
458
+ const selectCodeLines = (annoteEl) => {
459
+ const doc = window.document;
460
+ const targetCell = annoteEl.getAttribute("data-target-cell");
461
+ const targetAnnotation = annoteEl.getAttribute("data-target-annotation");
462
+ const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation));
463
+ const lines = annoteSpan.getAttribute("data-code-lines").split(",");
464
+ const lineIds = lines.map((line) => {
465
+ return targetCell + "-" + line;
466
+ })
467
+ let top = null;
468
+ let height = null;
469
+ let parent = null;
470
+ if (lineIds.length > 0) {
471
+ //compute the position of the single el (top and bottom and make a div)
472
+ const el = window.document.getElementById(lineIds[0]);
473
+ top = el.offsetTop;
474
+ height = el.offsetHeight;
475
+ parent = el.parentElement.parentElement;
476
+ if (lineIds.length > 1) {
477
+ const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]);
478
+ const bottom = lastEl.offsetTop + lastEl.offsetHeight;
479
+ height = bottom - top;
480
+ }
481
+ if (top !== null && height !== null && parent !== null) {
482
+ // cook up a div (if necessary) and position it
483
+ let div = window.document.getElementById("code-annotation-line-highlight");
484
+ if (div === null) {
485
+ div = window.document.createElement("div");
486
+ div.setAttribute("id", "code-annotation-line-highlight");
487
+ div.style.position = 'absolute';
488
+ parent.appendChild(div);
489
+ }
490
+ div.style.top = top - 2 + "px";
491
+ div.style.height = height + 4 + "px";
492
+ div.style.left = 0;
493
+ let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter");
494
+ if (gutterDiv === null) {
495
+ gutterDiv = window.document.createElement("div");
496
+ gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter");
497
+ gutterDiv.style.position = 'absolute';
498
+ const codeCell = window.document.getElementById(targetCell);
499
+ const gutter = codeCell.querySelector('.code-annotation-gutter');
500
+ gutter.appendChild(gutterDiv);
501
+ }
502
+ gutterDiv.style.top = top - 2 + "px";
503
+ gutterDiv.style.height = height + 4 + "px";
504
+ }
505
+ selectedAnnoteEl = annoteEl;
506
+ }
507
+ };
508
+ const unselectCodeLines = () => {
509
+ const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"];
510
+ elementsIds.forEach((elId) => {
511
+ const div = window.document.getElementById(elId);
512
+ if (div) {
513
+ div.remove();
514
+ }
515
+ });
516
+ selectedAnnoteEl = undefined;
517
+ };
518
+ // Handle positioning of the toggle
519
+ window.addEventListener(
520
+ "resize",
521
+ throttle(() => {
522
+ elRect = undefined;
523
+ if (selectedAnnoteEl) {
524
+ selectCodeLines(selectedAnnoteEl);
525
+ }
526
+ }, 10)
527
+ );
528
+ function throttle(fn, ms) {
529
+ let throttle = false;
530
+ let timer;
531
+ return (...args) => {
532
+ if(!throttle) { // first call gets through
533
+ fn.apply(this, args);
534
+ throttle = true;
535
+ } else { // all the others get throttled
536
+ if(timer) clearTimeout(timer); // cancel #2
537
+ timer = setTimeout(() => {
538
+ fn.apply(this, args);
539
+ timer = throttle = false;
540
+ }, ms);
541
+ }
542
+ };
543
+ }
544
+ // Attach click handler to the DT
545
+ const annoteDls = window.document.querySelectorAll('dt[data-target-cell]');
546
+ for (const annoteDlNode of annoteDls) {
547
+ annoteDlNode.addEventListener('click', (event) => {
548
+ const clickedEl = event.target;
549
+ if (clickedEl !== selectedAnnoteEl) {
550
+ unselectCodeLines();
551
+ const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active');
552
+ if (activeEl) {
553
+ activeEl.classList.remove('code-annotation-active');
554
+ }
555
+ selectCodeLines(clickedEl);
556
+ clickedEl.classList.add('code-annotation-active');
557
+ } else {
558
+ // Unselect the line
559
+ unselectCodeLines();
560
+ clickedEl.classList.remove('code-annotation-active');
561
+ }
562
+ });
563
+ }
564
+ const findCites = (el) => {
565
+ const parentEl = el.parentElement;
566
+ if (parentEl) {
567
+ const cites = parentEl.dataset.cites;
568
+ if (cites) {
569
+ return {
570
+ el,
571
+ cites: cites.split(' ')
572
+ };
573
+ } else {
574
+ return findCites(el.parentElement)
575
+ }
576
+ } else {
577
+ return undefined;
578
+ }
579
+ };
580
+ var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]');
581
+ for (var i=0; i<bibliorefs.length; i++) {
582
+ const ref = bibliorefs[i];
583
+ const citeInfo = findCites(ref);
584
+ if (citeInfo) {
585
+ tippyHover(citeInfo.el, function() {
586
+ var popup = window.document.createElement('div');
587
+ citeInfo.cites.forEach(function(cite) {
588
+ var citeDiv = window.document.createElement('div');
589
+ citeDiv.classList.add('hanging-indent');
590
+ citeDiv.classList.add('csl-entry');
591
+ var biblioDiv = window.document.getElementById('ref-' + cite);
592
+ if (biblioDiv) {
593
+ citeDiv.innerHTML = biblioDiv.innerHTML;
594
+ }
595
+ popup.appendChild(citeDiv);
596
+ });
597
+ return popup.innerHTML;
598
+ });
599
+ }
600
+ }
601
+ });
602
+ </script>
603
+ <nav class="page-navigation">
604
+ <div class="nav-page nav-page-previous">
605
+ <a href="../index.html" class="pagination-link" aria-label="Welcome">
606
+ <i class="bi bi-arrow-left-short"></i> <span class="nav-page-text">Welcome</span>
607
+ </a>
608
+ </div>
609
+ <div class="nav-page nav-page-next">
610
+ <a href="../patterns/structured-generation/intro.html" class="pagination-link" aria-label="Example Pattern: Structured Document Processing">
611
+ <span class="nav-page-text"><span class="chapter-number">2</span>&nbsp; <span class="chapter-title">Example Pattern: Structured Document Processing</span></span> <i class="bi bi-arrow-right-short"></i>
612
+ </a>
613
+ </div>
614
+ </nav>
615
+ </div> <!-- /content -->
616
+
617
+
618
+
619
+
620
+ </body></html>
search.json ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "objectID": "index.html",
4
+ "href": "index.html",
5
+ "title": "AI Design Patterns for GLAM",
6
+ "section": "",
7
+ "text": "Welcome",
8
+ "crumbs": [
9
+ "Welcome"
10
+ ]
11
+ },
12
+ {
13
+ "objectID": "index.html#about-this-book",
14
+ "href": "index.html#about-this-book",
15
+ "title": "AI Design Patterns for GLAM",
16
+ "section": "About This Book",
17
+ "text": "About This Book\n\n\n\n\n\n\nNote\n\n\n\nThis is a work in progress. Content will be added and updated over time. This is an early draft!\n\n\nThis book presents practical AI design patterns for Galleries, Libraries, Archives, and Museums (GLAM). Rather than focusing on specific technologies that will inevitably change, we document patterns which aim to capture reusable solutions to common challenges faced when implementing AI in GLAM contexts.\n\nGoal of this book\nThere are many exciting possibilities for applying AI in GLAM contexts, but also significant challenges. This book aims to provide a practical guide to help practitioners navigate this rapidly evolving landscape.\nWhilst there is a lot of hype around the adoption of AI in the GLAM sector there isn’t always clear guidance on how to approach projects in a structured way. This book aims to help fill that gap by providing a set of patterns that can be adapted and applied to a wide range of use cases.",
18
+ "crumbs": [
19
+ "Welcome"
20
+ ]
21
+ },
22
+ {
23
+ "objectID": "index.html#background-to-this-work",
24
+ "href": "index.html#background-to-this-work",
25
+ "title": "AI Design Patterns for GLAM",
26
+ "section": "Background to this work",
27
+ "text": "Background to this work\nThis documentation emerged from work with the National Library of Scotland, but the patterns and approaches apply broadly across the GLAM sector.\n\nAbout the Author\nDaniel van Strien",
28
+ "crumbs": [
29
+ "Welcome"
30
+ ]
31
+ },
32
+ {
33
+ "objectID": "patterns/structured-generation/intro.html",
34
+ "href": "patterns/structured-generation/intro.html",
35
+ "title": "2  Example Pattern: Structured Document Processing",
36
+ "section": "",
37
+ "text": "2.1 The Challenge\nMany GLAM institutions have vast collections of structured documents - index cards, forms, registers - containing valuable information locked in physical or image formats. Manual transcription doesn’t scale, but the structured nature of these documents makes them ideal candidates for AI-powered processing.",
38
+ "crumbs": [
39
+ "Structured Information Extraction",
40
+ "<span class='chapter-number'>2</span>  <span class='chapter-title'>Example Pattern: Structured Document Processing</span>"
41
+ ]
42
+ },
43
+ {
44
+ "objectID": "patterns/structured-generation/intro.html#dont-we-just-need-ocr",
45
+ "href": "patterns/structured-generation/intro.html#dont-we-just-need-ocr",
46
+ "title": "2  Example Pattern: Structured Document Processing",
47
+ "section": "2.2 Don’t we just need OCR?",
48
+ "text": "2.2 Don’t we just need OCR?",
49
+ "crumbs": [
50
+ "Structured Information Extraction",
51
+ "<span class='chapter-number'>2</span>  <span class='chapter-title'>Example Pattern: Structured Document Processing</span>"
52
+ ]
53
+ },
54
+ {
55
+ "objectID": "patterns/structured-generation/intro.html#solution-overview",
56
+ "href": "patterns/structured-generation/intro.html#solution-overview",
57
+ "title": "2  Example Pattern: Structured Document Processing",
58
+ "section": "2.3 Solution Overview",
59
+ "text": "2.3 Solution Overview\nThis pattern uses Visual Language Models (VLMs) combined with structured output generation to automatically extract information from document images while preserving the original structure and relationships.\n\n2.3.1 What this pattern looks like?",
60
+ "crumbs": [
61
+ "Structured Information Extraction",
62
+ "<span class='chapter-number'>2</span>  <span class='chapter-title'>Example Pattern: Structured Document Processing</span>"
63
+ ]
64
+ },
65
+ {
66
+ "objectID": "patterns/structured-generation/vlm-structured-generation.html",
67
+ "href": "patterns/structured-generation/vlm-structured-generation.html",
68
+ "title": "3  Structured Information Extraction with Vision Language Models",
69
+ "section": "",
70
+ "text": "3.1 Introduction\nIn this chapter we’ll start to look at how we can use Visual Language Models (VLMs) to extract structured information from images of documents.\nWe already saw what this looked like at a conceptual level in the previous chapter. In this chapter we’ll get hands on with some code examples to illustrate how this can be done in practice. To start we’ll focus on some relativey simple documents and tasks. This allows us to focus on the core concepts without getting bogged down in too many complexities. It also means we can use open source models that can be run locally.",
71
+ "crumbs": [
72
+ "Structured Information Extraction",
73
+ "<span class='chapter-number'>3</span>  <span class='chapter-title'>Structured Information Extraction with Vision Language Models</span>"
74
+ ]
75
+ },
76
+ {
77
+ "objectID": "patterns/structured-generation/vlm-structured-generation.html#the-sloane-index-cards-dataset",
78
+ "href": "patterns/structured-generation/vlm-structured-generation.html#the-sloane-index-cards-dataset",
79
+ "title": "3  Structured Information Extraction with Vision Language Models",
80
+ "section": "3.2 The Sloane Index Cards Dataset",
81
+ "text": "3.2 The Sloane Index Cards Dataset\nWe’ll use the Sloane Index Cards Dataset from Hugging Face for our examples. This is a publicly available dataset that is well suited to demonstrating structured information extraction with VLMs.\n\nThe files in this dataset are derived from microfilm copies of the original library catalogue of Sir Hans Sloane, now presented across 9 volumes, Sloane MS 3972 C 1-8, and the name index to the Sloane library catalogue, Sloane MS 3972 D. The catalogues are crucial for understanding the development of Sloane’s collections, the present-day collections of the British Library, British Museum and Natural History Museum, and to identifying collection items which are now dispersed across a number of institutions.\n\nThe dataset is available in Parquet format on Hugging Face so it can be easily loaded using the datasets library.\nLet’s load the dataset and take a look at one row.\n\nfrom datasets import load_dataset\n\nds = load_dataset(\"biglam/sloane-catalogues\", split=\"train\")\nds[0]\n\n{'image': &lt;PIL.JpegImagePlugin.JpegImageFile image mode=L size=3144x2267&gt;,\n 'filename': 'sloane_ms_3972_c!1_001.jpg',\n 'collection': 'sloane_ms_3972_c!1_jpegs',\n 'page_number': 1,\n 'page_index_in_directory': 0,\n 'source': 'British Library Sloane Manuscripts'}\n\n\nWe can see that we have a dictionary that contains an image as well as some additional metadata fields.\nLet’s take a look at an actual example image from the dataset.\n\nds[0][\"image\"]\n\n\n\n\n\n\n\n\nLet’s look at a couple more examples to get a sense of the variety in the dataset.\n\nds[2][\"image\"]\n\n\n\n\n\n\n\n\none more from later in the dataset\n\nds[50][\"image\"]\n\n\n\n\n\n\n\n\nWe can see we have a mixture of different types of digitised content here including index cards from the original microfilm as well as the actual handwritten manuscript pages from Sloane’s collection.\nWe’ll look at how we can use VLMs to work with kind of collection.",
82
+ "crumbs": [
83
+ "Structured Information Extraction",
84
+ "<span class='chapter-number'>3</span>  <span class='chapter-title'>Structured Information Extraction with Vision Language Models</span>"
85
+ ]
86
+ },
87
+ {
88
+ "objectID": "patterns/structured-generation/vlm-structured-generation.html#setup",
89
+ "href": "patterns/structured-generation/vlm-structured-generation.html#setup",
90
+ "title": "3  Structured Information Extraction with Vision Language Models",
91
+ "section": "3.3 Setup",
92
+ "text": "3.3 Setup\n\n3.3.1 Start LM Studio\n\nWe’ll use LM Studio for this notebook. Since we’ll be using the OpenAI Python client to interact with models run by LM Studio it will be fairly easy to switch to a different model/tool for running the models since many tools have an OpenAI compatible endpoint.\n\n\n\n\n\n\nIf you haven’t already, make sure to install LM Studio by following the instructions on the LM Studio website.\n\n\n\nWhile LM Studio is primarily known as a GUI tool for interacting with local LLMs, it also includes a built-in API server that is compatible with the OpenAI API. This allows us to use the same code we would use for OpenAI hosted models to interact with local models running in LM Studio.\nLM Studio has a command line interface (CLI) that we can use to start the server. We can check that the lms command is available here:\n\n!lms\n\n\n __ __ ___ ______ ___ _______ ____\n\n / / / |/ / / __/ /___ _____/ (_)__ / ___/ / / _/\n\n / /__/ /|_/ / _\\ \\/ __/ // / _ / / _ \\ / /__/ /___/ / \n\n/____/_/ /_/ /___/\\__/\\_,_/\\_,_/_/\\___/ \\___/____/___/ \n\n\n\nlms - LM Studio CLI - v0.0.47\n\nGitHub: https://github.com/lmstudio-ai/lms\n\n\n\nUsage\n\nUsage: lms [options] [command]\n\n\n\nLM Studio CLI\n\n\n\nOptions:\n\n -h, --help display help for command\n\n\n\nManage Models:\n\n get Searching and downloading a model from online.\n\n import Import a model file into LM Studio\n\n ls List all downloaded models\n\n\n\nUse Models:\n\n chat Open an interactive chat with the currently loaded model.\n\n load Load a model\n\n ps List all loaded models\n\n server Commands for managing the local server\n\n unload Unload a model\n\n\n\nDevelop & Publish Artifacts:\n\n clone Clone an artifact from LM Studio Hub to a local folder.\n\n create Create a new project with scaffolding\n\n dev Starts the development server for the plugin in the current folder.\n\n login Authenticate with LM Studio\n\n push Uploads the plugin in the current folder to LM Studio Hub.\n\n\n\nSystem Management:\n\n bootstrap Bootstrap the CLI\n\n flags Set or get experiment flags\n\n log Log operations. Currently only supports streaming logs from LM Studio via `lms log\n\n stream`\n\n runtime Manage runtime engines\n\n status Prints the status of LM Studio\n\n version Prints the version of the CLI\n\n\n\nCommands:\n\n help display help for command\n\n\n\n\nWe can check that LM Studio server is running by using the lms server start command.\n\n!lms server start\n\nSuccess! Server is now running on port 1234\n\n\n\n\n3.3.2 Connect to LM Studio\nWe can use the OpenAI Python client (TODO add link), to connect to LM studio. By default LM studio is running on port 1234 on localhost so we can connect to it here. The default api_key is lm-studio.\n\nfrom openai import OpenAI\n\nclient = OpenAI(\n base_url=\"http://localhost:1234/v1\",\n api_key=\"lm-studio\"\n)\n\nWe can use various different methods with the client, for example we can access the models available:\n\nfrom rich import print as rprint\nmodels = client.models.list()\nrprint(f\"Connected. Models: {[m.id for m in models.data]}\")\n\nConnected. Models: ['qwen3-vl-2b-instruct-mlx', 'qwen/qwen2.5-vl-7b', 'qwen/qwen3-vl-8b', 'qwen/qwen3-vl-4b', \n'text-embedding-nomic-embed-text-v1.5', 'qwen3-vl-30b-a3b-instruct', 'qwen3-vl-30b-a3b-thinking@4bit', \n'qwen3-vl-30b-a3b-thinking@3bit', 'qwen/qwen3-4b-thinking-2507', 'google/gemma-3-12b', 'google/gemma-3-4b', \n'qwen2-0.5b-instruct-fingreylit', 'google/gemma-3n-e4b', 'granite-vision-3.3-2b', 'ibm/granite-4-h-tiny', \n'iconclass-vlm', 'mlx-community/qwen2.5-vl-3b-instruct', 'lmstudio-community/qwen2.5-vl-3b-instruct', \n'lfm2-vl-1.6b', 'mimo-vl-7b-rl-2508@q4_k_s', 'mimo-vl-7b-rl-2508@q8_0', 'qwen3-30b-a3b-instruct-2507', \n'qwen3-4b-instruct-2507-mlx', 'openai/gpt-oss-20b', 'mistralai/mistral-small-3.2', \n'qwen3-30b-a3b-instruct-2507-mlx', 'liquid/lfm2-1.2b', 'smollm3-3b-mlx', 'unsloth/smollm3-3b', \n'ggml-org/smollm3-3b', 'mlx-community/smollm3-3b']\n\n\n\n\n\n\n\n\n\n\nNoteAlternative: Using Hugging Face Inference Providers\n\n\n\n\n\nThis is WIP. TODO add an alternative path that doesn’t require any local model hosting using Inference Providers instead. sl\n\nfrom huggingface_hub import list_models\nfrom rich import print as rprint\nvlm_models = list(list_models(filter=\"image-text-to-text\", inference_provider='all', sort='trending_score', expand=['safetensors']))\nrprint(vlm_models[:5])\nvlm_models[0].safetensors.total\n\n[\n ModelInfo(\n id='Qwen/Qwen3-VL-8B-Instruct',\n author=None,\n sha=None,\n created_at=None,\n last_modified=None,\n private=None,\n disabled=None,\n downloads=None,\n downloads_all_time=None,\n gated=None,\n gguf=None,\n inference=None,\n inference_provider_mapping=None,\n likes=None,\n library_name=None,\n tags=None,\n pipeline_tag=None,\n mask_token=None,\n card_data=None,\n widget_data=None,\n model_index=None,\n config=None,\n transformers_info=None,\n trending_score=116,\n siblings=None,\n spaces=None,\n safetensors=SafeTensorsInfo(parameters={'BF16': 8767123696}, total=8767123696),\n security_repo_status=None,\n xet_enabled=None\n ),\n ModelInfo(\n id='Qwen/Qwen3-VL-30B-A3B-Instruct',\n author=None,\n sha=None,\n created_at=None,\n last_modified=None,\n private=None,\n disabled=None,\n downloads=None,\n downloads_all_time=None,\n gated=None,\n gguf=None,\n inference=None,\n inference_provider_mapping=None,\n likes=None,\n library_name=None,\n tags=None,\n pipeline_tag=None,\n mask_token=None,\n card_data=None,\n widget_data=None,\n model_index=None,\n config=None,\n transformers_info=None,\n trending_score=38,\n siblings=None,\n spaces=None,\n safetensors=SafeTensorsInfo(parameters={'BF16': 31070754032}, total=31070754032),\n security_repo_status=None,\n xet_enabled=None\n ),\n ModelInfo(\n id='Qwen/Qwen2.5-VL-7B-Instruct',\n author=None,\n sha=None,\n created_at=None,\n last_modified=None,\n private=None,\n disabled=None,\n downloads=None,\n downloads_all_time=None,\n gated=None,\n gguf=None,\n inference=None,\n inference_provider_mapping=None,\n likes=None,\n library_name=None,\n tags=None,\n pipeline_tag=None,\n mask_token=None,\n card_data=None,\n widget_data=None,\n model_index=None,\n config=None,\n transformers_info=None,\n trending_score=16,\n siblings=None,\n spaces=None,\n safetensors=SafeTensorsInfo(parameters={'BF16': 8292166656}, total=8292166656),\n security_repo_status=None,\n xet_enabled=None\n ),\n ModelInfo(\n id='google/gemma-3-27b-it',\n author=None,\n sha=None,\n created_at=None,\n last_modified=None,\n private=None,\n disabled=None,\n downloads=None,\n downloads_all_time=None,\n gated=None,\n gguf=None,\n inference=None,\n inference_provider_mapping=None,\n likes=None,\n library_name=None,\n tags=None,\n pipeline_tag=None,\n mask_token=None,\n card_data=None,\n widget_data=None,\n model_index=None,\n config=None,\n transformers_info=None,\n trending_score=16,\n siblings=None,\n spaces=None,\n safetensors=SafeTensorsInfo(parameters={'BF16': 27432406640}, total=27432406640),\n security_repo_status=None,\n xet_enabled=None\n ),\n ModelInfo(\n id='Qwen/Qwen3-VL-30B-A3B-Thinking',\n author=None,\n sha=None,\n created_at=None,\n last_modified=None,\n private=None,\n disabled=None,\n downloads=None,\n downloads_all_time=None,\n gated=None,\n gguf=None,\n inference=None,\n inference_provider_mapping=None,\n likes=None,\n library_name=None,\n tags=None,\n pipeline_tag=None,\n mask_token=None,\n card_data=None,\n widget_data=None,\n model_index=None,\n config=None,\n transformers_info=None,\n trending_score=14,\n siblings=None,\n spaces=None,\n safetensors=SafeTensorsInfo(parameters={'BF16': 31070754032}, total=31070754032),\n security_repo_status=None,\n xet_enabled=None\n )\n]\n\n\n\n8767123696\n\n\n\nmax_params = 8767123696 # ~8.7B parameters\nvlm_models = list_models(filter=\"image-text-to-text\", inference_provider='all', sort='trending_score', expand=['safetensors'])\nvlm_models_small = [m for m in vlm_models if m.safetensors and m.safetensors.total &lt;= max_params]\n[m.id for m in vlm_models_small]\n\n['Qwen/Qwen3-VL-8B-Instruct',\n 'Qwen/Qwen2.5-VL-7B-Instruct',\n 'xtuner/llava-llama-3-8b-v1_1',\n 'TheFinAI/StockLLM',\n 'dinalad0/my-LLM_RAG-model']\n\n\n\nimport os\nfrom openai import OpenAI\nfrom dotenv import load_dotenv\nload_dotenv()\n\nhf_client = OpenAI(\n base_url=\"https://router.huggingface.co/v1\",\n api_key=os.environ[\"HF_TOKEN\"],\n)\n\ncompletion = hf_client.chat.completions.create(\n model=\"Qwen/Qwen3-VL-8B-Instruct\",\n messages=[\n {\n \"role\": \"user\",\n \"content\": [\n {\n \"type\": \"text\",\n \"text\": \"Describe this image in one sentence.\"\n },\n {\n \"type\": \"image_url\",\n \"image_url\": {\n \"url\": \"https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg\"\n }\n }\n ]\n }\n ],\n)\n\nprint(completion.choices[0].message)\n\nChatCompletionMessage(content='The Statue of Liberty stands tall on her island in New York Harbor, with the iconic Manhattan skyline rising majestically behind her under a clear blue sky.', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None)",
93
+ "crumbs": [
94
+ "Structured Information Extraction",
95
+ "<span class='chapter-number'>3</span>  <span class='chapter-title'>Structured Information Extraction with Vision Language Models</span>"
96
+ ]
97
+ },
98
+ {
99
+ "objectID": "patterns/structured-generation/vlm-structured-generation.html#basic-vlm-query",
100
+ "href": "patterns/structured-generation/vlm-structured-generation.html#basic-vlm-query",
101
+ "title": "3  Structured Information Extraction with Vision Language Models",
102
+ "section": "3.4 Basic VLM Query",
103
+ "text": "3.4 Basic VLM Query\nLet’s start by defining a simple function that we can use to query a VLM with an image and a prompt. This function will handle converting the image to base64 and sending the request to the model.\nFor this notebook we’ll default to using the qwen3-vl-2b model which is a small 2 billion parameter model that can be run locally in LM Studio. We may want to experiment with different models later on or try slightly bigger models but this one should be sufficient for our initial experiments.\n\n\nCode\nimport base64\nfrom PIL.Image import Image as PILImage\nfrom io import BytesIO\n\ndef query_image(image: str | PILImage, prompt: str, model: str='qwen3-vl-2b-instruct-mlx', max_image_size: int=1024):\n \"\"\"Query VLM with an image.\"\"\"\n if isinstance(image, PILImage):\n # Convert PIL Image to bytes and encode to base64\n buffered = BytesIO()\n # ensure image is not too big\n if image.size &gt; (max_image_size, max_image_size):\n image.thumbnail((max_image_size, max_image_size)) \n image.save(buffered, format=\"JPEG\")\n image_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')\n else:\n # Assume image is a file path\n with open(image, \"rb\") as f:\n image_base64 = base64.b64encode(f.read()).decode('utf-8')\n #\n # Query\n response = client.chat.completions.create(\n model=model,\n messages=[{\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"text\", \"text\": prompt},\n {\"type\": \"image_url\", \"image_url\": {\"url\": f\"data:image/jpeg;base64,{image_base64}\"}}\n ]\n }]\n )\n return response.choices[0].message.content",
104
+ "crumbs": [
105
+ "Structured Information Extraction",
106
+ "<span class='chapter-number'>3</span>  <span class='chapter-title'>Structured Information Extraction with Vision Language Models</span>"
107
+ ]
108
+ },
109
+ {
110
+ "objectID": "patterns/structured-generation/vlm-structured-generation.html#simple-vlm-query-example",
111
+ "href": "patterns/structured-generation/vlm-structured-generation.html#simple-vlm-query-example",
112
+ "title": "3  Structured Information Extraction with Vision Language Models",
113
+ "section": "3.5 102: Simple VLM Query Example",
114
+ "text": "3.5 102: Simple VLM Query Example\nTo get started let’s do a simple query to describe an image from the dataset.\n\nimage = ds[0][\"image\"]\n\n# Query the VLM to describe the image\ndescription = query_image(image, \"Describe this image.\", model='qwen3-vl-2b-instruct-mlx')\nrprint(description)\n\nThis is a library reference card from The British Library's Reference Division, specifically for the Reprographic \nSection. It is a form used to catalog and manage manuscripts.\n\nThe card has several fields filled in with handwritten information, likely for a specific manuscript. The main \ndetails are:\n\n- **Department:** Manuscripts\n- **Shelfmark:** SLDANE 3972 C. (Vol 1)\n- **Order SCH No:** 98876\n- **Author:** SIR HANS SLOANES LIBRARY (This appears to be a typographical error, likely meant to be \"SIR HANS \nSLOANES\")\n- **Title:** CATALOGUE OF SIR HANS SLOANES LIBRARY\n- **Place and date of publication:** (This field is blank)\n- **Centimetres:** 1, 2, 3, 4, 5\n- **Inches:** 1, 2\n\nThe card also includes a reduction number: \"RD RS8\" and \"Reduction 12\". The card is for a manuscript titled \n\"Catalogue of Sir Hans Sloane's Library\" with the shelfmark SLDANE 3972 C. (Vol 1) and Order SCH No 98876.\n\nThe card is from The British Library, Reprographic Section, and the address is Gt Russell St, London WC1B 3DG.\n\n\n\nWe can see we get a fairly useful description of the card. If we compare against the image we can see most of the details it mentions appear to be largely correct.\n\nimage\n\n\n\n\n\n\n\n\nThere are workflows where open ended description like this could be useful but this isn’t usually the kind of format we want if we want to take some action or do something based on the predictions of the model. In these cases it’s usually nice to have some more controlled output, for example, a label.",
115
+ "crumbs": [
116
+ "Structured Information Extraction",
117
+ "<span class='chapter-number'>3</span>  <span class='chapter-title'>Structured Information Extraction with Vision Language Models</span>"
118
+ ]
119
+ },
120
+ {
121
+ "objectID": "patterns/structured-generation/vlm-structured-generation.html#classification",
122
+ "href": "patterns/structured-generation/vlm-structured-generation.html#classification",
123
+ "title": "3  Structured Information Extraction with Vision Language Models",
124
+ "section": "3.6 Classification",
125
+ "text": "3.6 Classification\n\nWe’ll define a fairly simple prompt that asks the VLM to decide if a page is one of three categories. We describe each of these categopries and then ask the model to only return one of these as the output. We’ll do this for ten examples and we’ll also log how long it’s taking.\n\nimport time\nfrom tqdm.auto import tqdm\n\nsample_size = 10\n\nsample = ds.take(sample_size)\n\nprompt = \"\"\"Classify this image into one of the following categories:\n\n1. **Index/Reference Card**: A library catalog or reference card\n\n2. **Manuscript Page**: A handwritten or historical document page\n\n3. **Other**: Any document that doesn't fit the above categories\n\nExamine the overall structure, layout, and content type to determine the classification. Focus on whether the document is a structured catalog/reference tool (Index Card) or a historical manuscript with continuous text (Manuscript Page).\n\nReturn only the category name: \"Index/Reference Card\", \"Manuscript Page\", or \"Other\"\n\"\"\"\n\nresults = []\n# Time the execution using standard Python\nstart_time = time.time()\nfor row in tqdm(sample):\n image = row['image']\n results.append(query_image(image, prompt))\nelapsed_time = time.time() - start_time\nprint(f\"Execution time: {elapsed_time:.2f} seconds\")\nrprint(results)\n\n\n\n\nExecution time: 100.05 seconds\n\n\n[\n 'Index/Reference Card',\n 'Manuscript Page',\n 'Manuscript Page',\n 'Manuscript Page',\n 'Manuscript Page',\n 'Manuscript Page',\n 'Manuscript Page',\n 'Manuscript Page',\n 'Manuscript Page',\n 'Manuscript Page'\n]\n\n\n\nLet’s check the result that was predicted as “index/reference card”\n\nsample[0]['image']\n\n\n\n\n\n\n\n\nWe can extrapolate how long this would take for the full dataset\n\n# Calculate average time per image\navg_time_per_image = elapsed_time / sample_size\n\n# Project time for full dataset\ntotal_images = len(ds)\nprojected_time = avg_time_per_image * total_images\n\nprint(f\"Sample processing time: {elapsed_time:.2f} seconds ({elapsed_time/60:.2f} minutes)\")\nprint(f\"Average time per image: {avg_time_per_image:.2f} seconds\")\nprint(f\"Total images in dataset: {total_images}\")\nprint(f\"Projected time for full dataset: {projected_time/60:.2f} minutes ({projected_time/3600:.2f} hours)\")\n\nSample processing time: 100.05 seconds (1.67 minutes)\nAverage time per image: 10.01 seconds\nTotal images in dataset: 2734\nProjected time for full dataset: 455.91 minutes (7.60 hours)\n\n\n\n3.6.1 Classifying with structured labels\nIn the previous example, we relied on the model to return the label in the correct format. While this often works, it can sometimes lead to inconsistencies in the output. To address this, we can use Pydantic models to define a structured output format. This way, we can ensure that the output adheres to a specific schema.\nIn this example, we’ll define a Pydantic model for our classification task. The model will have a single field category which can take one of three literal values \"Index/Reference Card\", \"Manuscript Page\", or \"other\".\nWhat this means in practice is that the model will only be able to return one of these three values for the category field.\n\nfrom pydantic import BaseModel, Field\nfrom typing import Literal\n\nclass PageCategory(BaseModel):\n category: Literal[\"Index/Reference Card\", \"Manuscript Page\", \"other\"] = Field(\n ..., description=\"The category of the image\"\n )\n\nWhen using the OpenAI client we can specify this Pydantic model as the response_format when making the request. This tells the model to return the output in a format that can be parsed into the Pydantic model (the APIs for this are still evolving so may change slightly over time).\n\nbuffered = BytesIO()\nimage.save(buffered, format=\"JPEG\")\nimage_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')\ncompletion = client.beta.chat.completions.parse(\n model=\"qwen/qwen2.5-vl-7b\",\n messages=[\n {\n \"role\": \"user\",\n \"content\": [\n {\n \"type\": \"text\",\n \"text\": prompt,\n },\n {\n \"type\": \"image_url\",\n \"image_url\": {\"url\": f\"data:image/jpeg;base64,{image_base64}\"},\n },\n ],\n },\n ],\n max_tokens=100,\n temperature=0.7,\n response_format=PageCategory,\n)\nrprint(completion)\nrprint(completion.choices[0].message.parsed)\n\nParsedChatCompletion[PageCategory](\n id='chatcmpl-v8bizojixwds0z7pg8j0th',\n choices=[\n ParsedChoice[PageCategory](\n finish_reason='stop',\n index=0,\n logprobs=None,\n message=ParsedChatCompletionMessage[PageCategory](\n content='{\"category\": \"Manuscript Page\"}',\n refusal=None,\n role='assistant',\n annotations=None,\n audio=None,\n function_call=None,\n tool_calls=None,\n parsed=PageCategory(category='Manuscript Page')\n )\n )\n ],\n created=1761588374,\n model='qwen/qwen2.5-vl-7b',\n object='chat.completion',\n service_tier=None,\n system_fingerprint='qwen/qwen2.5-vl-7b',\n usage=CompletionUsage(\n completion_tokens=10,\n prompt_tokens=142,\n total_tokens=152,\n completion_tokens_details=None,\n prompt_tokens_details=None\n ),\n stats={}\n)\n\n\n\nPageCategory(category='Manuscript Page')\n\n\n\n\nimage\n\n\n\n\n\n\n\n\n\n\n3.6.2 Beyond classifying\nSo far we’ve focused on classifying images but what if we want to extract information from the images? Let’s take the first example from the dataset again.\n\nindex_image = ds[0]['image']\nindex_image\n\n\n\n\n\n\n\n\nIf we have an image like this we don’t just want to assign a label from it (we may do this as a first step) we actually want to extract the various fields from the card in a structured way. We can again use a Pydantic model to define the structure of the data we want to extract.\n\nfrom pydantic import BaseModel, Field\nfrom typing import Optional\n\n\nclass BritishLibraryReprographicCard(BaseModel):\n \"\"\"\n Pydantic model for extracting information from British Library Reference Division \n reprographic cards used to document manuscripts and other materials.\n \"\"\"\n \n department: str = Field(\n ..., \n description=\"The division that holds the material (e.g., 'MANUSCRIPTS')\"\n )\n \n shelfmark: str = Field(\n ..., \n description=\"The library's classification/location code (e.g., 'SLOANE 3972.C. (VOL 1)')\"\n )\n \n order: str = Field(\n ..., \n description=\"Order reference, typically starting with 'SCH NO' followed by numbers\"\n )\n \n author: Optional[str] = Field(\n None, \n description=\"Author name if present, null if blank or marked with diagonal line\"\n )\n \n title: str = Field(\n ..., \n description=\"The name of the work or manuscript\"\n )\n \n place_and_date_of_publication: Optional[str] = Field(\n None, \n description=\"Place and date of publication if present, null if blank\"\n )\n \n reduction: int = Field(\n ..., \n description=\"The reduction number shown at the bottom of the card\"\n )\n\nWe’ll now create a function to handle the querying process using this structured schema.\n\ndef query_image_structured(image, prompt, schema, model='qwen3-vl-2b-instruct-mlx'):\n \"\"\"\n Query VLM with an image and get structured output based on a Pydantic schema.\n \n Args:\n image: PIL Image or file path to the image\n prompt: Text prompt describing what to extract\n schema: Pydantic model class defining the expected output structure\n model: Model ID to use for the query\n \n Returns:\n Parsed Pydantic model instance with the extracted data\n \"\"\"\n # Convert image to base64\n if isinstance(image, PILImage):\n buffered = BytesIO()\n image.save(buffered, format=\"JPEG\")\n image_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')\n else:\n with open(image, \"rb\") as f:\n image_base64 = base64.b64encode(f.read()).decode('utf-8')\n \n # Query with structured output\n completion = client.beta.chat.completions.parse(\n model=model,\n messages=[{\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"text\", \"text\": prompt},\n {\"type\": \"image_url\", \"image_url\": {\"url\": f\"data:image/jpeg;base64,{image_base64}\"}}\n ]\n }],\n response_format=schema,\n temperature=0.3 # Lower temperature for more consistent extraction\n )\n \n # Return the parsed structured data\n return completion.choices[0].message.parsed\n\nWe also need to define a prompt that describes what information we want to extract from the card.\n\n# Example usage\nextraction_prompt = \"\"\"\nExtract the information from this British Library card into structured data (JSON format).\n\nRead each field on the card and extract the following information:\n- department: The division name (e.g., \"MANUSCRIPTS\")\n- shelfmark: The catalog number (e.g., \"SLOANE 3972.C. (VOL 1)\")\n- order: The SCH NO reference number\n- author: The author name, or null if blank\n- title: The full title of the work\n- place_and_date_of_publication: Publication info, or null if blank\n- reduction: The reduction number (as integer) at bottom of card\n\nReturn the exact text as shown on the card. For empty fields with diagonal lines or no text, use null.\n\"\"\"\nresult = query_image_structured(index_image, extraction_prompt, BritishLibraryReprographicCard)\nrprint(result)\n\nBritishLibraryReprographicCard(\n department='MANUSCRIPTS',\n shelfmark='SLOANE 3972.C. (VOL 1)',\n order='98876',\n author='HANS SLOANES',\n title='CATALOGUE OF SIR HANS SLOANES LIBRARY',\n place_and_date_of_publication=None,\n reduction=12\n)\n\n\n\n\nrprint(result)\n\nBritishLibraryReprographicCard(\n department='MANUSCRIPTS',\n shelfmark='SLOANE 3972.C. (VOL 1)',\n order='98876',\n author='HANS SLOANES',\n title='CATALOGUE OF SIR HANS SLOANES LIBRARY',\n place_and_date_of_publication=None,\n reduction=12\n)\n\n\n\n\nindex_image",
126
+ "crumbs": [
127
+ "Structured Information Extraction",
128
+ "<span class='chapter-number'>3</span>  <span class='chapter-title'>Structured Information Extraction with Vision Language Models</span>"
129
+ ]
130
+ },
131
+ {
132
+ "objectID": "patterns/structured-generation/advisor-index-cards.html",
133
+ "href": "patterns/structured-generation/advisor-index-cards.html",
134
+ "title": "4  Practical Application: Advisor Index Card Extraction",
135
+ "section": "",
136
+ "text": "4.1 Introduction\nThis chapter demonstrates a practical application of VLM-based structured extraction on a real-world GLAM digitization project: extracting structured metadata from historical index cards from the National Library of Scotland’s Advocate’s Library collection.\nUnlike the previous chapter which focused on explaining VLM concepts and setup, this chapter assumes you’re familiar with the basics and focuses on:\nThis represents a real consulting engagement with specific requirements from library curators.",
137
+ "crumbs": [
138
+ "Structured Information Extraction",
139
+ "<span class='chapter-number'>4</span>  <span class='chapter-title'>Practical Application: Advisor Index Card Extraction</span>"
140
+ ]
141
+ },
142
+ {
143
+ "objectID": "patterns/structured-generation/advisor-index-cards.html#introduction",
144
+ "href": "patterns/structured-generation/advisor-index-cards.html#introduction",
145
+ "title": "4  Practical Application: Advisor Index Card Extraction",
146
+ "section": "",
147
+ "text": "Designing schemas for real catalog requirements\nRunning extractions at scale\nEvaluating extraction quality - different strategies for assessing accuracy\nHandling edge cases and failures",
148
+ "crumbs": [
149
+ "Structured Information Extraction",
150
+ "<span class='chapter-number'>4</span>  <span class='chapter-title'>Practical Application: Advisor Index Card Extraction</span>"
151
+ ]
152
+ },
153
+ {
154
+ "objectID": "patterns/structured-generation/advisor-index-cards.html#the-task-advisor-index-cards",
155
+ "href": "patterns/structured-generation/advisor-index-cards.html#the-task-advisor-index-cards",
156
+ "title": "4  Practical Application: Advisor Index Card Extraction",
157
+ "section": "4.2 The Task: Advisor Index Cards",
158
+ "text": "4.2 The Task: Advisor Index Cards\nThe National Library of Scotland has a collection of historical index cards documenting manuscripts and correspondence. Each card follows a consistent format:\n\nSurname: Family name\nForenames: Given names\nEpithet: Role, title, or occupation\nMS no: Manuscript reference number\nDescription: Document type and date\nFolios: Page references\n\nThe goal is to extract this structured information to enable: - Searchable digital catalog - Integration with library management systems - Research access to historical collections\n\n4.2.1 Example Cards\nLet’s look at a few sample cards from the collection:\n\nfrom pathlib import Path\nimport matplotlib.pyplot as plt\n\nimages = list(Path(\"assets/vllm-structured-generation/indexes\").rglob(\"*.JPG\"))\nimages\n\n# display a grid of images using matplotlib (len of images)\nnumber_of_images = len(images)\ncols = 3\nrows = (number_of_images + cols - 1) // cols\nfig, axs = plt.subplots(rows, cols, figsize=(15, 5 * rows))\nfor i, img_path in enumerate(images):\n img = plt.imread(img_path)\n ax = axs[i // cols, i % cols] if rows &gt; 1 else axs[i % cols]\n ax.imshow(img)\n ax.axis('off')\n ax.set_title(img_path.stem)\nplt.tight_layout()\nplt.show()",
159
+ "crumbs": [
160
+ "Structured Information Extraction",
161
+ "<span class='chapter-number'>4</span>  <span class='chapter-title'>Practical Application: Advisor Index Card Extraction</span>"
162
+ ]
163
+ },
164
+ {
165
+ "objectID": "patterns/structured-generation/advisor-index-cards.html#schema-design",
166
+ "href": "patterns/structured-generation/advisor-index-cards.html#schema-design",
167
+ "title": "4  Practical Application: Advisor Index Card Extraction",
168
+ "section": "4.3 Schema Design",
169
+ "text": "4.3 Schema Design\nWorking with the library curators, we designed a schema that matches their cataloging requirements. The schema is intentionally simple - complex schemas are harder for VLMs to extract reliably.\n\nfrom pydantic import BaseModel, Field\nfrom typing import Optional\n\nclass IndexCardEntry(BaseModel):\n \"\"\"Schema for index card extraction matching curator specification\"\"\"\n \n surname: str = Field(..., description=\"Family name as written on card\")\n forenames: Optional[str] = Field(None, description=\"Given names\")\n epithet: Optional[str] = Field(None, description=\"Title, occupation, or role\")\n ms_no: str = Field(..., description=\"Manuscript number\")\n description: str = Field(..., description=\"Document description with date\")\n folios: str = Field(..., description=\"Folio reference\")\n \n failed_to_parse: bool = Field(\n False,\n description=\"Set to True if the card cannot be reliably extracted (illegible, damaged, etc.)\"\n )\n notes: Optional[str] = Field(\n None, \n description=\"Optional notes about the card: handwritten annotations, ambiguities, \"\n \"corrections, or reasons for failed parsing.\"\n )\n\n# Display the schema\nprint(IndexCardEntry.model_json_schema())\n\n{'description': 'Schema for index card extraction matching curator specification', 'properties': {'surname': {'description': 'Family name as written on card', 'title': 'Surname', 'type': 'string'}, 'forenames': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Given names', 'title': 'Forenames'}, 'epithet': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Title, occupation, or role', 'title': 'Epithet'}, 'ms_no': {'description': 'Manuscript number', 'title': 'Ms No', 'type': 'string'}, 'description': {'description': 'Document description with date', 'title': 'Description', 'type': 'string'}, 'folios': {'description': 'Folio reference', 'title': 'Folios', 'type': 'string'}, 'failed_to_parse': {'default': False, 'description': 'Set to True if the card cannot be reliably extracted (illegible, damaged, etc.)', 'title': 'Failed To Parse', 'type': 'boolean'}, 'notes': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Optional notes about the card: handwritten annotations, ambiguities, corrections, or reasons for failed parsing.', 'title': 'Notes'}}, 'required': ['surname', 'ms_no', 'description', 'folios'], 'title': 'IndexCardEntry', 'type': 'object'}\n\n\n\n4.3.1 Design Decisions\nKey choices in this schema:\n\nSimple over complex: Only 6 core fields + 2 metadata fields\nString types: Manuscript numbers stay as strings (can be complex like “33.1.7”)\nOptional fields: Forenames, epithet, and notes can be missing\nQuality tracking: failed_to_parse flag and notes field for edge cases\nPreserve original: Don’t over-normalize - keep dates/descriptions as written",
170
+ "crumbs": [
171
+ "Structured Information Extraction",
172
+ "<span class='chapter-number'>4</span>  <span class='chapter-title'>Practical Application: Advisor Index Card Extraction</span>"
173
+ ]
174
+ },
175
+ {
176
+ "objectID": "patterns/structured-generation/advisor-index-cards.html#setup",
177
+ "href": "patterns/structured-generation/advisor-index-cards.html#setup",
178
+ "title": "4  Practical Application: Advisor Index Card Extraction",
179
+ "section": "4.4 Setup",
180
+ "text": "4.4 Setup\nWe’ll reuse the VLM setup from the previous chapter. If you haven’t already, make sure LM Studio is running with a VLM loaded.\n\nfrom openai import OpenAI\nimport base64\nfrom io import BytesIO\nfrom PIL import Image as PILImage\n\n\n\nclient = OpenAI(\n base_url=\"http://localhost:1234/v1\",\n api_key=\"lm-studio\"\n)\n\n\nclient.models.list() \n\nSyncPage[Model](data=[Model(id='qwen/qwen3-vl-4b', created=None, object='model', owned_by='organization_owner'), Model(id='qwen3-vl-2b-instruct-mlx', created=None, object='model', owned_by='organization_owner'), Model(id='qwen/qwen3-vl-8b', created=None, object='model', owned_by='organization_owner'), Model(id='text-embedding-nomic-embed-text-v1.5', created=None, object='model', owned_by='organization_owner'), Model(id='qwen3-vl-30b-a3b-instruct', created=None, object='model', owned_by='organization_owner'), Model(id='qwen3-vl-30b-a3b-thinking@4bit', created=None, object='model', owned_by='organization_owner'), Model(id='qwen3-vl-30b-a3b-thinking@3bit', created=None, object='model', owned_by='organization_owner'), Model(id='qwen/qwen3-4b-thinking-2507', created=None, object='model', owned_by='organization_owner'), Model(id='google/gemma-3-12b', created=None, object='model', owned_by='organization_owner'), Model(id='google/gemma-3-4b', created=None, object='model', owned_by='organization_owner'), Model(id='qwen2-0.5b-instruct-fingreylit', created=None, object='model', owned_by='organization_owner'), Model(id='google/gemma-3n-e4b', created=None, object='model', owned_by='organization_owner'), Model(id='granite-vision-3.3-2b', created=None, object='model', owned_by='organization_owner'), Model(id='ibm/granite-4-h-tiny', created=None, object='model', owned_by='organization_owner'), Model(id='iconclass-vlm', created=None, object='model', owned_by='organization_owner'), Model(id='mlx-community/qwen2.5-vl-3b-instruct', created=None, object='model', owned_by='organization_owner'), Model(id='lmstudio-community/qwen2.5-vl-3b-instruct', created=None, object='model', owned_by='organization_owner'), Model(id='lfm2-vl-1.6b', created=None, object='model', owned_by='organization_owner'), Model(id='mimo-vl-7b-rl-2508@q4_k_s', created=None, object='model', owned_by='organization_owner'), Model(id='mimo-vl-7b-rl-2508@q8_0', created=None, object='model', owned_by='organization_owner'), Model(id='qwen3-30b-a3b-instruct-2507', created=None, object='model', owned_by='organization_owner'), Model(id='qwen3-4b-instruct-2507-mlx', created=None, object='model', owned_by='organization_owner'), Model(id='openai/gpt-oss-20b', created=None, object='model', owned_by='organization_owner'), Model(id='qwen/qwen2.5-vl-7b', created=None, object='model', owned_by='organization_owner'), Model(id='mistralai/mistral-small-3.2', created=None, object='model', owned_by='organization_owner'), Model(id='qwen3-30b-a3b-instruct-2507-mlx', created=None, object='model', owned_by='organization_owner'), Model(id='liquid/lfm2-1.2b', created=None, object='model', owned_by='organization_owner'), Model(id='smollm3-3b-mlx', created=None, object='model', owned_by='organization_owner'), Model(id='unsloth/smollm3-3b', created=None, object='model', owned_by='organization_owner'), Model(id='ggml-org/smollm3-3b', created=None, object='model', owned_by='organization_owner'), Model(id='mlx-community/smollm3-3b', created=None, object='model', owned_by='organization_owner')], object='list')\n\n\n\n\n\npydantic._internal._model_construction.ModelMetaclass\n\n\n\nfrom typing import Union\ndef query_image_structured(image: Union[PILImage.Image, str], prompt: str, schema: BaseModel, model='qwen/qwen3-vl-4b'):\n \"\"\"\n Query VLM with an image and get structured output based on a Pydantic schema.\n \n Args:\n image: PIL Image or file path to the image\n prompt: Text prompt describing what to extract\n schema: Pydantic model class defining the expected output structure\n model: Model ID to use for the query\n \n Returns:\n Parsed Pydantic model instance with the extracted data\n \"\"\"\n # Convert image to base64\n if isinstance(image, PILImage.Image):\n buffered = BytesIO()\n image.save(buffered, format=\"JPEG\")\n image_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')\n else:\n with open(image, \"rb\") as f:\n image_base64 = base64.b64encode(f.read()).decode('utf-8')\n \n # Query with structured output\n completion = client.beta.chat.completions.parse(\n model=model,\n messages=[{\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"text\", \"text\": prompt},\n {\"type\": \"image_url\", \"image_url\": {\"url\": f\"data:image/jpeg;base64,{image_base64}\"}}\n ]\n }],\n response_format=schema,\n temperature=0.3 # Lower temperature for more consistent extraction\n )\n \n # Return the parsed structured data\n return completion.choices[0].message.parsed",
181
+ "crumbs": [
182
+ "Structured Information Extraction",
183
+ "<span class='chapter-number'>4</span>  <span class='chapter-title'>Practical Application: Advisor Index Card Extraction</span>"
184
+ ]
185
+ },
186
+ {
187
+ "objectID": "patterns/structured-generation/advisor-index-cards.html#extraction-examples",
188
+ "href": "patterns/structured-generation/advisor-index-cards.html#extraction-examples",
189
+ "title": "4  Practical Application: Advisor Index Card Extraction",
190
+ "section": "4.5 Extraction Examples",
191
+ "text": "4.5 Extraction Examples\nLet’s run extraction on several sample cards to see how the model performs.\n\nprompt = \"\"\"Extract structured information from this historical library index card and return it as JSON.\n\n This is an index card from the National Library of Scotland's Advocate's Library collection. Each card documents a person and associated manuscript references.\n\n Return a JSON object with these exact fields:\n\n {\n \"surname\": \"Family name exactly as typed (e.g., 'ABAD', 'ABARACA Y BOLEA')\",\n \"forenames\": \"Given names (e.g., 'Joseph', 'Thomas') or null if not present\",\n \"epithet\": \"Title, occupation, or role (e.g., 'Captain, Spanish Army') or null if not present\",\n \"ms_no\": \"Manuscript number exactly as written (e.g., '5538', '5529')\",\n \"description\": \"Document description with date (e.g., 'letter of (1783)', 'copy of petition of (ca. 1783)')\",\n \"folios\": \"Folio reference exactly as written (e.g., 'f.11', 'f.169')\",\n \"failed_to_parse\": false (or true if card is illegible/severely damaged),\n \"notes\": \"Optional notes about handwritten corrections, ambiguities, or parsing issues\"\n }\n\n Guidelines:\n - Extract text exactly as it appears - do not correct spelling or expand abbreviations\n - Preserve original punctuation and formatting\n - If a field is unclear but you can make a reasonable inference, extract it and note the ambiguity in \"notes\"\n - Only set \"failed_to_parse\" to true if you genuinely cannot extract the required fields\n - Use null for optional fields (forenames, epithet, notes) if they are not present or marked with a line\"\"\"\n\n\nimage = PILImage.open(images[0])\nimage \n\n\n\n\n\n\n\n\n\nfrom rich import print\nresult = query_image_structured(image, prompt, IndexCardEntry, model='qwen/qwen3-vl-4b')\n \n\n\nprint(result)\n\nIndexCardEntry(\n surname='ABBAATE',\n forenames='Itala',\n epithet='Daughter of the Physician',\n ms_no='2633',\n description='letter of (1878)',\n folios='f. 38',\n failed_to_parse=False,\n notes=\"Handwritten correction: 'Cairo' appears to be a miswrite of 'ABBAATE (Itala)' and 'Cairo' is likely a \nmisread of 'ABBATE (Itala)'. Also, 'Physician' is underlined and possibly misread as 'Physician' in the original \ntext. The handwritten note 'Auchio' appears to be a scribble or correction, possibly 'Auchio' is a misread of \n'Physician' or a separate annotation. The text 'Cairo' is likely a misread of 'ABBATE (Itala)' and 'Cairo' is not a\nvalid surname in this context. The text 'Daughter of the Physician' is underlined, suggesting it may be a \ncorrection or a clarification.\"\n)\n\n\n\n\n4.5.1 Comparing Extraction to Ground Truth\nLet’s compare a few extractions to the actual card content:\n\nfrom tqdm.auto import tqdm\n\nresults = []\nfor img_path in tqdm(images):\n image = PILImage.open(img_path)\n result = query_image_structured(image, prompt, IndexCardEntry, model='qwen/qwen3-vl-4b')\n results.append((img_path.stem, result))",
192
+ "crumbs": [
193
+ "Structured Information Extraction",
194
+ "<span class='chapter-number'>4</span>  <span class='chapter-title'>Practical Application: Advisor Index Card Extraction</span>"
195
+ ]
196
+ },
197
+ {
198
+ "objectID": "patterns/structured-generation/advisor-index-cards.html#evaluation-strategies",
199
+ "href": "patterns/structured-generation/advisor-index-cards.html#evaluation-strategies",
200
+ "title": "4  Practical Application: Advisor Index Card Extraction",
201
+ "section": "4.6 Evaluation Strategies",
202
+ "text": "4.6 Evaluation Strategies\nHow do we know if the extraction is working well? There are several approaches to evaluation, each with different tradeoffs.\nChecking against the images\n\n# Display images with extracted data side-by-side\n# Two columns: left = image, right = extracted text\n\nfor i, (img_stem, result) in enumerate(results):\n fig, (ax_img, ax_text) = plt.subplots(1, 2, figsize=(16, 6), \n gridspec_kw={'width_ratios': [1, 1]})\n \n # Left: Display image\n img = plt.imread(images[i])\n ax_img.imshow(img)\n ax_img.axis('off')\n ax_img.set_title(f\"Card {i+1}: {img_stem}\", fontsize=14, fontweight='bold')\n \n # Right: Display extracted data as formatted text\n ax_text.axis('off')\n \n # Format the extracted data nicely\n text_lines = [\n \"Extracted Data:\",\n \"\",\n f\"Surname: {result.surname}\",\n f\"Forenames: {result.forenames or 'N/A'}\",\n f\"Epithet: {result.epithet or 'N/A'}\",\n f\"MS No: {result.ms_no}\",\n f\"Description: {result.description}\",\n f\"Folios: {result.folios}\",\n \"\",\n f\"Failed to Parse: {result.failed_to_parse}\",\n ]\n \n # Add notes if present\n if result.notes:\n text_lines.append(\"\")\n text_lines.append(\"Notes:\")\n # Wrap long notes\n import textwrap\n wrapped_notes = textwrap.fill(result.notes, width=60)\n text_lines.append(wrapped_notes)\n \n # Join and display\n formatted_text = \"\\n\".join(text_lines)\n ax_text.text(0.05, 0.95, formatted_text, \n transform=ax_text.transAxes,\n fontsize=11,\n verticalalignment='top',\n fontfamily='monospace',\n bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.3))\n \n plt.tight_layout()\n plt.show()\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nnotes\nNotes don’t seem that useful and waste tokens. Let’s remove.\nWe should also test that the “failed to parse” flag works correctly. let’s try an image that is blank or has no text.\n\nfrom pydantic import BaseModel, Field\nfrom typing import Optional\n\nclass IndexCardEntry(BaseModel):\n \"\"\"Schema for index card extraction matching curator specification\"\"\"\n \n surname: str = Field(..., description=\"Family name as written on card\")\n forenames: Optional[str] = Field(None, description=\"Given names\")\n epithet: Optional[str] = Field(None, description=\"Title, occupation, or role\")\n ms_no: str = Field(..., description=\"Manuscript number\")\n description: str = Field(..., description=\"Document description with date\")\n folios: str = Field(..., description=\"Folio reference\")\n \n\nprompt = \"\"\"Extract structured information from this historical library index card and return it as JSON.\n\n This is an index card from the National Library of Scotland's Advocate's Library collection. Each card documents a person and associated manuscript references.\n\n Return a JSON object with these exact fields:\n\n {\n \"surname\": \"Family name exactly as typed (e.g., 'ABAD', 'ABARACA Y BOLEA')\",\n \"forenames\": \"Given names (e.g., 'Joseph', 'Thomas') or null if not present\",\n \"epithet\": \"Title, occupation, or role (e.g., 'Captain, Spanish Army') or null if not present\",\n \"ms_no\": \"Manuscript number exactly as written (e.g., '5538', '5529')\",\n \"description\": \"Document description with date (e.g., 'letter of (1783)', 'copy of petition of (ca. 1783)')\",\n \"folios\": \"Folio reference exactly as written (e.g., 'f.11', 'f.169')\",\n \"notes\": \"Optional notes about handwritten corrections, ambiguities, or parsing issues\"\n }\n\n Guidelines:\n - Extract text exactly as it appears - do not correct spelling or expand abbreviations\n - Preserve original punctuation and formatting\n - Use null for optional fields (forenames, epithet, notes) if they are not present or marked with a line\"\"\"\n\n\nresults = []\nfor img_path in tqdm(images):\n image = PILImage.open(img_path)\n result = query_image_structured(image, prompt, IndexCardEntry, model='qwen/qwen3-vl-4b')\n results.append((img_path.stem, result))\n\n\n\n\n\n# Display images with extracted data side-by-side\n# Two columns: left = image, right = extracted text\n\nfor i, (img_stem, result) in enumerate(results):\n fig, (ax_img, ax_text) = plt.subplots(1, 2, figsize=(16, 6), \n gridspec_kw={'width_ratios': [1, 1]})\n \n # Left: Display image\n img = plt.imread(images[i])\n ax_img.imshow(img)\n ax_img.axis('off')\n ax_img.set_title(f\"Card {i+1}: {img_stem}\", fontsize=14, fontweight='bold')\n \n # Right: Display extracted data as formatted text\n ax_text.axis('off')\n \n # Format the extracted data nicely\n text_lines = [\n \"Extracted Data:\",\n \"\",\n f\"Surname: {result.surname}\",\n f\"Forenames: {result.forenames or 'N/A'}\",\n f\"Epithet: {result.epithet or 'N/A'}\",\n f\"MS No: {result.ms_no}\",\n f\"Description: {result.description}\",\n f\"Folios: {result.folios}\",\n \"\",\n f\"Failed to Parse: {result.failed_to_parse}\",\n ]\n \n # Add notes if present\n if result.notes:\n text_lines.append(\"\")\n text_lines.append(\"Notes:\")\n # Wrap long notes\n import textwrap\n wrapped_notes = textwrap.fill(result.notes, width=60)\n text_lines.append(wrapped_notes)\n \n # Join and display\n formatted_text = \"\\n\".join(text_lines)\n ax_text.text(0.05, 0.95, formatted_text, \n transform=ax_text.transAxes,\n fontsize=11,\n verticalalignment='top',\n fontfamily='monospace',\n bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.3))\n \n plt.tight_layout()\n plt.show()\n\n\n---------------------------------------------------------------------------\nAttributeError Traceback (most recent call last)\nCell In[69], line 28\n 15 ax_text.axis('off')\n 17 # Format the extracted data nicely\n 18 text_lines = [\n 19 \"Extracted Data:\",\n 20 \"\",\n 21 f\"Surname: {result.surname}\",\n 22 f\"Forenames: {result.forenames or 'N/A'}\",\n 23 f\"Epithet: {result.epithet or 'N/A'}\",\n 24 f\"MS No: {result.ms_no}\",\n 25 f\"Description: {result.description}\",\n 26 f\"Folios: {result.folios}\",\n 27 \"\",\n---&gt; 28 f\"Failed to Parse: {result.failed_to_parse}\",\n 29 ]\n 31 # Add notes if present\n 32 if result.notes:\n\nFile ~/Documents/nls-work/ai-patterns-for-glam/.venv/lib/python3.13/site-packages/pydantic/main.py:991, in BaseModel.__getattr__(self, item)\n 988 return super().__getattribute__(item) # Raises AttributeError if appropriate\n 989 else:\n 990 # this is the current error\n--&gt; 991 raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\n\nAttributeError: 'IndexCardEntry' object has no attribute 'failed_to_parse'\n\n\n\n\n\n\n\n\n\n\n\n4.6.1 1. Manual Ground Truth Evaluation\nThe Gold Standard: Manually annotate a sample of cards and compare.\nPros: - Most accurate measure of performance - Catches all types of errors - Builds training data for future improvements\nCons: - Time consuming - Requires expert annotators - Limited sample size\nBest for: Final validation, establishing baselines, understanding failure modes\n\n# TODO: Load manually annotated ground truth\n# Compare predictions to ground truth\n# Calculate field-level accuracy\n\n# Example metrics:\n# - Exact match accuracy per field\n# - Character error rate\n# - Common error patterns\n\n\n\n4.6.2 2. Cross-Model Evaluation (Model-as-Judge)\nThe Pragmatic Approach: Use a stronger/different model to evaluate outputs.\nPros: - Much faster than manual annotation - Can evaluate full dataset - Good for catching obvious errors\nCons: - Requires access to multiple models - May miss subtle errors - Judge model can be wrong too\nBest for: Large-scale quality monitoring, automated testing, identifying problem areas for manual review\n\n# TODO: Implement model-as-judge evaluation\n# - Extract with Model A (e.g., local Qwen)\n# - Show image + extraction to Model B (e.g., Claude/GPT-4)\n# - Ask Model B to rate accuracy and identify errors\n# - Aggregate results\n\n# Example judge prompt:\n# \"\"\"\n# Compare this extracted data to the index card image:\n# [extraction]\n# \n# For each field, rate accuracy:\n# - Correct: Field matches card exactly\n# - Minor error: Small typo or formatting difference\n# - Major error: Wrong information\n# - Missing: Field is on card but not extracted\n# \"\"\"\n\n\n\n4.6.3 3. Internal Consistency Checks\nThe Automated Approach: Use business rules and patterns to identify suspicious outputs.\nExamples: - Manuscript numbers should follow known patterns - Dates should be within expected ranges - Folio references have consistent formats - Certain fields should always be present\nPros: - Completely automated - Fast - can run on full dataset - No additional model costs\nCons: - Only catches specific error types - Requires domain knowledge to design rules - Can miss errors that follow valid patterns\nBest for: Flagging outliers for review, automated quality gates, monitoring production systems\n\n# TODO: Implement consistency checks\n\n# def validate_extraction(entry: IndexCardEntry) -&gt; list[str]:\n# \"\"\"Run validation checks and return list of warnings.\"\"\"\n# warnings = []\n# \n# # Check MS number format\n# if not re.match(r'^\\d+', entry.ms_no):\n# warnings.append(f\"Unusual MS number format: {entry.ms_no}\")\n# \n# # Check for dates in expected range\n# dates = re.findall(r'\\d{4}', entry.description)\n# for date in dates:\n# if not (1500 &lt;= int(date) &lt;= 1950):\n# warnings.append(f\"Date outside expected range: {date}\")\n# \n# # Check folio format\n# if not re.match(r'^f+\\.?\\s*\\d+', entry.folios, re.IGNORECASE):\n# warnings.append(f\"Unusual folio format: {entry.folios}\")\n# \n# return warnings\n\n\n\n4.6.4 4. Confidence Scoring\nMany VLM APIs return confidence scores or logprobs. We can use these to identify uncertain extractions.\nPros: - No additional cost or models needed - Can prioritize review efforts - Helps establish quality thresholds\nCons: - Not all models/APIs provide confidence scores - High confidence doesn’t guarantee correctness - Requires calibration\nBest for: Prioritizing manual review, quality-based routing, understanding model uncertainty\n\n# TODO: If available, extract and analyze confidence scores\n# Plot distribution of confidence scores\n# Correlate confidence with manual evaluation results\n\n\n\n4.6.5 Combining Evaluation Approaches\nIn practice, a robust evaluation strategy uses multiple approaches:\n\nStart with manual ground truth on a small sample (~50-100 cards) to establish baseline accuracy\nUse consistency checks to automatically flag suspicious outputs\nApply model-as-judge on a larger sample to monitor quality\nPrioritize review using confidence scores or validation warnings\nContinuous monitoring as you process the full collection\n\nThis gives you both rigorous accuracy metrics and practical quality assurance at scale.",
203
+ "crumbs": [
204
+ "Structured Information Extraction",
205
+ "<span class='chapter-number'>4</span>  <span class='chapter-title'>Practical Application: Advisor Index Card Extraction</span>"
206
+ ]
207
+ },
208
+ {
209
+ "objectID": "patterns/structured-generation/advisor-index-cards.html#batch-processing",
210
+ "href": "patterns/structured-generation/advisor-index-cards.html#batch-processing",
211
+ "title": "4  Practical Application: Advisor Index Card Extraction",
212
+ "section": "4.7 Batch Processing",
213
+ "text": "4.7 Batch Processing\nNow let’s process a larger batch of cards and analyze the results.\n\n# TODO: Process all available cards\n# Track timing, failures, warnings\n# Save results to file\n\n\n4.7.1 Results Analysis\n\n# TODO: Analyze batch results\n# - Success rate\n# - Failed to parse rate\n# - Validation warnings distribution\n# - Processing time statistics",
214
+ "crumbs": [
215
+ "Structured Information Extraction",
216
+ "<span class='chapter-number'>4</span>  <span class='chapter-title'>Practical Application: Advisor Index Card Extraction</span>"
217
+ ]
218
+ },
219
+ {
220
+ "objectID": "patterns/structured-generation/advisor-index-cards.html#edge-cases-and-failure-modes",
221
+ "href": "patterns/structured-generation/advisor-index-cards.html#edge-cases-and-failure-modes",
222
+ "title": "4  Practical Application: Advisor Index Card Extraction",
223
+ "section": "4.8 Edge Cases and Failure Modes",
224
+ "text": "4.8 Edge Cases and Failure Modes\nWhat kinds of cards are hard for the model to process?\n\n# TODO: Examine failed/problematic extractions\n# Common patterns:\n# - Handwritten corrections/additions\n# - Faded or damaged cards\n# - Unusual formats or layouts\n# - Multiple entries per card",
225
+ "crumbs": [
226
+ "Structured Information Extraction",
227
+ "<span class='chapter-number'>4</span>  <span class='chapter-title'>Practical Application: Advisor Index Card Extraction</span>"
228
+ ]
229
+ },
230
+ {
231
+ "objectID": "patterns/structured-generation/advisor-index-cards.html#export-for-cataloging",
232
+ "href": "patterns/structured-generation/advisor-index-cards.html#export-for-cataloging",
233
+ "title": "4  Practical Application: Advisor Index Card Extraction",
234
+ "section": "4.9 Export for Cataloging",
235
+ "text": "4.9 Export for Cataloging\nConvert the extracted data to formats suitable for library systems.\n\n# TODO: Export to CSV/JSON/XML\n# Consider catalog system requirements (MARC, Dublin Core, etc.)",
236
+ "crumbs": [
237
+ "Structured Information Extraction",
238
+ "<span class='chapter-number'>4</span>  <span class='chapter-title'>Practical Application: Advisor Index Card Extraction</span>"
239
+ ]
240
+ },
241
+ {
242
+ "objectID": "patterns/structured-generation/advisor-index-cards.html#next-steps",
243
+ "href": "patterns/structured-generation/advisor-index-cards.html#next-steps",
244
+ "title": "4  Practical Application: Advisor Index Card Extraction",
245
+ "section": "4.10 Next Steps",
246
+ "text": "4.10 Next Steps\nThis notebook demonstrates the core extraction and evaluation workflow. For production deployment, you would need:\n\nRobust error handling - retry logic, fallbacks, logging\nQuality assurance workflow - human review interface for flagged items\nBatch processing infrastructure - queue management, progress tracking\nModel optimization - prompt tuning, model selection, cost optimization\n\nThese production considerations are covered in the appendices and separate infrastructure documentation.",
247
+ "crumbs": [
248
+ "Structured Information Extraction",
249
+ "<span class='chapter-number'>4</span>  <span class='chapter-title'>Practical Application: Advisor Index Card Extraction</span>"
250
+ ]
251
+ },
252
+ {
253
+ "objectID": "patterns/structured-generation/advisor-index-cards.html#key-takeaways",
254
+ "href": "patterns/structured-generation/advisor-index-cards.html#key-takeaways",
255
+ "title": "4  Practical Application: Advisor Index Card Extraction",
256
+ "section": "4.11 Key Takeaways",
257
+ "text": "4.11 Key Takeaways\n\nSimple schemas work better - Don’t over-engineer the structure\nMultiple evaluation strategies - Combine automated and manual approaches\nPlan for failure - Build in quality flags and review workflows\nDomain expertise matters - Work closely with catalogers to define requirements\nIterate based on results - Start small, evaluate, adjust, scale",
258
+ "crumbs": [
259
+ "Structured Information Extraction",
260
+ "<span class='chapter-number'>4</span>  <span class='chapter-title'>Practical Application: Advisor Index Card Extraction</span>"
261
+ ]
262
+ }
263
+ ]
site_libs/bootstrap/bootstrap-27c261d06b905028a18691de25d09dde.min.css ADDED
The diff for this file is too large to render. See raw diff
 
site_libs/bootstrap/bootstrap-icons.css ADDED
The diff for this file is too large to render. See raw diff
 
site_libs/bootstrap/bootstrap-icons.woff ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f55513b7b591cb84a3b87ff0e34ea24d4831d6fedc22e54b911ca64b5b544a15
3
+ size 180288
site_libs/bootstrap/bootstrap.min.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * Bootstrap v5.3.1 (https://getbootstrap.com/)
3
+ * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
4
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
5
+ */
6
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),s=t=>{t.dispatchEvent(new Event(i))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,a=t=>{if(!o(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},l=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),c=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?c(t.parentNode):null},h=()=>{},d=t=>{t.offsetHeight},u=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,f=[],p=()=>"rtl"===document.documentElement.dir,m=t=>{var e;e=()=>{const e=u();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(f.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of f)t()})),f.push(e)):e()},g=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,_=(t,e,n=!0)=>{if(!n)return void g(t);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),g(t))};e.addEventListener(i,a),setTimeout((()=>{r||s(e)}),o)},b=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,A={};let E=1;const T={mouseenter:"mouseover",mouseleave:"mouseout"},C=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${E++}`||t.uidEvent||E++}function x(t){const e=O(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function k(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function L(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=I(t);return C.has(o)||(o=t),[n,s,o]}function S(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=L(e,i,n);if(e in T){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=x(t),c=l[a]||(l[a]={}),h=k(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=O(r,e.replace(v,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return P(s,{delegateTarget:r}),n.oneOff&&N.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return P(n,{delegateTarget:t}),i.oneOff&&N.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function D(t,e,i,n,s){const o=k(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function $(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&D(t,e,i,r.callable,r.delegationSelector)}function I(t){return t=t.replace(y,""),T[t]||t}const N={on(t,e,i,n){S(t,e,i,n,!1)},one(t,e,i,n){S(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=L(e,i,n),a=r!==e,l=x(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))$(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(w,"");a&&!e.includes(s)||D(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;D(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=u();let s=null,o=!0,r=!0,a=!1;e!==I(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=P(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function P(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function M(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function j(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const F={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${j(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${j(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=M(t.dataset[n])}return e},getDataAttribute:(t,e)=>M(t.getAttribute(`data-bs-${j(e)}`))};class H{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=o(e)?F.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...o(e)?F.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],r=o(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(r))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${r}" but expected type "${s}".`)}var i}}class W extends H{constructor(t,i){super(),(t=r(t))&&(this._element=t,this._config=this._getConfig(i),e.set(this._element,this.constructor.DATA_KEY,this))}dispose(){e.remove(this._element,this.constructor.DATA_KEY),N.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){_(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return e.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.1"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const B=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return n(e)},z={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!l(t)&&a(t)))},getSelectorFromElement(t){const e=B(t);return e&&z.findOne(e)?e:null},getElementFromSelector(t){const e=B(t);return e?z.findOne(e):null},getMultipleElementsFromSelector(t){const e=B(t);return e?z.find(e):[]}},R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;N.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),l(this))return;const s=z.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},q=".bs.alert",V=`close${q}`,K=`closed${q}`;class Q extends W{static get NAME(){return"alert"}close(){if(N.trigger(this._element,V).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),N.trigger(this._element,K),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Q.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(Q,"close"),m(Q);const X='[data-bs-toggle="button"]';class Y extends W{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=Y.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}N.on(document,"click.bs.button.data-api",X,(t=>{t.preventDefault();const e=t.target.closest(X);Y.getOrCreateInstance(e).toggle()})),m(Y);const U=".bs.swipe",G=`touchstart${U}`,J=`touchmove${U}`,Z=`touchend${U}`,tt=`pointerdown${U}`,et=`pointerup${U}`,it={endCallback:null,leftCallback:null,rightCallback:null},nt={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class st extends H{constructor(t,e){super(),this._element=t,t&&st.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return it}static get DefaultType(){return nt}static get NAME(){return"swipe"}dispose(){N.off(this._element,U)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),g(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&g(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(N.on(this._element,tt,(t=>this._start(t))),N.on(this._element,et,(t=>this._end(t))),this._element.classList.add("pointer-event")):(N.on(this._element,G,(t=>this._start(t))),N.on(this._element,J,(t=>this._move(t))),N.on(this._element,Z,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const ot=".bs.carousel",rt=".data-api",at="next",lt="prev",ct="left",ht="right",dt=`slide${ot}`,ut=`slid${ot}`,ft=`keydown${ot}`,pt=`mouseenter${ot}`,mt=`mouseleave${ot}`,gt=`dragstart${ot}`,_t=`load${ot}${rt}`,bt=`click${ot}${rt}`,vt="carousel",yt="active",wt=".active",At=".carousel-item",Et=wt+At,Tt={ArrowLeft:ht,ArrowRight:ct},Ct={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Ot={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class xt extends W{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=z.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===vt&&this.cycle()}static get Default(){return Ct}static get DefaultType(){return Ot}static get NAME(){return"carousel"}next(){this._slide(at)}nextWhenVisible(){!document.hidden&&a(this._element)&&this.next()}prev(){this._slide(lt)}pause(){this._isSliding&&s(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?N.one(this._element,ut,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void N.one(this._element,ut,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?at:lt;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&N.on(this._element,ft,(t=>this._keydown(t))),"hover"===this._config.pause&&(N.on(this._element,pt,(()=>this.pause())),N.on(this._element,mt,(()=>this._maybeEnableCycle()))),this._config.touch&&st.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of z.find(".carousel-item img",this._element))N.on(t,gt,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(ct)),rightCallback:()=>this._slide(this._directionToOrder(ht)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new st(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=Tt[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=z.findOne(wt,this._indicatorsElement);e.classList.remove(yt),e.removeAttribute("aria-current");const i=z.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(yt),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===at,s=e||b(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>N.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(dt).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),d(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(yt),i.classList.remove(yt,c,l),this._isSliding=!1,r(ut)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return z.findOne(Et,this._element)}_getItems(){return z.find(At,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===ct?lt:at:t===ct?at:lt}_orderToDirection(t){return p()?t===lt?ct:ht:t===lt?ht:ct}static jQueryInterface(t){return this.each((function(){const e=xt.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}N.on(document,bt,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=z.getElementFromSelector(this);if(!e||!e.classList.contains(vt))return;t.preventDefault();const i=xt.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===F.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),N.on(window,_t,(()=>{const t=z.find('[data-bs-ride="carousel"]');for(const e of t)xt.getOrCreateInstance(e)})),m(xt);const kt=".bs.collapse",Lt=`show${kt}`,St=`shown${kt}`,Dt=`hide${kt}`,$t=`hidden${kt}`,It=`click${kt}.data-api`,Nt="show",Pt="collapse",Mt="collapsing",jt=`:scope .${Pt} .${Pt}`,Ft='[data-bs-toggle="collapse"]',Ht={parent:null,toggle:!0},Wt={parent:"(null|element)",toggle:"boolean"};class Bt extends W{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=z.find(Ft);for(const t of i){const e=z.getSelectorFromElement(t),i=z.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return Ht}static get DefaultType(){return Wt}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Bt.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(N.trigger(this._element,Lt).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(Pt),this._element.classList.add(Mt),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Mt),this._element.classList.add(Pt,Nt),this._element.style[e]="",N.trigger(this._element,St)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(N.trigger(this._element,Dt).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,d(this._element),this._element.classList.add(Mt),this._element.classList.remove(Pt,Nt);for(const t of this._triggerArray){const e=z.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Mt),this._element.classList.add(Pt),N.trigger(this._element,$t)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(Nt)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=r(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(Ft);for(const e of t){const t=z.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=z.find(jt,this._config.parent);return z.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Bt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}N.on(document,It,Ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of z.getMultipleElementsFromSelector(this))Bt.getOrCreateInstance(t,{toggle:!1}).toggle()})),m(Bt);var zt="top",Rt="bottom",qt="right",Vt="left",Kt="auto",Qt=[zt,Rt,qt,Vt],Xt="start",Yt="end",Ut="clippingParents",Gt="viewport",Jt="popper",Zt="reference",te=Qt.reduce((function(t,e){return t.concat([e+"-"+Xt,e+"-"+Yt])}),[]),ee=[].concat(Qt,[Kt]).reduce((function(t,e){return t.concat([e,e+"-"+Xt,e+"-"+Yt])}),[]),ie="beforeRead",ne="read",se="afterRead",oe="beforeMain",re="main",ae="afterMain",le="beforeWrite",ce="write",he="afterWrite",de=[ie,ne,se,oe,re,ae,le,ce,he];function ue(t){return t?(t.nodeName||"").toLowerCase():null}function fe(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function pe(t){return t instanceof fe(t).Element||t instanceof Element}function me(t){return t instanceof fe(t).HTMLElement||t instanceof HTMLElement}function ge(t){return"undefined"!=typeof ShadowRoot&&(t instanceof fe(t).ShadowRoot||t instanceof ShadowRoot)}const _e={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];me(s)&&ue(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});me(n)&&ue(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function be(t){return t.split("-")[0]}var ve=Math.max,ye=Math.min,we=Math.round;function Ae(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function Ee(){return!/^((?!chrome|android).)*safari/i.test(Ae())}function Te(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&me(t)&&(s=t.offsetWidth>0&&we(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&we(n.height)/t.offsetHeight||1);var r=(pe(t)?fe(t):window).visualViewport,a=!Ee()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function Ce(t){var e=Te(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Oe(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&ge(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function xe(t){return fe(t).getComputedStyle(t)}function ke(t){return["table","td","th"].indexOf(ue(t))>=0}function Le(t){return((pe(t)?t.ownerDocument:t.document)||window.document).documentElement}function Se(t){return"html"===ue(t)?t:t.assignedSlot||t.parentNode||(ge(t)?t.host:null)||Le(t)}function De(t){return me(t)&&"fixed"!==xe(t).position?t.offsetParent:null}function $e(t){for(var e=fe(t),i=De(t);i&&ke(i)&&"static"===xe(i).position;)i=De(i);return i&&("html"===ue(i)||"body"===ue(i)&&"static"===xe(i).position)?e:i||function(t){var e=/firefox/i.test(Ae());if(/Trident/i.test(Ae())&&me(t)&&"fixed"===xe(t).position)return null;var i=Se(t);for(ge(i)&&(i=i.host);me(i)&&["html","body"].indexOf(ue(i))<0;){var n=xe(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Ie(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function Ne(t,e,i){return ve(t,ye(e,i))}function Pe(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function Me(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const je={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=be(i.placement),l=Ie(a),c=[Vt,qt].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return Pe("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:Me(t,Qt))}(s.padding,i),d=Ce(o),u="y"===l?zt:Vt,f="y"===l?Rt:qt,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=$e(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,A=Ne(v,w,y),E=l;i.modifiersData[n]=((e={})[E]=A,e.centerOffset=A-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Oe(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Fe(t){return t.split("-")[1]}var He={top:"auto",right:"auto",bottom:"auto",left:"auto"};function We(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=t.isFixed,u=r.x,f=void 0===u?0:u,p=r.y,m=void 0===p?0:p,g="function"==typeof h?h({x:f,y:m}):{x:f,y:m};f=g.x,m=g.y;var _=r.hasOwnProperty("x"),b=r.hasOwnProperty("y"),v=Vt,y=zt,w=window;if(c){var A=$e(i),E="clientHeight",T="clientWidth";A===fe(i)&&"static"!==xe(A=Le(i)).position&&"absolute"===a&&(E="scrollHeight",T="scrollWidth"),(s===zt||(s===Vt||s===qt)&&o===Yt)&&(y=Rt,m-=(d&&A===w&&w.visualViewport?w.visualViewport.height:A[E])-n.height,m*=l?1:-1),s!==Vt&&(s!==zt&&s!==Rt||o!==Yt)||(v=qt,f-=(d&&A===w&&w.visualViewport?w.visualViewport.width:A[T])-n.width,f*=l?1:-1)}var C,O=Object.assign({position:a},c&&He),x=!0===h?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:we(i*s)/s||0,y:we(n*s)/s||0}}({x:f,y:m},fe(i)):{x:f,y:m};return f=x.x,m=x.y,l?Object.assign({},O,((C={})[y]=b?"0":"",C[v]=_?"0":"",C.transform=(w.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",C)):Object.assign({},O,((e={})[y]=b?m+"px":"",e[v]=_?f+"px":"",e.transform="",e))}const Be={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:be(e.placement),variation:Fe(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,We(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,We(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var ze={passive:!0};const Re={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=fe(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,ze)})),a&&l.addEventListener("resize",i.update,ze),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,ze)})),a&&l.removeEventListener("resize",i.update,ze)}},data:{}};var qe={left:"right",right:"left",bottom:"top",top:"bottom"};function Ve(t){return t.replace(/left|right|bottom|top/g,(function(t){return qe[t]}))}var Ke={start:"end",end:"start"};function Qe(t){return t.replace(/start|end/g,(function(t){return Ke[t]}))}function Xe(t){var e=fe(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ye(t){return Te(Le(t)).left+Xe(t).scrollLeft}function Ue(t){var e=xe(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ge(t){return["html","body","#document"].indexOf(ue(t))>=0?t.ownerDocument.body:me(t)&&Ue(t)?t:Ge(Se(t))}function Je(t,e){var i;void 0===e&&(e=[]);var n=Ge(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=fe(n),r=s?[o].concat(o.visualViewport||[],Ue(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Je(Se(r)))}function Ze(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function ti(t,e,i){return e===Gt?Ze(function(t,e){var i=fe(t),n=Le(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=Ee();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+Ye(t),y:l}}(t,i)):pe(e)?function(t,e){var i=Te(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):Ze(function(t){var e,i=Le(t),n=Xe(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ve(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ve(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ye(t),l=-n.scrollTop;return"rtl"===xe(s||i).direction&&(a+=ve(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Le(t)))}function ei(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?be(s):null,r=s?Fe(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case zt:e={x:a,y:i.y-n.height};break;case Rt:e={x:a,y:i.y+i.height};break;case qt:e={x:i.x+i.width,y:l};break;case Vt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?Ie(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case Xt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Yt:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ii(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.strategy,r=void 0===o?t.strategy:o,a=i.boundary,l=void 0===a?Ut:a,c=i.rootBoundary,h=void 0===c?Gt:c,d=i.elementContext,u=void 0===d?Jt:d,f=i.altBoundary,p=void 0!==f&&f,m=i.padding,g=void 0===m?0:m,_=Pe("number"!=typeof g?g:Me(g,Qt)),b=u===Jt?Zt:Jt,v=t.rects.popper,y=t.elements[p?b:u],w=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=Je(Se(t)),i=["absolute","fixed"].indexOf(xe(t).position)>=0&&me(t)?$e(t):t;return pe(i)?e.filter((function(t){return pe(t)&&Oe(t,i)&&"body"!==ue(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=ti(t,i,n);return e.top=ve(s.top,e.top),e.right=ye(s.right,e.right),e.bottom=ye(s.bottom,e.bottom),e.left=ve(s.left,e.left),e}),ti(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(pe(y)?y:y.contextElement||Le(t.elements.popper),l,h,r),A=Te(t.elements.reference),E=ei({reference:A,element:v,strategy:"absolute",placement:s}),T=Ze(Object.assign({},v,E)),C=u===Jt?T:A,O={top:w.top-C.top+_.top,bottom:C.bottom-w.bottom+_.bottom,left:w.left-C.left+_.left,right:C.right-w.right+_.right},x=t.modifiersData.offset;if(u===Jt&&x){var k=x[s];Object.keys(O).forEach((function(t){var e=[qt,Rt].indexOf(t)>=0?1:-1,i=[zt,Rt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function ni(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?ee:l,h=Fe(n),d=h?a?te:te.filter((function(t){return Fe(t)===h})):Qt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ii(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[be(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const si={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=be(g),b=l||(_!==g&&p?function(t){if(be(t)===Kt)return[];var e=Ve(t);return[Qe(t),e,Qe(e)]}(g):[Ve(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(be(i)===Kt?ni(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,A=new Map,E=!0,T=v[0],C=0;C<v.length;C++){var O=v[C],x=be(O),k=Fe(O)===Xt,L=[zt,Rt].indexOf(x)>=0,S=L?"width":"height",D=ii(e,{placement:O,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),$=L?k?qt:Vt:k?Rt:zt;y[S]>w[S]&&($=Ve($));var I=Ve($),N=[];if(o&&N.push(D[x]<=0),a&&N.push(D[$]<=0,D[I]<=0),N.every((function(t){return t}))){T=O,E=!1;break}A.set(O,N)}if(E)for(var P=function(t){var e=v.find((function(e){var i=A.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==P(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function oi(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function ri(t){return[zt,qt,Rt,Vt].some((function(e){return t[e]>=0}))}const ai={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ii(e,{elementContext:"reference"}),a=ii(e,{altBoundary:!0}),l=oi(r,n),c=oi(a,s,o),h=ri(l),d=ri(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},li={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=ee.reduce((function(t,i){return t[i]=function(t,e,i){var n=be(t),s=[Vt,zt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[Vt,qt].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},ci={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=ei({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},hi={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ii(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=be(e.placement),b=Fe(e.placement),v=!b,y=Ie(_),w="x"===y?"y":"x",A=e.modifiersData.popperOffsets,E=e.rects.reference,T=e.rects.popper,C="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,O="number"==typeof C?{mainAxis:C,altAxis:C}:Object.assign({mainAxis:0,altAxis:0},C),x=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,k={x:0,y:0};if(A){if(o){var L,S="y"===y?zt:Vt,D="y"===y?Rt:qt,$="y"===y?"height":"width",I=A[y],N=I+g[S],P=I-g[D],M=f?-T[$]/2:0,j=b===Xt?E[$]:T[$],F=b===Xt?-T[$]:-E[$],H=e.elements.arrow,W=f&&H?Ce(H):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},z=B[S],R=B[D],q=Ne(0,E[$],W[$]),V=v?E[$]/2-M-q-z-O.mainAxis:j-q-z-O.mainAxis,K=v?-E[$]/2+M+q+R+O.mainAxis:F+q+R+O.mainAxis,Q=e.elements.arrow&&$e(e.elements.arrow),X=Q?"y"===y?Q.clientTop||0:Q.clientLeft||0:0,Y=null!=(L=null==x?void 0:x[y])?L:0,U=I+K-Y,G=Ne(f?ye(N,I+V-Y-X):N,I,f?ve(P,U):P);A[y]=G,k[y]=G-I}if(a){var J,Z="x"===y?zt:Vt,tt="x"===y?Rt:qt,et=A[w],it="y"===w?"height":"width",nt=et+g[Z],st=et-g[tt],ot=-1!==[zt,Vt].indexOf(_),rt=null!=(J=null==x?void 0:x[w])?J:0,at=ot?nt:et-E[it]-T[it]-rt+O.altAxis,lt=ot?et+E[it]+T[it]-rt-O.altAxis:st,ct=f&&ot?function(t,e,i){var n=Ne(t,e,i);return n>i?i:n}(at,et,lt):Ne(f?at:nt,et,f?lt:st);A[w]=ct,k[w]=ct-et}e.modifiersData[n]=k}},requiresIfExists:["offset"]};function di(t,e,i){void 0===i&&(i=!1);var n,s,o=me(e),r=me(e)&&function(t){var e=t.getBoundingClientRect(),i=we(e.width)/t.offsetWidth||1,n=we(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=Le(e),l=Te(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==ue(e)||Ue(a))&&(c=(n=e)!==fe(n)&&me(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:Xe(n)),me(e)?((h=Te(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=Ye(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function ui(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var fi={placement:"bottom",modifiers:[],strategy:"absolute"};function pi(){for(var t=arguments.length,e=new Array(t),i=0;i<t;i++)e[i]=arguments[i];return!e.some((function(t){return!(t&&"function"==typeof t.getBoundingClientRect)}))}function mi(t){void 0===t&&(t={});var e=t,i=e.defaultModifiers,n=void 0===i?[]:i,s=e.defaultOptions,o=void 0===s?fi:s;return function(t,e,i){void 0===i&&(i=o);var s,r,a={placement:"bottom",orderedModifiers:[],options:Object.assign({},fi,o),modifiersData:{},elements:{reference:t,popper:e},attributes:{},styles:{}},l=[],c=!1,h={state:a,setOptions:function(i){var s="function"==typeof i?i(a.options):i;d(),a.options=Object.assign({},o,a.options,s),a.scrollParents={reference:pe(t)?Je(t):t.contextElement?Je(t.contextElement):[],popper:Je(e)};var r,c,u=function(t){var e=ui(t);return de.reduce((function(t,i){return t.concat(e.filter((function(t){return t.phase===i})))}),[])}((r=[].concat(n,a.options.modifiers),c=r.reduce((function(t,e){var i=t[e.name];return t[e.name]=i?Object.assign({},i,e,{options:Object.assign({},i.options,e.options),data:Object.assign({},i.data,e.data)}):e,t}),{}),Object.keys(c).map((function(t){return c[t]}))));return a.orderedModifiers=u.filter((function(t){return t.enabled})),a.orderedModifiers.forEach((function(t){var e=t.name,i=t.options,n=void 0===i?{}:i,s=t.effect;if("function"==typeof s){var o=s({state:a,name:e,instance:h,options:n});l.push(o||function(){})}})),h.update()},forceUpdate:function(){if(!c){var t=a.elements,e=t.reference,i=t.popper;if(pi(e,i)){a.rects={reference:di(e,$e(i),"fixed"===a.options.strategy),popper:Ce(i)},a.reset=!1,a.placement=a.options.placement,a.orderedModifiers.forEach((function(t){return a.modifiersData[t.name]=Object.assign({},t.data)}));for(var n=0;n<a.orderedModifiers.length;n++)if(!0!==a.reset){var s=a.orderedModifiers[n],o=s.fn,r=s.options,l=void 0===r?{}:r,d=s.name;"function"==typeof o&&(a=o({state:a,options:l,name:d,instance:h})||a)}else a.reset=!1,n=-1}}},update:(s=function(){return new Promise((function(t){h.forceUpdate(),t(a)}))},function(){return r||(r=new Promise((function(t){Promise.resolve().then((function(){r=void 0,t(s())}))}))),r}),destroy:function(){d(),c=!0}};if(!pi(t,e))return h;function d(){l.forEach((function(t){return t()})),l=[]}return h.setOptions(i).then((function(t){!c&&i.onFirstUpdate&&i.onFirstUpdate(t)})),h}}var gi=mi(),_i=mi({defaultModifiers:[Re,ci,Be,_e]}),bi=mi({defaultModifiers:[Re,ci,Be,_e,li,si,hi,je,ai]});const vi=Object.freeze(Object.defineProperty({__proto__:null,afterMain:ae,afterRead:se,afterWrite:he,applyStyles:_e,arrow:je,auto:Kt,basePlacements:Qt,beforeMain:oe,beforeRead:ie,beforeWrite:le,bottom:Rt,clippingParents:Ut,computeStyles:Be,createPopper:bi,createPopperBase:gi,createPopperLite:_i,detectOverflow:ii,end:Yt,eventListeners:Re,flip:si,hide:ai,left:Vt,main:re,modifierPhases:de,offset:li,placements:ee,popper:Jt,popperGenerator:mi,popperOffsets:ci,preventOverflow:hi,read:ne,reference:Zt,right:qt,start:Xt,top:zt,variationPlacements:te,viewport:Gt,write:ce},Symbol.toStringTag,{value:"Module"})),yi="dropdown",wi=".bs.dropdown",Ai=".data-api",Ei="ArrowUp",Ti="ArrowDown",Ci=`hide${wi}`,Oi=`hidden${wi}`,xi=`show${wi}`,ki=`shown${wi}`,Li=`click${wi}${Ai}`,Si=`keydown${wi}${Ai}`,Di=`keyup${wi}${Ai}`,$i="show",Ii='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',Ni=`${Ii}.${$i}`,Pi=".dropdown-menu",Mi=p()?"top-end":"top-start",ji=p()?"top-start":"top-end",Fi=p()?"bottom-end":"bottom-start",Hi=p()?"bottom-start":"bottom-end",Wi=p()?"left-start":"right-start",Bi=p()?"right-start":"left-start",zi={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},Ri={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"};class qi extends W{constructor(t,e){super(t,e),this._popper=null,this._parent=this._element.parentNode,this._menu=z.next(this._element,Pi)[0]||z.prev(this._element,Pi)[0]||z.findOne(Pi,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return zi}static get DefaultType(){return Ri}static get NAME(){return yi}toggle(){return this._isShown()?this.hide():this.show()}show(){if(l(this._element)||this._isShown())return;const t={relatedTarget:this._element};if(!N.trigger(this._element,xi,t).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(".navbar-nav"))for(const t of[].concat(...document.body.children))N.on(t,"mouseover",h);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add($i),this._element.classList.add($i),N.trigger(this._element,ki,t)}}hide(){if(l(this._element)||!this._isShown())return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){if(!N.trigger(this._element,Ci,t).defaultPrevented){if("ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.off(t,"mouseover",h);this._popper&&this._popper.destroy(),this._menu.classList.remove($i),this._element.classList.remove($i),this._element.setAttribute("aria-expanded","false"),F.removeDataAttribute(this._menu,"popper"),N.trigger(this._element,Oi,t)}}_getConfig(t){if("object"==typeof(t=super._getConfig(t)).reference&&!o(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${yi.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(){if(void 0===vi)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let t=this._element;"parent"===this._config.reference?t=this._parent:o(this._config.reference)?t=r(this._config.reference):"object"==typeof this._config.reference&&(t=this._config.reference);const e=this._getPopperConfig();this._popper=bi(t,this._menu,e)}_isShown(){return this._menu.classList.contains($i)}_getPlacement(){const t=this._parent;if(t.classList.contains("dropend"))return Wi;if(t.classList.contains("dropstart"))return Bi;if(t.classList.contains("dropup-center"))return"top";if(t.classList.contains("dropdown-center"))return"bottom";const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ji:Mi:e?Hi:Fi}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(F.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...g(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=z.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>a(t)));i.length&&b(i,e,t===Ti,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=qi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=z.find(Ni);for(const i of e){const e=qi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Ei,Ti].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Ii)?this:z.prev(this,Ii)[0]||z.next(this,Ii)[0]||z.findOne(Ii,t.delegateTarget.parentNode),o=qi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}N.on(document,Si,Ii,qi.dataApiKeydownHandler),N.on(document,Si,Pi,qi.dataApiKeydownHandler),N.on(document,Li,qi.clearMenus),N.on(document,Di,qi.clearMenus),N.on(document,Li,Ii,(function(t){t.preventDefault(),qi.getOrCreateInstance(this).toggle()})),m(qi);const Vi="backdrop",Ki="show",Qi=`mousedown.bs.${Vi}`,Xi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Yi={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Ui extends H{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Xi}static get DefaultType(){return Yi}static get NAME(){return Vi}show(t){if(!this._config.isVisible)return void g(t);this._append();const e=this._getElement();this._config.isAnimated&&d(e),e.classList.add(Ki),this._emulateAnimation((()=>{g(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Ki),this._emulateAnimation((()=>{this.dispose(),g(t)}))):g(t)}dispose(){this._isAppended&&(N.off(this._element,Qi),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=r(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),N.on(t,Qi,(()=>{g(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){_(t,this._getElement(),this._config.isAnimated)}}const Gi=".bs.focustrap",Ji=`focusin${Gi}`,Zi=`keydown.tab${Gi}`,tn="backward",en={autofocus:!0,trapElement:null},nn={autofocus:"boolean",trapElement:"element"};class sn extends H{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return en}static get DefaultType(){return nn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),N.off(document,Gi),N.on(document,Ji,(t=>this._handleFocusin(t))),N.on(document,Zi,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,N.off(document,Gi))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=z.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===tn?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?tn:"forward")}}const on=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",rn=".sticky-top",an="padding-right",ln="margin-right";class cn{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,an,(e=>e+t)),this._setElementAttributes(on,an,(e=>e+t)),this._setElementAttributes(rn,ln,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,an),this._resetElementAttributes(on,an),this._resetElementAttributes(rn,ln)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&F.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=F.getDataAttribute(t,e);null!==i?(F.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(o(t))e(t);else for(const i of z.find(t,this._element))e(i)}}const hn=".bs.modal",dn=`hide${hn}`,un=`hidePrevented${hn}`,fn=`hidden${hn}`,pn=`show${hn}`,mn=`shown${hn}`,gn=`resize${hn}`,_n=`click.dismiss${hn}`,bn=`mousedown.dismiss${hn}`,vn=`keydown.dismiss${hn}`,yn=`click${hn}.data-api`,wn="modal-open",An="show",En="modal-static",Tn={backdrop:!0,focus:!0,keyboard:!0},Cn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class On extends W{constructor(t,e){super(t,e),this._dialog=z.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new cn,this._addEventListeners()}static get Default(){return Tn}static get DefaultType(){return Cn}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||N.trigger(this._element,pn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(wn),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(N.trigger(this._element,dn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(An),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){N.off(window,hn),N.off(this._dialog,hn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Ui({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=z.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),d(this._element),this._element.classList.add(An),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,N.trigger(this._element,mn,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){N.on(this._element,vn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),N.on(window,gn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),N.on(this._element,bn,(t=>{N.one(this._element,_n,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(wn),this._resetAdjustments(),this._scrollBar.reset(),N.trigger(this._element,fn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(N.trigger(this._element,un).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(En)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(En),this._queueCallback((()=>{this._element.classList.remove(En),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=p()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=p()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=On.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}N.on(document,yn,'[data-bs-toggle="modal"]',(function(t){const e=z.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),N.one(e,pn,(t=>{t.defaultPrevented||N.one(e,fn,(()=>{a(this)&&this.focus()}))}));const i=z.findOne(".modal.show");i&&On.getInstance(i).hide(),On.getOrCreateInstance(e).toggle(this)})),R(On),m(On);const xn=".bs.offcanvas",kn=".data-api",Ln=`load${xn}${kn}`,Sn="show",Dn="showing",$n="hiding",In=".offcanvas.show",Nn=`show${xn}`,Pn=`shown${xn}`,Mn=`hide${xn}`,jn=`hidePrevented${xn}`,Fn=`hidden${xn}`,Hn=`resize${xn}`,Wn=`click${xn}${kn}`,Bn=`keydown.dismiss${xn}`,zn={backdrop:!0,keyboard:!0,scroll:!1},Rn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class qn extends W{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return zn}static get DefaultType(){return Rn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||N.trigger(this._element,Nn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new cn).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Dn),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Sn),this._element.classList.remove(Dn),N.trigger(this._element,Pn,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(N.trigger(this._element,Mn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add($n),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(Sn,$n),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new cn).reset(),N.trigger(this._element,Fn)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Ui({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():N.trigger(this._element,jn)}:null})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_addEventListeners(){N.on(this._element,Bn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():N.trigger(this._element,jn))}))}static jQueryInterface(t){return this.each((function(){const e=qn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}N.on(document,Wn,'[data-bs-toggle="offcanvas"]',(function(t){const e=z.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this))return;N.one(e,Fn,(()=>{a(this)&&this.focus()}));const i=z.findOne(In);i&&i!==e&&qn.getInstance(i).hide(),qn.getOrCreateInstance(e).toggle(this)})),N.on(window,Ln,(()=>{for(const t of z.find(In))qn.getOrCreateInstance(t).show()})),N.on(window,Hn,(()=>{for(const t of z.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&qn.getOrCreateInstance(t).hide()})),R(qn),m(qn);const Vn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Kn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Qn=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Xn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Kn.has(i)||Boolean(Qn.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Yn={allowList:Vn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"<div></div>"},Un={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Gn={entry:"(string|element|function|null)",selector:"(string|element)"};class Jn extends H{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Yn}static get DefaultType(){return Un}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Gn)}_setContent(t,e,i){const n=z.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?o(e)?this._putElementInTemplate(r(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Xn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return g(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const Zn=new Set(["sanitize","allowList","sanitizeFn"]),ts="fade",es="show",is=".modal",ns="hide.bs.modal",ss="hover",os="focus",rs={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},as={allowList:Vn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',title:"",trigger:"hover focus"},ls={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class cs extends W{constructor(t,e){if(void 0===vi)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return as}static get DefaultType(){return ls}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),N.off(this._element.closest(is),ns,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=N.trigger(this._element,this.constructor.eventName("show")),e=(c(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),N.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.on(t,"mouseover",h);this._queueCallback((()=>{N.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!N.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.off(t,"mouseover",h);this._activeTrigger.click=!1,this._activeTrigger[os]=!1,this._activeTrigger[ss]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),N.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(ts,es),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(ts),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Jn({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(ts)}_isShown(){return this.tip&&this.tip.classList.contains(es)}_createPopper(t){const e=g(this._config.placement,[this,t,this._element]),i=rs[e.toUpperCase()];return bi(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return g(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...g(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)N.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===ss?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===ss?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");N.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?os:ss]=!0,e._enter()})),N.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?os:ss]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},N.on(this._element.closest(is),ns,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=F.getDataAttributes(this._element);for(const t of Object.keys(e))Zn.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=cs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(cs);const hs={...cs.Default,content:"",offset:[0,8],placement:"right",template:'<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>',trigger:"click"},ds={...cs.DefaultType,content:"(null|string|element|function)"};class us extends cs{static get Default(){return hs}static get DefaultType(){return ds}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=us.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(us);const fs=".bs.scrollspy",ps=`activate${fs}`,ms=`click${fs}`,gs=`load${fs}.data-api`,_s="active",bs="[href]",vs=".nav-link",ys=`${vs}, .nav-item > ${vs}, .list-group-item`,ws={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},As={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Es extends W{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return ws}static get DefaultType(){return As}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=r(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(N.off(this._config.target,ms),N.on(this._config.target,ms,bs,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=z.find(bs,this._config.target);for(const e of t){if(!e.hash||l(e))continue;const t=z.findOne(decodeURI(e.hash),this._element);a(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(_s),this._activateParents(t),N.trigger(this._element,ps,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))z.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(_s);else for(const e of z.parents(t,".nav, .list-group"))for(const t of z.prev(e,ys))t.classList.add(_s)}_clearActiveClass(t){t.classList.remove(_s);const e=z.find(`${bs}.${_s}`,t);for(const t of e)t.classList.remove(_s)}static jQueryInterface(t){return this.each((function(){const e=Es.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(window,gs,(()=>{for(const t of z.find('[data-bs-spy="scroll"]'))Es.getOrCreateInstance(t)})),m(Es);const Ts=".bs.tab",Cs=`hide${Ts}`,Os=`hidden${Ts}`,xs=`show${Ts}`,ks=`shown${Ts}`,Ls=`click${Ts}`,Ss=`keydown${Ts}`,Ds=`load${Ts}`,$s="ArrowLeft",Is="ArrowRight",Ns="ArrowUp",Ps="ArrowDown",Ms="Home",js="End",Fs="active",Hs="fade",Ws="show",Bs=":not(.dropdown-toggle)",zs='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Rs=`.nav-link${Bs}, .list-group-item${Bs}, [role="tab"]${Bs}, ${zs}`,qs=`.${Fs}[data-bs-toggle="tab"], .${Fs}[data-bs-toggle="pill"], .${Fs}[data-bs-toggle="list"]`;class Vs extends W{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),N.on(this._element,Ss,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?N.trigger(e,Cs,{relatedTarget:t}):null;N.trigger(t,xs,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(Fs),this._activate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),N.trigger(t,ks,{relatedTarget:e})):t.classList.add(Ws)}),t,t.classList.contains(Hs)))}_deactivate(t,e){t&&(t.classList.remove(Fs),t.blur(),this._deactivate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),N.trigger(t,Os,{relatedTarget:e})):t.classList.remove(Ws)}),t,t.classList.contains(Hs)))}_keydown(t){if(![$s,Is,Ns,Ps,Ms,js].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!l(t)));let i;if([Ms,js].includes(t.key))i=e[t.key===Ms?0:e.length-1];else{const n=[Is,Ps].includes(t.key);i=b(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Vs.getOrCreateInstance(i).show())}_getChildren(){return z.find(Rs,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=z.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=z.findOne(t,i);s&&s.classList.toggle(n,e)};n(".dropdown-toggle",Fs),n(".dropdown-menu",Ws),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(Fs)}_getInnerElement(t){return t.matches(Rs)?t:z.findOne(Rs,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Vs.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(document,Ls,zs,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this)||Vs.getOrCreateInstance(this).show()})),N.on(window,Ds,(()=>{for(const t of z.find(qs))Vs.getOrCreateInstance(t)})),m(Vs);const Ks=".bs.toast",Qs=`mouseover${Ks}`,Xs=`mouseout${Ks}`,Ys=`focusin${Ks}`,Us=`focusout${Ks}`,Gs=`hide${Ks}`,Js=`hidden${Ks}`,Zs=`show${Ks}`,to=`shown${Ks}`,eo="hide",io="show",no="showing",so={animation:"boolean",autohide:"boolean",delay:"number"},oo={animation:!0,autohide:!0,delay:5e3};class ro extends W{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return oo}static get DefaultType(){return so}static get NAME(){return"toast"}show(){N.trigger(this._element,Zs).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(eo),d(this._element),this._element.classList.add(io,no),this._queueCallback((()=>{this._element.classList.remove(no),N.trigger(this._element,to),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(N.trigger(this._element,Gs).defaultPrevented||(this._element.classList.add(no),this._queueCallback((()=>{this._element.classList.add(eo),this._element.classList.remove(no,io),N.trigger(this._element,Js)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(io),super.dispose()}isShown(){return this._element.classList.contains(io)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){N.on(this._element,Qs,(t=>this._onInteraction(t,!0))),N.on(this._element,Xs,(t=>this._onInteraction(t,!1))),N.on(this._element,Ys,(t=>this._onInteraction(t,!0))),N.on(this._element,Us,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=ro.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(ro),m(ro),{Alert:Q,Button:Y,Carousel:xt,Collapse:Bt,Dropdown:qi,Modal:On,Offcanvas:qn,Popover:us,ScrollSpy:Es,Tab:Vs,Toast:ro,Tooltip:cs}}));
7
+ //# sourceMappingURL=bootstrap.bundle.min.js.map
site_libs/clipboard/clipboard.min.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * clipboard.js v2.0.11
3
+ * https://clipboardjs.com/
4
+ *
5
+ * Licensed MIT © Zeno Rocha
6
+ */
7
+ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{container:document.body},n="";return"string"==typeof t?n=o(t,e):t instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(null==t?void 0:t.type)?n=o(t.value,e):(n=r()(t),c("copy")),n};function l(t){return(l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var s=function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{},e=t.action,n=void 0===e?"copy":e,o=t.container,e=t.target,t=t.text;if("copy"!==n&&"cut"!==n)throw new Error('Invalid "action" value, use either "copy" or "cut"');if(void 0!==e){if(!e||"object"!==l(e)||1!==e.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===n&&e.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===n&&(e.hasAttribute("readonly")||e.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes')}return t?f(t,{container:o}):e?"cut"===n?a(e):f(e,{container:o}):void 0};function p(t){return(p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function d(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}function y(t,e){return(y=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function h(n){var o=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}();return function(){var t,e=v(n);return t=o?(t=v(this).constructor,Reflect.construct(e,arguments,t)):e.apply(this,arguments),e=this,!(t=t)||"object"!==p(t)&&"function"!=typeof t?function(t){if(void 0!==t)return t;throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}(e):t}}function v(t){return(v=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function m(t,e){t="data-clipboard-".concat(t);if(e.hasAttribute(t))return e.getAttribute(t)}var b=function(){!function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&y(t,e)}(r,i());var t,e,n,o=h(r);function r(t,e){var n;return function(t){if(!(t instanceof r))throw new TypeError("Cannot call a class as a function")}(this),(n=o.call(this)).resolveOptions(e),n.listenClick(t),n}return t=r,n=[{key:"copy",value:function(t){var e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{container:document.body};return f(t,e)}},{key:"cut",value:function(t){return a(t)}},{key:"isSupported",value:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:["copy","cut"],t="string"==typeof t?[t]:t,e=!!document.queryCommandSupported;return t.forEach(function(t){e=e&&!!document.queryCommandSupported(t)}),e}}],(e=[{key:"resolveOptions",value:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===p(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=u()(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget,n=this.action(e)||"copy",t=s({action:n,container:this.container,target:this.target(e),text:this.text(e)});this.emit(t?"success":"error",{action:n,text:t,trigger:e,clearSelection:function(){e&&e.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(t){return m("action",t)}},{key:"defaultTarget",value:function(t){t=m("target",t);if(t)return document.querySelector(t)}},{key:"defaultText",value:function(t){return m("text",t)}},{key:"destroy",value:function(){this.listener.destroy()}}])&&d(t.prototype,e),n&&d(t,n),r}()},828:function(t){var e;"undefined"==typeof Element||Element.prototype.matches||((e=Element.prototype).matches=e.matchesSelector||e.mozMatchesSelector||e.msMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector),t.exports=function(t,e){for(;t&&9!==t.nodeType;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}},438:function(t,e,n){var u=n(828);function i(t,e,n,o,r){var i=function(e,n,t,o){return function(t){t.delegateTarget=u(t.target,n),t.delegateTarget&&o.call(e,t)}}.apply(this,arguments);return t.addEventListener(n,i,r),{destroy:function(){t.removeEventListener(n,i,r)}}}t.exports=function(t,e,n,o,r){return"function"==typeof t.addEventListener?i.apply(null,arguments):"function"==typeof n?i.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return i(t,e,n,o,r)}))}},879:function(t,n){n.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},n.nodeList=function(t){var e=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===e||"[object HTMLCollection]"===e)&&"length"in t&&(0===t.length||n.node(t[0]))},n.string=function(t){return"string"==typeof t||t instanceof String},n.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},370:function(t,e,n){var f=n(879),l=n(438);t.exports=function(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!f.string(e))throw new TypeError("Second argument must be a String");if(!f.fn(n))throw new TypeError("Third argument must be a Function");if(f.node(t))return c=e,a=n,(u=t).addEventListener(c,a),{destroy:function(){u.removeEventListener(c,a)}};if(f.nodeList(t))return o=t,r=e,i=n,Array.prototype.forEach.call(o,function(t){t.addEventListener(r,i)}),{destroy:function(){Array.prototype.forEach.call(o,function(t){t.removeEventListener(r,i)})}};if(f.string(t))return t=t,e=e,n=n,l(document.body,t,e,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList");var o,r,i,u,c,a}},817:function(t){t.exports=function(t){var e,n="SELECT"===t.nodeName?(t.focus(),t.value):"INPUT"===t.nodeName||"TEXTAREA"===t.nodeName?((e=t.hasAttribute("readonly"))||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),e||t.removeAttribute("readonly"),t.value):(t.hasAttribute("contenteditable")&&t.focus(),n=window.getSelection(),(e=document.createRange()).selectNodeContents(t),n.removeAllRanges(),n.addRange(e),n.toString());return n}},279:function(t){function e(){}e.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){var o=this;function r(){o.off(t,r),e.apply(n,arguments)}return r._=e,this.on(t,r,n)},emit:function(t){for(var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;o<r;o++)n[o].fn.apply(n[o].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),o=n[t],r=[];if(o&&e)for(var i=0,u=o.length;i<u;i++)o[i].fn!==e&&o[i].fn._!==e&&r.push(o[i]);return r.length?n[t]=r:delete n[t],this}},t.exports=e,t.exports.TinyEmitter=e}},r={},o.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(e,{a:e}),e},o.d=function(t,e){for(var n in e)o.o(e,n)&&!o.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},o.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},o(686).default;function o(t){if(r[t])return r[t].exports;var e=r[t]={exports:{}};return n[t](e,e.exports,o),e.exports}var n,r});
site_libs/quarto-html/anchor.min.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ // @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat
2
+ //
3
+ // AnchorJS - v5.0.0 - 2023-01-18
4
+ // https://www.bryanbraun.com/anchorjs/
5
+ // Copyright (c) 2023 Bryan Braun; Licensed MIT
6
+ //
7
+ // @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat
8
+ !function(A,e){"use strict";"function"==typeof define&&define.amd?define([],e):"object"==typeof module&&module.exports?module.exports=e():(A.AnchorJS=e(),A.anchors=new A.AnchorJS)}(globalThis,function(){"use strict";return function(A){function u(A){A.icon=Object.prototype.hasOwnProperty.call(A,"icon")?A.icon:"",A.visible=Object.prototype.hasOwnProperty.call(A,"visible")?A.visible:"hover",A.placement=Object.prototype.hasOwnProperty.call(A,"placement")?A.placement:"right",A.ariaLabel=Object.prototype.hasOwnProperty.call(A,"ariaLabel")?A.ariaLabel:"Anchor",A.class=Object.prototype.hasOwnProperty.call(A,"class")?A.class:"",A.base=Object.prototype.hasOwnProperty.call(A,"base")?A.base:"",A.truncate=Object.prototype.hasOwnProperty.call(A,"truncate")?Math.floor(A.truncate):64,A.titleText=Object.prototype.hasOwnProperty.call(A,"titleText")?A.titleText:""}function d(A){var e;if("string"==typeof A||A instanceof String)e=[].slice.call(document.querySelectorAll(A));else{if(!(Array.isArray(A)||A instanceof NodeList))throw new TypeError("The selector provided to AnchorJS was invalid.");e=[].slice.call(A)}return e}this.options=A||{},this.elements=[],u(this.options),this.add=function(A){var e,t,o,i,n,s,a,r,l,c,h,p=[];if(u(this.options),0!==(e=d(A=A||"h2, h3, h4, h5, h6")).length){for(null===document.head.querySelector("style.anchorjs")&&((A=document.createElement("style")).className="anchorjs",A.appendChild(document.createTextNode("")),void 0===(h=document.head.querySelector('[rel="stylesheet"],style'))?document.head.appendChild(A):document.head.insertBefore(A,h),A.sheet.insertRule(".anchorjs-link{opacity:0;text-decoration:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}",A.sheet.cssRules.length),A.sheet.insertRule(":hover>.anchorjs-link,.anchorjs-link:focus{opacity:1}",A.sheet.cssRules.length),A.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",A.sheet.cssRules.length),A.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',A.sheet.cssRules.length)),h=document.querySelectorAll("[id]"),t=[].map.call(h,function(A){return A.id}),i=0;i<e.length;i++)if(this.hasAnchorJSLink(e[i]))p.push(i);else{if(e[i].hasAttribute("id"))o=e[i].getAttribute("id");else if(e[i].hasAttribute("data-anchor-id"))o=e[i].getAttribute("data-anchor-id");else{for(r=a=this.urlify(e[i].textContent),s=0;n=t.indexOf(r=void 0!==n?a+"-"+s:r),s+=1,-1!==n;);n=void 0,t.push(r),e[i].setAttribute("id",r),o=r}(l=document.createElement("a")).className="anchorjs-link "+this.options.class,l.setAttribute("aria-label",this.options.ariaLabel),l.setAttribute("data-anchorjs-icon",this.options.icon),this.options.titleText&&(l.title=this.options.titleText),c=document.querySelector("base")?window.location.pathname+window.location.search:"",c=this.options.base||c,l.href=c+"#"+o,"always"===this.options.visible&&(l.style.opacity="1"),""===this.options.icon&&(l.style.font="1em/1 anchorjs-icons","left"===this.options.placement)&&(l.style.lineHeight="inherit"),"left"===this.options.placement?(l.style.position="absolute",l.style.marginLeft="-1.25em",l.style.paddingRight=".25em",l.style.paddingLeft=".25em",e[i].insertBefore(l,e[i].firstChild)):(l.style.marginLeft=".1875em",l.style.paddingRight=".1875em",l.style.paddingLeft=".1875em",e[i].appendChild(l))}for(i=0;i<p.length;i++)e.splice(p[i]-i,1);this.elements=this.elements.concat(e)}return this},this.remove=function(A){for(var e,t,o=d(A),i=0;i<o.length;i++)(t=o[i].querySelector(".anchorjs-link"))&&(-1!==(e=this.elements.indexOf(o[i]))&&this.elements.splice(e,1),o[i].removeChild(t));return this},this.removeAll=function(){this.remove(this.elements)},this.urlify=function(A){var e=document.createElement("textarea");return e.innerHTML=A,A=e.value,this.options.truncate||u(this.options),A.trim().replace(/'/gi,"").replace(/[& +$,:;=?@"#{}|^~[`%!'<>\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}});
9
+ // @license-end
site_libs/quarto-html/axe/axe-check.js ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class QuartoAxeReporter {
2
+ constructor(axeResult, options) {
3
+ this.axeResult = axeResult;
4
+ this.options = options;
5
+ }
6
+
7
+ report() {
8
+ throw new Error("report() is an abstract method");
9
+ }
10
+ }
11
+
12
+ class QuartoAxeJsonReporter extends QuartoAxeReporter {
13
+ constructor(axeResult, options) {
14
+ super(axeResult, options);
15
+ }
16
+
17
+ report() {
18
+ console.log(JSON.stringify(this.axeResult, null, 2));
19
+ }
20
+ }
21
+
22
+ class QuartoAxeConsoleReporter extends QuartoAxeReporter {
23
+ constructor(axeResult, options) {
24
+ super(axeResult, options);
25
+ }
26
+
27
+ report() {
28
+ for (const violation of this.axeResult.violations) {
29
+ console.log(violation.description);
30
+ for (const node of violation.nodes) {
31
+ for (const target of node.target) {
32
+ console.log(target);
33
+ console.log(document.querySelector(target));
34
+ }
35
+ }
36
+ }
37
+ }
38
+ }
39
+
40
+ class QuartoAxeDocumentReporter extends QuartoAxeReporter {
41
+ constructor(axeResult, options) {
42
+ super(axeResult, options);
43
+ }
44
+
45
+ createViolationElement(violation) {
46
+ const violationElement = document.createElement("div");
47
+
48
+ const descriptionElement = document.createElement("div");
49
+ descriptionElement.className = "quarto-axe-violation-description";
50
+ descriptionElement.innerText = `${violation.impact.replace(/^[a-z]/, match => match.toLocaleUpperCase())}: ${violation.description}`;
51
+ violationElement.appendChild(descriptionElement);
52
+
53
+ const helpElement = document.createElement("div");
54
+ helpElement.className = "quarto-axe-violation-help";
55
+ helpElement.innerText = violation.help;
56
+ violationElement.appendChild(helpElement);
57
+
58
+ const nodesElement = document.createElement("div");
59
+ nodesElement.className = "quarto-axe-violation-nodes";
60
+ violationElement.appendChild(nodesElement);
61
+ const nodeElement = document.createElement("div");
62
+ nodeElement.className = "quarto-axe-violation-selector";
63
+ for (const node of violation.nodes) {
64
+ for (const target of node.target) {
65
+ const targetElement = document.createElement("span");
66
+ targetElement.className = "quarto-axe-violation-target";
67
+ targetElement.innerText = target;
68
+ nodeElement.appendChild(targetElement);
69
+ nodeElement.addEventListener("mouseenter", () => {
70
+ const element = document.querySelector(target);
71
+ if (element) {
72
+ element.scrollIntoView({ behavior: "smooth", block: "center" });
73
+ element.classList.add("quarto-axe-hover-highlight");
74
+ setTimeout(() => {
75
+ element.style.border = "";
76
+ }, 2000);
77
+ }
78
+ });
79
+ nodeElement.addEventListener("mouseleave", () => {
80
+ const element = document.querySelector(target);
81
+ if (element) {
82
+ element.classList.remove("quarto-axe-hover-highlight");
83
+ }
84
+ });
85
+ nodeElement.addEventListener("click", () => {
86
+ console.log(document.querySelector(target));
87
+ });
88
+ nodeElement.appendChild(targetElement);
89
+ }
90
+ nodesElement.appendChild(nodeElement);
91
+ }
92
+ return violationElement;
93
+ }
94
+
95
+ report() {
96
+ const violations = this.axeResult.violations;
97
+ const reportElement = document.createElement("div");
98
+ reportElement.className = "quarto-axe-report";
99
+ if (violations.length === 0) {
100
+ const noViolationsElement = document.createElement("div");
101
+ noViolationsElement.className = "quarto-axe-no-violations";
102
+ noViolationsElement.innerText = "No axe-core violations found.";
103
+ reportElement.appendChild(noViolationsElement);
104
+ }
105
+ violations.forEach((violation) => {
106
+ reportElement.appendChild(this.createViolationElement(violation));
107
+ });
108
+ document.querySelector("main").appendChild(reportElement);
109
+ }
110
+ }
111
+
112
+ const reporters = {
113
+ json: QuartoAxeJsonReporter,
114
+ console: QuartoAxeConsoleReporter,
115
+ document: QuartoAxeDocumentReporter,
116
+ };
117
+
118
+ class QuartoAxeChecker {
119
+ constructor(opts) {
120
+ this.options = opts;
121
+ }
122
+ async init() {
123
+ const axe = (await import("https://cdn.skypack.dev/pin/axe-core@v4.10.3-aVOFXWsJaCpVrtv89pCa/mode=imports,min/optimized/axe-core.js")).default;
124
+ const result = await axe.run({
125
+ exclude: [
126
+ // https://github.com/microsoft/tabster/issues/288
127
+ // MS has claimed they won't fix this, so we need to add an exclusion to
128
+ // all tabster elements
129
+ "[data-tabster-dummy]"
130
+ ],
131
+ preload: { assets: ['cssom'], timeout: 50000 }
132
+ });
133
+ const reporter = this.options === true ? new QuartoAxeConsoleReporter(result) : new reporters[this.options.output](result, this.options);
134
+ reporter.report();
135
+ }
136
+ }
137
+
138
+ export async function init() {
139
+ const opts = document.querySelector("#quarto-axe-checker-options");
140
+ if (opts) {
141
+ const jsonOptions = JSON.parse(atob(opts.textContent));
142
+ const checker = new QuartoAxeChecker(jsonOptions);
143
+ await checker.init();
144
+ }
145
+ }