llms-py 3.0.0b9__py3-none-any.whl → 3.0.0b10__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/extensions/app/README.md +20 -0
- llms/extensions/app/__init__.py +16 -5
- llms/extensions/app/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/app/__pycache__/db.cpython-314.pyc +0 -0
- llms/extensions/app/db.py +7 -6
- llms/extensions/app/ui/index.mjs +1 -1
- llms/extensions/app/ui/threadStore.mjs +21 -17
- llms/extensions/core_tools/CALCULATOR.md +32 -0
- llms/extensions/core_tools/__init__.py +1 -1
- llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
- llms/extensions/core_tools/ui/index.mjs +4 -4
- llms/extensions/gallery/README.md +61 -0
- llms/extensions/gallery/ui/index.mjs +1 -0
- llms/extensions/katex/README.md +39 -0
- llms/extensions/system_prompts/README.md +22 -0
- llms/llms.json +9 -12
- llms/main.py +25 -5
- llms/ui/ai.mjs +20 -5
- llms/ui/ctx.mjs +22 -0
- llms/ui/modules/chat/ChatBody.mjs +43 -27
- llms/ui/modules/chat/index.mjs +17 -29
- {llms_py-3.0.0b9.dist-info → llms_py-3.0.0b10.dist-info}/METADATA +1 -1
- {llms_py-3.0.0b9.dist-info → llms_py-3.0.0b10.dist-info}/RECORD +28 -24
- llms/ui/modules/chat/HomeTools.mjs +0 -12
- {llms_py-3.0.0b9.dist-info → llms_py-3.0.0b10.dist-info}/WHEEL +0 -0
- {llms_py-3.0.0b9.dist-info → llms_py-3.0.0b10.dist-info}/entry_points.txt +0 -0
- {llms_py-3.0.0b9.dist-info → llms_py-3.0.0b10.dist-info}/licenses/LICENSE +0 -0
- {llms_py-3.0.0b9.dist-info → llms_py-3.0.0b10.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# App Extension
|
|
2
|
+
|
|
3
|
+
This extension provides the core application logic and data persistence for the LLMs platform.
|
|
4
|
+
|
|
5
|
+
## Data Storage & Architecture
|
|
6
|
+
|
|
7
|
+
### Server-Side SQLite Migration
|
|
8
|
+
The application has migrated from client-side IndexedDB storage to a robust server-side SQLite solution. This architectural shift ensures better data consistency, improved performance, and enables multi-device access to your chat history.
|
|
9
|
+
|
|
10
|
+
### Asset Management
|
|
11
|
+
To keep the database efficient and portable, binary assets (images, audio, etc.) are not stored directly in the SQLite database. Instead:
|
|
12
|
+
- All generated assets are stored in the local file system cache at `~/.llms/cache`.
|
|
13
|
+
- The database stores only **relative URLs** pointing to these assets.
|
|
14
|
+
- This approach allows for efficient caching and serving of static media.
|
|
15
|
+
|
|
16
|
+
### Concurrency Model
|
|
17
|
+
To ensure data integrity and high performance without complex locking mechanisms, the system utilizes a **single background thread** for managing all write operations to the database. This design improves concurrency handling and eliminates database locking issues during high-load scenarios.
|
|
18
|
+
|
|
19
|
+
### Multi-Tenancy & Security
|
|
20
|
+
When authentication is enabled, data isolation is automatically enforced. All core tables, including `threads` and `requests`, are scoped to the authenticated user, ensuring that users can only access their own data.
|
llms/extensions/app/__init__.py
CHANGED
|
@@ -60,7 +60,7 @@ def install(ctx):
|
|
|
60
60
|
return to
|
|
61
61
|
|
|
62
62
|
def thread_dto(row):
|
|
63
|
-
return row and to_dto(row, ["messages", "modalities", "args", "modelInfo", "stats"])
|
|
63
|
+
return row and to_dto(row, ["messages", "modalities", "args", "modelInfo", "stats", "metadata"])
|
|
64
64
|
|
|
65
65
|
def request_dto(row):
|
|
66
66
|
return row and to_dto(row, ["usage"])
|
|
@@ -174,12 +174,13 @@ def install(ctx):
|
|
|
174
174
|
if not thread:
|
|
175
175
|
raise Exception("Thread not found")
|
|
176
176
|
|
|
177
|
+
metadata = thread.get("metadata", {})
|
|
177
178
|
chat = {
|
|
178
179
|
"model": thread.get("model"),
|
|
179
180
|
"messages": thread.get("messages"),
|
|
180
181
|
"modalities": thread.get("modalities"),
|
|
181
182
|
"systemPrompt": thread.get("systemPrompt"),
|
|
182
|
-
"metadata":
|
|
183
|
+
"metadata": metadata,
|
|
183
184
|
}
|
|
184
185
|
for k, v in thread.get("args", {}).items():
|
|
185
186
|
if k in ctx.request_args:
|
|
@@ -189,7 +190,8 @@ def install(ctx):
|
|
|
189
190
|
"chat": chat,
|
|
190
191
|
"user": user,
|
|
191
192
|
"threadId": id,
|
|
192
|
-
"
|
|
193
|
+
"metadata": metadata,
|
|
194
|
+
"tools": metadata.get("tools", "all"),
|
|
193
195
|
}
|
|
194
196
|
|
|
195
197
|
# execute chat in background thread
|
|
@@ -341,13 +343,22 @@ def install(ctx):
|
|
|
341
343
|
|
|
342
344
|
ctx.register_chat_request_filter(chat_request)
|
|
343
345
|
|
|
344
|
-
async def tool_request(
|
|
345
|
-
|
|
346
|
+
async def tool_request(chat_request, context):
|
|
347
|
+
messages = chat_request.get("messages", [])
|
|
348
|
+
ctx.dbg(f"tool_request: messages {len(messages)}")
|
|
346
349
|
thread_id = context.get("threadId", None)
|
|
347
350
|
if not thread_id:
|
|
348
351
|
ctx.dbg("Missing threadId")
|
|
349
352
|
return
|
|
350
353
|
user = context.get("user", None)
|
|
354
|
+
await g_db.update_thread_async(
|
|
355
|
+
thread_id,
|
|
356
|
+
{
|
|
357
|
+
"messages": messages,
|
|
358
|
+
},
|
|
359
|
+
user=user,
|
|
360
|
+
)
|
|
361
|
+
|
|
351
362
|
completed_at = g_db.get_thread_column(thread_id, "completedAt", user=user)
|
|
352
363
|
if completed_at:
|
|
353
364
|
context["completed"] = True
|
|
Binary file
|
|
Binary file
|
llms/extensions/app/db.py
CHANGED
|
@@ -91,6 +91,7 @@ class AppDB:
|
|
|
91
91
|
"publishedAt": "TIMESTAMP",
|
|
92
92
|
"startedAt": "TIMESTAMP",
|
|
93
93
|
"completedAt": "TIMESTAMP",
|
|
94
|
+
"metadata": "JSON",
|
|
94
95
|
"error": "TEXT",
|
|
95
96
|
"ref": "TEXT",
|
|
96
97
|
},
|
|
@@ -444,7 +445,7 @@ class AppDB:
|
|
|
444
445
|
event.wait()
|
|
445
446
|
return ret[0]
|
|
446
447
|
|
|
447
|
-
def prepare_thread(self, thread, id=None):
|
|
448
|
+
def prepare_thread(self, thread, id=None, user=None):
|
|
448
449
|
now = datetime.now()
|
|
449
450
|
if id:
|
|
450
451
|
thread["id"] = id
|
|
@@ -454,19 +455,19 @@ class AppDB:
|
|
|
454
455
|
if "messages" in thread:
|
|
455
456
|
for m in thread["messages"]:
|
|
456
457
|
self.ctx.cache_message_inline_data(m)
|
|
457
|
-
return thread
|
|
458
|
+
return with_user(thread, user=user)
|
|
458
459
|
|
|
459
460
|
def create_thread(self, thread: Dict[str, Any], user=None):
|
|
460
|
-
return self.insert("thread",
|
|
461
|
+
return self.insert("thread", self.prepare_thread(thread, user=user))
|
|
461
462
|
|
|
462
463
|
async def create_thread_async(self, thread: Dict[str, Any], user=None):
|
|
463
|
-
return await self.insert_async("thread",
|
|
464
|
+
return await self.insert_async("thread", self.prepare_thread(thread, user=user))
|
|
464
465
|
|
|
465
466
|
def update_thread(self, id, thread: Dict[str, Any], user=None):
|
|
466
|
-
return self.update("thread",
|
|
467
|
+
return self.update("thread", self.prepare_thread(thread, id, user=user))
|
|
467
468
|
|
|
468
469
|
async def update_thread_async(self, id, thread: Dict[str, Any], user=None):
|
|
469
|
-
return await self.update_async("thread",
|
|
470
|
+
return await self.update_async("thread", self.prepare_thread(thread, id, user=user))
|
|
470
471
|
|
|
471
472
|
def delete_thread(self, id, user=None, callback=None):
|
|
472
473
|
sql_where, params = self.get_user_filter(user, {"id": id})
|
llms/extensions/app/ui/index.mjs
CHANGED
|
@@ -219,7 +219,7 @@ const ThreadsSidebar = {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
const createNewThread = async () => {
|
|
222
|
-
ctx.threads.startNewThread({ title: 'New Chat', model: ctx.chat.getSelectedModel() })
|
|
222
|
+
ctx.threads.startNewThread({ title: 'New Chat', model: ctx.chat.getSelectedModel(), redirect: true })
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
const goToInitialState = () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ref, computed } from 'vue'
|
|
2
|
-
import { appendQueryString
|
|
2
|
+
import { appendQueryString } from '@servicestack/client'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Returns an ever-increasing unique integer id.
|
|
@@ -21,11 +21,6 @@ const isLoading = ref(false)
|
|
|
21
21
|
let ctx = null
|
|
22
22
|
let ext = null
|
|
23
23
|
|
|
24
|
-
// Generate unique thread ID
|
|
25
|
-
function generateThreadId() {
|
|
26
|
-
return Date.now().toString()
|
|
27
|
-
}
|
|
28
|
-
|
|
29
24
|
function setError(error, msg = null) {
|
|
30
25
|
ctx?.setError(error, msg)
|
|
31
26
|
}
|
|
@@ -170,7 +165,7 @@ async function redoMessageFromThread(threadId, timestamp) {
|
|
|
170
165
|
// Find the index of the message to redo
|
|
171
166
|
const messageIndex = thread.messages.findIndex(m => m.timestamp === timestamp)
|
|
172
167
|
if (messageIndex === -1) {
|
|
173
|
-
setError(
|
|
168
|
+
setError({ message: `Message not found for timestamp ${timestamp}` })
|
|
174
169
|
return
|
|
175
170
|
}
|
|
176
171
|
|
|
@@ -188,7 +183,8 @@ async function redoMessageFromThread(threadId, timestamp) {
|
|
|
188
183
|
const updatedMessages = thread.messages.slice(0, messageIndex + 1)
|
|
189
184
|
|
|
190
185
|
// Update the thread with the new messages
|
|
191
|
-
const
|
|
186
|
+
const request = { messages: updatedMessages }
|
|
187
|
+
const api = await queueChat({ request, thread })
|
|
192
188
|
if (api.response) {
|
|
193
189
|
replaceThread(api.response)
|
|
194
190
|
} else {
|
|
@@ -311,7 +307,7 @@ function getLatestCachedThread() {
|
|
|
311
307
|
return threads.value[0]
|
|
312
308
|
}
|
|
313
309
|
|
|
314
|
-
async function startNewThread({ title, model }) {
|
|
310
|
+
async function startNewThread({ title, model, redirect }) {
|
|
315
311
|
if (!model) {
|
|
316
312
|
console.error('No model selected')
|
|
317
313
|
return
|
|
@@ -337,8 +333,10 @@ async function startNewThread({ title, model }) {
|
|
|
337
333
|
})
|
|
338
334
|
|
|
339
335
|
console.log('newThread', newThread, model)
|
|
340
|
-
|
|
341
|
-
|
|
336
|
+
if (redirect) {
|
|
337
|
+
// Navigate to the new thread URL
|
|
338
|
+
ctx.to(`/c/${newThread.id}`)
|
|
339
|
+
}
|
|
342
340
|
|
|
343
341
|
// Get the thread to check for duplicates
|
|
344
342
|
let thread = await getThread(newThread.id)
|
|
@@ -346,12 +344,19 @@ async function startNewThread({ title, model }) {
|
|
|
346
344
|
return thread
|
|
347
345
|
}
|
|
348
346
|
|
|
349
|
-
async function queueChat(
|
|
350
|
-
|
|
347
|
+
async function queueChat(ctxRequest, options = {}) {
|
|
348
|
+
if (!ctxRequest.request) return ctx.createErrorResult({ message: 'No request provided' })
|
|
349
|
+
if (!ctxRequest.thread) return ctx.createErrorResult({ message: 'No thread provided' })
|
|
350
|
+
if (!ctxRequest.request.metadata) {
|
|
351
|
+
ctxRequest.request.metadata = {}
|
|
352
|
+
}
|
|
353
|
+
ctx.chatRequestFilters.forEach(f => f(ctxRequest))
|
|
354
|
+
const { thread, request } = ctxRequest
|
|
355
|
+
const api = await ctx.postJson(`/ext/app/threads/${thread.id}/chat`, {
|
|
351
356
|
...options,
|
|
352
|
-
body: typeof
|
|
353
|
-
?
|
|
354
|
-
: JSON.stringify(
|
|
357
|
+
body: typeof request == 'string'
|
|
358
|
+
? request
|
|
359
|
+
: JSON.stringify(request),
|
|
355
360
|
})
|
|
356
361
|
return api
|
|
357
362
|
}
|
|
@@ -380,7 +385,6 @@ export function useThreadStore() {
|
|
|
380
385
|
clearCurrentThread,
|
|
381
386
|
getGroupedThreads,
|
|
382
387
|
getLatestCachedThread,
|
|
383
|
-
generateThreadId,
|
|
384
388
|
startNewThread,
|
|
385
389
|
replaceThread,
|
|
386
390
|
queueChat,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Calculator
|
|
2
|
+
|
|
3
|
+
A powerful and safe mathematical expression evaluator with a rich web interface.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### 🖥️ UX Friendly Interface
|
|
8
|
+
Experience a clean, modern interface designed for efficiency. The UI is fully responsive and supports dark mode, seamlessly integrating with the rest of the application.
|
|
9
|
+
|
|
10
|
+
### 💾 Persistent History
|
|
11
|
+
Never lose track of your calculations. The Calculator automatically saves your history to `localStorage`, ensuring your previous expressions and results are preserved between sessions.
|
|
12
|
+
|
|
13
|
+
### ⚡ 1-Click Interaction
|
|
14
|
+
Streamline your workflow with interactive history items:
|
|
15
|
+
- **Load & Copy**: Click on any past expression or answer to instantly load it into the input field and copy it to your clipboard.
|
|
16
|
+
- **Visual Feedback**: Temporary checkmarks confirm successful copy actions.
|
|
17
|
+
|
|
18
|
+
### ⌨️ Keyboard-Free Access
|
|
19
|
+
While full keyboard support is available, you can perform complex calculations entirely via the UI:
|
|
20
|
+
- **Numbers & Constants**: Quick access to digits and mathematical constants like `pi`, `e`, `inf`.
|
|
21
|
+
- **Operators**: Comprehensive set of buttons for arithmetic (`+`, `-`, `*`, `/`, `%`, `^`) and boolean logic (`and`, `or`, `not`).
|
|
22
|
+
- **Functions**: One-click insertion or wrapping of selection for all supported math functions.
|
|
23
|
+
|
|
24
|
+
### 🐍 Python Math Support
|
|
25
|
+
Unlock the power of Python's math library directly in the browser.
|
|
26
|
+
- **Math Functions**: Support for `sin`, `cos`, `tan`, `sqrt`, `log`, `factorial`, and many more.
|
|
27
|
+
- **Statistics**: Built-in functions for `mean`, `median`, `stdev`, and `variance`.
|
|
28
|
+
|
|
29
|
+
### 🛡️ Safe Evaluation
|
|
30
|
+
Security is a priority. Instead of using Python's unsafe `eval()`, the Calculator uses a robust **AST (Abstract Syntax Tree) evaluator**.
|
|
31
|
+
- **Restricted Environment**: Only allowed mathematical operations and functions are executed.
|
|
32
|
+
- **No Side Effects**: Prevents arbitrary code execution, making it safe to evaluate expressions from untrusted sources.
|
|
@@ -523,7 +523,7 @@ def install(ctx):
|
|
|
523
523
|
# Examples of registering tools using automatic definition generation
|
|
524
524
|
ctx.register_tool(memory_read)
|
|
525
525
|
ctx.register_tool(memory_write)
|
|
526
|
-
ctx.register_tool(semantic_search)
|
|
526
|
+
# ctx.register_tool(semantic_search) # TODO: implement
|
|
527
527
|
ctx.register_tool(read_file)
|
|
528
528
|
ctx.register_tool(write_file)
|
|
529
529
|
ctx.register_tool(list_directory)
|
|
Binary file
|
|
@@ -381,7 +381,7 @@ const CalcPage = {
|
|
|
381
381
|
type="button"
|
|
382
382
|
@click="insert(num)"
|
|
383
383
|
class="px-3 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-blue-100 dark:hover:bg-blue-900/30 text-gray-700 dark:text-gray-300 hover:text-blue-700 dark:hover:text-blue-300 border border-gray-200 dark:border-gray-700 rounded text-sm font-mono transition-colors"
|
|
384
|
-
title="
|
|
384
|
+
:title="'insert number ' + num"
|
|
385
385
|
>
|
|
386
386
|
{{ num }}
|
|
387
387
|
</button>
|
|
@@ -392,7 +392,7 @@ const CalcPage = {
|
|
|
392
392
|
type="button"
|
|
393
393
|
@click="insert(c)"
|
|
394
394
|
class="px-3 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-blue-100 dark:hover:bg-blue-900/30 text-gray-700 dark:text-gray-300 hover:text-blue-700 dark:hover:text-blue-300 border border-gray-200 dark:border-gray-700 rounded text-sm font-mono transition-colors"
|
|
395
|
-
title="
|
|
395
|
+
:title="'insert constant ' + c"
|
|
396
396
|
>
|
|
397
397
|
{{ c }}
|
|
398
398
|
</button>
|
|
@@ -408,7 +408,7 @@ const CalcPage = {
|
|
|
408
408
|
type="button"
|
|
409
409
|
@click="insert(op)"
|
|
410
410
|
class="px-3 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-blue-100 dark:hover:bg-blue-900/30 text-gray-700 dark:text-gray-300 hover:text-blue-700 dark:hover:text-blue-300 border border-gray-200 dark:border-gray-700 rounded text-sm font-mono transition-colors"
|
|
411
|
-
title="
|
|
411
|
+
:title="'insert operator ' + op"
|
|
412
412
|
>
|
|
413
413
|
{{ op }}
|
|
414
414
|
</button>
|
|
@@ -425,7 +425,7 @@ const CalcPage = {
|
|
|
425
425
|
type="button"
|
|
426
426
|
@click="wrapWithFunction(func)"
|
|
427
427
|
class="px-3 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-purple-100 dark:hover:bg-purple-900/30 text-gray-700 dark:text-gray-300 hover:text-purple-700 dark:hover:text-purple-300 border border-gray-200 dark:border-gray-700 rounded text-sm font-mono transition-colors"
|
|
428
|
-
title="
|
|
428
|
+
:title="'use function ' + func"
|
|
429
429
|
>
|
|
430
430
|
{{ func }}
|
|
431
431
|
</button>
|
|
@@ -0,0 +1,61 @@
|
|
|
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.
|
|
@@ -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.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# System Prompts Extension
|
|
2
|
+
|
|
3
|
+
This extension configures AI requests with a library of **over 200+** awesome curated system prompts that can be selected from the UI.
|
|
4
|
+
|
|
5
|
+
## Custom System Prompts
|
|
6
|
+
|
|
7
|
+
You can also maintain your own library of system prompts which can be maintained for all anonymous users at:
|
|
8
|
+
`~/.llms/user/default/system-prompts.json`
|
|
9
|
+
|
|
10
|
+
Or for signed in users at:
|
|
11
|
+
`~/.llms/user/<github-user>/system-prompts.json`
|
|
12
|
+
|
|
13
|
+
The JSON file should contain an array of Prompt objects, e.g:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
[
|
|
17
|
+
{
|
|
18
|
+
"name": "Helpful Assistant",
|
|
19
|
+
"prompt": "You are a helpful assistant."
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
```
|
llms/llms.json
CHANGED
|
@@ -9,14 +9,11 @@
|
|
|
9
9
|
"restrict_to": "GITHUB_USERS"
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
|
-
"disable_extensions": [
|
|
13
|
-
"xmas",
|
|
14
|
-
"duckduckgo"
|
|
15
|
-
],
|
|
12
|
+
"disable_extensions": [],
|
|
16
13
|
"defaults": {
|
|
17
14
|
"headers": {
|
|
18
15
|
"Content-Type": "application/json",
|
|
19
|
-
"User-Agent": "llmspy.org/
|
|
16
|
+
"User-Agent": "llmspy.org/3.0"
|
|
20
17
|
},
|
|
21
18
|
"text": {
|
|
22
19
|
"model": "kimi-k2",
|
|
@@ -95,7 +92,7 @@
|
|
|
95
92
|
]
|
|
96
93
|
},
|
|
97
94
|
"out:image": {
|
|
98
|
-
"model": "
|
|
95
|
+
"model": "gemini-2.5-flash-image",
|
|
99
96
|
"messages": [
|
|
100
97
|
{
|
|
101
98
|
"role": "user",
|
|
@@ -202,10 +199,10 @@
|
|
|
202
199
|
}
|
|
203
200
|
},
|
|
204
201
|
"github-copilot": {
|
|
205
|
-
"enabled":
|
|
202
|
+
"enabled": true
|
|
206
203
|
},
|
|
207
204
|
"github-models": {
|
|
208
|
-
"enabled":
|
|
205
|
+
"enabled": true,
|
|
209
206
|
"check": {
|
|
210
207
|
"messages": [
|
|
211
208
|
{
|
|
@@ -230,13 +227,13 @@
|
|
|
230
227
|
"temperature": 1.0
|
|
231
228
|
},
|
|
232
229
|
"ollama": {
|
|
233
|
-
"enabled":
|
|
230
|
+
"enabled": false,
|
|
234
231
|
"id": "ollama",
|
|
235
232
|
"npm": "ollama",
|
|
236
233
|
"api": "http://localhost:11434"
|
|
237
234
|
},
|
|
238
235
|
"lmstudio": {
|
|
239
|
-
"enabled":
|
|
236
|
+
"enabled": false,
|
|
240
237
|
"npm": "lmstudio",
|
|
241
238
|
"api": "http://127.0.0.1:1234/v1",
|
|
242
239
|
"models": {}
|
|
@@ -351,7 +348,7 @@
|
|
|
351
348
|
"enabled": true
|
|
352
349
|
},
|
|
353
350
|
"moonshotai": {
|
|
354
|
-
"enabled":
|
|
351
|
+
"enabled": true
|
|
355
352
|
},
|
|
356
353
|
"nvidia": {
|
|
357
354
|
"enabled": true,
|
|
@@ -372,7 +369,7 @@
|
|
|
372
369
|
"enabled": true
|
|
373
370
|
},
|
|
374
371
|
"fireworks-ai": {
|
|
375
|
-
"enabled":
|
|
372
|
+
"enabled": true
|
|
376
373
|
},
|
|
377
374
|
"openrouter": {
|
|
378
375
|
"enabled": true,
|
llms/main.py
CHANGED
|
@@ -29,7 +29,7 @@ from importlib import resources # Py≥3.9 (pip install importlib_resources fo
|
|
|
29
29
|
from io import BytesIO
|
|
30
30
|
from pathlib import Path
|
|
31
31
|
from typing import get_type_hints
|
|
32
|
-
from urllib.parse import parse_qs, urlencode
|
|
32
|
+
from urllib.parse import parse_qs, urlencode, urljoin
|
|
33
33
|
|
|
34
34
|
import aiohttp
|
|
35
35
|
from aiohttp import web
|
|
@@ -41,7 +41,7 @@ try:
|
|
|
41
41
|
except ImportError:
|
|
42
42
|
HAS_PIL = False
|
|
43
43
|
|
|
44
|
-
VERSION = "3.0.
|
|
44
|
+
VERSION = "3.0.0b10"
|
|
45
45
|
_ROOT = None
|
|
46
46
|
DEBUG = os.getenv("DEBUG") == "1"
|
|
47
47
|
MOCK = os.getenv("MOCK") == "1"
|
|
@@ -204,6 +204,12 @@ def id_to_name(id):
|
|
|
204
204
|
return id.replace("-", " ").title()
|
|
205
205
|
|
|
206
206
|
|
|
207
|
+
def pluralize(word, count):
|
|
208
|
+
if count == 1:
|
|
209
|
+
return word
|
|
210
|
+
return word + "s"
|
|
211
|
+
|
|
212
|
+
|
|
207
213
|
def get_file_mime_type(filename):
|
|
208
214
|
mime_type, _ = mimetypes.guess_type(filename)
|
|
209
215
|
return mime_type or "application/octet-stream"
|
|
@@ -368,6 +374,9 @@ async def process_chat(chat, provider_id=None):
|
|
|
368
374
|
raise Exception("No chat provided")
|
|
369
375
|
if "stream" not in chat:
|
|
370
376
|
chat["stream"] = False
|
|
377
|
+
# Some providers don't support empty tools
|
|
378
|
+
if "tools" in chat and len(chat["tools"]) == 0:
|
|
379
|
+
del chat["tools"]
|
|
371
380
|
if "messages" not in chat:
|
|
372
381
|
return chat
|
|
373
382
|
|
|
@@ -700,6 +709,7 @@ def save_image_to_cache(base64_data, filename, image_info, ignore_info=False):
|
|
|
700
709
|
async def response_json(response):
|
|
701
710
|
text = await response.text()
|
|
702
711
|
if response.status >= 400:
|
|
712
|
+
_dbg(f"HTTP {response.status} {response.reason}: {text}")
|
|
703
713
|
raise HTTPError(response.status, reason=response.reason, body=text, headers=dict(response.headers))
|
|
704
714
|
response.raise_for_status()
|
|
705
715
|
body = json.loads(text)
|
|
@@ -1427,6 +1437,8 @@ async def g_chat_completion(chat, context=None):
|
|
|
1427
1437
|
current_chat["messages"].append(message)
|
|
1428
1438
|
tool_history.append(message)
|
|
1429
1439
|
|
|
1440
|
+
await g_app.on_chat_tool(current_chat, context)
|
|
1441
|
+
|
|
1430
1442
|
for tool_call in tool_calls:
|
|
1431
1443
|
function_name = tool_call["function"]["name"]
|
|
1432
1444
|
try:
|
|
@@ -1450,8 +1462,7 @@ async def g_chat_completion(chat, context=None):
|
|
|
1450
1462
|
current_chat["messages"].append(tool_msg)
|
|
1451
1463
|
tool_history.append(tool_msg)
|
|
1452
1464
|
|
|
1453
|
-
|
|
1454
|
-
await filter_func(current_chat, context)
|
|
1465
|
+
await g_app.on_chat_tool(current_chat, context)
|
|
1455
1466
|
|
|
1456
1467
|
if should_cancel_thread(context):
|
|
1457
1468
|
return
|
|
@@ -1606,7 +1617,7 @@ async def cli_chat(chat, tools=None, image=None, audio=None, file=None, args=Non
|
|
|
1606
1617
|
for file in generated_files:
|
|
1607
1618
|
if file.startswith("/~cache"):
|
|
1608
1619
|
print(get_cache_path(file[8:]))
|
|
1609
|
-
print(
|
|
1620
|
+
print(urljoin("http://localhost:8000", file))
|
|
1610
1621
|
else:
|
|
1611
1622
|
print(file)
|
|
1612
1623
|
|
|
@@ -2443,6 +2454,15 @@ class AppExtensions:
|
|
|
2443
2454
|
except Exception as e:
|
|
2444
2455
|
_err("chat error filter failed", e)
|
|
2445
2456
|
|
|
2457
|
+
async def on_chat_tool(self, chat, context):
|
|
2458
|
+
m_len = len(chat.get("messages", []))
|
|
2459
|
+
t_len = len(self.chat_tool_filters)
|
|
2460
|
+
_dbg(
|
|
2461
|
+
f"on_tool_call for thread {context.get('threadId', None)} with {m_len} {pluralize('message', m_len)}, invoking {t_len} {pluralize('filter', t_len)}:"
|
|
2462
|
+
)
|
|
2463
|
+
for filter_func in self.chat_tool_filters:
|
|
2464
|
+
await filter_func(chat, context)
|
|
2465
|
+
|
|
2446
2466
|
def exit(self, exit_code=0):
|
|
2447
2467
|
if len(self.shutdown_handlers) > 0:
|
|
2448
2468
|
_dbg(f"running {len(self.shutdown_handlers)} shutdown handlers...")
|
llms/ui/ai.mjs
CHANGED
|
@@ -6,7 +6,7 @@ const headers = { 'Accept': 'application/json' }
|
|
|
6
6
|
const prefsKey = 'llms.prefs'
|
|
7
7
|
|
|
8
8
|
export const o = {
|
|
9
|
-
version: '3.0.
|
|
9
|
+
version: '3.0.0b10',
|
|
10
10
|
base,
|
|
11
11
|
prefsKey,
|
|
12
12
|
welcome: 'Welcome to llms.py',
|
|
@@ -74,12 +74,27 @@ export const o = {
|
|
|
74
74
|
return { responseStatus }
|
|
75
75
|
}
|
|
76
76
|
},
|
|
77
|
+
createErrorStatus({ message, errorCode, stackTrace, errors, meta }) {
|
|
78
|
+
const ret = {
|
|
79
|
+
errorCode: errorCode || 'Error',
|
|
80
|
+
message: message,
|
|
81
|
+
}
|
|
82
|
+
if (stackTrace) {
|
|
83
|
+
ret.stackTrace = stackTrace
|
|
84
|
+
}
|
|
85
|
+
if (errors && Array.isArray(errors)) {
|
|
86
|
+
ret.errors = errors
|
|
87
|
+
}
|
|
88
|
+
if (meta) {
|
|
89
|
+
ret.meta = meta
|
|
90
|
+
}
|
|
91
|
+
return ret
|
|
92
|
+
},
|
|
77
93
|
createErrorResult(e) {
|
|
78
94
|
return new ApiResult({
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
message: `${e.message ?? e}`
|
|
82
|
-
}
|
|
95
|
+
error: e.errorCode
|
|
96
|
+
? this.createErrorStatus(e)
|
|
97
|
+
: this.createErrorStatus({ message: `${e.message ?? e}` })
|
|
83
98
|
})
|
|
84
99
|
},
|
|
85
100
|
async getConfig() {
|
llms/ui/ctx.mjs
CHANGED
|
@@ -43,6 +43,18 @@ export class ExtensionScope {
|
|
|
43
43
|
post(url, options) {
|
|
44
44
|
return this.ctx.ai.post(combinePaths(this.baseUrl, url), options)
|
|
45
45
|
}
|
|
46
|
+
put(url, options) {
|
|
47
|
+
return this.ctx.ai.post(combinePaths(this.baseUrl, url), {
|
|
48
|
+
...options,
|
|
49
|
+
method: 'PUT'
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
patch(url, options) {
|
|
53
|
+
return this.ctx.ai.post(combinePaths(this.baseUrl, url), {
|
|
54
|
+
...options,
|
|
55
|
+
method: 'PATCH'
|
|
56
|
+
})
|
|
57
|
+
}
|
|
46
58
|
async postForm(url, options) {
|
|
47
59
|
return await this.ctx.ai.postForm(combinePaths(this.baseUrl, url), options)
|
|
48
60
|
}
|
|
@@ -66,6 +78,9 @@ export class ExtensionScope {
|
|
|
66
78
|
async createJsonResult(res) {
|
|
67
79
|
return this.ctx.ai.createJsonResult(res)
|
|
68
80
|
}
|
|
81
|
+
createErrorStatus(status) {
|
|
82
|
+
return this.ctx.ai.createErrorStatus(status)
|
|
83
|
+
}
|
|
69
84
|
createErrorResult(e) {
|
|
70
85
|
return this.ctx.ai.createErrorResult(e)
|
|
71
86
|
}
|
|
@@ -263,6 +278,12 @@ export class AppContext {
|
|
|
263
278
|
return toggle
|
|
264
279
|
}
|
|
265
280
|
|
|
281
|
+
createErrorStatus(status) {
|
|
282
|
+
return this.ai.createErrorStatus(status)
|
|
283
|
+
}
|
|
284
|
+
createErrorResult(e) {
|
|
285
|
+
return this.ai.createErrorResult(e)
|
|
286
|
+
}
|
|
266
287
|
setError(error, msg = null) {
|
|
267
288
|
this.state.error = error
|
|
268
289
|
if (error) {
|
|
@@ -331,6 +352,7 @@ export class AppContext {
|
|
|
331
352
|
if (Array.isArray(content)) {
|
|
332
353
|
content = content.filter(c => c.type === 'text').map(c => c.text).join('\n')
|
|
333
354
|
}
|
|
355
|
+
// Handled by katex
|
|
334
356
|
// if (content) {
|
|
335
357
|
// content = content
|
|
336
358
|
// .replaceAll(`\\[ \\boxed{`, '\n<span class="inline-block text-xl text-blue-500 bg-blue-50 dark:text-blue-400 dark:bg-blue-950 px-3 py-1 rounded">')
|
|
@@ -20,9 +20,50 @@ const MessageUsage = {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
const MessageReasoning = {
|
|
24
|
+
template: `
|
|
25
|
+
<div class="mt-2 mb-2">
|
|
26
|
+
<button type="button" @click="toggleReasoning(message.id)" class="text-xs text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 flex items-center space-x-1">
|
|
27
|
+
<svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" :class="isReasoningExpanded(message.id) ? 'transform rotate-90' : ''"><path fill="currentColor" d="M7 5l6 5l-6 5z"/></svg>
|
|
28
|
+
<span>{{ isReasoningExpanded(message.id) ? 'Hide reasoning' : 'Show reasoning' }}</span>
|
|
29
|
+
</button>
|
|
30
|
+
<div v-if="isReasoningExpanded(message.id)" class="reasoning mt-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 p-2">
|
|
31
|
+
<div v-if="typeof reasoning === 'string'" v-html="$fmt.markdown(reasoning)" class="prose prose-xs max-w-none dark:prose-invert"></div>
|
|
32
|
+
<pre v-else class="text-xs whitespace-pre-wrap overflow-x-auto">{{ formatReasoning(reasoning) }}</pre>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
`,
|
|
36
|
+
props: {
|
|
37
|
+
reasoning: String,
|
|
38
|
+
message: Object,
|
|
39
|
+
},
|
|
40
|
+
setup(props) {
|
|
41
|
+
const expandedReasoning = ref(new Set())
|
|
42
|
+
const isReasoningExpanded = (id) => expandedReasoning.value.has(id)
|
|
43
|
+
const toggleReasoning = (id) => {
|
|
44
|
+
const s = new Set(expandedReasoning.value)
|
|
45
|
+
if (s.has(id)) {
|
|
46
|
+
s.delete(id)
|
|
47
|
+
} else {
|
|
48
|
+
s.add(id)
|
|
49
|
+
}
|
|
50
|
+
expandedReasoning.value = s
|
|
51
|
+
}
|
|
52
|
+
const formatReasoning = (r) => typeof r === 'string' ? r : JSON.stringify(r, null, 2)
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
expandedReasoning,
|
|
56
|
+
isReasoningExpanded,
|
|
57
|
+
toggleReasoning,
|
|
58
|
+
formatReasoning,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
23
63
|
export default {
|
|
24
64
|
components: {
|
|
25
65
|
MessageUsage,
|
|
66
|
+
MessageReasoning,
|
|
26
67
|
},
|
|
27
68
|
template: `
|
|
28
69
|
<div class="flex flex-col h-full">
|
|
@@ -110,16 +151,8 @@ export default {
|
|
|
110
151
|
></div>
|
|
111
152
|
|
|
112
153
|
<!-- Collapsible reasoning section -->
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
<svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" :class="isReasoningExpanded(message.id) ? 'transform rotate-90' : ''"><path fill="currentColor" d="M7 5l6 5l-6 5z"/></svg>
|
|
116
|
-
<span>{{ isReasoningExpanded(message.id) ? 'Hide reasoning' : 'Show reasoning' }}</span>
|
|
117
|
-
</button>
|
|
118
|
-
<div v-if="isReasoningExpanded(message.id)" class="reasoning mt-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 p-2">
|
|
119
|
-
<div v-if="typeof message.reasoning === 'string'" v-html="$fmt.markdown(message.reasoning)" class="prose prose-xs max-w-none dark:prose-invert"></div>
|
|
120
|
-
<pre v-else class="text-xs whitespace-pre-wrap overflow-x-auto">{{ formatReasoning(message.reasoning) }}</pre>
|
|
121
|
-
</div>
|
|
122
|
-
</div>
|
|
154
|
+
<MessageReasoning v-if="message.role === 'assistant' && (message.reasoning || message.thinking || message.reasoning_content)"
|
|
155
|
+
:reasoning="message.reasoning || message.thinking || message.reasoning_content" :message="message" />
|
|
123
156
|
|
|
124
157
|
<!-- Tool Calls & Outputs -->
|
|
125
158
|
<div v-if="message.tool_calls && message.tool_calls.length > 0" class="mb-3 space-y-4">
|
|
@@ -435,20 +468,6 @@ export default {
|
|
|
435
468
|
}
|
|
436
469
|
}
|
|
437
470
|
|
|
438
|
-
// Reasoning collapse state and helpers
|
|
439
|
-
const expandedReasoning = ref(new Set())
|
|
440
|
-
const isReasoningExpanded = (id) => expandedReasoning.value.has(id)
|
|
441
|
-
const toggleReasoning = (id) => {
|
|
442
|
-
const s = new Set(expandedReasoning.value)
|
|
443
|
-
if (s.has(id)) {
|
|
444
|
-
s.delete(id)
|
|
445
|
-
} else {
|
|
446
|
-
s.add(id)
|
|
447
|
-
}
|
|
448
|
-
expandedReasoning.value = s
|
|
449
|
-
}
|
|
450
|
-
const formatReasoning = (r) => typeof r === 'string' ? r : JSON.stringify(r, null, 2)
|
|
451
|
-
|
|
452
471
|
const copyMessageContent = async (message) => {
|
|
453
472
|
let content = ''
|
|
454
473
|
if (Array.isArray(message.content)) {
|
|
@@ -651,9 +670,6 @@ export default {
|
|
|
651
670
|
selectedModelObj,
|
|
652
671
|
messagesContainer,
|
|
653
672
|
copying,
|
|
654
|
-
isReasoningExpanded,
|
|
655
|
-
toggleReasoning,
|
|
656
|
-
formatReasoning,
|
|
657
673
|
copyMessageContent,
|
|
658
674
|
redoMessage,
|
|
659
675
|
editMessage,
|
llms/ui/modules/chat/index.mjs
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
|
|
2
|
-
import { ref,
|
|
3
|
-
import { useRouter } from 'vue-router'
|
|
2
|
+
import { ref, watch, nextTick, inject } from 'vue'
|
|
4
3
|
import { $$, createElement, lastRightPart, ApiResult, createErrorStatus, pick } from "@servicestack/client"
|
|
5
4
|
import SettingsDialog, { useSettings } from './SettingsDialog.mjs'
|
|
6
5
|
import ChatBody from './ChatBody.mjs'
|
|
7
|
-
import HomeTools from './HomeTools.mjs'
|
|
8
6
|
import { AppContext } from '../../ctx.mjs'
|
|
9
7
|
|
|
10
8
|
const imageExts = 'png,webp,jpg,jpeg,gif,bmp,svg,tiff,ico'.split(',')
|
|
@@ -238,7 +236,7 @@ export function useChatPrompt(ctx) {
|
|
|
238
236
|
return request
|
|
239
237
|
}
|
|
240
238
|
|
|
241
|
-
async function completion({ request,
|
|
239
|
+
async function completion({ request, thread, model, controller, redirect }) {
|
|
242
240
|
try {
|
|
243
241
|
let error
|
|
244
242
|
if (!model) {
|
|
@@ -250,20 +248,18 @@ export function useChatPrompt(ctx) {
|
|
|
250
248
|
}
|
|
251
249
|
|
|
252
250
|
if (!model) {
|
|
253
|
-
return
|
|
254
|
-
error: createErrorStatus(`Model ${request.model || ''} not found`, 'NotFound')
|
|
255
|
-
})
|
|
251
|
+
return ctx.createErrorResult({ message: `Model ${request.model || ''} not found`, errorCode: 'NotFound' })
|
|
256
252
|
}
|
|
257
253
|
|
|
258
254
|
if (!request.messages) request.messages = []
|
|
259
255
|
if (!request.metadata) request.metadata = {}
|
|
260
256
|
|
|
261
|
-
if (
|
|
257
|
+
if (!thread) {
|
|
262
258
|
const title = getTextContent(request) || 'New Chat'
|
|
263
|
-
thread = await ctx.threads.startNewThread({ title, model })
|
|
259
|
+
thread = await ctx.threads.startNewThread({ title, model, redirect })
|
|
264
260
|
}
|
|
265
261
|
|
|
266
|
-
const threadId = thread?.id
|
|
262
|
+
const threadId = thread?.id
|
|
267
263
|
|
|
268
264
|
const ctxRequest = {
|
|
269
265
|
request,
|
|
@@ -282,7 +278,7 @@ export function useChatPrompt(ctx) {
|
|
|
282
278
|
|
|
283
279
|
let response = null
|
|
284
280
|
if (!res.ok) {
|
|
285
|
-
error = createErrorStatus(
|
|
281
|
+
error = ctx.createErrorStatus({ message: `HTTP ${res.status} ${res.statusText}` })
|
|
286
282
|
let errorBody = null
|
|
287
283
|
try {
|
|
288
284
|
errorBody = await res.text()
|
|
@@ -338,22 +334,6 @@ export function useChatPrompt(ctx) {
|
|
|
338
334
|
}
|
|
339
335
|
}
|
|
340
336
|
|
|
341
|
-
// Add assistant response (save entire message including reasoning)
|
|
342
|
-
const assistantMessage = response.choices?.[0]?.message
|
|
343
|
-
|
|
344
|
-
const usage = response.usage
|
|
345
|
-
if (usage) {
|
|
346
|
-
if (response.metadata?.pricing) {
|
|
347
|
-
const [input, output] = response.metadata.pricing.split('/')
|
|
348
|
-
usage.duration = response.metadata.duration ?? (Date.now() - startTime)
|
|
349
|
-
usage.input = input
|
|
350
|
-
usage.output = output
|
|
351
|
-
usage.tokens = usage.completion_tokens
|
|
352
|
-
usage.price = usage.output
|
|
353
|
-
usage.cost = ctx.fmt.tokenCost(usage.prompt_tokens / 1_000_000 * parseFloat(input) + usage.completion_tokens / 1_000_000 * parseFloat(output))
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
337
|
nextTick(addCopyButtons)
|
|
358
338
|
|
|
359
339
|
return new ApiResult({ response })
|
|
@@ -666,7 +646,7 @@ const ChatPrompt = {
|
|
|
666
646
|
|
|
667
647
|
// Create thread if none exists
|
|
668
648
|
if (!ctx.threads.currentThread.value) {
|
|
669
|
-
thread = await ctx.threads.startNewThread({ model: props.model })
|
|
649
|
+
thread = await ctx.threads.startNewThread({ model: props.model, redirect: true })
|
|
670
650
|
} else {
|
|
671
651
|
thread = ctx.threads.currentThread.value
|
|
672
652
|
}
|
|
@@ -740,7 +720,7 @@ const ChatPrompt = {
|
|
|
740
720
|
console.debug(`thread title is '${thread.title}'`, request.title)
|
|
741
721
|
}
|
|
742
722
|
|
|
743
|
-
const api = await ctx.threads.queueChat(
|
|
723
|
+
const api = await ctx.threads.queueChat({ request, thread })
|
|
744
724
|
if (api.response) {
|
|
745
725
|
// success
|
|
746
726
|
ctx.chat.editingMessage.value = null
|
|
@@ -794,6 +774,14 @@ const ChatPrompt = {
|
|
|
794
774
|
}
|
|
795
775
|
}
|
|
796
776
|
|
|
777
|
+
const HomeTools = {
|
|
778
|
+
template: `
|
|
779
|
+
<div class="mt-4 flex space-x-3 justify-center items-center">
|
|
780
|
+
<DarkModeToggle />
|
|
781
|
+
</div>
|
|
782
|
+
`,
|
|
783
|
+
}
|
|
784
|
+
|
|
797
785
|
export default {
|
|
798
786
|
/**@param {AppContext} ctx */
|
|
799
787
|
install(ctx) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: llms-py
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.0b10
|
|
4
4
|
Summary: A lightweight CLI tool and OpenAI-compatible server for querying multiple Large Language Model (LLM) providers
|
|
5
5
|
Home-page: https://github.com/ServiceStack/llms
|
|
6
6
|
Author: ServiceStack
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
llms/__init__.py,sha256=DKwTZDsyYL_wHe7yvLw49Nf8PSgPSyWaeVdotUqSvrQ,84
|
|
2
2
|
llms/__main__.py,sha256=hrBulHIt3lmPm1BCyAEVtB6DQ0Hvc3gnIddhHCmJasg,151
|
|
3
3
|
llms/index.html,sha256=nGk1Djtn9p7l6LuKp4Kg0JIB9fCzxtTWXFfmDb4ggpc,1658
|
|
4
|
-
llms/llms.json,sha256=
|
|
5
|
-
llms/main.py,sha256=
|
|
4
|
+
llms/llms.json,sha256=sgHUcE6iu-MOIoEbs8ZOH1O07ziXP58UUGnEILeeAxg,11086
|
|
5
|
+
llms/main.py,sha256=VdZ6-vCM9Eopg0-UtXUGaiXjaoU9PV4tJsvFLGPryes,154262
|
|
6
6
|
llms/providers-extra.json,sha256=w7_5gB0YUPK0PJNeViM7vRDfNGChXUKMHfGHenVxEkM,10165
|
|
7
7
|
llms/providers.json,sha256=mP4vQ37LeRAZCpT3WPO2GZaZ3YsXodXBV5OVbVMBAmo,256222
|
|
8
8
|
llms/__pycache__/__init__.cpython-312.pyc,sha256=lg2oFc0aKgj536NOJxcIpbCpEWi47ptF8NufPphgUUk,204
|
|
@@ -13,23 +13,25 @@ llms/__pycache__/__main__.cpython-314.pyc,sha256=IFxtGVpJq_3whKuM5Ln7YMweKFNbHVp
|
|
|
13
13
|
llms/__pycache__/llms.cpython-312.pyc,sha256=S5dFI79JdUe2dQW4ogdB-CCNhudQeFaFGcfKxgJGBms,72080
|
|
14
14
|
llms/__pycache__/main.cpython-312.pyc,sha256=HrqApYAiiBmYN73HIx_Hl2-Xm1Gy7I_0zuR5j86qoRM,104940
|
|
15
15
|
llms/__pycache__/main.cpython-313.pyc,sha256=6NQ__SJ2rC9ItFLKLHL5ewb5RqxLzZabwgczA9wZd-w,74814
|
|
16
|
-
llms/__pycache__/main.cpython-314.pyc,sha256=
|
|
16
|
+
llms/__pycache__/main.cpython-314.pyc,sha256=WOJ65yTgeGeGZlSGDi11uqPBhVl7cudnIhbOGLzVo58,199833
|
|
17
17
|
llms/__pycache__/plugins.cpython-314.pyc,sha256=fer8nTkidG_vQSx80tL2bAvMS0opDom93bewjseFcyg,3560
|
|
18
18
|
llms/extensions/analytics/ui/index.mjs,sha256=cr9dPmEJjha2xX6A_7xbJxkOolWFP1p6TgIoO8M8juI,69540
|
|
19
|
-
llms/extensions/app/
|
|
20
|
-
llms/extensions/app/
|
|
19
|
+
llms/extensions/app/README.md,sha256=TKoblZpHlheLCh_dfXOxqTc5OvxlgMBa-vKo8Hqb2gg,1370
|
|
20
|
+
llms/extensions/app/__init__.py,sha256=2fJNC-zxaXQnBz6WePV61zjITeAQEW9BMboyErJuZAE,19285
|
|
21
|
+
llms/extensions/app/db.py,sha256=n_K0dYqXUeaX1DQnPeixCZBrKMvjnz867MtjeBIxmv4,24188
|
|
21
22
|
llms/extensions/app/db_manager.py,sha256=NcdOK_FzEmzCYEoQjdivZTrgJdFf9dGlAelJvP4VecA,7046
|
|
22
23
|
llms/extensions/app/requests.json,sha256=xMQ_FgMaBxbrCcTnsdkaBvufy40yFT7U1kR6nn_GaNE,264556
|
|
23
24
|
llms/extensions/app/threads.json,sha256=f_xPjkmY2OGbGnSXxVTcSGKRU-2rvfAXnHbf2pH1K5Q,741352
|
|
24
|
-
llms/extensions/app/__pycache__/__init__.cpython-314.pyc,sha256=
|
|
25
|
-
llms/extensions/app/__pycache__/db.cpython-314.pyc,sha256=
|
|
25
|
+
llms/extensions/app/__pycache__/__init__.cpython-314.pyc,sha256=mZAo7Asql7gWqNhT0x1f62I6KBM076BleSkUJIMsRPA,26938
|
|
26
|
+
llms/extensions/app/__pycache__/db.cpython-314.pyc,sha256=ZhUSgfpZ8CdyF7Z1fDLdzsBCEriTj37hlwTF3Oscn-o,35495
|
|
26
27
|
llms/extensions/app/__pycache__/db_manager.cpython-314.pyc,sha256=mS-NkAbnGYTDQfsKXZufkI9LnOBSWD5fYNMqTVjQrnE,11071
|
|
27
28
|
llms/extensions/app/ui/Recents.mjs,sha256=HT9R2IEus3dGEqtKPGrgX1eMp4EQ2j2jLwv-Fn1Us5E,9253
|
|
28
|
-
llms/extensions/app/ui/index.mjs,sha256=
|
|
29
|
-
llms/extensions/app/ui/threadStore.mjs,sha256=
|
|
30
|
-
llms/extensions/core_tools/
|
|
31
|
-
llms/extensions/core_tools/
|
|
32
|
-
llms/extensions/core_tools/
|
|
29
|
+
llms/extensions/app/ui/index.mjs,sha256=sB9176LLNuKFsZ28yL-tROA6J4xePNtvxtSrzFcinRo,13271
|
|
30
|
+
llms/extensions/app/ui/threadStore.mjs,sha256=oCPPegkG3QHZIB82jwSCZ4d9dWEyhn1z1_Zo2dho4Bg,11588
|
|
31
|
+
llms/extensions/core_tools/CALCULATOR.md,sha256=pJRtCVF01BgxFrSNh2Ys_lrRi3SFwLgJzAX93AGh93Q,1944
|
|
32
|
+
llms/extensions/core_tools/__init__.py,sha256=S9Pr08RtZtVpx8v6hyG_qVhfEJbT2JvmAD-AJTQZWmU,21468
|
|
33
|
+
llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc,sha256=O2aQ9pFmAP82JSl5l_rLvj0l-dR2dXJZu2RNaev2y_Y,32333
|
|
34
|
+
llms/extensions/core_tools/ui/index.mjs,sha256=KycJ2FcQ6BieBY7fjWGxVBGHN6WuFx712OFrO6flXww,31770
|
|
33
35
|
llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js,sha256=FDwwFMKSVPNTHMML5tkCBQhLz8Ns_6b5sqRv1CpAviA,7123
|
|
34
36
|
llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js,sha256=b74rk8wXRNnhStHWE0SffhbfTCqihC9ljvwXq_Zi-FA,8544
|
|
35
37
|
llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js,sha256=a6nNlSi3Tn929LnOntBne_7XBX0areAFAYU2jP0OouM,3989
|
|
@@ -50,11 +52,13 @@ llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html,sha256=
|
|
|
50
52
|
llms/extensions/core_tools/ui/codemirror/mode/python/python.js,sha256=GaWco4et2wTpUALJrb4rjCMUJ85JNprFNxB-MIimlHw,14984
|
|
51
53
|
llms/extensions/core_tools/ui/codemirror/theme/dracula.css,sha256=Av32BJYI3djQ189kA7blslEzB0Dfw5N3T2i7ZHJ7PG4,2042
|
|
52
54
|
llms/extensions/core_tools/ui/codemirror/theme/mocha.css,sha256=0Y1y9DUgzBSakbXep55HuqOYiPMC-YK3xjVQBiwUQ4g,2402
|
|
55
|
+
llms/extensions/gallery/README.md,sha256=zif27qiMef1dBboMEPvTJqPDnLvrqntraVSw8oQ-1RM,2985
|
|
53
56
|
llms/extensions/gallery/__init__.py,sha256=6YrgWsrfQXviFsJETFJxDlaEf0DAaPPBzokPxeK6AvU,1583
|
|
54
57
|
llms/extensions/gallery/db.py,sha256=hMsy0NMSKudA4HPUMvffMhXhOmqgVD31Ej_UmPrlbEg,11436
|
|
55
58
|
llms/extensions/gallery/__pycache__/__init__.cpython-314.pyc,sha256=qVYj7_SMudEoxz7BJKsUN_BNEfm9COYKArAIrl30Ynw,3384
|
|
56
59
|
llms/extensions/gallery/__pycache__/db.cpython-314.pyc,sha256=Is7YTzoWVOp-2NnafjibWw7mbCnw2MejGZUrlcyCfeg,15708
|
|
57
|
-
llms/extensions/gallery/ui/index.mjs,sha256=
|
|
60
|
+
llms/extensions/gallery/ui/index.mjs,sha256=1kPvtavUie0KtXCw3YjJpokJyl9zixAoEkcDfSYSfVk,28313
|
|
61
|
+
llms/extensions/katex/README.md,sha256=fD_0riNXrKW0SfehifIc3jnXKg9diQdlXVq5X_e1zTc,1473
|
|
58
62
|
llms/extensions/katex/__init__.py,sha256=CvEe9CPtzoANc4AFuxZTbrJZdggoWLuKnENKfEdncPQ,206
|
|
59
63
|
llms/extensions/katex/__pycache__/__init__.cpython-314.pyc,sha256=0oIxzaTcTIWR7KuMF_Wkpj-nlxJjVKKv021xXb74hPk,594
|
|
60
64
|
llms/extensions/katex/ui/README.md,sha256=Xkjgncuvv4WT4TzNX4muWHYKJOwZ-kSpNnyveLlJDNU,7268
|
|
@@ -156,6 +160,7 @@ llms/extensions/providers/__pycache__/google.cpython-314.pyc,sha256=STxidyM6xp0v
|
|
|
156
160
|
llms/extensions/providers/__pycache__/nvidia.cpython-314.pyc,sha256=UOsgdqA2-9E5jVo2CbytViJxrk5AEdXJvH9X0gmK_mA,6216
|
|
157
161
|
llms/extensions/providers/__pycache__/openai.cpython-314.pyc,sha256=mu8QC52_OUwtFX89FN_WXZgkgoFglvF1gDukN4ge8Rk,8735
|
|
158
162
|
llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc,sha256=LknnfQPVrXB2-7GCKVHoXIk6mW2ghRlQmn8piwQCv3I,5335
|
|
163
|
+
llms/extensions/system_prompts/README.md,sha256=ayr18lnSsGp62bunC6kxkvcSvZki775rbZ-8VoMDQEc,597
|
|
159
164
|
llms/extensions/system_prompts/__init__.py,sha256=TZy1CS2dPkBNBA_Ovf9BlVetZqTt2NgnsrZi6Mtg_C0,1535
|
|
160
165
|
llms/extensions/system_prompts/__pycache__/__init__.cpython-314.pyc,sha256=mftPdTu1qDLxifGSy9TkAAYPXxOkuNv7dUmQoZBC5Uk,2168
|
|
161
166
|
llms/extensions/system_prompts/ui/index.mjs,sha256=uUUGSiBGOECzvHriqnAJIGXmlyB3JLKlg5F-PgjnBx8,12197
|
|
@@ -164,9 +169,9 @@ llms/extensions/tools/__init__.py,sha256=yfIK7dVqYiZGX5VeJ3x7HQWLPQfuPqeNYJsd0lp
|
|
|
164
169
|
llms/extensions/tools/__pycache__/__init__.cpython-314.pyc,sha256=3H5i6ooI1LCCdVjZPDBteU2Ov6nYSeLAk9kUl6F9pZg,641
|
|
165
170
|
llms/extensions/tools/ui/index.mjs,sha256=Nu69U6odCUh8uu1i8d5f8ryO4Lj_OFbPX2LnVyYu1fk,9602
|
|
166
171
|
llms/ui/App.mjs,sha256=zU-GtbcSMREizjUS9nWiMK6m_oT14MNJ-p_RCz2RVqA,7445
|
|
167
|
-
llms/ui/ai.mjs,sha256=
|
|
172
|
+
llms/ui/ai.mjs,sha256=XXPspMy-KUCmVNQUNA0rsCvKu0NHGcYYb9HqKXMgX-g,6543
|
|
168
173
|
llms/ui/app.css,sha256=VcyYn6-qinFH7iOkIxUb5dmPDjLDcDBvgEOaRaeyg1E,173909
|
|
169
|
-
llms/ui/ctx.mjs,sha256=
|
|
174
|
+
llms/ui/ctx.mjs,sha256=9NESEjpts5Y4dNQUN_OnAPwn9WUl9HOwm-Ymaw-pR7Q,11171
|
|
170
175
|
llms/ui/fav.svg,sha256=_R6MFeXl6wBFT0lqcUxYQIDWgm246YH_3hSTW0oO8qw,734
|
|
171
176
|
llms/ui/index.mjs,sha256=j2rkXwpxPpBsurWLvvN6a25cVu7r9s9avP9ozKOpzMA,4253
|
|
172
177
|
llms/ui/markdown.mjs,sha256=ZeGXxX4_UEUCVkLZzmwXlqWBfReSFzBivdxNu8uSgFk,6648
|
|
@@ -186,13 +191,12 @@ llms/ui/lib/vue.min.mjs,sha256=T1TVu9SFEoLArPLKKEp6gsj_eScmHl5TkUebxGK6ldk,16628
|
|
|
186
191
|
llms/ui/lib/vue.mjs,sha256=75FuLhUTPk19sncwNIrm0BGEL0_Qw298-_v01fPWYoI,542872
|
|
187
192
|
llms/ui/modules/layout.mjs,sha256=ft5dOT-fBegSmSPQKnktHKfpbmNFan8ZN44z25T8ig8,10892
|
|
188
193
|
llms/ui/modules/model-selector.mjs,sha256=TrXxOIeNgibNMu_6M-wROjQgmHJPrVxH404v1_kXF4c,67486
|
|
189
|
-
llms/ui/modules/chat/ChatBody.mjs,sha256=
|
|
190
|
-
llms/ui/modules/chat/HomeTools.mjs,sha256=Jak12OvR8DZ6CmrWMMxELoNS8864EtMrhmiQp4SFfoM,213
|
|
194
|
+
llms/ui/modules/chat/ChatBody.mjs,sha256=0G2E82RnlRxVjW6xWRFXWJF8vjMZJfdeddcE66M4Wxg,42289
|
|
191
195
|
llms/ui/modules/chat/SettingsDialog.mjs,sha256=HMBJTwrapKrRIAstIIqp0QlJL5O-ho4hzgvfagPfsX8,19930
|
|
192
|
-
llms/ui/modules/chat/index.mjs,sha256=
|
|
193
|
-
llms_py-3.0.
|
|
194
|
-
llms_py-3.0.
|
|
195
|
-
llms_py-3.0.
|
|
196
|
-
llms_py-3.0.
|
|
197
|
-
llms_py-3.0.
|
|
198
|
-
llms_py-3.0.
|
|
196
|
+
llms/ui/modules/chat/index.mjs,sha256=sTKf7pwCkyu_Ls--pE3yrr2DSztuKQtLT7kFffaHIVU,33678
|
|
197
|
+
llms_py-3.0.0b10.dist-info/licenses/LICENSE,sha256=bus9cuAOWeYqBk2OuhSABVV1P4z7hgrEFISpyda_H5w,1532
|
|
198
|
+
llms_py-3.0.0b10.dist-info/METADATA,sha256=XoY86Pic2gn6gdvax_BbxLJo4r-gCuKXZiqufG8zuEQ,2194
|
|
199
|
+
llms_py-3.0.0b10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
200
|
+
llms_py-3.0.0b10.dist-info/entry_points.txt,sha256=WswyE7PfnkZMIxboC-MS6flBD6wm-CYU7JSUnMhqMfM,40
|
|
201
|
+
llms_py-3.0.0b10.dist-info/top_level.txt,sha256=gC7hk9BKSeog8gyg-EM_g2gxm1mKHwFRfK-10BxOsa4,5
|
|
202
|
+
llms_py-3.0.0b10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|