llms-py 3.0.0__py3-none-any.whl → 3.0.0b1__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 (206) hide show
  1. llms/index.html +77 -35
  2. llms/llms.json +23 -72
  3. llms/main.py +732 -1786
  4. llms/providers.json +1 -1
  5. llms/{extensions/analytics/ui/index.mjs → ui/Analytics.mjs} +238 -154
  6. llms/ui/App.mjs +60 -151
  7. llms/ui/Avatar.mjs +85 -0
  8. llms/ui/Brand.mjs +52 -0
  9. llms/ui/ChatPrompt.mjs +606 -0
  10. llms/ui/Main.mjs +873 -0
  11. llms/ui/ModelSelector.mjs +693 -0
  12. llms/ui/OAuthSignIn.mjs +92 -0
  13. llms/ui/ProviderIcon.mjs +36 -0
  14. llms/ui/ProviderStatus.mjs +105 -0
  15. llms/{extensions/app/ui → ui}/Recents.mjs +65 -91
  16. llms/ui/{modules/chat/SettingsDialog.mjs → SettingsDialog.mjs} +9 -9
  17. llms/{extensions/app/ui/index.mjs → ui/Sidebar.mjs} +58 -124
  18. llms/ui/SignIn.mjs +64 -0
  19. llms/ui/SystemPromptEditor.mjs +31 -0
  20. llms/ui/SystemPromptSelector.mjs +56 -0
  21. llms/ui/Welcome.mjs +8 -0
  22. llms/ui/ai.mjs +53 -125
  23. llms/ui/app.css +111 -1837
  24. llms/ui/lib/charts.mjs +13 -9
  25. llms/ui/lib/servicestack-vue.mjs +3 -3
  26. llms/ui/lib/vue.min.mjs +9 -10
  27. llms/ui/lib/vue.mjs +1602 -1763
  28. llms/ui/markdown.mjs +2 -10
  29. llms/ui/tailwind.input.css +80 -496
  30. llms/ui/threadStore.mjs +572 -0
  31. llms/ui/utils.mjs +117 -113
  32. llms/ui.json +1069 -0
  33. {llms_py-3.0.0.dist-info → llms_py-3.0.0b1.dist-info}/METADATA +1 -1
  34. llms_py-3.0.0b1.dist-info/RECORD +49 -0
  35. llms/__pycache__/__init__.cpython-312.pyc +0 -0
  36. llms/__pycache__/__init__.cpython-313.pyc +0 -0
  37. llms/__pycache__/__init__.cpython-314.pyc +0 -0
  38. llms/__pycache__/__main__.cpython-312.pyc +0 -0
  39. llms/__pycache__/__main__.cpython-314.pyc +0 -0
  40. llms/__pycache__/llms.cpython-312.pyc +0 -0
  41. llms/__pycache__/main.cpython-312.pyc +0 -0
  42. llms/__pycache__/main.cpython-313.pyc +0 -0
  43. llms/__pycache__/main.cpython-314.pyc +0 -0
  44. llms/__pycache__/plugins.cpython-314.pyc +0 -0
  45. llms/extensions/app/README.md +0 -20
  46. llms/extensions/app/__init__.py +0 -530
  47. llms/extensions/app/__pycache__/__init__.cpython-314.pyc +0 -0
  48. llms/extensions/app/__pycache__/db.cpython-314.pyc +0 -0
  49. llms/extensions/app/__pycache__/db_manager.cpython-314.pyc +0 -0
  50. llms/extensions/app/db.py +0 -644
  51. llms/extensions/app/db_manager.py +0 -195
  52. llms/extensions/app/requests.json +0 -9073
  53. llms/extensions/app/threads.json +0 -15290
  54. llms/extensions/app/ui/threadStore.mjs +0 -411
  55. llms/extensions/core_tools/CALCULATOR.md +0 -32
  56. llms/extensions/core_tools/__init__.py +0 -598
  57. llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
  58. llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +0 -201
  59. llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +0 -185
  60. llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +0 -101
  61. llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +0 -160
  62. llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +0 -66
  63. llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +0 -27
  64. llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +0 -72
  65. llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +0 -119
  66. llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +0 -98
  67. llms/extensions/core_tools/ui/codemirror/doc/docs.css +0 -225
  68. llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  69. llms/extensions/core_tools/ui/codemirror/lib/codemirror.css +0 -344
  70. llms/extensions/core_tools/ui/codemirror/lib/codemirror.js +0 -9884
  71. llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +0 -942
  72. llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +0 -118
  73. llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +0 -962
  74. llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +0 -62
  75. llms/extensions/core_tools/ui/codemirror/mode/python/python.js +0 -402
  76. llms/extensions/core_tools/ui/codemirror/theme/dracula.css +0 -40
  77. llms/extensions/core_tools/ui/codemirror/theme/mocha.css +0 -135
  78. llms/extensions/core_tools/ui/index.mjs +0 -650
  79. llms/extensions/gallery/README.md +0 -61
  80. llms/extensions/gallery/__init__.py +0 -61
  81. llms/extensions/gallery/__pycache__/__init__.cpython-314.pyc +0 -0
  82. llms/extensions/gallery/__pycache__/db.cpython-314.pyc +0 -0
  83. llms/extensions/gallery/db.py +0 -298
  84. llms/extensions/gallery/ui/index.mjs +0 -482
  85. llms/extensions/katex/README.md +0 -39
  86. llms/extensions/katex/__init__.py +0 -6
  87. llms/extensions/katex/__pycache__/__init__.cpython-314.pyc +0 -0
  88. llms/extensions/katex/ui/README.md +0 -125
  89. llms/extensions/katex/ui/contrib/auto-render.js +0 -338
  90. llms/extensions/katex/ui/contrib/auto-render.min.js +0 -1
  91. llms/extensions/katex/ui/contrib/auto-render.mjs +0 -244
  92. llms/extensions/katex/ui/contrib/copy-tex.js +0 -127
  93. llms/extensions/katex/ui/contrib/copy-tex.min.js +0 -1
  94. llms/extensions/katex/ui/contrib/copy-tex.mjs +0 -105
  95. llms/extensions/katex/ui/contrib/mathtex-script-type.js +0 -109
  96. llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +0 -1
  97. llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +0 -24
  98. llms/extensions/katex/ui/contrib/mhchem.js +0 -3213
  99. llms/extensions/katex/ui/contrib/mhchem.min.js +0 -1
  100. llms/extensions/katex/ui/contrib/mhchem.mjs +0 -3109
  101. llms/extensions/katex/ui/contrib/render-a11y-string.js +0 -887
  102. llms/extensions/katex/ui/contrib/render-a11y-string.min.js +0 -1
  103. llms/extensions/katex/ui/contrib/render-a11y-string.mjs +0 -800
  104. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  105. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  106. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  107. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  108. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  109. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  110. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  111. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  112. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  113. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  114. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  115. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  116. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  117. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  118. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  119. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  120. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  121. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  122. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  123. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  124. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  125. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  126. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  127. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  128. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  129. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  130. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  131. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  132. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  133. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  134. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  135. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  136. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  137. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  138. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  139. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  140. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  141. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  142. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  143. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  144. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  145. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  146. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  147. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  148. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  149. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  150. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  151. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  152. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  153. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  154. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  155. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  156. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  157. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  158. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  159. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  160. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  161. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  162. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  163. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  164. llms/extensions/katex/ui/index.mjs +0 -92
  165. llms/extensions/katex/ui/katex-swap.css +0 -1230
  166. llms/extensions/katex/ui/katex-swap.min.css +0 -1
  167. llms/extensions/katex/ui/katex.css +0 -1230
  168. llms/extensions/katex/ui/katex.js +0 -19080
  169. llms/extensions/katex/ui/katex.min.css +0 -1
  170. llms/extensions/katex/ui/katex.min.js +0 -1
  171. llms/extensions/katex/ui/katex.min.mjs +0 -1
  172. llms/extensions/katex/ui/katex.mjs +0 -18547
  173. llms/extensions/providers/__init__.py +0 -18
  174. llms/extensions/providers/__pycache__/__init__.cpython-314.pyc +0 -0
  175. llms/extensions/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
  176. llms/extensions/providers/__pycache__/chutes.cpython-314.pyc +0 -0
  177. llms/extensions/providers/__pycache__/google.cpython-314.pyc +0 -0
  178. llms/extensions/providers/__pycache__/nvidia.cpython-314.pyc +0 -0
  179. llms/extensions/providers/__pycache__/openai.cpython-314.pyc +0 -0
  180. llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
  181. llms/extensions/providers/anthropic.py +0 -229
  182. llms/extensions/providers/chutes.py +0 -155
  183. llms/extensions/providers/google.py +0 -378
  184. llms/extensions/providers/nvidia.py +0 -105
  185. llms/extensions/providers/openai.py +0 -156
  186. llms/extensions/providers/openrouter.py +0 -72
  187. llms/extensions/system_prompts/README.md +0 -22
  188. llms/extensions/system_prompts/__init__.py +0 -45
  189. llms/extensions/system_prompts/__pycache__/__init__.cpython-314.pyc +0 -0
  190. llms/extensions/system_prompts/ui/index.mjs +0 -280
  191. llms/extensions/system_prompts/ui/prompts.json +0 -1067
  192. llms/extensions/tools/__init__.py +0 -5
  193. llms/extensions/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  194. llms/extensions/tools/ui/index.mjs +0 -204
  195. llms/providers-extra.json +0 -356
  196. llms/ui/ctx.mjs +0 -365
  197. llms/ui/index.mjs +0 -129
  198. llms/ui/modules/chat/ChatBody.mjs +0 -691
  199. llms/ui/modules/chat/index.mjs +0 -828
  200. llms/ui/modules/layout.mjs +0 -243
  201. llms/ui/modules/model-selector.mjs +0 -851
  202. llms_py-3.0.0.dist-info/RECORD +0 -202
  203. {llms_py-3.0.0.dist-info → llms_py-3.0.0b1.dist-info}/WHEEL +0 -0
  204. {llms_py-3.0.0.dist-info → llms_py-3.0.0b1.dist-info}/entry_points.txt +0 -0
  205. {llms_py-3.0.0.dist-info → llms_py-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
  206. {llms_py-3.0.0.dist-info → llms_py-3.0.0b1.dist-info}/top_level.txt +0 -0
@@ -1,61 +0,0 @@
1
- # Gallery Extension
2
-
3
- The Gallery extension intercepts all generated image, audio & file assets and uploaded files in `~/.llms/cache` file storage whose metadata is maintained in a SQLite database.
4
-
5
- Dedicated UIs are available for quickly browsing and navigating or generated images / audio files with optimized UIs for viewing portrait, square and landscape images.
6
-
7
- ## Generated Asset Interception
8
-
9
- The Gallery extension automatically monitors the creation of new cache entries. Whenever a file is saved to the LLMs cache (located at `~/.llms/cache`), the extension captures its metadata and stores it in the gallery database.
10
-
11
- This includes:
12
- - **Generated Images**: Images created by AI models (e.g., DALL-E, Stable Diffusion).
13
- - **Generated Audio**: Audio files generated by text-to-speech or audio models.
14
- - **Uploaded Files**: Any files uploaded through the UI.
15
-
16
- All metadata is stored in a dedicated SQLite database located at `~/.llms/user/default/gallery/gallery.sqlite`, in the `media` table.
17
-
18
- ## User Interface
19
-
20
- The Gallery UI provides a rich, interactive way to explore your generated assets. You can access it via the **Gallery** tab in the sidebar or by navigating to `/gallery`.
21
-
22
- ### Image Gallery
23
-
24
- The image view offers a responsive grid layout optimized for different aspect ratios.
25
-
26
- - **Filtering**:
27
- - **By Format**: Easily switch between **Portrait**, **Square**, and **Landscape** views to see images in their best light.
28
- - **Search**: Real-time search by prompt, model name, or other metadata.
29
- - **Interactions**:
30
- - **Lightbox**: Click any image to view it in full screen.
31
- - **Details**: View comprehensive metadata including the prompt used, generation model, dimensions, file size, creation date, and generation cost.
32
- - **Download**: extensive download options.
33
- - **Remix**: Quickly re-use the prompt and settings of an existing image to generate a new one.
34
- - **Delete**: Remove unwanted images from the gallery.
35
-
36
- ### Audio Gallery
37
-
38
- The audio view presents a list layout designed for easy listening and management.
39
-
40
- - **Playback**: Integrated audio player to preview generated sounds directly in the list.
41
- - **Metadata**: Displays the caption/prompt, model, and creation time.
42
- - **Actions**:
43
- - **Remix**: Regenerate audio using the same prompt.
44
- - **Delete**: Remove audio files.
45
-
46
- ## Storage Data model
47
-
48
- The `media` table tracks extensive information about each asset to support the search and filtering capabilities:
49
-
50
- | Column | Description |
51
- |---|---|
52
- | `url` | Relative path to the file in `~/.llms/cache` |
53
- | `type` | Asset type (image, audio, video) |
54
- | `prompt` | The prompt used to generate the asset |
55
- | `model` | The AI model used |
56
- | `aspect_ratio` | Aspect ratio (e.g., "1:1", "16:9") |
57
- | `cost` | Generation cost |
58
- | `metadata` | Additional JSON metadata |
59
- | `created` | Timestamp of creation |
60
-
61
- This local database ensures your gallery remains fast and responsive, even with a large collection of generated assets.
@@ -1,61 +0,0 @@
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
@@ -1,298 +0,0 @@
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)