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.
- llms/__pycache__/main.cpython-314.pyc +0 -0
- llms/{ui/modules/analytics.mjs → extensions/analytics/ui/index.mjs} +55 -164
- llms/extensions/app/__init__.py +519 -0
- llms/extensions/app/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/app/__pycache__/db.cpython-314.pyc +0 -0
- llms/extensions/app/__pycache__/db_manager.cpython-314.pyc +0 -0
- llms/extensions/app/db.py +641 -0
- llms/extensions/app/db_manager.py +195 -0
- llms/extensions/app/requests.json +9073 -0
- llms/extensions/app/threads.json +15290 -0
- llms/{ui/modules/threads → extensions/app/ui}/Recents.mjs +82 -55
- llms/{ui/modules/threads → extensions/app/ui}/index.mjs +83 -20
- llms/extensions/app/ui/threadStore.mjs +407 -0
- llms/extensions/core_tools/__init__.py +598 -0
- llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -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/doc/docs.css +225 -0
- llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
- llms/extensions/core_tools/ui/codemirror/lib/codemirror.css +344 -0
- llms/extensions/core_tools/ui/codemirror/lib/codemirror.js +9884 -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/__init__.py +61 -0
- llms/extensions/gallery/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/gallery/__pycache__/db.cpython-314.pyc +0 -0
- llms/extensions/gallery/db.py +298 -0
- llms/extensions/gallery/ui/index.mjs +481 -0
- llms/extensions/katex/__init__.py +6 -0
- llms/extensions/katex/__pycache__/__init__.cpython-314.pyc +0 -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 +18 -0
- llms/extensions/providers/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/chutes.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/google.cpython-314.pyc +0 -0
- llms/{providers → extensions/providers}/__pycache__/nvidia.cpython-314.pyc +0 -0
- llms/{providers → extensions/providers}/__pycache__/openai.cpython-314.pyc +0 -0
- llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
- llms/{providers → extensions/providers}/anthropic.py +45 -5
- llms/{providers → extensions/providers}/chutes.py +21 -18
- llms/{providers → extensions/providers}/google.py +99 -27
- llms/{providers → extensions/providers}/nvidia.py +6 -8
- llms/{providers → extensions/providers}/openai.py +3 -6
- llms/{providers → extensions/providers}/openrouter.py +12 -10
- llms/extensions/system_prompts/__init__.py +45 -0
- llms/extensions/system_prompts/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/system_prompts/ui/index.mjs +285 -0
- llms/extensions/system_prompts/ui/prompts.json +1067 -0
- llms/extensions/tools/__init__.py +5 -0
- llms/extensions/tools/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/{ui/modules/tools.mjs → extensions/tools/ui/index.mjs} +12 -10
- llms/index.html +26 -38
- llms/llms.json +20 -1
- llms/main.py +845 -245
- llms/providers-extra.json +0 -32
- llms/ui/App.mjs +18 -20
- llms/ui/ai.mjs +38 -15
- llms/ui/app.css +1440 -59
- llms/ui/ctx.mjs +154 -18
- llms/ui/index.mjs +17 -14
- llms/ui/lib/vue.min.mjs +10 -9
- llms/ui/lib/vue.mjs +1796 -1635
- llms/ui/markdown.mjs +4 -2
- llms/ui/modules/chat/ChatBody.mjs +101 -334
- llms/ui/modules/chat/HomeTools.mjs +12 -0
- llms/ui/modules/chat/SettingsDialog.mjs +1 -1
- llms/ui/modules/chat/index.mjs +351 -314
- llms/ui/modules/layout.mjs +2 -26
- llms/ui/modules/model-selector.mjs +3 -3
- llms/ui/tailwind.input.css +35 -1
- llms/ui/utils.mjs +33 -3
- {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/METADATA +1 -1
- llms_py-3.0.0b8.dist-info/RECORD +198 -0
- llms/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
- llms/providers/__pycache__/chutes.cpython-314.pyc +0 -0
- llms/providers/__pycache__/google.cpython-314.pyc +0 -0
- llms/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
- llms/ui/modules/threads/threadStore.mjs +0 -586
- llms_py-3.0.0b6.dist-info/RECORD +0 -66
- {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/WHEEL +0 -0
- {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/entry_points.txt +0 -0
- {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/licenses/LICENSE +0 -0
- {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from aiohttp import web
|
|
5
|
+
|
|
6
|
+
g_db = None
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from llms.extensions.gallery.db import GalleryDB
|
|
10
|
+
except ImportError as e:
|
|
11
|
+
print(f"Failed to import GalleryDB: {e}")
|
|
12
|
+
GalleryDB = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def install(ctx):
|
|
16
|
+
def get_gallery_db():
|
|
17
|
+
global g_db
|
|
18
|
+
if g_db is None and GalleryDB:
|
|
19
|
+
try:
|
|
20
|
+
db_path = os.path.join(ctx.get_user_path(), "gallery", "gallery.sqlite")
|
|
21
|
+
g_db = GalleryDB(ctx, db_path)
|
|
22
|
+
except Exception as e:
|
|
23
|
+
ctx.err("Failed to init GalleryDB", e)
|
|
24
|
+
return g_db
|
|
25
|
+
|
|
26
|
+
if not get_gallery_db():
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
def on_cache_save(context):
|
|
30
|
+
url = context["url"]
|
|
31
|
+
info = context["info"]
|
|
32
|
+
ctx.log(f"cache saved: {url}")
|
|
33
|
+
ctx.log(json.dumps(info, indent=2))
|
|
34
|
+
|
|
35
|
+
if "url" not in info:
|
|
36
|
+
info["url"] = url
|
|
37
|
+
g_db.insert_media(info)
|
|
38
|
+
|
|
39
|
+
ctx.register_cache_saved_filter(on_cache_save)
|
|
40
|
+
|
|
41
|
+
async def query_media(request):
|
|
42
|
+
rows = g_db.query_media(request.query, user=ctx.get_username(request))
|
|
43
|
+
return web.json_response(rows)
|
|
44
|
+
|
|
45
|
+
ctx.add_get("media", query_media)
|
|
46
|
+
|
|
47
|
+
async def media_totals(request):
|
|
48
|
+
rows = g_db.media_totals(user=ctx.get_username(request))
|
|
49
|
+
return web.json_response(rows)
|
|
50
|
+
|
|
51
|
+
ctx.add_get("media/totals", media_totals)
|
|
52
|
+
|
|
53
|
+
async def delete_media(request):
|
|
54
|
+
hash = request.match_info["hash"]
|
|
55
|
+
g_db.delete_media(hash, user=ctx.get_username(request))
|
|
56
|
+
return web.json_response({})
|
|
57
|
+
|
|
58
|
+
ctx.add_delete("media/{hash}", delete_media)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
__install__ = install
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import sqlite3
|
|
4
|
+
from typing import Any, Dict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def ratio_format(ratio):
|
|
8
|
+
w, h = ratio.split(":")
|
|
9
|
+
if int(w) < int(h):
|
|
10
|
+
return -1
|
|
11
|
+
if int(w) > int(h):
|
|
12
|
+
return 1
|
|
13
|
+
return 0
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GalleryDB:
|
|
17
|
+
def __init__(self, ctx, db_path=None):
|
|
18
|
+
if db_path is None:
|
|
19
|
+
raise Exception("db_path is required")
|
|
20
|
+
|
|
21
|
+
self.ctx = ctx
|
|
22
|
+
self.db_path = str(db_path)
|
|
23
|
+
self.columns = {
|
|
24
|
+
"name": "TEXT", # chutes-hunyuan-image-3.png (filename)
|
|
25
|
+
"type": "TEXT", # image|audio|video
|
|
26
|
+
"prompt": "TEXT",
|
|
27
|
+
"model": "TEXT", # gemini-2.5-flash-image
|
|
28
|
+
"created": "TIMESTAMP",
|
|
29
|
+
"cost": "REAL", # 0.03836745
|
|
30
|
+
"seed": "INTEGER", # 1
|
|
31
|
+
"url": "TEXT", # /~cache/23/238841878a0ebeeea8d0034cfdafc82b15d3a6d00c344b0b5e174acbb19572ef.png
|
|
32
|
+
"hash": "TEXT", # 238841878a0ebeeea8d0034cfdafc82b15d3a6d00c344b0b5e174acbb19572ef
|
|
33
|
+
"aspect_ratio": "TEXT", # 9:16
|
|
34
|
+
"width": "INTEGER", # 768
|
|
35
|
+
"height": "INTEGER", # 1344
|
|
36
|
+
"size": "INTEGER", # 1593817 (bytes)
|
|
37
|
+
"duration": "INTEGER", # 100 (secs)
|
|
38
|
+
"user": "TEXT",
|
|
39
|
+
"reactions": "JSON", # {"❤": 1, "👍": 2}
|
|
40
|
+
"caption": "TEXT",
|
|
41
|
+
"description": "TEXT",
|
|
42
|
+
"phash": "TEXT", # 95482f9e1c3f63a1
|
|
43
|
+
"color": "TEXT", # #040609
|
|
44
|
+
"category": "JSON", # {"fantasy": 0.216552734375, "game character": 0.282470703125}
|
|
45
|
+
"tags": "JSON", # {"bug": 0.9706085920333862, "mask": 0.9348311424255371, "glowing": 0.8394700884819031}
|
|
46
|
+
"rating": "TEXT", # "M"
|
|
47
|
+
"ratings": "JSON", # {"predicted_rating":"G","confidence":0.2164306640625,"all_scores":{"G":0.2164306640625,"PG":0.21240234375,"PG-13":0.1915283203125,"M":0.2069091796875,"R":0.2064208984375}}
|
|
48
|
+
"objects": "JSON", # [{"model":"640m","class":"FACE_FEMALE","score":0.5220243334770203,"box":[361,346,367,451]},{"model":"640m","class":"FEMALE_BREAST_EXPOSED","score":0.31755316257476807,"box":[672,1068,212,272]}]
|
|
49
|
+
"variantId": "TEXT", # 1
|
|
50
|
+
"variantName": "TEXT", # 4x Upscaled
|
|
51
|
+
"published": "TIMESTAMP",
|
|
52
|
+
"metadata": "JSON", # {"date":1767111852}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
ratios = ctx.aspect_ratios.keys()
|
|
56
|
+
|
|
57
|
+
self.formats = {
|
|
58
|
+
"square": [ratio for ratio in ratios if ratio_format(ratio) == 0],
|
|
59
|
+
"landscape": [ratio for ratio in ratios if ratio_format(ratio) == 1],
|
|
60
|
+
"portrait": [ratio for ratio in ratios if ratio_format(ratio) == -1],
|
|
61
|
+
}
|
|
62
|
+
self.init_db()
|
|
63
|
+
|
|
64
|
+
def closest_aspect_ratio(self, width, height):
|
|
65
|
+
target_ratio = width / height
|
|
66
|
+
closest_ratio = "1:1"
|
|
67
|
+
min_diff = float("inf")
|
|
68
|
+
|
|
69
|
+
for ratio in self.ctx.aspect_ratios:
|
|
70
|
+
w, h = ratio.split(":")
|
|
71
|
+
diff = abs(target_ratio - (int(w) / int(h)))
|
|
72
|
+
if diff < min_diff:
|
|
73
|
+
min_diff = diff
|
|
74
|
+
closest_ratio = ratio
|
|
75
|
+
|
|
76
|
+
return closest_ratio
|
|
77
|
+
|
|
78
|
+
def get_connection(self):
|
|
79
|
+
return sqlite3.connect(self.db_path)
|
|
80
|
+
|
|
81
|
+
def exec(self, conn, sql, parameters=None):
|
|
82
|
+
self.ctx.dbg("SQL>" + ("\n" if "\n" in sql else " ") + sql)
|
|
83
|
+
return conn.execute(sql, parameters or ())
|
|
84
|
+
|
|
85
|
+
def all(self, conn, sql, parameters=None):
|
|
86
|
+
conn.row_factory = sqlite3.Row
|
|
87
|
+
cursor = self.exec(conn, sql, parameters)
|
|
88
|
+
return [dict(row) for row in cursor.fetchall()]
|
|
89
|
+
|
|
90
|
+
def init_db(self):
|
|
91
|
+
dirname = os.path.dirname(self.db_path)
|
|
92
|
+
if dirname:
|
|
93
|
+
os.makedirs(dirname, exist_ok=True)
|
|
94
|
+
with self.get_connection() as conn:
|
|
95
|
+
# Create table with all columns
|
|
96
|
+
self.exec(
|
|
97
|
+
conn,
|
|
98
|
+
"""
|
|
99
|
+
CREATE TABLE IF NOT EXISTS media (
|
|
100
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
101
|
+
name TEXT,
|
|
102
|
+
type TEXT NOT NULL,
|
|
103
|
+
prompt TEXT,
|
|
104
|
+
model TEXT,
|
|
105
|
+
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
106
|
+
cost REAL,
|
|
107
|
+
seed INTEGER,
|
|
108
|
+
url TEXT NOT NULL UNIQUE,
|
|
109
|
+
hash TEXT NOT NULL UNIQUE,
|
|
110
|
+
aspect_ratio TEXT,
|
|
111
|
+
width INTEGER,
|
|
112
|
+
height INTEGER,
|
|
113
|
+
size INTEGER,
|
|
114
|
+
duration INTEGER,
|
|
115
|
+
user TEXT,
|
|
116
|
+
reactions JSON,
|
|
117
|
+
caption TEXT,
|
|
118
|
+
description TEXT,
|
|
119
|
+
phash TEXT,
|
|
120
|
+
color TEXT,
|
|
121
|
+
category JSON,
|
|
122
|
+
tags JSON,
|
|
123
|
+
rating TEXT,
|
|
124
|
+
ratings JSON,
|
|
125
|
+
objects JSON,
|
|
126
|
+
variantId TEXT,
|
|
127
|
+
variantName TEXT,
|
|
128
|
+
published TIMESTAMP,
|
|
129
|
+
metadata JSON
|
|
130
|
+
)
|
|
131
|
+
""",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
self.exec(conn, "CREATE INDEX IF NOT EXISTS idx_media_user ON media(user)")
|
|
135
|
+
self.exec(conn, "CREATE INDEX IF NOT EXISTS idx_media_type ON media(type)")
|
|
136
|
+
|
|
137
|
+
# Check for missing columns and migrate if necessary
|
|
138
|
+
cur = self.exec(conn, "PRAGMA table_info(media)")
|
|
139
|
+
columns = {row[1] for row in cur.fetchall()}
|
|
140
|
+
|
|
141
|
+
for col, dtype in self.columns.items():
|
|
142
|
+
if col not in columns:
|
|
143
|
+
try:
|
|
144
|
+
self.exec(conn, f"ALTER TABLE media ADD COLUMN {col} {dtype}")
|
|
145
|
+
except Exception as e:
|
|
146
|
+
self.ctx.err(f"adding column {col}", e)
|
|
147
|
+
|
|
148
|
+
def insert_media(self, info):
|
|
149
|
+
try:
|
|
150
|
+
if not info:
|
|
151
|
+
raise Exception("info is required")
|
|
152
|
+
|
|
153
|
+
# Helper to safely dump JSON if value exists
|
|
154
|
+
def db_value(val):
|
|
155
|
+
if val is None or val == "":
|
|
156
|
+
return None
|
|
157
|
+
if isinstance(val, (dict, list)):
|
|
158
|
+
return json.dumps(val)
|
|
159
|
+
return val
|
|
160
|
+
|
|
161
|
+
meta = {}
|
|
162
|
+
metadata = {}
|
|
163
|
+
known_columns = self.columns.keys()
|
|
164
|
+
for k in known_columns:
|
|
165
|
+
val = info.get(k, None)
|
|
166
|
+
if k == "metadata":
|
|
167
|
+
continue
|
|
168
|
+
if k == "created" and not val:
|
|
169
|
+
continue
|
|
170
|
+
if k == "type":
|
|
171
|
+
parts = val.split("/")
|
|
172
|
+
if parts[0] == "image" or parts[0] == "video" or parts[0] == "audio":
|
|
173
|
+
meta[k] = parts[0]
|
|
174
|
+
else:
|
|
175
|
+
meta[k] = db_value(val)
|
|
176
|
+
# for items not in known_columns, add to metadata
|
|
177
|
+
for k in info:
|
|
178
|
+
if k not in known_columns:
|
|
179
|
+
metadata[k] = info[k]
|
|
180
|
+
|
|
181
|
+
if not meta.get("hash"):
|
|
182
|
+
meta["hash"] = meta["url"].split("/")[-1].split(".")[0]
|
|
183
|
+
|
|
184
|
+
if "width" in meta and "height" in meta and meta["width"] and meta["height"]:
|
|
185
|
+
meta["aspect_ratio"] = self.closest_aspect_ratio(int(meta["width"]), int(meta["height"]))
|
|
186
|
+
|
|
187
|
+
meta["metadata"] = db_value(metadata)
|
|
188
|
+
|
|
189
|
+
with self.get_connection() as conn:
|
|
190
|
+
insert_keys = list(meta.keys())
|
|
191
|
+
insert_body = ", ".join(insert_keys)
|
|
192
|
+
insert_values = ", ".join(["?" for _ in insert_keys])
|
|
193
|
+
|
|
194
|
+
self.exec(
|
|
195
|
+
conn,
|
|
196
|
+
f"""
|
|
197
|
+
INSERT INTO media (
|
|
198
|
+
{insert_body}
|
|
199
|
+
)
|
|
200
|
+
VALUES ({insert_values})
|
|
201
|
+
""",
|
|
202
|
+
tuple(meta[k] for k in insert_keys),
|
|
203
|
+
)
|
|
204
|
+
except sqlite3.IntegrityError as e:
|
|
205
|
+
# unique constraint failed, file already exists.
|
|
206
|
+
self.ctx.dbg(f"media already exists {e}")
|
|
207
|
+
except Exception as e:
|
|
208
|
+
self.ctx.err("insert media", e)
|
|
209
|
+
|
|
210
|
+
def get_user_filter(self, user=None):
|
|
211
|
+
if user is None:
|
|
212
|
+
return "WHERE user IS NULL ", {}
|
|
213
|
+
else:
|
|
214
|
+
return "WHERE user = :user ", {"user": user}
|
|
215
|
+
|
|
216
|
+
def media_totals(self, user=None):
|
|
217
|
+
try:
|
|
218
|
+
with self.get_connection() as conn:
|
|
219
|
+
sql_where, params = self.get_user_filter(user)
|
|
220
|
+
return self.all(
|
|
221
|
+
conn,
|
|
222
|
+
f"SELECT type, COUNT(*) as count FROM media {sql_where} GROUP BY type ORDER BY count DESC",
|
|
223
|
+
params,
|
|
224
|
+
)
|
|
225
|
+
except Exception as e:
|
|
226
|
+
self.ctx.err("media_totals", e)
|
|
227
|
+
return []
|
|
228
|
+
|
|
229
|
+
def all_media(self, limit=100, offset=0, user=None):
|
|
230
|
+
try:
|
|
231
|
+
with self.get_connection() as conn:
|
|
232
|
+
sql_where, params = self.get_user_filter(user)
|
|
233
|
+
params.update({"limit": limit, "offset": offset})
|
|
234
|
+
return self.all(
|
|
235
|
+
conn,
|
|
236
|
+
f"SELECT * FROM media {sql_where} ORDER BY id DESC LIMIT :limit OFFSET :offset",
|
|
237
|
+
params,
|
|
238
|
+
)
|
|
239
|
+
except Exception as e:
|
|
240
|
+
self.ctx.err(f"all_media ({limit}, {offset})", e)
|
|
241
|
+
return []
|
|
242
|
+
|
|
243
|
+
def query_media(self, query: Dict[str, Any], user=None):
|
|
244
|
+
try:
|
|
245
|
+
take = query.get("take", 50)
|
|
246
|
+
skip = query.get("skip", 0)
|
|
247
|
+
sort = query.get("sort", "-id")
|
|
248
|
+
|
|
249
|
+
# always filter by user
|
|
250
|
+
sql_where, params = self.get_user_filter(user)
|
|
251
|
+
params.update(
|
|
252
|
+
{
|
|
253
|
+
"take": take,
|
|
254
|
+
"skip": skip,
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
filter = {}
|
|
259
|
+
for k in query:
|
|
260
|
+
if k in self.columns:
|
|
261
|
+
filter[k] = query[k]
|
|
262
|
+
params[k] = query[k]
|
|
263
|
+
|
|
264
|
+
if len(filter) > 0:
|
|
265
|
+
sql_where += " AND " + " AND ".join([f"{k} = :{k}" for k in filter])
|
|
266
|
+
|
|
267
|
+
if "q" in query:
|
|
268
|
+
sql_where += " AND " if sql_where else "WHERE "
|
|
269
|
+
sql_where += "(prompt LIKE :q OR name LIKE :q OR description LIKE :q OR caption LIKE :q)"
|
|
270
|
+
params["q"] = f"%{query['q']}%"
|
|
271
|
+
|
|
272
|
+
if "format" in query:
|
|
273
|
+
sql_where += " AND " if sql_where else "WHERE "
|
|
274
|
+
format_ratios = self.formats.get(query["format"], [])
|
|
275
|
+
ratios = ", ".join([f"'{ratio}'" for ratio in format_ratios])
|
|
276
|
+
sql_where += f"aspect_ratio IN ({ratios})"
|
|
277
|
+
|
|
278
|
+
sql_orderby = "ORDER BY " + sort
|
|
279
|
+
sql_orderby = sql_orderby[1:] + " DESC" if sql_orderby.startswith("-") else sql_orderby + " ASC"
|
|
280
|
+
|
|
281
|
+
with self.get_connection() as conn:
|
|
282
|
+
return self.all(
|
|
283
|
+
conn,
|
|
284
|
+
f"SELECT * FROM media {sql_where} {sql_orderby} LIMIT :take OFFSET :skip",
|
|
285
|
+
params,
|
|
286
|
+
)
|
|
287
|
+
except Exception as e:
|
|
288
|
+
self.ctx.err(f"query_media ({take}, {skip})", e)
|
|
289
|
+
return []
|
|
290
|
+
|
|
291
|
+
def delete_media(self, hash, user=None):
|
|
292
|
+
try:
|
|
293
|
+
with self.get_connection() as conn:
|
|
294
|
+
sql_where, params = self.get_user_filter(user)
|
|
295
|
+
params.update({"hash": hash})
|
|
296
|
+
self.exec(conn, f"DELETE FROM media {sql_where} AND hash = :hash", params)
|
|
297
|
+
except Exception as e:
|
|
298
|
+
self.ctx.err(f"delete_media ({hash})", e)
|