llms-py 3.0.2__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.
- llms/db.py +18 -9
- llms/extensions/app/__init__.py +1 -0
- llms/extensions/app/ui/threadStore.mjs +3 -0
- llms/main.py +3 -1
- llms/ui/ai.mjs +1 -1
- llms/ui/app.css +33 -23
- llms/ui/ctx.mjs +13 -0
- llms/ui/modules/chat/ChatBody.mjs +244 -248
- llms/ui/modules/chat/index.mjs +80 -7
- llms/ui/tailwind.input.css +1 -1
- {llms_py-3.0.2.dist-info → llms_py-3.0.3.dist-info}/METADATA +1 -1
- {llms_py-3.0.2.dist-info → llms_py-3.0.3.dist-info}/RECORD +16 -16
- {llms_py-3.0.2.dist-info → llms_py-3.0.3.dist-info}/WHEEL +0 -0
- {llms_py-3.0.2.dist-info → llms_py-3.0.3.dist-info}/entry_points.txt +0 -0
- {llms_py-3.0.2.dist-info → llms_py-3.0.3.dist-info}/licenses/LICENSE +0 -0
- {llms_py-3.0.2.dist-info → llms_py-3.0.3.dist-info}/top_level.txt +0 -0
llms/db.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import os
|
|
2
3
|
import sqlite3
|
|
3
4
|
import threading
|
|
4
5
|
from queue import Empty, Queue
|
|
5
6
|
from threading import Event, Thread
|
|
6
7
|
|
|
7
|
-
POOL =
|
|
8
|
+
POOL = os.getenv("LLMS_POOL", "1") == "1"
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def create_reader_connection(db_path):
|
|
@@ -39,7 +40,7 @@ def writer_thread(ctx, db_path, task_queue, stop_event):
|
|
|
39
40
|
sql, args, callback = task # Optional callback for results
|
|
40
41
|
|
|
41
42
|
try:
|
|
42
|
-
ctx.dbg("SQL>" + ("\n" if "\n" in sql else " ") + sql + ("\n" if args else "") + str(args))
|
|
43
|
+
ctx.dbg("SQL>" + ("\n" if "\n" in sql else " ") + sql + ("\n" if args else " ") + str(args))
|
|
43
44
|
cursor = conn.execute(sql, args)
|
|
44
45
|
conn.commit()
|
|
45
46
|
ctx.dbg(f"lastrowid {cursor.lastrowid}, rowcount {cursor.rowcount}")
|
|
@@ -172,7 +173,9 @@ class DbManager:
|
|
|
172
173
|
|
|
173
174
|
def log_sql(self, sql, parameters=None):
|
|
174
175
|
if self.ctx.debug:
|
|
175
|
-
self.ctx.dbg(
|
|
176
|
+
self.ctx.dbg(
|
|
177
|
+
"SQL>" + ("\n" if "\n" in sql else " ") + sql + ("\n" if parameters else " ") + str(parameters)
|
|
178
|
+
)
|
|
176
179
|
|
|
177
180
|
def exec(self, connection, sql, parameters=None):
|
|
178
181
|
self.log_sql(sql, parameters)
|
|
@@ -289,17 +292,20 @@ class DbManager:
|
|
|
289
292
|
async def insert_async(self, table, columns, info):
|
|
290
293
|
event = threading.Event()
|
|
291
294
|
|
|
292
|
-
ret = [None]
|
|
295
|
+
ret = [None, None]
|
|
293
296
|
|
|
294
297
|
def cb(lastrowid, rowcount, error=None):
|
|
295
298
|
nonlocal ret
|
|
296
299
|
if error:
|
|
297
|
-
|
|
298
|
-
|
|
300
|
+
ret[1] = error
|
|
301
|
+
else:
|
|
302
|
+
ret[0] = lastrowid
|
|
299
303
|
event.set()
|
|
300
304
|
|
|
301
305
|
self.insert(table, columns, info, cb)
|
|
302
306
|
event.wait()
|
|
307
|
+
if ret[1]:
|
|
308
|
+
raise ret[1]
|
|
303
309
|
return ret[0]
|
|
304
310
|
|
|
305
311
|
def update(self, table, columns, info, callback=None):
|
|
@@ -323,17 +329,20 @@ class DbManager:
|
|
|
323
329
|
async def update_async(self, table, columns, info):
|
|
324
330
|
event = threading.Event()
|
|
325
331
|
|
|
326
|
-
ret = [None]
|
|
332
|
+
ret = [None, None]
|
|
327
333
|
|
|
328
334
|
def cb(lastrowid, rowcount, error=None):
|
|
329
335
|
nonlocal ret
|
|
330
336
|
if error:
|
|
331
|
-
|
|
332
|
-
|
|
337
|
+
ret[1] = error
|
|
338
|
+
else:
|
|
339
|
+
ret[0] = rowcount
|
|
333
340
|
event.set()
|
|
334
341
|
|
|
335
342
|
self.update(table, columns, info, cb)
|
|
336
343
|
event.wait()
|
|
344
|
+
if ret[1]:
|
|
345
|
+
raise ret[1]
|
|
337
346
|
return ret[0]
|
|
338
347
|
|
|
339
348
|
def close(self):
|
llms/extensions/app/__init__.py
CHANGED
|
@@ -116,6 +116,9 @@ function replaceThread(thread) {
|
|
|
116
116
|
if (currentThread.value?.id === thread.id) {
|
|
117
117
|
currentThread.value = thread
|
|
118
118
|
}
|
|
119
|
+
if (thread.completedAt || thread.error) {
|
|
120
|
+
threadDetails.value[thread.id] = thread
|
|
121
|
+
}
|
|
119
122
|
startWatchingThread()
|
|
120
123
|
return thread
|
|
121
124
|
}
|
llms/main.py
CHANGED
|
@@ -41,7 +41,7 @@ try:
|
|
|
41
41
|
except ImportError:
|
|
42
42
|
HAS_PIL = False
|
|
43
43
|
|
|
44
|
-
VERSION = "3.0.
|
|
44
|
+
VERSION = "3.0.3"
|
|
45
45
|
_ROOT = None
|
|
46
46
|
DEBUG = os.getenv("DEBUG") == "1"
|
|
47
47
|
MOCK = os.getenv("MOCK") == "1"
|
|
@@ -1288,6 +1288,8 @@ def to_error_message(e):
|
|
|
1288
1288
|
# check if has 'message' attribute
|
|
1289
1289
|
if hasattr(e, "message"):
|
|
1290
1290
|
return e.message
|
|
1291
|
+
if hasattr(e, "status"):
|
|
1292
|
+
return str(e.status)
|
|
1291
1293
|
return str(e)
|
|
1292
1294
|
|
|
1293
1295
|
|
llms/ui/ai.mjs
CHANGED
llms/ui/app.css
CHANGED
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
--color-red-700: oklch(50.5% 0.213 27.518);
|
|
19
19
|
--color-red-800: oklch(44.4% 0.177 26.899);
|
|
20
20
|
--color-red-900: oklch(39.6% 0.141 25.723);
|
|
21
|
+
--color-orange-100: oklch(95.4% 0.038 75.164);
|
|
22
|
+
--color-orange-200: oklch(90.1% 0.076 70.697);
|
|
23
|
+
--color-orange-400: oklch(75% 0.183 55.934);
|
|
24
|
+
--color-orange-600: oklch(64.6% 0.222 41.116);
|
|
25
|
+
--color-orange-800: oklch(47% 0.157 37.304);
|
|
26
|
+
--color-orange-900: oklch(40.8% 0.123 38.172);
|
|
21
27
|
--color-yellow-50: oklch(98.7% 0.026 102.212);
|
|
22
28
|
--color-yellow-100: oklch(97.3% 0.071 103.193);
|
|
23
29
|
--color-yellow-200: oklch(94.5% 0.129 101.54);
|
|
@@ -1103,6 +1109,9 @@
|
|
|
1103
1109
|
.cursor-default {
|
|
1104
1110
|
cursor: default;
|
|
1105
1111
|
}
|
|
1112
|
+
.cursor-help {
|
|
1113
|
+
cursor: help;
|
|
1114
|
+
}
|
|
1106
1115
|
.cursor-not-allowed {
|
|
1107
1116
|
cursor: not-allowed;
|
|
1108
1117
|
}
|
|
@@ -1668,6 +1677,9 @@
|
|
|
1668
1677
|
.bg-indigo-700 {
|
|
1669
1678
|
background-color: var(--color-indigo-700);
|
|
1670
1679
|
}
|
|
1680
|
+
.bg-orange-100 {
|
|
1681
|
+
background-color: var(--color-orange-100);
|
|
1682
|
+
}
|
|
1671
1683
|
.bg-purple-100 {
|
|
1672
1684
|
background-color: var(--color-purple-100);
|
|
1673
1685
|
}
|
|
@@ -1743,9 +1755,6 @@
|
|
|
1743
1755
|
.bg-yellow-50 {
|
|
1744
1756
|
background-color: var(--color-yellow-50);
|
|
1745
1757
|
}
|
|
1746
|
-
.bg-yellow-100 {
|
|
1747
|
-
background-color: var(--color-yellow-100);
|
|
1748
|
-
}
|
|
1749
1758
|
.bg-yellow-400 {
|
|
1750
1759
|
background-color: var(--color-yellow-400);
|
|
1751
1760
|
}
|
|
@@ -2313,6 +2322,12 @@
|
|
|
2313
2322
|
.text-indigo-700 {
|
|
2314
2323
|
color: var(--color-indigo-700);
|
|
2315
2324
|
}
|
|
2325
|
+
.text-orange-600 {
|
|
2326
|
+
color: var(--color-orange-600);
|
|
2327
|
+
}
|
|
2328
|
+
.text-orange-800 {
|
|
2329
|
+
color: var(--color-orange-800);
|
|
2330
|
+
}
|
|
2316
2331
|
.text-purple-600 {
|
|
2317
2332
|
color: var(--color-purple-600);
|
|
2318
2333
|
}
|
|
@@ -2385,9 +2400,6 @@
|
|
|
2385
2400
|
.text-yellow-700 {
|
|
2386
2401
|
color: var(--color-yellow-700);
|
|
2387
2402
|
}
|
|
2388
|
-
.text-yellow-800 {
|
|
2389
|
-
color: var(--color-yellow-800);
|
|
2390
|
-
}
|
|
2391
2403
|
.capitalize {
|
|
2392
2404
|
text-transform: capitalize;
|
|
2393
2405
|
}
|
|
@@ -4830,6 +4842,11 @@
|
|
|
4830
4842
|
background-color: var(--color-indigo-900);
|
|
4831
4843
|
}
|
|
4832
4844
|
}
|
|
4845
|
+
.dark\:bg-orange-900 {
|
|
4846
|
+
&:where(.dark, .dark *) {
|
|
4847
|
+
background-color: var(--color-orange-900);
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4833
4850
|
.dark\:bg-purple-600 {
|
|
4834
4851
|
&:where(.dark, .dark *) {
|
|
4835
4852
|
background-color: var(--color-purple-600);
|
|
@@ -4920,23 +4937,11 @@
|
|
|
4920
4937
|
background-color: var(--color-yellow-200);
|
|
4921
4938
|
}
|
|
4922
4939
|
}
|
|
4923
|
-
.dark\:bg-yellow-900 {
|
|
4924
|
-
&:where(.dark, .dark *) {
|
|
4925
|
-
background-color: var(--color-yellow-900);
|
|
4926
|
-
}
|
|
4927
|
-
}
|
|
4928
4940
|
.dark\:fill-gray-300 {
|
|
4929
4941
|
&:where(.dark, .dark *) {
|
|
4930
4942
|
fill: var(--color-gray-300);
|
|
4931
4943
|
}
|
|
4932
4944
|
}
|
|
4933
|
-
.dark\:dark\:text-gray-200 {
|
|
4934
|
-
&:where(.dark, .dark *) {
|
|
4935
|
-
&:where(.dark, .dark *) {
|
|
4936
|
-
color: var(--color-gray-200);
|
|
4937
|
-
}
|
|
4938
|
-
}
|
|
4939
|
-
}
|
|
4940
4945
|
.dark\:text-black {
|
|
4941
4946
|
&:where(.dark, .dark *) {
|
|
4942
4947
|
color: var(--color-black);
|
|
@@ -5073,6 +5078,16 @@
|
|
|
5073
5078
|
color: var(--color-indigo-500);
|
|
5074
5079
|
}
|
|
5075
5080
|
}
|
|
5081
|
+
.dark\:text-orange-200 {
|
|
5082
|
+
&:where(.dark, .dark *) {
|
|
5083
|
+
color: var(--color-orange-200);
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
.dark\:text-orange-400 {
|
|
5087
|
+
&:where(.dark, .dark *) {
|
|
5088
|
+
color: var(--color-orange-400);
|
|
5089
|
+
}
|
|
5090
|
+
}
|
|
5076
5091
|
.dark\:text-purple-300 {
|
|
5077
5092
|
&:where(.dark, .dark *) {
|
|
5078
5093
|
color: var(--color-purple-300);
|
|
@@ -5118,11 +5133,6 @@
|
|
|
5118
5133
|
color: var(--color-white);
|
|
5119
5134
|
}
|
|
5120
5135
|
}
|
|
5121
|
-
.dark\:text-yellow-200 {
|
|
5122
|
-
&:where(.dark, .dark *) {
|
|
5123
|
-
color: var(--color-yellow-200);
|
|
5124
|
-
}
|
|
5125
|
-
}
|
|
5126
5136
|
.dark\:placeholder-gray-400 {
|
|
5127
5137
|
&:where(.dark, .dark *) {
|
|
5128
5138
|
&::placeholder {
|
llms/ui/ctx.mjs
CHANGED
|
@@ -30,6 +30,7 @@ export class ExtensionScope {
|
|
|
30
30
|
return this.ctx.ai.get(combinePaths(this.baseUrl, url), options)
|
|
31
31
|
}
|
|
32
32
|
delete(url, options) {
|
|
33
|
+
this.ctx.clearError()
|
|
33
34
|
return this.ctx.ai.get(combinePaths(this.baseUrl, url), {
|
|
34
35
|
...options,
|
|
35
36
|
method: 'DELETE'
|
|
@@ -39,41 +40,49 @@ export class ExtensionScope {
|
|
|
39
40
|
return this.ctx.ai.getJson(combinePaths(this.baseUrl, url), options)
|
|
40
41
|
}
|
|
41
42
|
async deleteJson(url, options) {
|
|
43
|
+
this.ctx.clearError()
|
|
42
44
|
return this.ctx.ai.getJson(combinePaths(this.baseUrl, url), {
|
|
43
45
|
...options,
|
|
44
46
|
method: 'DELETE'
|
|
45
47
|
})
|
|
46
48
|
}
|
|
47
49
|
post(url, options) {
|
|
50
|
+
this.ctx.clearError()
|
|
48
51
|
return this.ctx.ai.post(combinePaths(this.baseUrl, url), options)
|
|
49
52
|
}
|
|
50
53
|
put(url, options) {
|
|
54
|
+
this.ctx.clearError()
|
|
51
55
|
return this.ctx.ai.post(combinePaths(this.baseUrl, url), {
|
|
52
56
|
...options,
|
|
53
57
|
method: 'PUT'
|
|
54
58
|
})
|
|
55
59
|
}
|
|
56
60
|
patch(url, options) {
|
|
61
|
+
this.ctx.clearError()
|
|
57
62
|
return this.ctx.ai.post(combinePaths(this.baseUrl, url), {
|
|
58
63
|
...options,
|
|
59
64
|
method: 'PATCH'
|
|
60
65
|
})
|
|
61
66
|
}
|
|
62
67
|
async postForm(url, options) {
|
|
68
|
+
this.ctx.clearError()
|
|
63
69
|
return await this.ctx.ai.postForm(combinePaths(this.baseUrl, url), options)
|
|
64
70
|
}
|
|
65
71
|
async postJson(url, body) {
|
|
72
|
+
this.ctx.clearError()
|
|
66
73
|
return this.ctx.ai.postJson(combinePaths(this.baseUrl, url), {
|
|
67
74
|
body: body instanceof FormData ? body : JSON.stringify(body)
|
|
68
75
|
})
|
|
69
76
|
}
|
|
70
77
|
async putJson(url, body) {
|
|
78
|
+
this.ctx.clearError()
|
|
71
79
|
return this.ctx.ai.postJson(combinePaths(this.baseUrl, url), {
|
|
72
80
|
method: 'PUT',
|
|
73
81
|
body: body instanceof FormData ? body : JSON.stringify(body)
|
|
74
82
|
})
|
|
75
83
|
}
|
|
76
84
|
async patchJson(url, body) {
|
|
85
|
+
this.ctx.clearError()
|
|
77
86
|
return this.ctx.ai.postJson(combinePaths(this.baseUrl, url), {
|
|
78
87
|
method: 'PATCH',
|
|
79
88
|
body: body instanceof FormData ? body : JSON.stringify(body)
|
|
@@ -136,6 +145,7 @@ export class AppContext {
|
|
|
136
145
|
this.chatErrorFilters = []
|
|
137
146
|
this.createThreadFilters = []
|
|
138
147
|
this.updateThreadFilters = []
|
|
148
|
+
this.threadHeaderComponents = {}
|
|
139
149
|
this.threadFooterComponents = {}
|
|
140
150
|
this.top = {}
|
|
141
151
|
this.left = {}
|
|
@@ -297,6 +307,9 @@ export class AppContext {
|
|
|
297
307
|
this.toggleLayout('left', toggle)
|
|
298
308
|
return toggle
|
|
299
309
|
}
|
|
310
|
+
setThreadHeaders(components) {
|
|
311
|
+
Object.assign(this.threadHeaderComponents, components)
|
|
312
|
+
}
|
|
300
313
|
setThreadFooters(components) {
|
|
301
314
|
Object.assign(this.threadFooterComponents, components)
|
|
302
315
|
}
|
|
@@ -70,6 +70,7 @@ export default {
|
|
|
70
70
|
<!-- Messages Area -->
|
|
71
71
|
<div class="flex-1 overflow-y-auto" ref="messagesContainer">
|
|
72
72
|
<div class="mx-auto max-w-6xl px-4 py-6">
|
|
73
|
+
|
|
73
74
|
<div v-if="!$ai.hasAccess">
|
|
74
75
|
<OAuthSignIn v-if="$ai.authType === 'oauth'" @done="$ai.signIn($event)" />
|
|
75
76
|
<SignIn v-else @done="$ai.signIn($event)" />
|
|
@@ -81,291 +82,287 @@ export default {
|
|
|
81
82
|
</div>
|
|
82
83
|
|
|
83
84
|
<!-- Messages -->
|
|
84
|
-
<div v-else-if="currentThread
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
class="flex items-center cursor-pointer px-1.5 py-0.5 text-xs rounded text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-gray-100 transition-colors border hover:border-gray-300 dark:hover:border-gray-700">
|
|
88
|
-
<ProviderIcon class="size-4 mr-1" :provider="$chat.getProviderForModel(currentThread.model)" />
|
|
89
|
-
{{currentThread.model}}
|
|
90
|
-
</span>
|
|
91
|
-
</div>
|
|
92
|
-
<div
|
|
93
|
-
v-for="message in currentThread.messages.filter(x => x.role !== 'system')"
|
|
94
|
-
:key="message.id"
|
|
95
|
-
v-show="!(message.role === 'tool' && isToolLinked(message))"
|
|
96
|
-
class="flex items-start space-x-3 group"
|
|
97
|
-
:class="message.role === 'user' ? 'flex-row-reverse space-x-reverse' : ''"
|
|
98
|
-
>
|
|
99
|
-
<!-- Avatar outside the bubble -->
|
|
100
|
-
<div class="flex-shrink-0 flex flex-col justify-center">
|
|
101
|
-
<div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium"
|
|
102
|
-
:class="message.role === 'user'
|
|
103
|
-
? 'bg-blue-100 dark:bg-blue-900 text-gray-900 dark:text-gray-100 border border-blue-200 dark:border-blue-700'
|
|
104
|
-
: message.role === 'tool'
|
|
105
|
-
? 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 border border-purple-200 dark:border-purple-800'
|
|
106
|
-
: 'bg-gray-600 dark:bg-gray-500 text-white'"
|
|
107
|
-
>
|
|
108
|
-
<span v-if="message.role === 'user'">U</span>
|
|
109
|
-
<svg v-else-if="message.role === 'tool'" class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
110
|
-
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path>
|
|
111
|
-
</svg>
|
|
112
|
-
<span v-else>AI</span>
|
|
113
|
-
</div>
|
|
114
|
-
|
|
115
|
-
<!-- Delete button (shown on hover) -->
|
|
116
|
-
<button type="button" @click.stop="$threads.deleteMessageFromThread(currentThread.id, message.id)"
|
|
117
|
-
class="mx-auto opacity-0 group-hover:opacity-100 mt-2 rounded text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 transition-all"
|
|
118
|
-
title="Delete message">
|
|
119
|
-
<svg class="size-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
120
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
|
121
|
-
</svg>
|
|
122
|
-
</button>
|
|
123
|
-
</div>
|
|
124
|
-
|
|
125
|
-
<!-- Message bubble -->
|
|
85
|
+
<div v-else-if="currentThread">
|
|
86
|
+
<ThreadHeader v-if="currentThread" :thread="currentThread" class="mb-2" />
|
|
87
|
+
<div class="space-y-2" v-if="currentThread?.messages?.length">
|
|
126
88
|
<div
|
|
127
|
-
|
|
128
|
-
:
|
|
129
|
-
|
|
130
|
-
|
|
89
|
+
v-for="message in currentThread.messages.filter(x => x.role !== 'system')"
|
|
90
|
+
:key="message.id"
|
|
91
|
+
v-show="!(message.role === 'tool' && isToolLinked(message))"
|
|
92
|
+
class="flex items-start space-x-3 group"
|
|
93
|
+
:class="message.role === 'user' ? 'flex-row-reverse space-x-reverse' : ''"
|
|
131
94
|
>
|
|
132
|
-
<!--
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
95
|
+
<!-- Avatar outside the bubble -->
|
|
96
|
+
<div class="flex-shrink-0 flex flex-col justify-center">
|
|
97
|
+
<div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium"
|
|
98
|
+
:class="message.role === 'user'
|
|
99
|
+
? 'bg-blue-100 dark:bg-blue-900 text-gray-900 dark:text-gray-100 border border-blue-200 dark:border-blue-700'
|
|
100
|
+
: message.role === 'tool'
|
|
101
|
+
? 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 border border-purple-200 dark:border-purple-800'
|
|
102
|
+
: 'bg-gray-600 dark:bg-gray-500 text-white'"
|
|
103
|
+
>
|
|
104
|
+
<span v-if="message.role === 'user'">U</span>
|
|
105
|
+
<svg v-else-if="message.role === 'tool'" class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
106
|
+
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path>
|
|
107
|
+
</svg>
|
|
108
|
+
<span v-else>AI</span>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<!-- Delete button (shown on hover) -->
|
|
112
|
+
<button type="button" @click.stop="$threads.deleteMessageFromThread(currentThread.id, message.id)"
|
|
113
|
+
class="mx-auto opacity-0 group-hover:opacity-100 mt-2 rounded text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 transition-all"
|
|
114
|
+
title="Delete message">
|
|
115
|
+
<svg class="size-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
116
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
|
117
|
+
</svg>
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
146
120
|
|
|
121
|
+
<!-- Message bubble -->
|
|
147
122
|
<div
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
123
|
+
class="message rounded-lg px-4 py-3 relative group"
|
|
124
|
+
:class="message.role === 'user'
|
|
125
|
+
? 'bg-blue-100 dark:bg-blue-900 text-gray-900 dark:text-gray-100 border border-blue-200 dark:border-blue-700'
|
|
126
|
+
: 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100 border border-gray-200 dark:border-gray-700'"
|
|
127
|
+
>
|
|
128
|
+
<!-- Copy button in top right corner -->
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
@click="copyMessageContent(message)"
|
|
132
|
+
class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 p-1 rounded hover:bg-black/10 dark:hover:bg-white/10 focus:outline-none focus:ring-0"
|
|
133
|
+
:class="message.role === 'user' ? 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200' : 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200'"
|
|
134
|
+
title="Copy message content"
|
|
135
|
+
>
|
|
136
|
+
<svg v-if="copying === message" class="size-4 text-green-500 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>
|
|
137
|
+
<svg v-else class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
138
|
+
<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
|
|
139
|
+
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
|
|
140
|
+
</svg>
|
|
141
|
+
</button>
|
|
142
|
+
|
|
143
|
+
<div
|
|
144
|
+
v-if="message.role === 'assistant'"
|
|
145
|
+
v-html="$fmt.markdown(message.content)"
|
|
146
|
+
class="prose prose-sm max-w-none dark:prose-invert"
|
|
147
|
+
></div>
|
|
148
|
+
|
|
149
|
+
<!-- Collapsible reasoning section -->
|
|
150
|
+
<MessageReasoning v-if="message.role === 'assistant' && (message.reasoning || message.thinking || message.reasoning_content)"
|
|
151
|
+
:reasoning="message.reasoning || message.thinking || message.reasoning_content" :message="message" />
|
|
152
|
+
|
|
153
|
+
<!-- Tool Calls & Outputs -->
|
|
154
|
+
<div v-if="message.tool_calls && message.tool_calls.length > 0" class="mb-3 space-y-4">
|
|
155
|
+
<div v-for="(tool, i) in message.tool_calls" :key="i" class="rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 overflow-hidden">
|
|
156
|
+
<!-- Tool Call Header -->
|
|
157
|
+
<div class="px-3 py-2 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between bg-gray-50/30 dark:bg-gray-800 space-x-4">
|
|
158
|
+
<div class="flex items-center gap-2">
|
|
159
|
+
<svg class="size-3.5 text-gray-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path></svg>
|
|
160
|
+
<span class="font-mono text-xs font-bold text-gray-700 dark:text-gray-300">{{ tool.function.name }}</span>
|
|
161
|
+
</div>
|
|
162
|
+
<span class="text-[10px] uppercase tracking-wider text-gray-400 font-medium">Tool Call</span>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<!-- Arguments -->
|
|
166
|
+
<div v-if="tool.function.arguments && tool.function.arguments != '{}'" class="not-prose px-3 py-2">
|
|
167
|
+
<HtmlFormat v-if="hasJsonStructure(tool.function.arguments)" :value="tryParseJson(tool.function.arguments)" :classes="customHtmlClasses" />
|
|
168
|
+
<pre v-else class="tool-arguments">{{ tool.function.arguments }}</pre>
|
|
165
169
|
</div>
|
|
166
|
-
<span class="text-[10px] uppercase tracking-wider text-gray-400 font-medium">Tool Call</span>
|
|
167
|
-
</div>
|
|
168
|
-
|
|
169
|
-
<!-- Arguments -->
|
|
170
|
-
<div v-if="tool.function.arguments && tool.function.arguments != '{}'" class="not-prose px-3 py-2">
|
|
171
|
-
<HtmlFormat v-if="hasJsonStructure(tool.function.arguments)" :value="tryParseJson(tool.function.arguments)" :classes="customHtmlClasses" />
|
|
172
|
-
<pre v-else class="tool-arguments">{{ tool.function.arguments }}</pre>
|
|
173
|
-
</div>
|
|
174
170
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
171
|
+
<!-- Tool Output (Nested) -->
|
|
172
|
+
<div v-if="getToolOutput(tool.id)" class="border-t border-gray-200 dark:border-gray-700">
|
|
173
|
+
<div class="px-3 py-1.5 flex justify-between items-center border-b border-gray-200 dark:border-gray-800 bg-gray-50/30 dark:bg-gray-800">
|
|
174
|
+
<div class="flex items-center gap-2 ">
|
|
175
|
+
<svg class="size-3.5 text-gray-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
|
176
|
+
<span class="text-[10px] uppercase tracking-wider text-gray-400 font-medium">Output</span>
|
|
177
|
+
</div>
|
|
178
|
+
<div v-if="hasJsonStructure(getToolOutput(tool.id).content)" class="flex items-center gap-2 text-[10px] uppercase tracking-wider font-medium select-none">
|
|
179
|
+
<span @click="setPrefs({ toolFormat: 'text' })"
|
|
180
|
+
class="cursor-pointer transition-colors"
|
|
181
|
+
:class="prefs.toolFormat !== 'preview' ? 'text-gray-600 dark:text-gray-300' : 'text-gray-400 hover:text-gray-600 dark:hover:text-gray-300'">
|
|
182
|
+
text
|
|
183
|
+
</span>
|
|
184
|
+
<span class="text-gray-300 dark:text-gray-700">|</span>
|
|
185
|
+
<span @click="setPrefs({ toolFormat: 'preview' })"
|
|
186
|
+
class="cursor-pointer transition-colors"
|
|
187
|
+
:class="prefs.toolFormat == 'preview' ? 'text-gray-600 dark:text-gray-300' : 'text-gray-400 hover:text-gray-600 dark:hover:text-gray-300'">
|
|
188
|
+
preview
|
|
189
|
+
</span>
|
|
190
|
+
</div>
|
|
194
191
|
</div>
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
192
|
+
<div class="not-prose px-3 py-2">
|
|
193
|
+
<pre v-if="prefs.toolFormat !== 'preview' || !hasJsonStructure(getToolOutput(tool.id).content)" class="tool-output">{{ getToolOutput(tool.id).content }}</pre>
|
|
194
|
+
<div v-else class="text-xs">
|
|
195
|
+
<HtmlFormat v-if="tryParseJson(getToolOutput(tool.id).content)" :value="tryParseJson(getToolOutput(tool.id).content)" :classes="customHtmlClasses" />
|
|
196
|
+
<div v-else class="text-gray-500 italic p-2">Invalid JSON content</div>
|
|
197
|
+
</div>
|
|
201
198
|
</div>
|
|
202
199
|
</div>
|
|
203
200
|
</div>
|
|
204
201
|
</div>
|
|
205
|
-
</div>
|
|
206
202
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
203
|
+
<!-- Tool Output (Orphaned) -->
|
|
204
|
+
<div v-if="message.role === 'tool' && !isToolLinked(message)" class="text-sm">
|
|
205
|
+
<div class="flex items-center gap-2 mb-1 opacity-70">
|
|
206
|
+
<div class="flex items-center text-xs font-mono font-medium text-gray-500 uppercase tracking-wider">
|
|
207
|
+
<svg class="size-3 mr-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
|
208
|
+
Tool Output
|
|
209
|
+
</div>
|
|
210
|
+
<div v-if="message.name" class="text-xs font-mono bg-gray-200 dark:bg-gray-700 px-1.5 rounded text-gray-700 dark:text-gray-300">
|
|
211
|
+
{{ message.name }}
|
|
212
|
+
</div>
|
|
213
|
+
<div v-if="message.tool_call_id" class="text-[10px] font-mono text-gray-400">
|
|
214
|
+
{{ message.tool_call_id.slice(0,8) }}
|
|
215
|
+
</div>
|
|
216
216
|
</div>
|
|
217
|
-
<div
|
|
218
|
-
{{ message.
|
|
217
|
+
<div class="not-prose bg-white dark:bg-gray-900 rounded border border-gray-200 dark:border-gray-800 p-2 overflow-x-auto">
|
|
218
|
+
<pre class="tool-output">{{ message.content }}</pre>
|
|
219
219
|
</div>
|
|
220
220
|
</div>
|
|
221
|
-
<div class="not-prose bg-white dark:bg-gray-900 rounded border border-gray-200 dark:border-gray-800 p-2 overflow-x-auto">
|
|
222
|
-
<pre class="tool-output">{{ message.content }}</pre>
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
|
|
226
|
-
<!-- Assistant Images -->
|
|
227
|
-
<div v-if="message.images && message.images.length > 0" class="mt-2 flex flex-wrap gap-2">
|
|
228
|
-
<template v-for="(img, i) in message.images" :key="i">
|
|
229
|
-
<div v-if="img.type === 'image_url'" class="group relative cursor-pointer" @click="openLightbox(resolveUrl(img.image_url.url))">
|
|
230
|
-
<img :src="resolveUrl(img.image_url.url)" class="max-w-[400px] max-h-96 rounded-lg border border-gray-200 dark:border-gray-700 object-contain bg-gray-50 dark:bg-gray-900 shadow-sm transition-transform hover:scale-[1.02]" />
|
|
231
|
-
</div>
|
|
232
|
-
</template>
|
|
233
|
-
</div>
|
|
234
|
-
|
|
235
|
-
<!-- Assistant Audios -->
|
|
236
|
-
<div v-if="message.audios && message.audios.length > 0" class="mt-2 flex flex-wrap gap-2">
|
|
237
|
-
<template v-for="(audio, i) in message.audios" :key="i">
|
|
238
|
-
<div v-if="audio.type === 'audio_url'" class="flex items-center gap-2 p-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
|
239
|
-
<audio controls :src="resolveUrl(audio.audio_url.url)" class="h-8 w-64"></audio>
|
|
240
|
-
</div>
|
|
241
|
-
</template>
|
|
242
|
-
</div>
|
|
243
221
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
<div v-if="hasAttachments(message)" class="mt-2 flex flex-wrap gap-2">
|
|
250
|
-
<template v-for="(part, i) in getAttachments(message)" :key="i">
|
|
251
|
-
<!-- Image -->
|
|
252
|
-
<div v-if="part.type === 'image_url'" class="group relative cursor-pointer" @click="openLightbox(part.image_url.url)">
|
|
253
|
-
<img :src="part.image_url.url" class="max-w-[400px] max-h-96 rounded-lg border border-gray-200 dark:border-gray-700 object-contain bg-gray-50 dark:bg-gray-900 shadow-sm transition-transform hover:scale-[1.02]" />
|
|
222
|
+
<!-- Assistant Images -->
|
|
223
|
+
<div v-if="message.images && message.images.length > 0" class="mt-2 flex flex-wrap gap-2">
|
|
224
|
+
<template v-for="(img, i) in message.images" :key="i">
|
|
225
|
+
<div v-if="img.type === 'image_url'" class="group relative cursor-pointer" @click="openLightbox(resolveUrl(img.image_url.url))">
|
|
226
|
+
<img :src="resolveUrl(img.image_url.url)" class="max-w-[400px] max-h-96 rounded-lg border border-gray-200 dark:border-gray-700 object-contain bg-gray-50 dark:bg-gray-900 shadow-sm transition-transform hover:scale-[1.02]" />
|
|
254
227
|
</div>
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
228
|
+
</template>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<!-- Assistant Audios -->
|
|
232
|
+
<div v-if="message.audios && message.audios.length > 0" class="mt-2 flex flex-wrap gap-2">
|
|
233
|
+
<template v-for="(audio, i) in message.audios" :key="i">
|
|
234
|
+
<div v-if="audio.type === 'audio_url'" class="flex items-center gap-2 p-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
|
235
|
+
<audio controls :src="resolveUrl(audio.audio_url.url)" class="h-8 w-64"></audio>
|
|
259
236
|
</div>
|
|
260
|
-
<!-- File -->
|
|
261
|
-
<a v-else-if="part.type === 'file'" :href="part.file.file_data" target="_blank"
|
|
262
|
-
class="flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors text-sm text-blue-600 dark:text-blue-400 hover:underline">
|
|
263
|
-
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
|
|
264
|
-
<span class="max-w-xs truncate">{{ part.file.filename || 'Attachment' }}</span>
|
|
265
|
-
</a>
|
|
266
237
|
</template>
|
|
267
238
|
</div>
|
|
239
|
+
|
|
240
|
+
<!-- User Message with separate attachments -->
|
|
241
|
+
<div v-else-if="message.role !== 'assistant' && message.role !== 'tool'">
|
|
242
|
+
<div v-html="$fmt.markdown(message.content)" class="prose prose-sm max-w-none dark:prose-invert break-words"></div>
|
|
243
|
+
|
|
244
|
+
<!-- Attachments Grid -->
|
|
245
|
+
<div v-if="hasAttachments(message)" class="mt-2 flex flex-wrap gap-2">
|
|
246
|
+
<template v-for="(part, i) in getAttachments(message)" :key="i">
|
|
247
|
+
<!-- Image -->
|
|
248
|
+
<div v-if="part.type === 'image_url'" class="group relative cursor-pointer" @click="openLightbox(part.image_url.url)">
|
|
249
|
+
<img :src="part.image_url.url" class="max-w-[400px] max-h-96 rounded-lg border border-gray-200 dark:border-gray-700 object-contain bg-gray-50 dark:bg-gray-900 shadow-sm transition-transform hover:scale-[1.02]" />
|
|
250
|
+
</div>
|
|
251
|
+
<!-- Audio -->
|
|
252
|
+
<div v-else-if="part.type === 'input_audio'" class="flex items-center gap-2 p-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
|
253
|
+
<svg class="w-5 h-5 text-gray-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>
|
|
254
|
+
<audio controls :src="part.input_audio.data" class="h-8 w-48"></audio>
|
|
255
|
+
</div>
|
|
256
|
+
<!-- File -->
|
|
257
|
+
<a v-else-if="part.type === 'file'" :href="part.file.file_data" target="_blank"
|
|
258
|
+
class="flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors text-sm text-blue-600 dark:text-blue-400 hover:underline">
|
|
259
|
+
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
|
|
260
|
+
<span class="max-w-xs truncate">{{ part.file.filename || 'Attachment' }}</span>
|
|
261
|
+
</a>
|
|
262
|
+
</template>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<MessageUsage :message="message" :usage="getMessageUsage(message)" />
|
|
268
267
|
</div>
|
|
269
268
|
|
|
270
|
-
|
|
269
|
+
<!-- Edit and Redo buttons (shown on hover for user messages, outside bubble) -->
|
|
270
|
+
<div v-if="message.role === 'user'" class="flex flex-col gap-2 opacity-0 group-hover:opacity-100 transition-opacity mt-1">
|
|
271
|
+
<button type="button" @click.stop="editMessage(message)"
|
|
272
|
+
class="whitespace-nowrap text-xs px-2 py-1 rounded text-gray-400 dark:text-gray-500 hover:text-green-600 dark:hover:text-green-400 hover:bg-green-50 dark:hover:bg-green-900/30 transition-all"
|
|
273
|
+
title="Edit message">
|
|
274
|
+
<svg class="size-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
275
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
|
276
|
+
</svg>
|
|
277
|
+
Edit
|
|
278
|
+
</button>
|
|
279
|
+
<button type="button" @click.stop="redoMessage(message)"
|
|
280
|
+
class="whitespace-nowrap text-xs px-2 py-1 rounded text-gray-400 dark:text-gray-500 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/30 transition-all"
|
|
281
|
+
title="Redo message (clears all responses after this message and re-runs it)">
|
|
282
|
+
<svg class="size-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
283
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
|
284
|
+
</svg>
|
|
285
|
+
Redo
|
|
286
|
+
</button>
|
|
287
|
+
</div>
|
|
271
288
|
</div>
|
|
272
289
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
title="Edit message">
|
|
278
|
-
<svg class="size-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
279
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
|
280
|
-
</svg>
|
|
281
|
-
Edit
|
|
282
|
-
</button>
|
|
283
|
-
<button type="button" @click.stop="redoMessage(message)"
|
|
284
|
-
class="whitespace-nowrap text-xs px-2 py-1 rounded text-gray-400 dark:text-gray-500 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/30 transition-all"
|
|
285
|
-
title="Redo message (clears all responses after this message and re-runs it)">
|
|
286
|
-
<svg class="size-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
287
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
|
288
|
-
</svg>
|
|
289
|
-
Redo
|
|
290
|
-
</button>
|
|
290
|
+
<div v-if="currentThread.stats && currentThread.stats.outputTokens" class="text-center text-gray-500 dark:text-gray-400 text-sm">
|
|
291
|
+
<span :title="$fmt.statsTitle(currentThread.stats)">
|
|
292
|
+
{{ currentThread.stats.cost ? $fmt.costLong(currentThread.stats.cost) + ' for ' : '' }} {{ $fmt.humanifyNumber(currentThread.stats.inputTokens) }} → {{ $fmt.humanifyNumber(currentThread.stats.outputTokens) }} tokens over {{ currentThread.stats.requests }} request{{currentThread.stats.requests===1?'':'s'}} in {{ $fmt.humanifyMs(currentThread.stats.duration) }}
|
|
293
|
+
</span>
|
|
291
294
|
</div>
|
|
292
|
-
</div>
|
|
293
|
-
|
|
294
|
-
<div v-if="currentThread.stats && currentThread.stats.outputTokens" class="text-center text-gray-500 dark:text-gray-400 text-sm">
|
|
295
|
-
<span :title="$fmt.statsTitle(currentThread.stats)">
|
|
296
|
-
{{ currentThread.stats.cost ? $fmt.costLong(currentThread.stats.cost) + ' for ' : '' }} {{ $fmt.humanifyNumber(currentThread.stats.inputTokens) }} → {{ $fmt.humanifyNumber(currentThread.stats.outputTokens) }} tokens over {{ currentThread.stats.requests }} request{{currentThread.stats.requests===1?'':'s'}} in {{ $fmt.humanifyMs(currentThread.stats.duration) }}
|
|
297
|
-
</span>
|
|
298
|
-
</div>
|
|
299
295
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
296
|
+
<!-- Loading indicator -->
|
|
297
|
+
<div v-if="$threads.watchingThread" class="flex items-start space-x-3 group">
|
|
298
|
+
<!-- Avatar outside the bubble -->
|
|
299
|
+
<div class="flex-shrink-0">
|
|
300
|
+
<div class="w-8 h-8 rounded-full bg-gray-600 dark:bg-gray-500 text-white flex items-center justify-center text-sm font-medium">
|
|
301
|
+
AI
|
|
302
|
+
</div>
|
|
306
303
|
</div>
|
|
307
|
-
</div>
|
|
308
304
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
305
|
+
<!-- Loading bubble -->
|
|
306
|
+
<div class="rounded-lg px-4 py-3 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
|
307
|
+
<div class="flex space-x-1">
|
|
308
|
+
<div class="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce"></div>
|
|
309
|
+
<div class="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce" style="animation-delay: 0.1s"></div>
|
|
310
|
+
<div class="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
|
|
311
|
+
</div>
|
|
315
312
|
</div>
|
|
316
|
-
</div>
|
|
317
313
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
314
|
+
<!-- Cancel button -->
|
|
315
|
+
<button type="button" @click="$threads.cancelThread()"
|
|
316
|
+
class="px-3 py-1 rounded text-sm text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 border border-transparent hover:border-red-300 dark:hover:border-red-600 transition-all"
|
|
317
|
+
title="Cancel request">
|
|
318
|
+
cancel
|
|
319
|
+
</button>
|
|
320
|
+
</div>
|
|
325
321
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
322
|
+
<!-- Thread error message bubble -->
|
|
323
|
+
<div v-if="currentThread?.error" class="mt-8 flex items-center space-x-3">
|
|
324
|
+
<!-- Avatar outside the bubble -->
|
|
325
|
+
<div class="flex-shrink-0">
|
|
326
|
+
<div class="size-8 rounded-full bg-red-600 dark:bg-red-500 text-white flex items-center justify-center text-lg font-bold">
|
|
327
|
+
!
|
|
328
|
+
</div>
|
|
332
329
|
</div>
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
330
|
+
<!-- Error bubble -->
|
|
331
|
+
<div class="max-w-[85%] rounded-lg px-3 py-1 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 text-red-800 dark:text-red-200 shadow-sm">
|
|
332
|
+
<div class="flex items-start space-x-2">
|
|
333
|
+
<div class="flex-1 min-w-0">
|
|
334
|
+
<div v-if="currentThread.error" class="text-base mb-1">{{ currentThread.error }}</div>
|
|
335
|
+
</div>
|
|
339
336
|
</div>
|
|
340
337
|
</div>
|
|
341
338
|
</div>
|
|
342
|
-
</div>
|
|
343
339
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
340
|
+
<!-- Error message bubble -->
|
|
341
|
+
<div v-if="$state.error" class="mt-8 flex items-start space-x-3">
|
|
342
|
+
<!-- Avatar outside the bubble -->
|
|
343
|
+
<div class="flex-shrink-0">
|
|
344
|
+
<div class="size-8 rounded-full bg-red-600 dark:bg-red-500 text-white flex items-center justify-center text-lg font-bold">
|
|
345
|
+
!
|
|
346
|
+
</div>
|
|
350
347
|
</div>
|
|
351
|
-
</div>
|
|
352
348
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
349
|
+
<!-- Error bubble -->
|
|
350
|
+
<div class="max-w-[85%] rounded-lg px-4 py-3 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 text-red-800 dark:text-red-200 shadow-sm">
|
|
351
|
+
<div class="flex items-start space-x-2">
|
|
352
|
+
<div class="flex-1 min-w-0">
|
|
353
|
+
<div class="flex justify-between items-start">
|
|
354
|
+
<div class="text-base font-medium mb-1">{{ $state.error?.errorCode || 'Error' }}</div>
|
|
355
|
+
<button type="button" @click="$ctx.clearError()" title="Clear Error"
|
|
356
|
+
class="text-red-400 dark:text-red-300 hover:text-red-600 dark:hover:text-red-100 flex-shrink-0">
|
|
357
|
+
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
358
|
+
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
|
359
|
+
</svg>
|
|
360
|
+
</button>
|
|
361
|
+
</div>
|
|
362
|
+
<div v-if="$state.error?.message" class="text-base mb-1">{{ $state.error.message }}</div>
|
|
363
|
+
<div v-if="$state.error?.stackTrace" class="mt-2 text-sm whitespace-pre-wrap break-words max-h-80 overflow-y-auto font-mono p-2 border border-red-200/70 dark:border-red-800/70">
|
|
364
|
+
{{ $state.error.stackTrace }}
|
|
365
|
+
</div>
|
|
369
366
|
</div>
|
|
370
367
|
</div>
|
|
371
368
|
</div>
|
|
@@ -374,7 +371,6 @@ export default {
|
|
|
374
371
|
<ThreadFooter v-if="$threads.threadDetails.value[currentThread.id]" :thread="$threads.threadDetails.value[currentThread.id]" />
|
|
375
372
|
</div>
|
|
376
373
|
</div>
|
|
377
|
-
|
|
378
374
|
</div>
|
|
379
375
|
|
|
380
376
|
<!-- Input Area -->
|
llms/ui/modules/chat/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { ref, watch, nextTick, inject } from 'vue'
|
|
2
|
+
import { ref, watch, computed, nextTick, inject } from 'vue'
|
|
3
3
|
import { $$, createElement, lastRightPart, ApiResult, createErrorStatus, pick } from "@servicestack/client"
|
|
4
4
|
import SettingsDialog, { useSettings } from './SettingsDialog.mjs'
|
|
5
5
|
import ChatBody from './ChatBody.mjs'
|
|
@@ -782,18 +782,79 @@ const HomeTools = {
|
|
|
782
782
|
`,
|
|
783
783
|
}
|
|
784
784
|
|
|
785
|
+
const ThreadHeader = {
|
|
786
|
+
template: `
|
|
787
|
+
<div v-if="showComponents.length" class="flex items-center justify-center gap-2">
|
|
788
|
+
<div v-for="component in showComponents">
|
|
789
|
+
<component :is="component" :thread="thread" />
|
|
790
|
+
</div>
|
|
791
|
+
</div>
|
|
792
|
+
`,
|
|
793
|
+
props: { thread: Object },
|
|
794
|
+
setup(props) {
|
|
795
|
+
const ctx = inject('ctx')
|
|
796
|
+
const showComponents = computed(() => {
|
|
797
|
+
const args = { thread: props.thread }
|
|
798
|
+
return Object.values(ctx.threadHeaderComponents).filter(def => def.show(args)).map(def => def.component)
|
|
799
|
+
})
|
|
800
|
+
return {
|
|
801
|
+
showComponents,
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
785
806
|
const ThreadFooter = {
|
|
786
807
|
template: `
|
|
787
|
-
<div>
|
|
788
|
-
<div v-for="
|
|
789
|
-
<component
|
|
808
|
+
<div v-if="showComponents.length">
|
|
809
|
+
<div v-for="component in showComponents">
|
|
810
|
+
<component :is="component" :thread="thread" />
|
|
790
811
|
</div>
|
|
791
812
|
</div>
|
|
792
813
|
`,
|
|
793
|
-
props: {
|
|
794
|
-
|
|
795
|
-
|
|
814
|
+
props: { thread: Object },
|
|
815
|
+
setup(props) {
|
|
816
|
+
const ctx = inject('ctx')
|
|
817
|
+
const showComponents = computed(() => {
|
|
818
|
+
const args = { thread: props.thread }
|
|
819
|
+
return Object.values(ctx.threadFooterComponents).filter(def => def.show(args)).map(def => def.component)
|
|
820
|
+
})
|
|
821
|
+
return {
|
|
822
|
+
showComponents,
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const ThreadModel = {
|
|
828
|
+
template: `
|
|
829
|
+
<span @click="$chat.setSelectedModel({ name: thread.model})"
|
|
830
|
+
class="flex items-center cursor-pointer px-1.5 py-0.5 text-xs rounded text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-gray-100 transition-colors border hover:border-gray-300 dark:hover:border-gray-700">
|
|
831
|
+
<ProviderIcon class="size-4 mr-1" :provider="$chat.getProviderForModel(thread.model)" />
|
|
832
|
+
{{thread.model}}
|
|
833
|
+
</span>
|
|
834
|
+
`,
|
|
835
|
+
props: { thread: Object },
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const ThreadTools = {
|
|
839
|
+
template: `
|
|
840
|
+
<div class="text-sm flex items-center gap-1 flex items-center px-1.5 py-0.5 text-xs rounded text-gray-600 dark:text-gray-300 border cursor-help" :title="title">
|
|
841
|
+
<svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 10h3V7L6.5 3.5a6 6 0 0 1 8 8l6 6a2 2 0 0 1-3 3l-6-6a6 6 0 0 1-8-8z"/></svg>
|
|
842
|
+
<span v-if="toolFns.length==1">{{toolFns[0].function.name}}</span>
|
|
843
|
+
<span v-else-if="toolFns.length>1">{{toolFns.length}} Tools</span>
|
|
844
|
+
</div>
|
|
845
|
+
`,
|
|
846
|
+
props: { thread: Object },
|
|
796
847
|
setup(props) {
|
|
848
|
+
const toolFns = computed(() => props.thread.tools.filter(x => x.type === 'function'))
|
|
849
|
+
const title = computed(() => toolFns.value.length == 1
|
|
850
|
+
? toolFns.value[0].function.name
|
|
851
|
+
: toolFns.value.length > 1
|
|
852
|
+
? toolFns.value.map(x => x.function.name).join('\n')
|
|
853
|
+
: '')
|
|
854
|
+
return {
|
|
855
|
+
toolFns,
|
|
856
|
+
title,
|
|
857
|
+
}
|
|
797
858
|
}
|
|
798
859
|
}
|
|
799
860
|
|
|
@@ -807,6 +868,7 @@ export default {
|
|
|
807
868
|
ChatBody,
|
|
808
869
|
HomeTools,
|
|
809
870
|
Home,
|
|
871
|
+
ThreadHeader,
|
|
810
872
|
ThreadFooter,
|
|
811
873
|
})
|
|
812
874
|
ctx.setGlobals({
|
|
@@ -835,6 +897,17 @@ export default {
|
|
|
835
897
|
{ path: '/c/:id', component: ChatBody, meta },
|
|
836
898
|
])
|
|
837
899
|
|
|
900
|
+
ctx.setThreadHeaders({
|
|
901
|
+
model: {
|
|
902
|
+
component: ThreadModel,
|
|
903
|
+
show({ thread }) { return thread.model }
|
|
904
|
+
},
|
|
905
|
+
tools: {
|
|
906
|
+
component: ThreadTools,
|
|
907
|
+
show({ thread }) { return (thread.tools || []).filter(x => x.type === 'function').length }
|
|
908
|
+
}
|
|
909
|
+
})
|
|
910
|
+
|
|
838
911
|
const prefs = ctx.getPrefs()
|
|
839
912
|
if (prefs.model) {
|
|
840
913
|
ctx.state.selectedModel = prefs.model
|
llms/ui/tailwind.input.css
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
llms/__init__.py,sha256=DKwTZDsyYL_wHe7yvLw49Nf8PSgPSyWaeVdotUqSvrQ,84
|
|
2
2
|
llms/__main__.py,sha256=hrBulHIt3lmPm1BCyAEVtB6DQ0Hvc3gnIddhHCmJasg,151
|
|
3
|
-
llms/db.py,sha256=
|
|
3
|
+
llms/db.py,sha256=BEwBSh7AOc6seylB6Q5k1ru07Zx8WDkSHkto0YWNDSg,11687
|
|
4
4
|
llms/index.html,sha256=nGk1Djtn9p7l6LuKp4Kg0JIB9fCzxtTWXFfmDb4ggpc,1658
|
|
5
5
|
llms/llms.json,sha256=gebw9PNWf5UTpU7RDIwo6QcBaJEkU0z3k9EeXR4hQiI,11147
|
|
6
|
-
llms/main.py,sha256=
|
|
6
|
+
llms/main.py,sha256=W9h1UX95pvcCb2gGm9eXHZUHXlb_u28zuGs2N5VHsPE,156278
|
|
7
7
|
llms/providers-extra.json,sha256=w7_5gB0YUPK0PJNeViM7vRDfNGChXUKMHfGHenVxEkM,10165
|
|
8
8
|
llms/providers.json,sha256=Hc1STJW9hqM_MWBjeJljs_0I3bTo6qhGlEsL0o-dUuA,285221
|
|
9
9
|
llms/extensions/analytics/ui/index.mjs,sha256=cr9dPmEJjha2xX6A_7xbJxkOolWFP1p6TgIoO8M8juI,69540
|
|
10
10
|
llms/extensions/app/README.md,sha256=TKoblZpHlheLCh_dfXOxqTc5OvxlgMBa-vKo8Hqb2gg,1370
|
|
11
|
-
llms/extensions/app/__init__.py,sha256=
|
|
11
|
+
llms/extensions/app/__init__.py,sha256=eOdIGG7jrQhBZ-ndNlvbTFOhZlH9n1tG-WvqJw8cakA,20812
|
|
12
12
|
llms/extensions/app/db.py,sha256=CqpHReXXjrLXXNxINo-wsnBJenKZVVHKlWlhtXFWj08,21503
|
|
13
13
|
llms/extensions/app/ui/Recents.mjs,sha256=HT9R2IEus3dGEqtKPGrgX1eMp4EQ2j2jLwv-Fn1Us5E,9253
|
|
14
14
|
llms/extensions/app/ui/index.mjs,sha256=sB9176LLNuKFsZ28yL-tROA6J4xePNtvxtSrzFcinRo,13271
|
|
15
|
-
llms/extensions/app/ui/threadStore.mjs,sha256=
|
|
15
|
+
llms/extensions/app/ui/threadStore.mjs,sha256=5qZv3maUfpLXnoEQN6EPYXUbe3B_OCgT_ttoBpvA4Dk,12192
|
|
16
16
|
llms/extensions/core_tools/CALCULATOR.md,sha256=pJRtCVF01BgxFrSNh2Ys_lrRi3SFwLgJzAX93AGh93Q,1944
|
|
17
17
|
llms/extensions/core_tools/__init__.py,sha256=nYeQH20Iy_Yl9S8qGcgLmU7x4e2C43BsiOMG8XYfPM4,23086
|
|
18
18
|
llms/extensions/core_tools/ui/index.mjs,sha256=KycJ2FcQ6BieBY7fjWGxVBGHN6WuFx712OFrO6flXww,31770
|
|
@@ -140,13 +140,13 @@ llms/extensions/system_prompts/ui/prompts.json,sha256=t5DD3bird-87wFa4OlW-bC2wdo
|
|
|
140
140
|
llms/extensions/tools/__init__.py,sha256=yfIK7dVqYiZGX5VeJ3x7HQWLPQfuPqeNYJsd0lpZUM4,120
|
|
141
141
|
llms/extensions/tools/ui/index.mjs,sha256=Nu69U6odCUh8uu1i8d5f8ryO4Lj_OFbPX2LnVyYu1fk,9602
|
|
142
142
|
llms/ui/App.mjs,sha256=zU-GtbcSMREizjUS9nWiMK6m_oT14MNJ-p_RCz2RVqA,7445
|
|
143
|
-
llms/ui/ai.mjs,sha256=
|
|
144
|
-
llms/ui/app.css,sha256=
|
|
145
|
-
llms/ui/ctx.mjs,sha256=
|
|
143
|
+
llms/ui/ai.mjs,sha256=W3l-b0tNhGq6B-0joEUgPMMylp_N6rNohLb2j7OAff0,6540
|
|
144
|
+
llms/ui/app.css,sha256=L0ZiQ0yN3fyIluZz-t94Qe4yac6u4m5TqBOekb3HK9c,176286
|
|
145
|
+
llms/ui/ctx.mjs,sha256=eFTkWzVBKRU5YDFFVUq3ZDd6eMPa4VRQpE3q0O5Sfgg,12337
|
|
146
146
|
llms/ui/fav.svg,sha256=_R6MFeXl6wBFT0lqcUxYQIDWgm246YH_3hSTW0oO8qw,734
|
|
147
147
|
llms/ui/index.mjs,sha256=o-J2GVYYGFtfV4Pzlj-kjK4UrVP6ovTre5jD44gAiIc,4316
|
|
148
148
|
llms/ui/markdown.mjs,sha256=ZeGXxX4_UEUCVkLZzmwXlqWBfReSFzBivdxNu8uSgFk,6648
|
|
149
|
-
llms/ui/tailwind.input.css,sha256=
|
|
149
|
+
llms/ui/tailwind.input.css,sha256=ZneSjo_uNcvqMT8cAU837yUZoLZ_sWfgr5Gto7TNJPc,15786
|
|
150
150
|
llms/ui/typography.css,sha256=6o7pbMIamRVlm2GfzSStpcOG4T5eFCK_WcQ3RIHKAsU,19587
|
|
151
151
|
llms/ui/utils.mjs,sha256=Vv2YsKaOJCNYrxv19q-gU1wSuuMhEcTcm6M7_g9Ff0Y,6664
|
|
152
152
|
llms/ui/lib/chart.js,sha256=dx8FdDX0Rv6OZtZjr9FQh5h-twFsKjfnb-FvFlQ--cU,196176
|
|
@@ -163,12 +163,12 @@ llms/ui/lib/vue.mjs,sha256=75FuLhUTPk19sncwNIrm0BGEL0_Qw298-_v01fPWYoI,542872
|
|
|
163
163
|
llms/ui/modules/icons.mjs,sha256=LGcH0ys0QLS2ZKCO42qHpwPYbBV_EssoWLezU4XZEzU,27751
|
|
164
164
|
llms/ui/modules/layout.mjs,sha256=8pAxs8bedQI3b3eRA9nrfpLZznLmrpp4BZvigYAQjpQ,12572
|
|
165
165
|
llms/ui/modules/model-selector.mjs,sha256=6U4rAZ7vmQELFRQGWk4YEtq02v3lyHdMq6yUOp-ArXg,43184
|
|
166
|
-
llms/ui/modules/chat/ChatBody.mjs,sha256=
|
|
166
|
+
llms/ui/modules/chat/ChatBody.mjs,sha256=YbpIEl6OeBi1RqCPkW5UFbUk6qNi5B_GfRUK5Pmfms8,42875
|
|
167
167
|
llms/ui/modules/chat/SettingsDialog.mjs,sha256=HMBJTwrapKrRIAstIIqp0QlJL5O-ho4hzgvfagPfsX8,19930
|
|
168
|
-
llms/ui/modules/chat/index.mjs,sha256=
|
|
169
|
-
llms_py-3.0.
|
|
170
|
-
llms_py-3.0.
|
|
171
|
-
llms_py-3.0.
|
|
172
|
-
llms_py-3.0.
|
|
173
|
-
llms_py-3.0.
|
|
174
|
-
llms_py-3.0.
|
|
168
|
+
llms/ui/modules/chat/index.mjs,sha256=NA_9R7JEqHKZRnYGqte_J7qoDbG3RY8969TTNXqZa1k,37011
|
|
169
|
+
llms_py-3.0.3.dist-info/licenses/LICENSE,sha256=bus9cuAOWeYqBk2OuhSABVV1P4z7hgrEFISpyda_H5w,1532
|
|
170
|
+
llms_py-3.0.3.dist-info/METADATA,sha256=NUThc-W_gsTuY1CbwnHiAuIS2MuqgksPmDFmMBhq2MM,2191
|
|
171
|
+
llms_py-3.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
172
|
+
llms_py-3.0.3.dist-info/entry_points.txt,sha256=WswyE7PfnkZMIxboC-MS6flBD6wm-CYU7JSUnMhqMfM,40
|
|
173
|
+
llms_py-3.0.3.dist-info/top_level.txt,sha256=gC7hk9BKSeog8gyg-EM_g2gxm1mKHwFRfK-10BxOsa4,5
|
|
174
|
+
llms_py-3.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|