llms-py 2.0.20__py3-none-any.whl → 3.0.10__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.
- llms/__init__.py +3 -1
- llms/db.py +359 -0
- llms/{ui/Analytics.mjs → extensions/analytics/ui/index.mjs} +254 -327
- llms/extensions/app/README.md +20 -0
- llms/extensions/app/__init__.py +589 -0
- llms/extensions/app/db.py +536 -0
- llms/{ui → extensions/app/ui}/Recents.mjs +99 -73
- llms/{ui/Sidebar.mjs → extensions/app/ui/index.mjs} +139 -68
- llms/extensions/app/ui/threadStore.mjs +433 -0
- llms/extensions/core_tools/CALCULATOR.md +32 -0
- llms/extensions/core_tools/__init__.py +637 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
- llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
- llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
- llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
- llms/extensions/core_tools/ui/codemirror/codemirror.css +344 -0
- llms/extensions/core_tools/ui/codemirror/codemirror.js +9884 -0
- llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
- llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
- llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
- llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
- llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
- llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
- llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
- llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
- llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
- llms/extensions/core_tools/ui/index.mjs +650 -0
- llms/extensions/gallery/README.md +61 -0
- llms/extensions/gallery/__init__.py +63 -0
- llms/extensions/gallery/db.py +243 -0
- llms/extensions/gallery/ui/index.mjs +482 -0
- llms/extensions/katex/README.md +39 -0
- llms/extensions/katex/__init__.py +6 -0
- llms/extensions/katex/ui/README.md +125 -0
- llms/extensions/katex/ui/contrib/auto-render.js +338 -0
- llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
- llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
- llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
- llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
- llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
- llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
- llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
- llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
- llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
- llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
- llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
- llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
- llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
- llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
- llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- llms/extensions/katex/ui/index.mjs +92 -0
- llms/extensions/katex/ui/katex-swap.css +1230 -0
- llms/extensions/katex/ui/katex-swap.min.css +1 -0
- llms/extensions/katex/ui/katex.css +1230 -0
- llms/extensions/katex/ui/katex.js +19080 -0
- llms/extensions/katex/ui/katex.min.css +1 -0
- llms/extensions/katex/ui/katex.min.js +1 -0
- llms/extensions/katex/ui/katex.min.mjs +1 -0
- llms/extensions/katex/ui/katex.mjs +18547 -0
- llms/extensions/providers/__init__.py +22 -0
- llms/extensions/providers/anthropic.py +233 -0
- llms/extensions/providers/cerebras.py +37 -0
- llms/extensions/providers/chutes.py +153 -0
- llms/extensions/providers/google.py +481 -0
- llms/extensions/providers/nvidia.py +103 -0
- llms/extensions/providers/openai.py +154 -0
- llms/extensions/providers/openrouter.py +74 -0
- llms/extensions/providers/zai.py +182 -0
- llms/extensions/system_prompts/README.md +22 -0
- llms/extensions/system_prompts/__init__.py +45 -0
- llms/extensions/system_prompts/ui/index.mjs +280 -0
- llms/extensions/system_prompts/ui/prompts.json +1067 -0
- llms/extensions/tools/__init__.py +144 -0
- llms/extensions/tools/ui/index.mjs +706 -0
- llms/index.html +36 -62
- llms/llms.json +180 -879
- llms/main.py +3640 -899
- llms/providers-extra.json +394 -0
- llms/providers.json +1 -0
- llms/ui/App.mjs +176 -8
- llms/ui/ai.mjs +156 -20
- llms/ui/app.css +3161 -244
- llms/ui/ctx.mjs +412 -0
- llms/ui/index.mjs +131 -0
- llms/ui/lib/chart.js +14 -0
- llms/ui/lib/charts.mjs +16 -0
- llms/ui/lib/color.js +14 -0
- llms/ui/lib/highlight.min.mjs +1243 -0
- llms/ui/lib/idb.min.mjs +8 -0
- llms/ui/lib/marked.min.mjs +8 -0
- llms/ui/lib/servicestack-client.mjs +1 -0
- llms/ui/lib/servicestack-vue.mjs +37 -0
- llms/ui/lib/vue-router.min.mjs +6 -0
- llms/ui/lib/vue.min.mjs +13 -0
- llms/ui/lib/vue.mjs +18530 -0
- llms/ui/markdown.mjs +25 -14
- llms/ui/modules/chat/ChatBody.mjs +976 -0
- llms/ui/{SettingsDialog.mjs → modules/chat/SettingsDialog.mjs} +74 -74
- llms/ui/modules/chat/index.mjs +991 -0
- llms/ui/modules/icons.mjs +46 -0
- llms/ui/modules/layout.mjs +271 -0
- llms/ui/modules/model-selector.mjs +811 -0
- llms/ui/tailwind.input.css +550 -78
- llms/ui/typography.css +54 -36
- llms/ui/utils.mjs +197 -92
- llms_py-3.0.10.dist-info/METADATA +49 -0
- llms_py-3.0.10.dist-info/RECORD +177 -0
- {llms_py-2.0.20.dist-info → llms_py-3.0.10.dist-info}/licenses/LICENSE +1 -2
- llms/ui/Avatar.mjs +0 -28
- llms/ui/Brand.mjs +0 -34
- llms/ui/ChatPrompt.mjs +0 -443
- llms/ui/Main.mjs +0 -740
- llms/ui/ModelSelector.mjs +0 -60
- llms/ui/ProviderIcon.mjs +0 -29
- llms/ui/ProviderStatus.mjs +0 -105
- llms/ui/SignIn.mjs +0 -64
- llms/ui/SystemPromptEditor.mjs +0 -31
- llms/ui/SystemPromptSelector.mjs +0 -36
- llms/ui/Welcome.mjs +0 -8
- llms/ui/threadStore.mjs +0 -524
- llms/ui.json +0 -1069
- llms_py-2.0.20.dist-info/METADATA +0 -931
- llms_py-2.0.20.dist-info/RECORD +0 -36
- {llms_py-2.0.20.dist-info → llms_py-3.0.10.dist-info}/WHEEL +0 -0
- {llms_py-2.0.20.dist-info → llms_py-3.0.10.dist-info}/entry_points.txt +0 -0
- {llms_py-2.0.20.dist-info → llms_py-3.0.10.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,482 @@
|
|
|
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
|
+
redirect: true,
|
|
394
|
+
})
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function remixAudio(item) {
|
|
398
|
+
const selected = item || lightboxItem.value
|
|
399
|
+
if (lightboxItem.value) closeLightbox()
|
|
400
|
+
|
|
401
|
+
ctx.chat.setSelectedModel(ctx.chat.getModel(selected.model))
|
|
402
|
+
ctx.chat.messageText.value = selected.prompt
|
|
403
|
+
ctx.threads.startNewThread({
|
|
404
|
+
title: selected.prompt,
|
|
405
|
+
model: ctx.chat.getSelectedModel(),
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async function deleteMedia(item) {
|
|
410
|
+
const target = item && item.hash ? item : lightboxItem.value
|
|
411
|
+
if (!target) return
|
|
412
|
+
|
|
413
|
+
if (!confirm('Are you sure you want to delete this media?')) return
|
|
414
|
+
|
|
415
|
+
const hash = target.hash
|
|
416
|
+
try {
|
|
417
|
+
const response = await fetch(`${ext.baseUrl}/media/${hash}`, {
|
|
418
|
+
method: 'DELETE'
|
|
419
|
+
})
|
|
420
|
+
if (response.ok) {
|
|
421
|
+
items.value = items.value.filter(item => item.hash !== hash)
|
|
422
|
+
if (lightboxItem.value && lightboxItem.value.hash === hash) {
|
|
423
|
+
closeLightbox()
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
console.error("Failed to delete media", response)
|
|
427
|
+
alert("Failed to delete media")
|
|
428
|
+
}
|
|
429
|
+
} catch (e) {
|
|
430
|
+
console.error("Error deleting media", e)
|
|
431
|
+
alert("Error deleting media")
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return {
|
|
436
|
+
ext,
|
|
437
|
+
items,
|
|
438
|
+
loading,
|
|
439
|
+
allLoaded,
|
|
440
|
+
loadingTrigger,
|
|
441
|
+
formats,
|
|
442
|
+
setFilter,
|
|
443
|
+
setFormat,
|
|
444
|
+
onSearch,
|
|
445
|
+
loadMedia,
|
|
446
|
+
lightboxIndex,
|
|
447
|
+
lightboxItem,
|
|
448
|
+
openLightbox,
|
|
449
|
+
closeLightbox,
|
|
450
|
+
nextItem,
|
|
451
|
+
prevItem,
|
|
452
|
+
hasNext,
|
|
453
|
+
hasPrev,
|
|
454
|
+
remixImage,
|
|
455
|
+
remixAudio,
|
|
456
|
+
deleteMedia,
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export default {
|
|
462
|
+
order: 40 - 100,
|
|
463
|
+
|
|
464
|
+
install(ctx) {
|
|
465
|
+
ext = ctx.scope('gallery')
|
|
466
|
+
|
|
467
|
+
ctx.components({
|
|
468
|
+
GalleryPage,
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
ctx.setLeftIcons({
|
|
472
|
+
gallery: {
|
|
473
|
+
component: {
|
|
474
|
+
template: `<svg @click="$ctx.togglePath('/gallery')" viewBox="0 0 15 15"><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>`,
|
|
475
|
+
},
|
|
476
|
+
isActive({ path }) { return path === '/gallery' }
|
|
477
|
+
}
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
ctx.routes.push({ path: '/gallery', component: GalleryPage, meta: { title: `Gallery` } })
|
|
481
|
+
}
|
|
482
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# KaTeX Extension
|
|
2
|
+
|
|
3
|
+
This extension enables beautiful rendering of LaTeX math expressions in AI responses using [KaTeX](https://katex.org/). It integrates automatically with the markdown parser to render math equations in both inline and block formats.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Fast Rendering**: Uses KaTeX for high-performance rendering of math expressions.
|
|
8
|
+
- **Inline Math**: Renders math within text using `$` or `$$` delimiters.
|
|
9
|
+
- **Block Math**: Renders complex equations in their own block using `$` or `$$` delimiters across multiple lines.
|
|
10
|
+
- **Auto-Integration**: Automatically extends the `marked` parser used in the application.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
The extension supports standard LaTeX math syntax.
|
|
15
|
+
|
|
16
|
+
### Inline Math
|
|
17
|
+
|
|
18
|
+
Surround your LaTeX expression with single `$` (for inline style) or double `$$` (for display style) delimiters.
|
|
19
|
+
|
|
20
|
+
**Example:**
|
|
21
|
+
`The quadratic formula is $x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$.`
|
|
22
|
+
|
|
23
|
+
### Block Math
|
|
24
|
+
|
|
25
|
+
For larger equations or when you want the math to be displayed on its own line, use block syntax by placing the delimiters on separate lines. Standard usage is to use double `$$` delimiters.
|
|
26
|
+
|
|
27
|
+
**Example:**
|
|
28
|
+
```latex
|
|
29
|
+
$$
|
|
30
|
+
\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
|
|
31
|
+
$$
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
The extension automatically registers:
|
|
37
|
+
- **Import Maps**: Loads `katex.min.mjs` for the frontend.
|
|
38
|
+
- **CSS**: Injects `katex.min.css` for styling.
|
|
39
|
+
- **Markdown Extension**: Adds a custom tokenizer and renderer to `marked` to detect and render LaTeX patterns.
|