llms-py 3.0.1__py3-none-any.whl → 3.0.3__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 (36) hide show
  1. llms/{extensions/app/db_manager.py → db.py} +180 -16
  2. llms/extensions/app/__init__.py +96 -29
  3. llms/extensions/app/db.py +16 -124
  4. llms/extensions/app/ui/threadStore.mjs +23 -2
  5. llms/extensions/core_tools/__init__.py +37 -0
  6. llms/extensions/gallery/__init__.py +15 -13
  7. llms/extensions/gallery/db.py +117 -172
  8. llms/extensions/gallery/ui/index.mjs +1 -1
  9. llms/extensions/providers/__init__.py +3 -1
  10. llms/extensions/providers/anthropic.py +7 -3
  11. llms/extensions/providers/cerebras.py +37 -0
  12. llms/extensions/providers/chutes.py +1 -1
  13. llms/extensions/providers/google.py +131 -28
  14. llms/extensions/providers/nvidia.py +2 -2
  15. llms/extensions/providers/openai.py +2 -2
  16. llms/extensions/providers/openrouter.py +4 -2
  17. llms/llms.json +3 -0
  18. llms/main.py +83 -34
  19. llms/providers.json +1 -1
  20. llms/ui/ai.mjs +1 -1
  21. llms/ui/app.css +106 -3
  22. llms/ui/ctx.mjs +34 -0
  23. llms/ui/index.mjs +2 -0
  24. llms/ui/modules/chat/ChatBody.mjs +245 -248
  25. llms/ui/modules/chat/index.mjs +93 -2
  26. llms/ui/modules/icons.mjs +46 -0
  27. llms/ui/modules/layout.mjs +28 -0
  28. llms/ui/modules/model-selector.mjs +0 -40
  29. llms/ui/tailwind.input.css +1 -1
  30. llms/ui/utils.mjs +9 -1
  31. {llms_py-3.0.1.dist-info → llms_py-3.0.3.dist-info}/METADATA +1 -1
  32. {llms_py-3.0.1.dist-info → llms_py-3.0.3.dist-info}/RECORD +36 -34
  33. {llms_py-3.0.1.dist-info → llms_py-3.0.3.dist-info}/WHEEL +0 -0
  34. {llms_py-3.0.1.dist-info → llms_py-3.0.3.dist-info}/entry_points.txt +0 -0
  35. {llms_py-3.0.1.dist-info → llms_py-3.0.3.dist-info}/licenses/LICENSE +0 -0
  36. {llms_py-3.0.1.dist-info → llms_py-3.0.3.dist-info}/top_level.txt +0 -0
llms/extensions/app/db.py CHANGED
@@ -1,10 +1,9 @@
1
1
  import json
2
2
  import os
3
- import threading
4
3
  from datetime import datetime, timedelta
5
4
  from typing import Any, Dict
6
5
 
7
- from .db_manager import DbManager
6
+ from llms.db import DbManager, order_by, select_columns, to_dto, valid_columns
8
7
 
9
8
 
10
9
  def with_user(data, user):
@@ -17,44 +16,6 @@ def with_user(data, user):
17
16
  return data
18
17
 
19
18
 
20
- def valid_columns(all_columns, fields):
21
- if fields:
22
- if not isinstance(fields, list):
23
- fields = fields.split(",")
24
- cols = []
25
- for k in fields:
26
- k = k.strip()
27
- if k in all_columns:
28
- cols.append(k)
29
- return cols
30
- return []
31
-
32
-
33
- def table_columns(all_columns, fields):
34
- cols = valid_columns(all_columns, fields)
35
- return ", ".join(cols) if len(cols) > 0 else ", ".join(all_columns)
36
-
37
-
38
- def select_columns(all_columns, fields, select=None):
39
- columns = table_columns(all_columns, fields)
40
- if select == "distinct":
41
- return f"SELECT DISTINCT {columns}"
42
- return f"SELECT {columns}"
43
-
44
-
45
- def order_by(all_columns, sort):
46
- cols = []
47
- for k in sort.split(","):
48
- k = k.strip()
49
- by = ""
50
- if k[0] == "-":
51
- by = " DESC"
52
- k = k[1:]
53
- if k in all_columns:
54
- cols.append(f"{k}{by}")
55
- return f"ORDER BY {', '.join(cols)} " if len(cols) > 0 else ""
56
-
57
-
58
19
  class AppDB:
59
20
  def __init__(self, ctx, db_path):
60
21
  if db_path is None:
@@ -81,6 +42,7 @@ class AppDB:
81
42
  "modalities": "JSON",
82
43
  "messages": "JSON",
83
44
  "args": "JSON",
45
+ "tools": "JSON",
84
46
  "toolHistory": "JSON",
85
47
  "cost": "REAL",
86
48
  "inputTokens": "INTEGER",
@@ -94,6 +56,7 @@ class AppDB:
94
56
  "metadata": "JSON",
95
57
  "error": "TEXT",
96
58
  "ref": "TEXT",
59
+ "providerResponse": "JSON",
97
60
  },
98
61
  "request": {
99
62
  "id": "INTEGER",
@@ -300,6 +263,9 @@ class AppDB:
300
263
  sql_params,
301
264
  ).lastrowid
302
265
 
266
+ def to_dto(self, row, json_columns):
267
+ return to_dto(self.ctx, row, json_columns)
268
+
303
269
  def get_user_filter(self, user=None, params=None):
304
270
  if user is None:
305
271
  return "WHERE user IS NULL", params or {}
@@ -309,12 +275,8 @@ class AppDB:
309
275
  return "WHERE user = :user", args
310
276
 
311
277
  def get_thread(self, id, user=None):
312
- try:
313
- sql_where, params = self.get_user_filter(user, {"id": id})
314
- return self.db.one(f"SELECT * FROM thread {sql_where} AND id = :id", params)
315
- except Exception as e:
316
- self.ctx.err(f"get_thread ({id}, {user})", e)
317
- return None
278
+ sql_where, params = self.get_user_filter(user, {"id": id})
279
+ return self.db.one(f"SELECT * FROM thread {sql_where} AND id = :id", params)
318
280
 
319
281
  def get_thread_column(self, id, column, user=None):
320
282
  if column not in self.columns["thread"]:
@@ -375,76 +337,6 @@ class AppDB:
375
337
  self.ctx.err(f"query_threads ({take}, {skip})", e)
376
338
  return []
377
339
 
378
- def insert(self, table, info, callback=None):
379
- if not info:
380
- raise Exception("info is required")
381
-
382
- columns = self.columns[table]
383
- args = {}
384
- known_columns = columns.keys()
385
- for k, val in info.items():
386
- if k in known_columns and k != "id":
387
- args[k] = self.db.value(val)
388
-
389
- insert_keys = list(args.keys())
390
- insert_body = ", ".join(insert_keys)
391
- insert_values = ", ".join(["?" for _ in insert_keys])
392
-
393
- sql = f"INSERT INTO {table} ({insert_body}) VALUES ({insert_values})"
394
-
395
- self.db.write(sql, tuple(args[k] for k in insert_keys), callback)
396
-
397
- async def insert_async(self, table, info):
398
- event = threading.Event()
399
-
400
- ret = [None]
401
-
402
- def cb(lastrowid, rowcount, error=None):
403
- nonlocal ret
404
- if error:
405
- raise error
406
- ret[0] = lastrowid
407
- event.set()
408
-
409
- self.insert(table, info, cb)
410
- event.wait()
411
- return ret[0]
412
-
413
- def update(self, table, info, callback=None):
414
- if not info:
415
- raise Exception("info is required")
416
-
417
- columns = self.columns[table]
418
- args = {}
419
- known_columns = columns.keys()
420
- for k, val in info.items():
421
- if k in known_columns and k != "id":
422
- args[k] = self.db.value(val)
423
-
424
- update_keys = list(args.keys())
425
- update_body = ", ".join([f"{k} = :{k}" for k in update_keys])
426
-
427
- args["id"] = info["id"]
428
- sql = f"UPDATE {table} SET {update_body} WHERE id = :id"
429
-
430
- self.db.write(sql, args, callback)
431
-
432
- async def update_async(self, table, info):
433
- event = threading.Event()
434
-
435
- ret = [None]
436
-
437
- def cb(lastrowid, rowcount, error=None):
438
- nonlocal ret
439
- if error:
440
- raise error
441
- ret[0] = rowcount
442
- event.set()
443
-
444
- self.update(table, info, cb)
445
- event.wait()
446
- return ret[0]
447
-
448
340
  def prepare_thread(self, thread, id=None, user=None):
449
341
  now = datetime.now()
450
342
  if id:
@@ -458,16 +350,16 @@ class AppDB:
458
350
  return with_user(thread, user=user)
459
351
 
460
352
  def create_thread(self, thread: Dict[str, Any], user=None):
461
- return self.insert("thread", self.prepare_thread(thread, user=user))
353
+ return self.db.insert("thread", self.columns["thread"], self.prepare_thread(thread, user=user))
462
354
 
463
355
  async def create_thread_async(self, thread: Dict[str, Any], user=None):
464
- return await self.insert_async("thread", self.prepare_thread(thread, user=user))
356
+ return await self.db.insert_async("thread", self.columns["thread"], self.prepare_thread(thread, user=user))
465
357
 
466
358
  def update_thread(self, id, thread: Dict[str, Any], user=None):
467
- return self.update("thread", self.prepare_thread(thread, id, user=user))
359
+ return self.db.update("thread", self.columns["thread"], self.prepare_thread(thread, id, user=user))
468
360
 
469
361
  async def update_thread_async(self, id, thread: Dict[str, Any], user=None):
470
- return await self.update_async("thread", self.prepare_thread(thread, id, user=user))
362
+ return await self.db.update_async("thread", self.columns["thread"], self.prepare_thread(thread, id, user=user))
471
363
 
472
364
  def delete_thread(self, id, user=None, callback=None):
473
365
  sql_where, params = self.get_user_filter(user, {"id": id})
@@ -609,21 +501,21 @@ class AppDB:
609
501
 
610
502
  def create_request(self, request: Dict[str, Any], user=None):
611
503
  request["createdAt"] = request["updatedAt"] = datetime.now()
612
- return self.insert("request", with_user(request, user=user))
504
+ return self.db.insert("request", self.columns["request"], with_user(request, user=user))
613
505
 
614
506
  async def create_request_async(self, request: Dict[str, Any], user=None):
615
507
  request["createdAt"] = request["updatedAt"] = datetime.now()
616
- return await self.insert_async("request", with_user(request, user=user))
508
+ return await self.db.insert_async("request", self.columns["request"], with_user(request, user=user))
617
509
 
618
510
  def update_request(self, id, request: Dict[str, Any], user=None):
619
511
  request["id"] = id
620
512
  request["updatedAt"] = datetime.now()
621
- return self.update("request", with_user(request, user=user))
513
+ return self.db.update("request", self.columns["request"], with_user(request, user=user))
622
514
 
623
515
  async def update_request_async(self, id, request: Dict[str, Any], user=None):
624
516
  request["id"] = id
625
517
  request["updatedAt"] = datetime.now()
626
- return await self.update_async("request", with_user(request, user=user))
518
+ return await self.db.update_async("request", self.columns["request"], with_user(request, user=user))
627
519
 
628
520
  def delete_request(self, id, user=None, callback=None):
629
521
  sql_where, params = self.get_user_filter(user, {"id": id})
@@ -15,6 +15,7 @@ export const nextId = (() => {
15
15
 
16
16
 
17
17
  const threads = ref([])
18
+ const threadDetails = ref({})
18
19
  const currentThread = ref(null)
19
20
  const isLoading = ref(false)
20
21
 
@@ -115,6 +116,9 @@ function replaceThread(thread) {
115
116
  if (currentThread.value?.id === thread.id) {
116
117
  currentThread.value = thread
117
118
  }
119
+ if (thread.completedAt || thread.error) {
120
+ threadDetails.value[thread.id] = thread
121
+ }
118
122
  startWatchingThread()
119
123
  return thread
120
124
  }
@@ -196,7 +200,7 @@ async function loadThreads() {
196
200
  isLoading.value = true
197
201
 
198
202
  try {
199
- const api = await ext.getJson('/threads?take=50')
203
+ const api = await ext.getJson('/threads?take=30')
200
204
  threads.value = api.response || []
201
205
  return threads.value
202
206
  } finally {
@@ -239,6 +243,7 @@ async function setCurrentThreadFromRoute(threadId, router) {
239
243
  return null
240
244
  }
241
245
 
246
+ loadThreadDetails(threadId)
242
247
  const thread = setCurrentThread(threadId)
243
248
  if (thread) {
244
249
  return thread
@@ -307,7 +312,7 @@ function getLatestCachedThread() {
307
312
  return threads.value[0]
308
313
  }
309
314
 
310
- async function startNewThread({ title, model, redirect }) {
315
+ async function startNewThread({ title, model, tools, redirect }) {
311
316
  if (!model) {
312
317
  console.error('No model selected')
313
318
  return
@@ -330,6 +335,7 @@ async function startNewThread({ title, model, redirect }) {
330
335
  title,
331
336
  model: model.name,
332
337
  info: ctx.utils.toModelInfo(model),
338
+ tools,
333
339
  })
334
340
 
335
341
  console.log('newThread', newThread, model)
@@ -361,6 +367,19 @@ async function queueChat(ctxRequest, options = {}) {
361
367
  return api
362
368
  }
363
369
 
370
+ async function loadThreadDetails(id, opt = null) {
371
+ if (!threadDetails.value[id] || opt?.force) {
372
+ const api = await ctx.getJson(`/ext/app/threads/${id}`)
373
+ if (api.response) {
374
+ threadDetails.value[id] = api.response
375
+ }
376
+ if (api.error) {
377
+ console.error(api.error)
378
+ }
379
+ }
380
+ return threadDetails.value[id]
381
+ }
382
+
364
383
  // Export the store
365
384
  export function useThreadStore() {
366
385
  return {
@@ -388,6 +407,8 @@ export function useThreadStore() {
388
407
  startNewThread,
389
408
  replaceThread,
390
409
  queueChat,
410
+ threadDetails,
411
+ loadThreadDetails,
391
412
  isWatchingThread,
392
413
  startWatchingThread,
393
414
  stopWatchingThread,
@@ -102,6 +102,24 @@ def write_file(path: str, content: str) -> bool:
102
102
  return True
103
103
 
104
104
 
105
+ def edit_file(path: str, old_str: str, new_str: str) -> Dict[str, Any]:
106
+ """
107
+ Replaces first occurrence of old_str with new_str in file. If old_str is empty,
108
+ create/overwrite file with new_str.
109
+ :return: A dictionary with the path to the file and the action taken.
110
+ """
111
+ safe_path = _resolve_safe_path(path)
112
+ if old_str == "":
113
+ safe_path.write_text(new_str, encoding="utf-8")
114
+ return {"path": str(safe_path), "action": "created_file"}
115
+ original = safe_path.read_text(encoding="utf-8")
116
+ if original.find(old_str) == -1:
117
+ return {"path": str(safe_path), "action": "old_str not found"}
118
+ edited = original.replace(old_str, new_str, 1)
119
+ safe_path.write_text(edited, encoding="utf-8")
120
+ return {"path": str(safe_path), "action": "edited"}
121
+
122
+
105
123
  def list_directory(path: str) -> str:
106
124
  """List directory contents"""
107
125
  safe_path = _resolve_safe_path(path)
@@ -526,6 +544,25 @@ def install(ctx):
526
544
  # ctx.register_tool(semantic_search) # TODO: implement
527
545
  ctx.register_tool(read_file)
528
546
  ctx.register_tool(write_file)
547
+ ctx.register_tool(
548
+ edit_file,
549
+ {
550
+ "type": "function",
551
+ "function": {
552
+ "name": "edit_file",
553
+ "description": "Replaces first occurrence of old_str with new_str in file. If old_str is empty, create/overwrite file with new_str.",
554
+ "parameters": {
555
+ "type": "object",
556
+ "properties": {
557
+ "path": {"type": "string", "description": "Path to the file to edit."},
558
+ "old_str": {"type": "string", "description": "String to replace."},
559
+ "new_str": {"type": "string", "description": "String to replace with."},
560
+ },
561
+ "required": ["path", "old_str", "new_str"],
562
+ },
563
+ },
564
+ },
565
+ )
529
566
  ctx.register_tool(list_directory)
530
567
  ctx.register_tool(glob_paths)
531
568
  ctx.register_tool(calc)
@@ -3,44 +3,46 @@ import os
3
3
 
4
4
  from aiohttp import web
5
5
 
6
- g_db = None
6
+ from .db import GalleryDB
7
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
8
+ g_db = None
13
9
 
14
10
 
15
11
  def install(ctx):
16
- def get_gallery_db():
12
+ def get_db():
17
13
  global g_db
18
14
  if g_db is None and GalleryDB:
19
15
  try:
20
16
  db_path = os.path.join(ctx.get_user_path(), "gallery", "gallery.sqlite")
21
17
  g_db = GalleryDB(ctx, db_path)
18
+ ctx.register_shutdown_handler(g_db.db.close)
22
19
  except Exception as e:
23
20
  ctx.err("Failed to init GalleryDB", e)
24
21
  return g_db
25
22
 
26
- if not get_gallery_db():
23
+ if not get_db():
27
24
  return
28
25
 
26
+ def media_dto(row):
27
+ return row and g_db.to_dto(row, ["reactions", "category", "tags", "ratings", "objects", "metadata"])
28
+
29
29
  def on_cache_save(context):
30
- url = context["url"]
31
- info = context["info"]
30
+ url = context.get("url", None)
31
+ info = context.get("info", {})
32
+ user = context.get("user", None)
32
33
  ctx.log(f"cache saved: {url}")
33
- ctx.log(json.dumps(info, indent=2))
34
+ ctx.dbg(json.dumps(info, indent=2))
34
35
 
35
36
  if "url" not in info:
36
37
  info["url"] = url
37
- g_db.insert_media(info)
38
+ g_db.insert_media(info, user=user)
38
39
 
39
40
  ctx.register_cache_saved_filter(on_cache_save)
40
41
 
41
42
  async def query_media(request):
42
43
  rows = g_db.query_media(request.query, user=ctx.get_username(request))
43
- return web.json_response(rows)
44
+ dtos = [media_dto(row) for row in rows]
45
+ return web.json_response(dtos)
44
46
 
45
47
  ctx.add_get("media", query_media)
46
48