llms-py 3.0.0b6__py3-none-any.whl → 3.0.0b7__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} +4 -2
- llms/extensions/core_tools/__init__.py +358 -0
- llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -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 +480 -0
- llms/extensions/providers/__init__.py +18 -0
- llms/extensions/providers/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/{providers → 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 +1 -4
- 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 +284 -0
- llms/extensions/system_prompts/ui/prompts.json +1067 -0
- llms/{ui/modules/tools.mjs → extensions/tools/ui/index.mjs} +4 -2
- llms/llms.json +17 -1
- llms/main.py +381 -170
- llms/providers-extra.json +0 -32
- llms/ui/App.mjs +17 -18
- llms/ui/ai.mjs +10 -3
- llms/ui/app.css +1553 -24
- llms/ui/ctx.mjs +70 -12
- llms/ui/index.mjs +13 -8
- llms/ui/modules/chat/ChatBody.mjs +11 -248
- llms/ui/modules/chat/HomeTools.mjs +254 -0
- llms/ui/modules/chat/SettingsDialog.mjs +1 -1
- llms/ui/modules/chat/index.mjs +278 -174
- llms/ui/modules/layout.mjs +2 -26
- llms/ui/modules/model-selector.mjs +1 -1
- llms/ui/modules/threads/index.mjs +5 -11
- llms/ui/modules/threads/threadStore.mjs +56 -2
- llms/ui/utils.mjs +21 -3
- {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b7.dist-info}/METADATA +1 -1
- llms_py-3.0.0b7.dist-info/RECORD +80 -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_py-3.0.0b6.dist-info/RECORD +0 -66
- {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b7.dist-info}/WHEEL +0 -0
- {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b7.dist-info}/entry_points.txt +0 -0
- {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b7.dist-info}/licenses/LICENSE +0 -0
- {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b7.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
@@ -339,8 +339,8 @@ export const Analytics = {
|
|
|
339
339
|
<div class="flex-1 min-w-0 w-full">
|
|
340
340
|
<div class="flex flex-col sm:flex-row justify-between gap-2 mb-2">
|
|
341
341
|
<div class="flex items-center gap-2 flex-wrap">
|
|
342
|
-
<span class="text-xs px-2 py-1 bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-300 rounded font-medium">{{ request.model }}</span>
|
|
343
|
-
<span class="text-xs px-2 py-1 bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300 rounded font-medium">{{ request.provider }}</span>
|
|
342
|
+
<span v-if="request.model" class="text-xs px-2 py-1 bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-300 rounded font-medium">{{ request.model }}</span>
|
|
343
|
+
<span v-if="request.provider" class="text-xs px-2 py-1 bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300 rounded font-medium">{{ request.provider }}</span>
|
|
344
344
|
<span v-if="request.providerRef" class="text-xs px-2 py-1 bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300 rounded font-medium">{{ request.providerRef }}</span>
|
|
345
345
|
<span v-if="request.finishReason" class="text-xs px-2 py-1 bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300 rounded font-medium">{{ request.finishReason }}</span>
|
|
346
346
|
</div>
|
|
@@ -1531,6 +1531,8 @@ export const Analytics = {
|
|
|
1531
1531
|
}
|
|
1532
1532
|
|
|
1533
1533
|
export default {
|
|
1534
|
+
order: 20 - 100,
|
|
1535
|
+
|
|
1534
1536
|
install(ctx) {
|
|
1535
1537
|
ctx.components({
|
|
1536
1538
|
MonthSelector,
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core System Tools providing essential file operations, memory persistence, math expression evaluation, and code execution
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import glob
|
|
7
|
+
import json
|
|
8
|
+
import math
|
|
9
|
+
import operator
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import tempfile
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from statistics import mean, median, stdev, variance
|
|
16
|
+
from typing import Any, Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
# -----------------------------
|
|
19
|
+
# In-memory storage (replace later)
|
|
20
|
+
# -----------------------------
|
|
21
|
+
|
|
22
|
+
_MEMORY_STORE: Dict[str, Any] = {}
|
|
23
|
+
_SEMANTIC_STORE: List[Dict[str, Any]] = [] # {id, text, metadata}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# -----------------------------
|
|
27
|
+
# Memory tools
|
|
28
|
+
# -----------------------------
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def memory_read(key: str) -> Any:
|
|
32
|
+
"""Read a value from persistent memory."""
|
|
33
|
+
return _MEMORY_STORE.get(key)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def memory_write(key: str, value: Any) -> bool:
|
|
37
|
+
"""Write a value to persistent memory."""
|
|
38
|
+
_MEMORY_STORE[key] = value
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# -----------------------------
|
|
43
|
+
# Path safety helpers
|
|
44
|
+
# -----------------------------
|
|
45
|
+
|
|
46
|
+
# Limit tools to only access files and folders within LLMS_BASE_DIR if specified, otherwise the current working directory
|
|
47
|
+
_BASE_DIR = os.environ.get("LLMS_BASE_DIR") or os.path.realpath(os.getcwd())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _resolve_safe_path(path: str) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Resolve a path and ensure it stays within the current working directory.
|
|
53
|
+
Raises ValueError if the path escapes the base directory.
|
|
54
|
+
"""
|
|
55
|
+
resolved = os.path.realpath(os.path.join(_BASE_DIR, path))
|
|
56
|
+
if not resolved.startswith(_BASE_DIR + os.sep) and resolved != _BASE_DIR:
|
|
57
|
+
raise ValueError("Access denied: path is outside the working directory")
|
|
58
|
+
return resolved
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# -----------------------------
|
|
62
|
+
# Semantic search (placeholder)
|
|
63
|
+
# -----------------------------
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def semantic_search(query: str, top_k: int = 5) -> List[Dict[str, Any]]:
|
|
67
|
+
"""
|
|
68
|
+
Naive semantic search placeholder.
|
|
69
|
+
Replace with embeddings + vector DB.
|
|
70
|
+
"""
|
|
71
|
+
results = []
|
|
72
|
+
for item in _SEMANTIC_STORE:
|
|
73
|
+
if query.lower() in item["text"].lower():
|
|
74
|
+
results.append(item)
|
|
75
|
+
return results[:top_k]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# -----------------------------
|
|
79
|
+
# File system tools (restricted to CWD)
|
|
80
|
+
# -----------------------------
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def read_file(path: str) -> str:
|
|
84
|
+
"""Read a text file from disk within the current working directory."""
|
|
85
|
+
safe_path = _resolve_safe_path(path)
|
|
86
|
+
with open(safe_path, encoding="utf-8") as f:
|
|
87
|
+
return f.read()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def write_file(path: str, content: str) -> bool:
|
|
91
|
+
"""Write text to a file within the current working directory (overwrites)."""
|
|
92
|
+
safe_path = _resolve_safe_path(path)
|
|
93
|
+
os.makedirs(os.path.dirname(safe_path) or _BASE_DIR, exist_ok=True)
|
|
94
|
+
with open(safe_path, "w", encoding="utf-8") as f:
|
|
95
|
+
f.write(content)
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def list_directory(path: str) -> str:
|
|
100
|
+
"""List directory contents"""
|
|
101
|
+
safe_path = _resolve_safe_path(path)
|
|
102
|
+
if not os.path.exists(safe_path):
|
|
103
|
+
return f"Error: Path not found: {path}"
|
|
104
|
+
|
|
105
|
+
entries = []
|
|
106
|
+
try:
|
|
107
|
+
for entry in os.scandir(safe_path):
|
|
108
|
+
stat = entry.stat()
|
|
109
|
+
entries.append(
|
|
110
|
+
{
|
|
111
|
+
"name": "/" + entry.name if entry.is_dir() else entry.name,
|
|
112
|
+
"size": stat.st_size,
|
|
113
|
+
"mtime": datetime.fromtimestamp(stat.st_mtime).isoformat(),
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
return json.dumps({"path": os.path.relpath(safe_path, _BASE_DIR), "entries": entries}, indent=2)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
return f"Error listing directory: {e}"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def glob_paths(
|
|
122
|
+
pattern: str,
|
|
123
|
+
extensions: Optional[List[str]] = None,
|
|
124
|
+
sort_by: str = "path", # "path" | "modified" | "size"
|
|
125
|
+
max_results: int = 100,
|
|
126
|
+
) -> Dict[str, List[Dict[str, str]]]:
|
|
127
|
+
"""
|
|
128
|
+
Find files and directories matching a glob pattern
|
|
129
|
+
"""
|
|
130
|
+
if sort_by not in {"path", "modified", "size"}:
|
|
131
|
+
raise ValueError("sort_by must be one of: path, modified, size")
|
|
132
|
+
|
|
133
|
+
safe_pattern = _resolve_safe_path(pattern)
|
|
134
|
+
|
|
135
|
+
results = []
|
|
136
|
+
|
|
137
|
+
for path in glob.glob(safe_pattern, recursive=True):
|
|
138
|
+
resolved = os.path.realpath(path)
|
|
139
|
+
|
|
140
|
+
# Enforce CWD restriction (important for symlinks)
|
|
141
|
+
if not resolved.startswith(_BASE_DIR):
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
is_dir = os.path.isdir(resolved)
|
|
145
|
+
|
|
146
|
+
# Extension filtering (files only)
|
|
147
|
+
if extensions and not is_dir:
|
|
148
|
+
ext = os.path.splitext(resolved)[1].lower().lstrip(".")
|
|
149
|
+
if ext not in {e.lower().lstrip(".") for e in extensions}:
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
stat = os.stat(resolved)
|
|
153
|
+
|
|
154
|
+
results.append(
|
|
155
|
+
{
|
|
156
|
+
"path": os.path.relpath(resolved, _BASE_DIR),
|
|
157
|
+
"type": "directory" if is_dir else "file",
|
|
158
|
+
"size_bytes": stat.st_size,
|
|
159
|
+
"modified_time": stat.st_mtime,
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if len(results) >= max_results:
|
|
164
|
+
break
|
|
165
|
+
|
|
166
|
+
# Sorting
|
|
167
|
+
if sort_by == "path":
|
|
168
|
+
results.sort(key=lambda x: x["path"])
|
|
169
|
+
elif sort_by == "modified":
|
|
170
|
+
results.sort(key=lambda x: x["modified_time"], reverse=True)
|
|
171
|
+
elif sort_by == "size":
|
|
172
|
+
results.sort(key=lambda x: x["size_bytes"], reverse=True)
|
|
173
|
+
|
|
174
|
+
return {"pattern": pattern, "count": len(results), "results": results}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# -----------------------------
|
|
178
|
+
# Expression evaluation tools
|
|
179
|
+
# -----------------------------
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def calc(expression: str) -> str:
|
|
183
|
+
"""Evaluate a mathematical expression with boolean operations"""
|
|
184
|
+
# 1. Define allowed operators
|
|
185
|
+
operators = {
|
|
186
|
+
ast.Add: operator.add,
|
|
187
|
+
ast.Sub: operator.sub,
|
|
188
|
+
ast.Mult: operator.mul,
|
|
189
|
+
ast.Div: operator.truediv,
|
|
190
|
+
ast.Pow: operator.pow,
|
|
191
|
+
ast.USub: operator.neg,
|
|
192
|
+
ast.Mod: operator.mod,
|
|
193
|
+
# Comparison operators
|
|
194
|
+
ast.Eq: operator.eq,
|
|
195
|
+
ast.NotEq: operator.ne,
|
|
196
|
+
ast.Lt: operator.lt,
|
|
197
|
+
ast.LtE: operator.le,
|
|
198
|
+
ast.Gt: operator.gt,
|
|
199
|
+
ast.GtE: operator.ge,
|
|
200
|
+
# Boolean operators
|
|
201
|
+
ast.And: operator.and_,
|
|
202
|
+
ast.Or: operator.or_,
|
|
203
|
+
ast.Not: operator.not_,
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
# 2. Define allowed math functions and constants
|
|
207
|
+
allowed_functions = {name: getattr(math, name) for name in dir(math) if not name.startswith("_")}
|
|
208
|
+
allowed_functions.update(
|
|
209
|
+
{
|
|
210
|
+
"mod": operator.mod,
|
|
211
|
+
"mean": mean,
|
|
212
|
+
"median": median,
|
|
213
|
+
"stdev": stdev,
|
|
214
|
+
"variance": variance,
|
|
215
|
+
"abs": abs,
|
|
216
|
+
"min": min,
|
|
217
|
+
"max": max,
|
|
218
|
+
"sum": sum,
|
|
219
|
+
"round": round,
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def eval_node(node):
|
|
224
|
+
if isinstance(node, ast.Constant): # Numbers and booleans
|
|
225
|
+
return node.value
|
|
226
|
+
elif isinstance(node, ast.BinOp): # Binary Ops (1 + 2)
|
|
227
|
+
return operators[type(node.op)](eval_node(node.left), eval_node(node.right))
|
|
228
|
+
elif isinstance(node, ast.UnaryOp): # Unary Ops (-5, not True)
|
|
229
|
+
return operators[type(node.op)](eval_node(node.operand))
|
|
230
|
+
elif isinstance(node, ast.Compare): # Comparison (5 > 3)
|
|
231
|
+
left = eval_node(node.left)
|
|
232
|
+
for op, comparator in zip(node.ops, node.comparators):
|
|
233
|
+
right = eval_node(comparator)
|
|
234
|
+
if not operators[type(op)](left, right):
|
|
235
|
+
return False
|
|
236
|
+
left = right
|
|
237
|
+
return True
|
|
238
|
+
elif isinstance(node, ast.BoolOp): # Boolean operations (True and False, True or False)
|
|
239
|
+
op = operators[type(node.op)]
|
|
240
|
+
if isinstance(node.op, ast.And):
|
|
241
|
+
# Short-circuit evaluation for 'and'
|
|
242
|
+
result = True
|
|
243
|
+
for value in node.values:
|
|
244
|
+
result = eval_node(value)
|
|
245
|
+
if not result:
|
|
246
|
+
return False
|
|
247
|
+
return result
|
|
248
|
+
elif isinstance(node.op, ast.Or):
|
|
249
|
+
# Short-circuit evaluation for 'or'
|
|
250
|
+
for value in node.values:
|
|
251
|
+
result = eval_node(value)
|
|
252
|
+
if result:
|
|
253
|
+
return True
|
|
254
|
+
return False
|
|
255
|
+
elif isinstance(node, ast.Call): # Function calls (sqrt(16))
|
|
256
|
+
func_name = node.func.id
|
|
257
|
+
if func_name in allowed_functions:
|
|
258
|
+
args = [eval_node(arg) for arg in node.args]
|
|
259
|
+
return allowed_functions[func_name](*args)
|
|
260
|
+
raise NameError(f"Function '{func_name}' is not allowed.")
|
|
261
|
+
elif isinstance(node, ast.Name): # Constants (pi, e, True, False)
|
|
262
|
+
if node.id in allowed_functions:
|
|
263
|
+
return allowed_functions[node.id]
|
|
264
|
+
elif node.id in ("True", "False"):
|
|
265
|
+
return node.id == "True"
|
|
266
|
+
raise NameError(f"Variable '{node.id}' is not defined.")
|
|
267
|
+
elif isinstance(node, ast.List): # List literals [1, 2, 3]
|
|
268
|
+
return [eval_node(item) for item in node.elts]
|
|
269
|
+
else:
|
|
270
|
+
raise TypeError(f"Unsupported operation: {type(node).__name__}")
|
|
271
|
+
|
|
272
|
+
# Replace XOR with power
|
|
273
|
+
expression = expression.replace("^", "**")
|
|
274
|
+
|
|
275
|
+
# Parse and evaluate
|
|
276
|
+
node = ast.parse(expression, mode="eval").body
|
|
277
|
+
return eval_node(node)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# -----------------------------
|
|
281
|
+
# Python execution tool
|
|
282
|
+
# -----------------------------
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def run_python(code: str) -> Dict[str, Any]:
|
|
286
|
+
"""
|
|
287
|
+
Execute Python code in a temporary sandboxed environment.
|
|
288
|
+
Uses ulimit for resource restriction and runs in a temporary directory.
|
|
289
|
+
"""
|
|
290
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
291
|
+
script_path = os.path.join(temp_dir, "script.py")
|
|
292
|
+
|
|
293
|
+
with open(script_path, "w", encoding="utf-8") as f:
|
|
294
|
+
f.write(code)
|
|
295
|
+
|
|
296
|
+
# Construct command with resource limits
|
|
297
|
+
# ulimit -t 5: Max CPU time 5 seconds
|
|
298
|
+
# ulimit -v 1048576: Max virtual memory 1GB
|
|
299
|
+
cmd = f"ulimit -t 5; ulimit -v 1048576; {sys.executable} script.py"
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
# Run with restricted environment
|
|
303
|
+
# We keep PATH to find basic tools if needed, but remove sensitive vars
|
|
304
|
+
clean_env = {"PATH": os.environ.get("PATH", "")}
|
|
305
|
+
|
|
306
|
+
result = subprocess.run(
|
|
307
|
+
["bash", "-c", cmd], cwd=temp_dir, env=clean_env, capture_output=True, text=True, timeout=10
|
|
308
|
+
)
|
|
309
|
+
return {"stdout": result.stdout, "stderr": result.stderr, "returncode": result.returncode}
|
|
310
|
+
except subprocess.TimeoutExpired:
|
|
311
|
+
return {"stdout": "", "stderr": "Execution timed out", "returncode": -1}
|
|
312
|
+
except Exception as e:
|
|
313
|
+
return {"stdout": "", "stderr": f"Error: {e}", "returncode": -1}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
# -----------------------------
|
|
317
|
+
# Time tool
|
|
318
|
+
# -----------------------------
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def get_current_time(tz_name: Optional[str] = None) -> str:
|
|
322
|
+
"""
|
|
323
|
+
Get current time in ISO-8601 format.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
tz_name: Optional timezone name (e.g. 'America/New_York'). Defaults to UTC.
|
|
327
|
+
"""
|
|
328
|
+
if tz_name:
|
|
329
|
+
try:
|
|
330
|
+
try:
|
|
331
|
+
from zoneinfo import ZoneInfo
|
|
332
|
+
except ImportError:
|
|
333
|
+
from backports.zoneinfo import ZoneInfo
|
|
334
|
+
|
|
335
|
+
tz = ZoneInfo(tz_name)
|
|
336
|
+
except Exception:
|
|
337
|
+
return f"Error: Invalid timezone '{tz_name}'"
|
|
338
|
+
else:
|
|
339
|
+
tz = timezone.utc
|
|
340
|
+
|
|
341
|
+
return datetime.now(tz).isoformat()
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def install(ctx):
|
|
345
|
+
# Examples of registering tools using automatic definition generation
|
|
346
|
+
ctx.register_tool(memory_read)
|
|
347
|
+
ctx.register_tool(memory_write)
|
|
348
|
+
ctx.register_tool(semantic_search)
|
|
349
|
+
ctx.register_tool(read_file)
|
|
350
|
+
ctx.register_tool(write_file)
|
|
351
|
+
ctx.register_tool(list_directory)
|
|
352
|
+
ctx.register_tool(glob_paths)
|
|
353
|
+
ctx.register_tool(calc)
|
|
354
|
+
ctx.register_tool(run_python)
|
|
355
|
+
ctx.register_tool(get_current_time)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
__install__ = install
|
|
Binary file
|
|
@@ -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
|