llms-py 3.0.0b6__py3-none-any.whl → 3.0.0b8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. llms/__pycache__/main.cpython-314.pyc +0 -0
  2. llms/{ui/modules/analytics.mjs → extensions/analytics/ui/index.mjs} +55 -164
  3. llms/extensions/app/__init__.py +519 -0
  4. llms/extensions/app/__pycache__/__init__.cpython-314.pyc +0 -0
  5. llms/extensions/app/__pycache__/db.cpython-314.pyc +0 -0
  6. llms/extensions/app/__pycache__/db_manager.cpython-314.pyc +0 -0
  7. llms/extensions/app/db.py +641 -0
  8. llms/extensions/app/db_manager.py +195 -0
  9. llms/extensions/app/requests.json +9073 -0
  10. llms/extensions/app/threads.json +15290 -0
  11. llms/{ui/modules/threads → extensions/app/ui}/Recents.mjs +82 -55
  12. llms/{ui/modules/threads → extensions/app/ui}/index.mjs +83 -20
  13. llms/extensions/app/ui/threadStore.mjs +407 -0
  14. llms/extensions/core_tools/__init__.py +598 -0
  15. llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
  16. llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
  17. llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
  18. llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
  19. llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
  20. llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
  21. llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
  22. llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
  23. llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
  24. llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
  25. llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
  26. llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  27. llms/extensions/core_tools/ui/codemirror/lib/codemirror.css +344 -0
  28. llms/extensions/core_tools/ui/codemirror/lib/codemirror.js +9884 -0
  29. llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
  30. llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
  31. llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
  32. llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
  33. llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
  34. llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
  35. llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
  36. llms/extensions/core_tools/ui/index.mjs +650 -0
  37. llms/extensions/gallery/__init__.py +61 -0
  38. llms/extensions/gallery/__pycache__/__init__.cpython-314.pyc +0 -0
  39. llms/extensions/gallery/__pycache__/db.cpython-314.pyc +0 -0
  40. llms/extensions/gallery/db.py +298 -0
  41. llms/extensions/gallery/ui/index.mjs +481 -0
  42. llms/extensions/katex/__init__.py +6 -0
  43. llms/extensions/katex/__pycache__/__init__.cpython-314.pyc +0 -0
  44. llms/extensions/katex/ui/README.md +125 -0
  45. llms/extensions/katex/ui/contrib/auto-render.js +338 -0
  46. llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
  47. llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
  48. llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
  49. llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
  50. llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
  51. llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
  52. llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
  53. llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
  54. llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
  55. llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
  56. llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
  57. llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
  58. llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
  59. llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
  60. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  61. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  62. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  63. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  64. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  65. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  66. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  67. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  68. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  69. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  70. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  71. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  72. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  73. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  74. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  75. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  76. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  77. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  78. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  79. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  80. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  81. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  82. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  83. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  84. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  85. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  86. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  87. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  88. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  89. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  90. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  91. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  92. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  93. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  94. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  95. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  96. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  97. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  98. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  99. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  100. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  101. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  102. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  103. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  104. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  105. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  106. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  107. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  108. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  109. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  110. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  111. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  112. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  113. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  114. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  115. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  116. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  117. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  118. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  119. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  120. llms/extensions/katex/ui/index.mjs +92 -0
  121. llms/extensions/katex/ui/katex-swap.css +1230 -0
  122. llms/extensions/katex/ui/katex-swap.min.css +1 -0
  123. llms/extensions/katex/ui/katex.css +1230 -0
  124. llms/extensions/katex/ui/katex.js +19080 -0
  125. llms/extensions/katex/ui/katex.min.css +1 -0
  126. llms/extensions/katex/ui/katex.min.js +1 -0
  127. llms/extensions/katex/ui/katex.min.mjs +1 -0
  128. llms/extensions/katex/ui/katex.mjs +18547 -0
  129. llms/extensions/providers/__init__.py +18 -0
  130. llms/extensions/providers/__pycache__/__init__.cpython-314.pyc +0 -0
  131. llms/extensions/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
  132. llms/extensions/providers/__pycache__/chutes.cpython-314.pyc +0 -0
  133. llms/extensions/providers/__pycache__/google.cpython-314.pyc +0 -0
  134. llms/{providers → extensions/providers}/__pycache__/nvidia.cpython-314.pyc +0 -0
  135. llms/{providers → extensions/providers}/__pycache__/openai.cpython-314.pyc +0 -0
  136. llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
  137. llms/{providers → extensions/providers}/anthropic.py +45 -5
  138. llms/{providers → extensions/providers}/chutes.py +21 -18
  139. llms/{providers → extensions/providers}/google.py +99 -27
  140. llms/{providers → extensions/providers}/nvidia.py +6 -8
  141. llms/{providers → extensions/providers}/openai.py +3 -6
  142. llms/{providers → extensions/providers}/openrouter.py +12 -10
  143. llms/extensions/system_prompts/__init__.py +45 -0
  144. llms/extensions/system_prompts/__pycache__/__init__.cpython-314.pyc +0 -0
  145. llms/extensions/system_prompts/ui/index.mjs +285 -0
  146. llms/extensions/system_prompts/ui/prompts.json +1067 -0
  147. llms/extensions/tools/__init__.py +5 -0
  148. llms/extensions/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  149. llms/{ui/modules/tools.mjs → extensions/tools/ui/index.mjs} +12 -10
  150. llms/index.html +26 -38
  151. llms/llms.json +20 -1
  152. llms/main.py +845 -245
  153. llms/providers-extra.json +0 -32
  154. llms/ui/App.mjs +18 -20
  155. llms/ui/ai.mjs +38 -15
  156. llms/ui/app.css +1440 -59
  157. llms/ui/ctx.mjs +154 -18
  158. llms/ui/index.mjs +17 -14
  159. llms/ui/lib/vue.min.mjs +10 -9
  160. llms/ui/lib/vue.mjs +1796 -1635
  161. llms/ui/markdown.mjs +4 -2
  162. llms/ui/modules/chat/ChatBody.mjs +101 -334
  163. llms/ui/modules/chat/HomeTools.mjs +12 -0
  164. llms/ui/modules/chat/SettingsDialog.mjs +1 -1
  165. llms/ui/modules/chat/index.mjs +351 -314
  166. llms/ui/modules/layout.mjs +2 -26
  167. llms/ui/modules/model-selector.mjs +3 -3
  168. llms/ui/tailwind.input.css +35 -1
  169. llms/ui/utils.mjs +33 -3
  170. {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/METADATA +1 -1
  171. llms_py-3.0.0b8.dist-info/RECORD +198 -0
  172. llms/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
  173. llms/providers/__pycache__/chutes.cpython-314.pyc +0 -0
  174. llms/providers/__pycache__/google.cpython-314.pyc +0 -0
  175. llms/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
  176. llms/ui/modules/threads/threadStore.mjs +0 -586
  177. llms_py-3.0.0b6.dist-info/RECORD +0 -66
  178. {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/WHEEL +0 -0
  179. {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/entry_points.txt +0 -0
  180. {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/licenses/LICENSE +0 -0
  181. {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,481 @@
1
+ import { ref, watch, computed, inject, onMounted, onUnmounted } from "vue"
2
+
3
+ let ext
4
+
5
+ const GalleryPage = {
6
+ template: `
7
+ <div class="w-full max-w-[1600px] mx-auto p-4 md:p-8 text-gray-900 dark:text-gray-200 font-sans selection:bg-blue-500/30">
8
+
9
+ <!-- Header -->
10
+ <div class="flex flex-col md:flex-row justify-between items-center mb-8 gap-4">
11
+
12
+ <!-- Left: Tabs -->
13
+ <div class="flex bg-gray-100 dark:bg-gray-800/50 p-1.5 rounded-xl border border-gray-200 dark:border-white/5 backdrop-blur-sm self-start md:self-auto">
14
+ <button type="button"
15
+ @click="setFilter('image')"
16
+ class="px-6 py-2 rounded-lg font-medium transition-all duration-200 text-sm"
17
+ :class="ext.prefs.type === 'image' ? 'bg-white dark:bg-blue-600 text-blue-600 dark:text-white shadow-sm dark:shadow-blue-500/20 shadow-gray-200/50' : 'text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-200/50 dark:hover:bg-white/5'"
18
+ >
19
+ Images
20
+ </button>
21
+ <button type="button"
22
+ @click="setFilter('audio')"
23
+ class="px-6 py-2 rounded-lg font-medium transition-all duration-200 text-sm"
24
+ :class="ext.prefs.type === 'audio' ? 'bg-white dark:bg-blue-600 text-blue-600 dark:text-white shadow-sm dark:shadow-blue-500/20 shadow-gray-200/50' : 'text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-200/50 dark:hover:bg-white/5'"
25
+ >
26
+ Audio
27
+ </button>
28
+ </div>
29
+
30
+ <!-- Center: Format Filter -->
31
+ <div v-if="ext.prefs.type === 'image'" class="flex justify-between w-full md:w-auto gap-2">
32
+ <button type="button"
33
+ v-for="fmt in formats"
34
+ :key="fmt.id"
35
+ @click="setFormat(fmt.id)"
36
+ class="p-2 rounded-xl transition-all duration-200 flex flex-col items-center gap-1 min-w-[4.5rem]"
37
+ :class="ext.prefs.format === fmt.id ? 'bg-blue-100 dark:bg-blue-600 text-blue-600 dark:text-white shadow-sm' : 'text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-white/5 hover:text-gray-900 dark:hover:text-white'"
38
+ >
39
+ <span v-html="fmt.icon" class="w-5 h-5"></span>
40
+ <span class="text-[10px] font-medium uppercase tracking-wider">{{ fmt.label }}</span>
41
+ </button>
42
+ </div>
43
+
44
+ <!-- Right: Search -->
45
+ <div class="relative group w-full md:w-72">
46
+ <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
47
+ <svg class="h-4 w-4 text-gray-400 dark:text-gray-500 group-focus-within:text-blue-500 dark:group-focus-within:text-blue-400 transition-colors" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
48
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
49
+ </svg>
50
+ </div>
51
+ <input
52
+ type="text"
53
+ class="block w-full pl-10 pr-3 py-2.5 bg-white dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 rounded-full leading-5 text-gray-900 dark:text-gray-300 placeholder-gray-400 dark:placeholder-gray-600 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 sm:text-sm transition-all shadow-sm"
54
+ placeholder="Search prompts, models..."
55
+ v-model="ext.prefs.q"
56
+ @input="onSearch"
57
+ >
58
+ </div>
59
+ </div>
60
+
61
+ <!-- Image Grid -->
62
+ <div v-if="ext.prefs.type === 'image'" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-5 gap-3">
63
+ <div
64
+ v-for="(item, index) in items"
65
+ :key="item.id"
66
+ class="group relative rounded-lg overflow-hidden bg-gray-100 dark:bg-gray-800/30 cursor-pointer border border-gray-200 dark:border-white/5 transition-all duration-300 hover:shadow-xl dark:hover:shadow-2xl hover:shadow-blue-500/10 hover:border-blue-400/50 dark:hover:border-blue-500/30"
67
+ :class="ext.prefs.format === 'landscape' ? 'aspect-video' : ext.prefs.format === 'square' ? 'aspect-square' : 'aspect-[3/4]'"
68
+ @click="openLightbox(index)"
69
+ >
70
+ <img
71
+ :src="item.url"
72
+ loading="lazy"
73
+ :alt="item.prompt"
74
+ class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
75
+ >
76
+ <div class="absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex flex-col justify-end p-4">
77
+ <div class="transform translate-y-4 group-hover:translate-y-0 transition-transform duration-300">
78
+ <div class="text-xs font-bold text-blue-300 mb-1 uppercase tracking-wider">{{ item.model }}</div>
79
+ <div class="text-xs text-gray-300 font-medium">{{ $fmt.formatDate(item.created) }}</div>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+
85
+ <!-- Audio List -->
86
+ <div v-if="ext.prefs.type === 'audio'" class="flex flex-col gap-4 max-w-3xl mx-auto">
87
+ <div v-for="(item, index) in items" :key="item.id" class="bg-white dark:bg-gray-800/40 p-4 rounded-2xl border border-gray-200 dark:border-white/5 flex items-center gap-4 hover:border-gray-300 dark:hover:border-gray-700 transition-colors shadow-sm">
88
+ <div class="flex flex-col items-center gap-2 shrink-0">
89
+ <div class="w-12 h-12 rounded-full bg-blue-100 dark:bg-blue-500/20 text-blue-600 dark:text-blue-400 flex items-center justify-center shrink-0">
90
+ <svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
91
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19V6l12-2v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-2" />
92
+ </svg>
93
+ </div>
94
+ <button type="button" @click="remixAudio(item)" class="mb-1 px-2 py-0.5 bg-fuchsia-700 text-white border border-fuchsia-600 hover:bg-fuchsia-600 hover:border-fuchsia-400 rounded-full text-[10px] font-bold uppercase tracking-wider shadow-lg shadow-fuchsia-500/10 hover:shadow-fuchsia-500/40 transition-all duration-200 shrink-0">
95
+ Remix
96
+ </button>
97
+ </div>
98
+ <div class="flex-1 min-w-0">
99
+ <div class="flex justify-between items-center mb-1">
100
+ <h3 class="text-gray-900 dark:text-white font-medium truncate pr-4" :title="item.caption || item.prompt || ''">
101
+ {{ item.caption || item.prompt || 'Untitled' }}
102
+ </h3>
103
+ <span class="text-xs text-gray-500 shrink-0">{{ $fmt.formatDate(item.created) }}</span>
104
+ </div>
105
+ <div class="flex justify-between items-center mb-2">
106
+ <div class="text-xs text-blue-600 dark:text-blue-300/80">{{ item.model }}</div>
107
+ </div>
108
+ <div class="flex items-center gap-2">
109
+ <audio controls class="w-full h-8 opacity-90" :src="item.url"></audio>
110
+ <button type="button" @click="deleteMedia(item)" class="p-1 text-gray-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-full transition-colors" title="Delete">
111
+ <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
112
+ </button>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ </div>
117
+
118
+ <!-- Loading State -->
119
+ <div class="h-20 flex items-center justify-center mt-8 text-gray-500" ref="loadingTrigger">
120
+ <div v-if="loading" class="flex items-center gap-3">
121
+ <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce [animation-delay:-0.3s]"></div>
122
+ <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce [animation-delay:-0.15s]"></div>
123
+ <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce"></div>
124
+ </div>
125
+ <div v-else-if="allLoaded && items.length > 0" class="text-sm font-medium opacity-50">
126
+ All caught up
127
+ </div>
128
+ <div v-else-if="allLoaded && items.length === 0" class="flex flex-col items-center gap-2 opacity-50 py-12">
129
+ <svg class="w-12 h-12" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
130
+ <span>No media found</span>
131
+ </div>
132
+ </div>
133
+
134
+ <!-- Lightbox -->
135
+ <transition
136
+ enter-active-class="transition ease-out duration-300"
137
+ enter-from-class="opacity-0"
138
+ enter-to-class="opacity-100"
139
+ leave-active-class="transition ease-in duration-200"
140
+ leave-from-class="opacity-100"
141
+ leave-to-class="opacity-0"
142
+ >
143
+ <div v-if="lightboxItem" class="fixed inset-0 z-100 flex bg-white/95 dark:bg-black/95 backdrop-blur-xl" @click.self="closeLightbox" @keydown.esc="closeLightbox" tabindex="0">
144
+
145
+ <!-- Main Content -->
146
+ <div class="flex-1 relative flex items-center justify-center p-4">
147
+
148
+ <button type="button" class="absolute top-4 right-4 z-50 p-2 text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white bg-gray-100 hover:bg-gray-200 dark:bg-black/50 dark:hover:bg-white/10 rounded-full transition-all" @click="closeLightbox">
149
+ <svg class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
150
+ </button>
151
+
152
+ <button v-if="hasPrev" type="button" class="hidden md:flex absolute left-4 top-1/2 -translate-y-1/2 p-3 text-gray-700 dark:text-white bg-white/80 dark:bg-white/10 hover:bg-white dark:hover:bg-white/20 hover:scale-110 rounded-full backdrop-blur-md transition-all border border-gray-200 dark:border-white/5 shadow-lg" @click.stop="prevItem">
153
+ <svg class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /></svg>
154
+ </button>
155
+
156
+ <img :src="lightboxItem.url" class="max-w-full max-h-[90vh] object-contain shadow-2xl rounded-sm" @click.stop>
157
+
158
+ <button v-if="hasNext" type="button" class="hidden md:flex absolute right-4 top-1/2 -translate-y-1/2 p-3 text-gray-700 dark:text-white bg-white/80 dark:bg-white/10 hover:bg-white dark:hover:bg-white/20 hover:scale-110 rounded-full backdrop-blur-md transition-all border border-gray-200 dark:border-white/5 shadow-lg" @click.stop="nextItem">
159
+ <svg class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
160
+ </button>
161
+ </div>
162
+
163
+ <!-- Sidebar -->
164
+ <div class="w-full md:w-[400px] h-full bg-gray-50 dark:bg-[#111111] border-l border-gray-200 dark:border-white/10 flex flex-col shadow-2xl text-gray-900 dark:text-gray-200">
165
+ <div class="p-6 overflow-y-auto custom-scrollbar flex-1 space-y-8">
166
+
167
+ <!-- Model Badge -->
168
+ <div>
169
+ <h3 class="text-xs uppercase tracking-widest text-gray-500 font-semibold mb-2">Generated With</h3>
170
+ <div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-blue-100 dark:bg-blue-500/10 border border-blue-200 dark:border-blue-500/20 text-blue-600 dark:text-blue-400 text-sm font-medium">
171
+ <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
172
+ {{ lightboxItem.model }}
173
+ </div>
174
+ </div>
175
+
176
+ <!-- Prompt -->
177
+ <div>
178
+ <div class="flex justify-between">
179
+ <h3 class="text-xs uppercase tracking-widest text-gray-500 font-semibold mb-3">Prompt</h3>
180
+ <button type="button" @click="remixImage" class="mb-2 px-3 py-1 bg-fuchsia-700 text-white border border-fuchsia-600 hover:bg-fuchsia-600 hover:border-fuchsia-400 rounded-full text-xs font-bold uppercase tracking-wider shadow-lg shadow-fuchsia-500/10 hover:shadow-fuchsia-500/40 transition-all duration-200">
181
+ Remix
182
+ </button>
183
+ </div>
184
+ <div class="bg-white dark:bg-gray-900/50 p-4 rounded-xl border border-gray-200 dark:border-white/5 text-gray-600 dark:text-gray-300 text-sm leading-relaxed font-mono max-h-60 overflow-y-auto custom-scrollbar shadow-inner">
185
+ {{ lightboxItem.prompt }}
186
+ </div>
187
+ </div>
188
+
189
+ <!-- Parameters -->
190
+ <div v-if="lightboxItem.params && Object.keys(lightboxItem.params).length">
191
+ <h3 class="text-xs uppercase tracking-widest text-gray-500 font-semibold mb-3">Parameters</h3>
192
+ <div class="flex flex-wrap gap-2">
193
+ <span v-for="(val, key) in lightboxItem.params" :key="key" class="px-2.5 py-1 bg-white dark:bg-gray-800 rounded-md text-xs text-gray-600 dark:text-gray-300 border border-gray-200 dark:border-white/5 shadow-sm">
194
+ <span class="text-gray-400 dark:text-gray-500 mr-1">{{key}}:</span> {{val}}
195
+ </span>
196
+ </div>
197
+ </div>
198
+
199
+ <!-- Details Grid -->
200
+ <div>
201
+ <h3 class="text-xs uppercase tracking-widest text-gray-500 font-semibold mb-3">Details</h3>
202
+ <div class="grid grid-cols-2 gap-4 text-sm">
203
+ <div class="bg-white dark:bg-gray-800/20 p-3 rounded-lg border border-gray-200 dark:border-white/5">
204
+ <div class="text-gray-500 text-xs mb-1">Dimensions</div>
205
+ <div class="font-mono text-gray-700 dark:text-gray-300">{{ lightboxItem.width }} × {{ lightboxItem.height }}</div>
206
+ </div>
207
+ <div class="bg-white dark:bg-gray-800/20 p-3 rounded-lg border border-gray-200 dark:border-white/5">
208
+ <div class="text-gray-500 text-xs mb-1">File Size</div>
209
+ <div class="font-mono text-gray-700 dark:text-gray-300">{{ $fmt.bytes(lightboxItem.size) }}</div>
210
+ </div>
211
+ <div class="bg-white dark:bg-gray-800/20 p-3 rounded-lg border border-gray-200 dark:border-white/5">
212
+ <div class="text-gray-500 text-xs mb-1">Created</div>
213
+ <div class="text-gray-700 dark:text-gray-300">{{ $fmt.shortDate(lightboxItem.created) }}</div>
214
+ </div>
215
+ <div v-if="lightboxItem.cost" class="bg-white dark:bg-gray-800/20 p-3 rounded-lg border border-gray-200 dark:border-white/5">
216
+ <div class="text-gray-500 text-xs mb-1">Cost</div>
217
+ <div class="text-green-600 dark:text-green-400 font-mono">$\{{ lightboxItem.cost.toFixed(5) }}</div>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ </div>
222
+
223
+ <!-- Footer Actions -->
224
+ <div class="p-6 border-t border-gray-200 dark:border-white/5 bg-gray-50 dark:bg-[#161616] flex gap-2">
225
+ <button type="button" @click="deleteMedia" class="flex items-center justify-center p-3 bg-red-100 dark:bg-red-900/20 text-red-600 dark:text-red-400 font-bold rounded-xl hover:bg-red-200 dark:hover:bg-red-900/40 transition-colors" title="Delete">
226
+ <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
227
+ </button>
228
+ <a :href="lightboxItem.url" download class="flex-1 flex items-center justify-center gap-2 bg-gray-900 dark:bg-white text-white dark:text-black font-bold py-3 px-6 rounded-xl hover:bg-gray-800 dark:hover:bg-gray-200 transition-colors shadow-lg shadow-black/5 dark:shadow-white/5">
229
+ <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg>
230
+ Download
231
+ </a>
232
+ </div>
233
+ </div>
234
+ </div>
235
+ </transition>
236
+ </div>
237
+ `,
238
+ setup() {
239
+ const ctx = inject('ctx')
240
+ const items = ref([])
241
+ const loading = ref(false)
242
+ const allLoaded = ref(false)
243
+ const lightboxIndex = ref(-1)
244
+ const loadingTrigger = ref(null)
245
+
246
+ const PAGE_SIZE = 50
247
+ let observer = null
248
+ let searchTimeout = null
249
+
250
+ async function loadMedia({ reset } = {}) {
251
+ if (loading.value) return
252
+ ext.savePrefs()
253
+ const skip = reset ? 0 : items.value.length
254
+ if (reset) {
255
+ allLoaded.value = false
256
+ }
257
+ if (allLoaded.value) return
258
+
259
+ loading.value = true
260
+ try {
261
+ const params = new URLSearchParams({
262
+ type: ext.prefs.type,
263
+ sort: '-id',
264
+ skip,
265
+ take: PAGE_SIZE,
266
+ })
267
+ if (ext.prefs.q) {
268
+ params.append('q', ext.prefs.q)
269
+ }
270
+ if (ext.prefs.format && ext.prefs.type !== 'audio') {
271
+ params.append('format', ext.prefs.format)
272
+ }
273
+
274
+ // USE ext.getJson AS REQUESTED
275
+ const api = await ext.getJson(`/media?${params}`)
276
+ const data = api.response || []
277
+
278
+ if (data.length < PAGE_SIZE) {
279
+ allLoaded.value = true
280
+ }
281
+
282
+ const processed = data.map(item => {
283
+ try {
284
+ if (typeof item.category === 'string') item.category = JSON.parse(item.category)
285
+ if (typeof item.tags === 'string') item.tags = JSON.parse(item.tags)
286
+ if (typeof item.metadata === 'string') item.metadata = JSON.parse(item.metadata)
287
+ } catch (e) { }
288
+
289
+ return {
290
+ ...item,
291
+ params: {
292
+ ...(item.aspect_ratio ? { aspect: item.aspect_ratio } : {}),
293
+ ...(item.seed ? { seed: item.seed } : {}),
294
+ }
295
+ }
296
+ })
297
+
298
+ items.value = reset ? processed : [...items.value, ...processed]
299
+ } catch (e) {
300
+ console.error("Failed to load media", e)
301
+ } finally {
302
+ loading.value = false
303
+ }
304
+ }
305
+
306
+ function setFilter(type) {
307
+ ext.setPrefs({ type })
308
+ loadMedia({ reset: true })
309
+ }
310
+
311
+ const formats = [
312
+ { id: 'portrait', label: 'Portrait', icon: `<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mx-auto" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"></rect></svg>` },
313
+ { id: 'square', label: 'Square', icon: `<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mx-auto" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect></svg>` },
314
+ { id: 'landscape', label: 'Landscape', icon: `<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mx-auto" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="5" width="20" height="14" rx="2" ry="2"></rect></svg>` },
315
+ ]
316
+
317
+ function setFormat(fmt) {
318
+ ext.prefs.format = fmt === ext.prefs.format ? '' : fmt
319
+ loadMedia({ reset: true })
320
+ }
321
+
322
+ function onSearch() {
323
+ if (searchTimeout) clearTimeout(searchTimeout)
324
+ searchTimeout = setTimeout(() => {
325
+ loadMedia({ reset: true })
326
+ }, 500)
327
+ }
328
+
329
+ onMounted(() => {
330
+ if (!ext.prefs.type) {
331
+ ext.setPrefs({ type: 'image' })
332
+ }
333
+ loadMedia({ reset: true })
334
+ observer = new IntersectionObserver((entries) => {
335
+ if (entries[0].isIntersecting) {
336
+ loadMedia()
337
+ }
338
+ }, { threshold: 0.1 })
339
+ if (loadingTrigger.value) observer.observe(loadingTrigger.value)
340
+ window.addEventListener('keydown', handleKeydown)
341
+ })
342
+
343
+ onUnmounted(() => {
344
+ if (observer) observer.disconnect()
345
+ window.removeEventListener('keydown', handleKeydown)
346
+ })
347
+
348
+ watch(loadingTrigger, (el) => {
349
+ if (el && observer) observer.observe(el)
350
+ })
351
+
352
+ const lightboxItem = computed(() => {
353
+ return lightboxIndex.value >= 0 ? items.value[lightboxIndex.value] : null
354
+ })
355
+
356
+ const hasNext = computed(() => lightboxIndex.value < items.value.length - 1)
357
+ const hasPrev = computed(() => lightboxIndex.value > 0)
358
+
359
+ function openLightbox(index) {
360
+ lightboxIndex.value = index
361
+ document.body.style.overflow = 'hidden'
362
+ }
363
+
364
+ function closeLightbox() {
365
+ lightboxIndex.value = -1
366
+ document.body.style.overflow = ''
367
+ }
368
+
369
+ function nextItem() {
370
+ if (hasNext.value) lightboxIndex.value++
371
+ }
372
+
373
+ function prevItem() {
374
+ if (hasPrev.value) lightboxIndex.value--
375
+ }
376
+
377
+ function handleKeydown(e) {
378
+ if (lightboxIndex.value === -1) return
379
+ if (e.key === 'ArrowRight') nextItem()
380
+ if (e.key === 'ArrowLeft') prevItem()
381
+ if (e.key === 'Escape') closeLightbox()
382
+ }
383
+
384
+ function remixImage() {
385
+ const selected = lightboxItem.value
386
+ closeLightbox()
387
+ ctx.chat.setSelectedModel(ctx.chat.getModel(selected.model))
388
+ ctx.chat.messageText.value = selected.prompt
389
+ ctx.chat.selectAspectRatio(selected.aspect_ratio)
390
+ ctx.threads.startNewThread({
391
+ title: selected.prompt,
392
+ model: ctx.chat.getSelectedModel(),
393
+ })
394
+ }
395
+
396
+ function remixAudio(item) {
397
+ const selected = item || lightboxItem.value
398
+ if (lightboxItem.value) closeLightbox()
399
+
400
+ ctx.chat.setSelectedModel(ctx.chat.getModel(selected.model))
401
+ ctx.chat.messageText.value = selected.prompt
402
+ ctx.threads.startNewThread({
403
+ title: selected.prompt,
404
+ model: ctx.chat.getSelectedModel(),
405
+ })
406
+ }
407
+
408
+ async function deleteMedia(item) {
409
+ const target = item && item.hash ? item : lightboxItem.value
410
+ if (!target) return
411
+
412
+ if (!confirm('Are you sure you want to delete this media?')) return
413
+
414
+ const hash = target.hash
415
+ try {
416
+ const response = await fetch(`${ext.baseUrl}/media/${hash}`, {
417
+ method: 'DELETE'
418
+ })
419
+ if (response.ok) {
420
+ items.value = items.value.filter(item => item.hash !== hash)
421
+ if (lightboxItem.value && lightboxItem.value.hash === hash) {
422
+ closeLightbox()
423
+ }
424
+ } else {
425
+ console.error("Failed to delete media", response)
426
+ alert("Failed to delete media")
427
+ }
428
+ } catch (e) {
429
+ console.error("Error deleting media", e)
430
+ alert("Error deleting media")
431
+ }
432
+ }
433
+
434
+ return {
435
+ ext,
436
+ items,
437
+ loading,
438
+ allLoaded,
439
+ loadingTrigger,
440
+ formats,
441
+ setFilter,
442
+ setFormat,
443
+ onSearch,
444
+ loadMedia,
445
+ lightboxIndex,
446
+ lightboxItem,
447
+ openLightbox,
448
+ closeLightbox,
449
+ nextItem,
450
+ prevItem,
451
+ hasNext,
452
+ hasPrev,
453
+ remixImage,
454
+ remixAudio,
455
+ deleteMedia,
456
+ }
457
+ }
458
+ }
459
+
460
+ export default {
461
+ order: 40 - 100,
462
+
463
+ install(ctx) {
464
+ ext = ctx.scope('gallery')
465
+
466
+ ctx.components({
467
+ GalleryPage,
468
+ })
469
+
470
+ ctx.setLeftIcons({
471
+ gallery: {
472
+ component: {
473
+ template: `<svg @click="$ctx.togglePath('/gallery')" viewBox="0 0 15 15" class="w-6 h-6"><path fill="currentColor" d="M10.71 3L7.85.15a.5.5 0 0 0-.707-.003L7.14.15L4.29 3H1.5a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h12a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5zM7.5 1.21L9.29 3H5.71zM13 12H2V4h11zM5 7a1 1 0 1 1 0-2a1 1 0 0 1 0 2m7 4H4.5L6 8l1.25 2.5L9.5 6z"/></svg>`,
474
+ },
475
+ isActive({ path }) { return path === '/gallery' }
476
+ }
477
+ })
478
+
479
+ ctx.routes.push({ path: '/gallery', component: GalleryPage, meta: { title: `Gallery` } })
480
+ }
481
+ }
@@ -0,0 +1,6 @@
1
+ def install(ctx):
2
+ ctx.add_importmaps({"katex": f"{ctx.ext_prefix}/katex.min.mjs"})
3
+ ctx.add_index_footer(f"""<link rel="stylesheet" href="{ctx.ext_prefix}/katex.min.css">""")
4
+
5
+
6
+ __install__ = install
@@ -0,0 +1,125 @@
1
+ <h1><a href="https://katex.org/">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://katex.org/img/katex-logo.svg">
4
+ <img alt="KaTeX" width=130 src="https://katex.org/img/katex-logo-black.svg">
5
+ </picture>
6
+ </a></h1>
7
+
8
+ [![npm](https://img.shields.io/npm/v/katex.svg)](https://www.npmjs.com/package/katex)
9
+ [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
10
+ [![CI](https://github.com/KaTeX/KaTeX/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/KaTeX/KaTeX/actions?query=workflow%3ACI)
11
+ [![codecov](https://codecov.io/gh/KaTeX/KaTeX/branch/main/graph/badge.svg)](https://codecov.io/gh/KaTeX/KaTeX)
12
+ [![Discussions](https://img.shields.io/badge/Discussions-join-brightgreen)](https://github.com/KaTeX/KaTeX/discussions)
13
+ [![jsDelivr](https://data.jsdelivr.com/v1/package/npm/katex/badge?style=rounded)](https://www.jsdelivr.com/package/npm/katex)
14
+ ![katex.min.js size](https://img.badgesize.io/https://unpkg.com/katex/dist/katex.min.js?compression=gzip)
15
+ [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/KaTeX/KaTeX)
16
+ [![Financial Contributors on Open Collective](https://opencollective.com/katex/all/badge.svg?label=financial+contributors)](https://opencollective.com/katex)
17
+
18
+ KaTeX is a fast, easy-to-use JavaScript library for TeX math rendering on the web.
19
+
20
+ * **Fast:** KaTeX renders its math synchronously and doesn't need to reflow the page. See how it compares to a competitor in [this speed test](https://www.intmath.com/cg5/katex-mathjax-comparison.php).
21
+ * **Print quality:** KaTeX's layout is based on Donald Knuth's TeX, the gold standard for math typesetting.
22
+ * **Self contained:** KaTeX has no dependencies and can easily be bundled with your website resources.
23
+ * **Server side rendering:** KaTeX produces the same output regardless of browser or environment, so you can pre-render expressions using Node.js and send them as plain HTML.
24
+
25
+ KaTeX is compatible with all major browsers, including Chrome, Safari, Firefox, Opera, Edge, and IE 11.
26
+
27
+ KaTeX supports much (but not all) of LaTeX and many LaTeX packages. See the [list of supported functions](https://katex.org/docs/supported.html).
28
+
29
+ Try out KaTeX [on the demo page](https://katex.org/#demo)!
30
+
31
+ ## Getting started
32
+
33
+ ### Starter template
34
+
35
+ ```html
36
+ <!DOCTYPE html>
37
+ <!-- KaTeX requires the use of the HTML5 doctype. Without it, KaTeX may not render properly -->
38
+ <html>
39
+ <head>
40
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.27/dist/katex.min.css" integrity="sha384-Pu5+C18nP5dwykLJOhd2U4Xen7rjScHN/qusop27hdd2drI+lL5KvX7YntvT8yew" crossorigin="anonymous">
41
+
42
+ <!-- The loading of KaTeX is deferred to speed up page rendering -->
43
+ <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.27/dist/katex.min.js" integrity="sha384-2B8pfmZZ6JlVoScJm/5hQfNS2TI/6hPqDZInzzPc8oHpN5SgeNOf4LzREO6p5YtZ" crossorigin="anonymous"></script>
44
+
45
+ <!-- To automatically render math in text elements, include the auto-render extension: -->
46
+ <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.27/dist/contrib/auto-render.min.js" integrity="sha384-hCXGrW6PitJEwbkoStFjeJxv+fSOOQKOPbJxSfM6G5sWZjAyWhXiTIIAmQqnlLlh" crossorigin="anonymous"
47
+ onload="renderMathInElement(document.body);"></script>
48
+ </head>
49
+ ...
50
+ </html>
51
+ ```
52
+
53
+ You can also [download KaTeX](https://github.com/KaTeX/KaTeX/releases) and host it yourself.
54
+
55
+ For details on how to configure auto-render extension, refer to [the documentation](https://katex.org/docs/autorender.html).
56
+
57
+ ### API
58
+
59
+ Call `katex.render` to render a TeX expression directly into a DOM element.
60
+ For example:
61
+
62
+ ```js
63
+ katex.render("c = \\pm\\sqrt{a^2 + b^2}", element, {
64
+ throwOnError: false
65
+ });
66
+ ```
67
+
68
+ Call `katex.renderToString` to generate an HTML string of the rendered math,
69
+ e.g., for server-side rendering. For example:
70
+
71
+ ```js
72
+ var html = katex.renderToString("c = \\pm\\sqrt{a^2 + b^2}", {
73
+ throwOnError: false
74
+ });
75
+ // '<span class="katex">...</span>'
76
+ ```
77
+
78
+ Make sure to include the CSS and font files in both cases.
79
+ If you are doing all rendering on the server, there is no need to include the
80
+ JavaScript on the client.
81
+
82
+ The examples above use the `throwOnError: false` option, which renders invalid
83
+ inputs as the TeX source code in red (by default), with the error message as
84
+ hover text. For other available options, see the
85
+ [API documentation](https://katex.org/docs/api.html),
86
+ [options documentation](https://katex.org/docs/options.html), and
87
+ [handling errors documentation](https://katex.org/docs/error.html).
88
+
89
+ ## Demo and Documentation
90
+
91
+ Learn more about using KaTeX [on the website](https://katex.org)!
92
+
93
+ ## Contributors
94
+
95
+ ### Code Contributors
96
+
97
+ This project exists thanks to all the people who contribute code. If you'd like to help, see [our guide to contributing code](CONTRIBUTING.md).
98
+ <a href="https://github.com/KaTeX/KaTeX/graphs/contributors"><img src="https://contributors-svg.opencollective.com/katex/contributors.svg?width=890&button=false" alt="Code contributors" /></a>
99
+
100
+ ### Financial Contributors
101
+
102
+ Become a financial contributor and help us sustain our community.
103
+
104
+ #### Individuals
105
+
106
+ <a href="https://opencollective.com/katex"><img src="https://opencollective.com/katex/individuals.svg?width=890" alt="Contribute on Open Collective"></a>
107
+
108
+ #### Organizations
109
+
110
+ Support this project with your organization. Your logo will show up here with a link to your website.
111
+
112
+ <a href="https://opencollective.com/katex/organization/0/website"><img src="https://opencollective.com/katex/organization/0/avatar.svg" alt="Organization 1"></a>
113
+ <a href="https://opencollective.com/katex/organization/1/website"><img src="https://opencollective.com/katex/organization/1/avatar.svg" alt="Organization 2"></a>
114
+ <a href="https://opencollective.com/katex/organization/2/website"><img src="https://opencollective.com/katex/organization/2/avatar.svg" alt="Organization 3"></a>
115
+ <a href="https://opencollective.com/katex/organization/3/website"><img src="https://opencollective.com/katex/organization/3/avatar.svg" alt="Organization 4"></a>
116
+ <a href="https://opencollective.com/katex/organization/4/website"><img src="https://opencollective.com/katex/organization/4/avatar.svg" alt="Organization 5"></a>
117
+ <a href="https://opencollective.com/katex/organization/5/website"><img src="https://opencollective.com/katex/organization/5/avatar.svg" alt="Organization 6"></a>
118
+ <a href="https://opencollective.com/katex/organization/6/website"><img src="https://opencollective.com/katex/organization/6/avatar.svg" alt="Organization 7"></a>
119
+ <a href="https://opencollective.com/katex/organization/7/website"><img src="https://opencollective.com/katex/organization/7/avatar.svg" alt="Organization 8"></a>
120
+ <a href="https://opencollective.com/katex/organization/8/website"><img src="https://opencollective.com/katex/organization/8/avatar.svg" alt="Organization 9"></a>
121
+ <a href="https://opencollective.com/katex/organization/9/website"><img src="https://opencollective.com/katex/organization/9/avatar.svg" alt="Organization 10"></a>
122
+
123
+ ## License
124
+
125
+ KaTeX is licensed under the [MIT License](https://opensource.org/licenses/MIT).