turbollm 1.6.3 → 1.6.5
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.
- package/README.md +16 -9
- package/dist/cli.js +255 -32
- package/dist/webdist/assets/{AgentsScreen-DSJoisHr.js → AgentsScreen-D0J8ZEtf.js} +1 -1
- package/dist/webdist/assets/ChatScreen-7bIHB35Z.js +85 -0
- package/dist/webdist/assets/CustomizeScreen-z5568762.js +4 -0
- package/dist/webdist/assets/{DeveloperScreen-fnTDP0tf.js → DeveloperScreen-C5opqGrE.js} +1 -1
- package/dist/webdist/assets/{EnginesScreen-rlz0E1mk.js → EnginesScreen-NqAMc4rS.js} +6 -6
- package/dist/webdist/assets/ModelDetailDialog-CtjNu2Qc.js +29 -0
- package/dist/webdist/assets/ModelDirs-C-riHLWK.js +1 -0
- package/dist/webdist/assets/{ModelsScreen-BQF-ZHhj.js → ModelsScreen-DnASH2bF.js} +2 -2
- package/dist/webdist/assets/SettingsScreen-v-LAgJFH.js +1 -0
- package/dist/webdist/assets/{arc-DJj3Lyu5.js → arc-OPogq9vx.js} +1 -1
- package/dist/webdist/assets/{architectureDiagram-3BPJPVTR-BcgkjjsC.js → architectureDiagram-3BPJPVTR-DI3zlQZC.js} +1 -1
- package/dist/webdist/assets/{arrow-left-C8---sig.js → arrow-left-DWtpbvUs.js} +1 -1
- package/dist/webdist/assets/{blockDiagram-GPEHLZMM-C5iJ2c-N.js → blockDiagram-GPEHLZMM-bbqbctKR.js} +1 -1
- package/dist/webdist/assets/{c4Diagram-AAUBKEIU-BnsiE4U5.js → c4Diagram-AAUBKEIU-CGj5BFj-.js} +1 -1
- package/dist/webdist/assets/channel-aq4Rip4D.js +1 -0
- package/dist/webdist/assets/check-CMGnr_Ph.js +1 -0
- package/dist/webdist/assets/chevron-right-CIKvJUaG.js +1 -0
- package/dist/webdist/assets/{chunk-2J33WTMH-D_Hm4Lii.js → chunk-2J33WTMH-CZKSYaHn.js} +1 -1
- package/dist/webdist/assets/{chunk-4BX2VUAB-Cooqn_zH.js → chunk-4BX2VUAB-D3SQCGwz.js} +1 -1
- package/dist/webdist/assets/{chunk-55IACEB6-C2XDZnC0.js → chunk-55IACEB6-DDmZTA0L.js} +1 -1
- package/dist/webdist/assets/{chunk-727SXJPM-CCEeI48H.js → chunk-727SXJPM-DRutYNQM.js} +1 -1
- package/dist/webdist/assets/{chunk-AQP2D5EJ-7FdU3GXV.js → chunk-AQP2D5EJ-B_u0ow9I.js} +1 -1
- package/dist/webdist/assets/{chunk-FMBD7UC4-Dpyt8woB.js → chunk-FMBD7UC4-Cu73-Koh.js} +1 -1
- package/dist/webdist/assets/{chunk-ND2GUHAM-ildY-Wx8.js → chunk-ND2GUHAM-Z71l0U4D.js} +1 -1
- package/dist/webdist/assets/{chunk-QZHKN3VN-DfdDD-h6.js → chunk-QZHKN3VN-D2PeraEx.js} +1 -1
- package/dist/webdist/assets/circle-check-DXOwCXEz.js +1 -0
- package/dist/webdist/assets/circle-x-caNFmCHm.js +1 -0
- package/dist/webdist/assets/classDiagram-4FO5ZUOK-C5KHEB9t.js +1 -0
- package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-C5KHEB9t.js +1 -0
- package/dist/webdist/assets/common-CQQGhjlM.js +1 -0
- package/dist/webdist/assets/{copy-button-Sp-U8ULJ.js → copy-button-Dt4Afcgb.js} +1 -1
- package/dist/webdist/assets/{cose-bilkent-S5V4N54A-DggDgWVR.js → cose-bilkent-S5V4N54A-D3rxBv0J.js} +1 -1
- package/dist/webdist/assets/{dagre-BM42HDAG-C3owUH7E.js → dagre-BM42HDAG--G2XvGhI.js} +1 -1
- package/dist/webdist/assets/{diagram-2AECGRRQ-Ch2E5js7.js → diagram-2AECGRRQ-Te1rwFql.js} +1 -1
- package/dist/webdist/assets/{diagram-5GNKFQAL-DLeGX2xz.js → diagram-5GNKFQAL-B5vALx3-.js} +1 -1
- package/dist/webdist/assets/{diagram-KO2AKTUF-DpxkKgZH.js → diagram-KO2AKTUF-CBxjZi0b.js} +1 -1
- package/dist/webdist/assets/{diagram-LMA3HP47-ia4luRgC.js → diagram-LMA3HP47-BGiirNkw.js} +1 -1
- package/dist/webdist/assets/{diagram-OG6HWLK6-BMmkGNMK.js → diagram-OG6HWLK6-BVgMIJkt.js} +1 -1
- package/dist/webdist/assets/dialog-coezioYj.js +1 -0
- package/dist/webdist/assets/dropdown-menu-Ce842M-W.js +1 -0
- package/dist/webdist/assets/{erDiagram-TEJ5UH35-DkKBBG05.js → erDiagram-TEJ5UH35-DowgN2O9.js} +1 -1
- package/dist/webdist/assets/{flowDiagram-I6XJVG4X-kUtoUBI6.js → flowDiagram-I6XJVG4X-BD0xs8q_.js} +1 -1
- package/dist/webdist/assets/{ganttDiagram-6RSMTGT7-DQReYR3Z.js → ganttDiagram-6RSMTGT7-BHHhhU_7.js} +1 -1
- package/dist/webdist/assets/{gitGraphDiagram-PVQCEYII-B2kU7JBk.js → gitGraphDiagram-PVQCEYII-Brchy1Vz.js} +1 -1
- package/dist/webdist/assets/index-CVyS_zSS.css +1 -0
- package/dist/webdist/assets/{index-CjGc01NF.js → index-oSSYX1Lk.js} +4 -4
- package/dist/webdist/assets/{infoDiagram-5YYISTIA-k1S105HR.js → infoDiagram-5YYISTIA-D4HdfOcs.js} +1 -1
- package/dist/webdist/assets/{ishikawaDiagram-YF4QCWOH-BgPQRD_p.js → ishikawaDiagram-YF4QCWOH-wmMjgjbp.js} +1 -1
- package/dist/webdist/assets/{journeyDiagram-JHISSGLW-_aNOepbu.js → journeyDiagram-JHISSGLW-CaF09KKI.js} +1 -1
- package/dist/webdist/assets/{kanban-definition-UN3LZRKU-ooXgg2sY.js → kanban-definition-UN3LZRKU-BuBI9QG2.js} +1 -1
- package/dist/webdist/assets/{linear-B6ijN2hK.js → linear-DHVGKuTX.js} +1 -1
- package/dist/webdist/assets/{mermaid.core-C39fo6yP.js → mermaid.core-CrU-xSQY.js} +4 -4
- package/dist/webdist/assets/{mindmap-definition-RKZ34NQL-Ci1PW-rt.js → mindmap-definition-RKZ34NQL-cBLMFEJ9.js} +1 -1
- package/dist/webdist/assets/pdf.worker.min-DEtVeC4l.mjs +29 -0
- package/dist/webdist/assets/pencil-BBioAa4x.js +1 -0
- package/dist/webdist/assets/{personas-CIW1sEuT.js → personas-DaUrwq7m.js} +9 -5
- package/dist/webdist/assets/{pieDiagram-4H26LBE5-SVL4rTNg.js → pieDiagram-4H26LBE5-BJnfOXlL.js} +1 -1
- package/dist/webdist/assets/plus-C8MMVqVA.js +1 -0
- package/dist/webdist/assets/{quadrantDiagram-W4KKPZXB-sTVWSdlQ.js → quadrantDiagram-W4KKPZXB-Cm-qlQtE.js} +1 -1
- package/dist/webdist/assets/{radix-9WP-OkMY.js → radix-BY-fjzwm.js} +8 -8
- package/dist/webdist/assets/refresh-cw-DTqN58WZ.js +1 -0
- package/dist/webdist/assets/{requirementDiagram-4Y6WPE33-3R4K8jZs.js → requirementDiagram-4Y6WPE33-DHY3RUt1.js} +1 -1
- package/dist/webdist/assets/{sankeyDiagram-5OEKKPKP-BMozn5XV.js → sankeyDiagram-5OEKKPKP-DQkDAi3z.js} +1 -1
- package/dist/webdist/assets/save-PN9--sf1.js +1 -0
- package/dist/webdist/assets/{sequenceDiagram-3UESZ5HK-B0bhq6qo.js → sequenceDiagram-3UESZ5HK-OJf-ZzIm.js} +1 -1
- package/dist/webdist/assets/skeleton-Dpg8YusE.js +1 -0
- package/dist/webdist/assets/{stateDiagram-AJRCARHV-CXaf-H6k.js → stateDiagram-AJRCARHV-QupYS2FV.js} +1 -1
- package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-DPP0kwbC.js +1 -0
- package/dist/webdist/assets/{timeline-definition-PNZ67QCA-BUO22RTP.js → timeline-definition-PNZ67QCA-B7kdKKeO.js} +1 -1
- package/dist/webdist/assets/trash-2-C5mszq8p.js +1 -0
- package/dist/webdist/assets/{vennDiagram-CIIHVFJN-B6l8WK-i.js → vennDiagram-CIIHVFJN-DCckmGsZ.js} +1 -1
- package/dist/webdist/assets/{wardley-L42UT6IY-nBQZIDHO.js → wardley-L42UT6IY-C66hVI6t.js} +1 -1
- package/dist/webdist/assets/{wardleyDiagram-YWT4CUSO-Bl5nA5Sj.js → wardleyDiagram-YWT4CUSO-0WD2JYDy.js} +1 -1
- package/dist/webdist/assets/{wrench-2q903Ji-.js → wrench-BotIMKYs.js} +1 -1
- package/dist/webdist/assets/{xychartDiagram-2RQKCTM6-BfRB7FkA.js → xychartDiagram-2RQKCTM6-Dc0w49oN.js} +1 -1
- package/dist/webdist/index.html +3 -3
- package/package.json +1 -1
- package/dist/webdist/assets/ChatScreen-CPTQTPDq.js +0 -29
- package/dist/webdist/assets/CustomizeScreen-CveMZwK6.js +0 -4
- package/dist/webdist/assets/ModelDetailDialog-b1xxFqb8.js +0 -29
- package/dist/webdist/assets/ModelDirs-DPJoCOcI.js +0 -1
- package/dist/webdist/assets/SettingsScreen-Z4AoirUg.js +0 -1
- package/dist/webdist/assets/alert-dialog-DzOwQljM.js +0 -1
- package/dist/webdist/assets/channel-DidzH_JR.js +0 -1
- package/dist/webdist/assets/check-CRM4LOGX.js +0 -1
- package/dist/webdist/assets/chevron-right-C18nBpLx.js +0 -1
- package/dist/webdist/assets/circle-check-C9nr7e7f.js +0 -1
- package/dist/webdist/assets/circle-x-CrulSiVz.js +0 -1
- package/dist/webdist/assets/classDiagram-4FO5ZUOK-DrgybMsY.js +0 -1
- package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-DrgybMsY.js +0 -1
- package/dist/webdist/assets/common-B6XHnwTG.js +0 -1
- package/dist/webdist/assets/dialog-DLncpxeJ.js +0 -1
- package/dist/webdist/assets/index-5NYGYU_t.css +0 -1
- package/dist/webdist/assets/pencil-CvIU0pkN.js +0 -1
- package/dist/webdist/assets/plus-3YM8ko_h.js +0 -1
- package/dist/webdist/assets/refresh-cw-BrUDSVRs.js +0 -1
- package/dist/webdist/assets/save-CeI7y6CK.js +0 -1
- package/dist/webdist/assets/skeleton-Dm2BPYbG.js +0 -1
- package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-BYFRKnq-.js +0 -1
- package/dist/webdist/assets/trash-2-D1fUAajM.js +0 -1
package/README.md
CHANGED
|
@@ -148,7 +148,8 @@ the detail:
|
|
|
148
148
|
picker so you can choose which one to download.
|
|
149
149
|
- **Quant recommendation per GPU** and a **VRAM-fit verdict** so you pick a quant that
|
|
150
150
|
actually fits before you commit.
|
|
151
|
-
- **Primary download folder**, real-time **measured t/s per model**,
|
|
151
|
+
- **Primary download folder**, real-time **measured t/s per model**, **delete-from-disk**, and
|
|
152
|
+
**pin your favourites to the top of the list**.
|
|
152
153
|
|
|
153
154
|
</details>
|
|
154
155
|
|
|
@@ -158,20 +159,24 @@ the detail:
|
|
|
158
159
|
<br/>
|
|
159
160
|
|
|
160
161
|
- **Auto-benchmark on load** derives fast defaults for your exact GPU.
|
|
161
|
-
- **Recommended sampling from
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
- **Recommended sampling from Hugging Face** — auto-tune checks a repo's structured params /
|
|
163
|
+
`generation_config.json` sidecar first when the quantizer publishes one (exact values, no
|
|
164
|
+
guessing), then falls back to reading the model's card (and the original model behind a
|
|
165
|
+
requant) and prefills the author's recommended `temperature / top_k / top_p / min_p`. No
|
|
166
|
+
recommendation → your sampling is left untouched.
|
|
164
167
|
- **Real measured tokens/sec** in the model list — **live** while generating, **last-session**
|
|
165
168
|
when idle (never a synthetic estimate).
|
|
166
169
|
- **Full load-parameter UI**, a superset of what other tools expose: context length, GPU offload
|
|
167
170
|
(`-ngl`), **MoE CPU-offload (`--n-cpu-moe`)**, parallel slots, **KV-cache quant type** (incl.
|
|
168
171
|
low-bit on supporting forks), CPU threads, flash attention, and **speculative decoding (NextN /
|
|
169
|
-
MTP / draft)**.
|
|
172
|
+
MTP / draft, with a configurable draft min/max window)**.
|
|
170
173
|
- **Fast by default:** flash attention on, NextN self-speculative decoding on for models that
|
|
171
174
|
carry a draft head, threads auto — safely gated to what your engine actually accepts.
|
|
172
175
|
- **Multi-GPU, per model** — split a model across cards (layer/row split + main-GPU pick on
|
|
173
176
|
llama.cpp, tensor-parallel on vLLM). Defaults are no-ops, so single-GPU rigs are untouched.
|
|
174
|
-
- **Saved per-model profiles** — tune once
|
|
177
|
+
- **Saved per-model profiles, per engine** — tune once per (model, engine) pair, so
|
|
178
|
+
switching engines (or between two installs of the same engine, e.g. a fork) never
|
|
179
|
+
overwrites another engine's tuning for the same model.
|
|
175
180
|
|
|
176
181
|
</details>
|
|
177
182
|
|
|
@@ -193,11 +198,13 @@ the detail:
|
|
|
193
198
|
Creative · Research) per conversation, no prompt-wrangling required. The **Designer** persona
|
|
194
199
|
produces polished, self-contained, previewable designs by default.
|
|
195
200
|
- **Edit, regenerate, delete, copy** any message; **persistent, searchable conversations**
|
|
196
|
-
with rename, delete, and **auto-generated titles
|
|
201
|
+
with rename, delete, and **auto-generated titles**, organized into **drag-resizable,
|
|
202
|
+
collapsible folders** you create/rename/move conversations into.
|
|
197
203
|
- **Per-chat system prompt** and **per-chat sampling** overrides — temperature, top-p/k, min-p,
|
|
198
204
|
repeat/presence/frequency penalties, and **stop strings**.
|
|
199
|
-
- **Image input** for vision models, and
|
|
200
|
-
|
|
205
|
+
- **Image input** for vision models, **PDF and code/text attachments** (real extracted text,
|
|
206
|
+
not raw bytes), and **TurboLLM Expert** — a built-in assistant that knows the app and your
|
|
207
|
+
hardware for onboarding and troubleshooting without leaving the UI.
|
|
201
208
|
- **Agentic tools** — built-in `web_search` (Tavily), `fetch_url`, and sandboxed `run_code`, plus
|
|
202
209
|
an **MCP marketplace** in Customize: one-click connect for hosted MCPs (GitHub, Linear, Stripe,
|
|
203
210
|
Atlassian, Neon, Supabase, Cloudflare, Zapier, Apify, Mixpanel) and open-source local MCPs
|
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import { randomUUID } from "crypto";
|
|
|
12
12
|
import { cpSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
13
13
|
import { homedir } from "os";
|
|
14
14
|
import { dirname, join } from "path";
|
|
15
|
-
var SCHEMA_VERSION =
|
|
15
|
+
var SCHEMA_VERSION = 3;
|
|
16
16
|
var ValueError = class extends Error {
|
|
17
17
|
constructor(field, msg2) {
|
|
18
18
|
super(`${field}: ${msg2}`);
|
|
@@ -176,7 +176,7 @@ function migrate(raw, _from) {
|
|
|
176
176
|
}
|
|
177
177
|
cfg2.version = SCHEMA_VERSION;
|
|
178
178
|
for (const [k, v] of Object.entries(raw)) {
|
|
179
|
-
if (
|
|
179
|
+
if (k !== "engine" && k !== "host" && k !== "port" && k !== "version") {
|
|
180
180
|
;
|
|
181
181
|
cfg2[k] = v;
|
|
182
182
|
}
|
|
@@ -217,6 +217,11 @@ function normalize(c) {
|
|
|
217
217
|
c.modelDirs ??= [];
|
|
218
218
|
c.primaryModelDir ??= "";
|
|
219
219
|
c.modelProfiles ??= {};
|
|
220
|
+
for (const [modelKey, val] of Object.entries(c.modelProfiles)) {
|
|
221
|
+
if (val && typeof val === "object" && typeof val.ctx === "number") {
|
|
222
|
+
c.modelProfiles[modelKey] = { "*": { profile: val, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
220
225
|
c.benchResults ??= {};
|
|
221
226
|
c.autoLoadOnStart ??= false;
|
|
222
227
|
c.featuredOverrideUrl ??= "";
|
|
@@ -299,6 +304,27 @@ function isAbsolutePath(p) {
|
|
|
299
304
|
function findEngine(engines, id) {
|
|
300
305
|
return engines.find((e) => e.id === id);
|
|
301
306
|
}
|
|
307
|
+
function getModelProfile(cfg2, modelKey, engineId) {
|
|
308
|
+
const byEngine = cfg2.modelProfiles[modelKey];
|
|
309
|
+
if (!byEngine) return void 0;
|
|
310
|
+
const exact = byEngine[engineId];
|
|
311
|
+
if (exact) return exact.profile;
|
|
312
|
+
let mostRecent;
|
|
313
|
+
for (const entry of Object.values(byEngine)) {
|
|
314
|
+
if (!mostRecent || entry.updatedAt >= mostRecent.updatedAt) mostRecent = entry;
|
|
315
|
+
}
|
|
316
|
+
return mostRecent?.profile;
|
|
317
|
+
}
|
|
318
|
+
function setModelProfile(cfg2, modelKey, engineId, profile) {
|
|
319
|
+
;
|
|
320
|
+
(cfg2.modelProfiles[modelKey] ??= {})[engineId] = { profile, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
321
|
+
}
|
|
322
|
+
function deleteModelProfile(cfg2, modelKey, engineId) {
|
|
323
|
+
const byEngine = cfg2.modelProfiles[modelKey];
|
|
324
|
+
if (!byEngine) return;
|
|
325
|
+
delete byEngine[engineId];
|
|
326
|
+
if (Object.keys(byEngine).length === 0) delete cfg2.modelProfiles[modelKey];
|
|
327
|
+
}
|
|
302
328
|
function clampMaxTokens(requested, limit) {
|
|
303
329
|
if (!Number.isFinite(limit) || limit <= 0) return requested ?? void 0;
|
|
304
330
|
if (requested == null || !Number.isFinite(requested) || requested <= 0) return limit;
|
|
@@ -2649,7 +2675,10 @@ function detectGpus() {
|
|
|
2649
2675
|
const [name, mb] = line.split(",");
|
|
2650
2676
|
return { name: (name ?? "").trim(), vramMb: parseInt((mb ?? "").trim(), 10) || 0, vendor: "nvidia" };
|
|
2651
2677
|
}).filter((g) => g.vramMb > 0);
|
|
2652
|
-
if (gpus.length)
|
|
2678
|
+
if (gpus.length) {
|
|
2679
|
+
console.log(`[sysinfo] nvidia-smi enumerated ${gpus.length} GPU(s): ${gpus.map((g) => `${g.name}=${g.vramMb}MB`).join(", ")}`);
|
|
2680
|
+
return gpus;
|
|
2681
|
+
}
|
|
2653
2682
|
} catch {
|
|
2654
2683
|
}
|
|
2655
2684
|
if (process.platform === "darwin") {
|
|
@@ -2670,6 +2699,11 @@ function detectGpus() {
|
|
|
2670
2699
|
return [];
|
|
2671
2700
|
}
|
|
2672
2701
|
function enumWindowsGpus() {
|
|
2702
|
+
try {
|
|
2703
|
+
const amd = rocmSmiGpus();
|
|
2704
|
+
if (amd.length) return amd;
|
|
2705
|
+
} catch {
|
|
2706
|
+
}
|
|
2673
2707
|
const ps = 'Get-CimInstance Win32_VideoController | ForEach-Object { "$($_.Name)|$($_.AdapterRAM)" }';
|
|
2674
2708
|
const out = execFileSync2("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], {
|
|
2675
2709
|
timeout: 8e3,
|
|
@@ -2682,6 +2716,37 @@ function enumWindowsGpus() {
|
|
|
2682
2716
|
return { name: nm, vramMb: bytes > 0 ? Math.round(bytes / 1e6) : 0, vendor: classifyVendor(nm) };
|
|
2683
2717
|
}).filter((g) => g.name && g.vendor !== "unknown");
|
|
2684
2718
|
}
|
|
2719
|
+
function rocmSmiGpus() {
|
|
2720
|
+
const memJson = execFileSync2("rocm-smi", ["--showmeminfo", "vram", "--json"], {
|
|
2721
|
+
timeout: 8e3,
|
|
2722
|
+
windowsHide: true
|
|
2723
|
+
}).toString();
|
|
2724
|
+
let nameJson = "";
|
|
2725
|
+
try {
|
|
2726
|
+
nameJson = execFileSync2("rocm-smi", ["--showproductname", "--json"], {
|
|
2727
|
+
timeout: 8e3,
|
|
2728
|
+
windowsHide: true
|
|
2729
|
+
}).toString();
|
|
2730
|
+
} catch {
|
|
2731
|
+
}
|
|
2732
|
+
return parseRocmSmi(memJson, nameJson);
|
|
2733
|
+
}
|
|
2734
|
+
function parseRocmSmi(memJson, nameJson = "") {
|
|
2735
|
+
const mem = JSON.parse(memJson);
|
|
2736
|
+
let names = {};
|
|
2737
|
+
if (nameJson.trim()) names = JSON.parse(nameJson);
|
|
2738
|
+
const gpus = [];
|
|
2739
|
+
for (const [card, fields] of Object.entries(mem)) {
|
|
2740
|
+
const totalKey = Object.keys(fields).find((k) => /VRAM Total Memory/i.test(k));
|
|
2741
|
+
const bytes = totalKey ? parseInt(String(fields[totalKey]).trim(), 10) || 0 : 0;
|
|
2742
|
+
if (bytes <= 0) continue;
|
|
2743
|
+
const nameFields = names[card] ?? {};
|
|
2744
|
+
const nameKey = Object.keys(nameFields).find((k) => /(Card Series|Card Model|Product Name|Device Name)/i.test(k));
|
|
2745
|
+
const name = (nameKey ? String(nameFields[nameKey]).trim() : "") || "AMD Radeon GPU";
|
|
2746
|
+
gpus.push({ name, vramMb: Math.round(bytes / 1e6), vendor: "amd" });
|
|
2747
|
+
}
|
|
2748
|
+
return gpus;
|
|
2749
|
+
}
|
|
2685
2750
|
function enumLinuxGpus() {
|
|
2686
2751
|
const out = execFileSync2("sh", ["-c", "lspci -mm 2>/dev/null | grep -iE 'VGA|3D|Display'"], {
|
|
2687
2752
|
timeout: 8e3
|
|
@@ -3303,8 +3368,10 @@ var Scanner = class {
|
|
|
3303
3368
|
const m = basename(f.path).match(SPLIT_RE);
|
|
3304
3369
|
if (m) {
|
|
3305
3370
|
const gkey = `${m[1]}|${m[3]}`;
|
|
3306
|
-
const g = splits.get(gkey) ?? { shards:
|
|
3307
|
-
|
|
3371
|
+
const g = splits.get(gkey) ?? { shards: /* @__PURE__ */ new Map(), total: Number(m[3]) };
|
|
3372
|
+
const idx = Number(m[2]);
|
|
3373
|
+
const prev = g.shards.get(idx);
|
|
3374
|
+
if (!prev || f.path.localeCompare(prev.path) < 0) g.shards.set(idx, f);
|
|
3308
3375
|
splits.set(gkey, g);
|
|
3309
3376
|
} else {
|
|
3310
3377
|
singles.push(f);
|
|
@@ -3315,10 +3382,17 @@ var Scanner = class {
|
|
|
3315
3382
|
await tick();
|
|
3316
3383
|
}
|
|
3317
3384
|
for (const { shards, total } of splits.values()) {
|
|
3318
|
-
shards.sort((a, b) => a.path.localeCompare(b.path));
|
|
3319
|
-
const first =
|
|
3320
|
-
const totalSize =
|
|
3321
|
-
|
|
3385
|
+
const present = [...shards.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
3386
|
+
const first = present[0];
|
|
3387
|
+
const totalSize = present.reduce((s, x) => s + x.size, 0);
|
|
3388
|
+
let incomplete = shards.size !== total;
|
|
3389
|
+
if (!incomplete) {
|
|
3390
|
+
for (let i = 1; i <= total; i++)
|
|
3391
|
+
if (!shards.has(i)) {
|
|
3392
|
+
incomplete = true;
|
|
3393
|
+
break;
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3322
3396
|
entries.push(await this.entryFor(first.path, totalSize, first.mtime, dir, mmprojPath, incomplete));
|
|
3323
3397
|
await tick();
|
|
3324
3398
|
}
|
|
@@ -3795,16 +3869,24 @@ function profileToArgs(p, m, caps, cores = 0) {
|
|
|
3795
3869
|
if (p.chatTemplateFile && has("--chat-template-file")) a.push("--chat-template-file", p.chatTemplateFile);
|
|
3796
3870
|
const specType = has("--spec-type");
|
|
3797
3871
|
const specAccepts = (v) => caps.flags.length === 0 || caps.flags.includes(`spec-type:${v}`);
|
|
3872
|
+
let specActive = false;
|
|
3798
3873
|
if (p.speculative === "mtp" && p.mtpHeadPath && has("--mtp-head")) {
|
|
3799
3874
|
if (specType) a.push("--spec-type", "mtp");
|
|
3800
3875
|
a.push("--mtp-head", p.mtpHeadPath);
|
|
3876
|
+
specActive = true;
|
|
3801
3877
|
} else if (p.speculative === "nextn" && specType && has("--model-draft")) {
|
|
3802
3878
|
const nextnVal = ["nextn", "draft-mtp"].find((v) => specAccepts(v));
|
|
3803
|
-
if (nextnVal)
|
|
3879
|
+
if (nextnVal) {
|
|
3880
|
+
a.push("--spec-type", nextnVal, "--model-draft", m.path);
|
|
3881
|
+
specActive = true;
|
|
3882
|
+
}
|
|
3804
3883
|
} else if (p.speculative === "draft" && p.draftModelPath && has("--model-draft")) {
|
|
3805
3884
|
if (specType) a.push("--spec-type", "draft");
|
|
3806
|
-
a.push("--model-draft", p.draftModelPath
|
|
3885
|
+
a.push("--model-draft", p.draftModelPath);
|
|
3886
|
+
specActive = true;
|
|
3807
3887
|
}
|
|
3888
|
+
if (specActive && has("--draft-max")) a.push("--draft-max", String(p.draftMax ?? 16));
|
|
3889
|
+
if (specActive && has("--draft-min")) a.push("--draft-min", String(p.draftMin ?? 1));
|
|
3808
3890
|
if (p.sampling.temp !== 0.8 && has("--temp")) a.push("--temp", String(p.sampling.temp));
|
|
3809
3891
|
if (p.sampling.topP !== 0.95 && has("--top-p")) a.push("--top-p", String(p.sampling.topP));
|
|
3810
3892
|
if (p.sampling.topK !== 40 && has("--top-k")) a.push("--top-k", String(p.sampling.topK));
|
|
@@ -3851,7 +3933,10 @@ function safeJson(s) {
|
|
|
3851
3933
|
}
|
|
3852
3934
|
}
|
|
3853
3935
|
function rowToConv(r) {
|
|
3854
|
-
return { id: r.id, title: r.title, systemPrompt: r.system_prompt, modelKey: r.model_key, sampling: safeJson(r.sampling), expertMode: r.expert_mode === 1, toolPolicy: r.tool_policy ?? void 0, kind: r.kind === "agent" ? "agent" : "chat", createdAt: r.created_at, updatedAt: r.updated_at };
|
|
3936
|
+
return { id: r.id, title: r.title, systemPrompt: r.system_prompt, modelKey: r.model_key, sampling: safeJson(r.sampling), expertMode: r.expert_mode === 1, toolPolicy: r.tool_policy ?? void 0, kind: r.kind === "agent" ? "agent" : "chat", folderId: r.folder_id ?? null, createdAt: r.created_at, updatedAt: r.updated_at };
|
|
3937
|
+
}
|
|
3938
|
+
function rowToFolder(r) {
|
|
3939
|
+
return { id: r.id, name: r.name, sortOrder: r.sort_order, createdAt: r.created_at, updatedAt: r.updated_at };
|
|
3855
3940
|
}
|
|
3856
3941
|
function rowToAgentRun(r) {
|
|
3857
3942
|
return {
|
|
@@ -3960,6 +4045,18 @@ var ConversationStore = class {
|
|
|
3960
4045
|
PRAGMA user_version = 9;
|
|
3961
4046
|
`);
|
|
3962
4047
|
}
|
|
4048
|
+
if (v < 10) {
|
|
4049
|
+
this.db.exec(`
|
|
4050
|
+
CREATE TABLE IF NOT EXISTS folders (
|
|
4051
|
+
id TEXT PRIMARY KEY, name TEXT NOT NULL,
|
|
4052
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
4053
|
+
created_at TEXT NOT NULL, updated_at TEXT NOT NULL
|
|
4054
|
+
);
|
|
4055
|
+
ALTER TABLE conversations ADD COLUMN folder_id TEXT;
|
|
4056
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_folder ON conversations(folder_id);
|
|
4057
|
+
PRAGMA user_version = 10;
|
|
4058
|
+
`);
|
|
4059
|
+
}
|
|
3963
4060
|
}
|
|
3964
4061
|
listConversations(q, kind = "all") {
|
|
3965
4062
|
if (q) {
|
|
@@ -3981,7 +4078,7 @@ var ConversationStore = class {
|
|
|
3981
4078
|
createConversation(partial) {
|
|
3982
4079
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3983
4080
|
const id = randomUUID3();
|
|
3984
|
-
this.db.prepare(`INSERT INTO conversations (id,title,system_prompt,model_key,sampling,expert_mode,tool_policy,kind,created_at,updated_at) VALUES ($id,$title,$sp,$mk,$samp,$expert,$tp,$kind,$now,$now)`).run({ $id: id, $title: partial?.title ?? "New chat", $sp: partial?.systemPrompt ?? "", $mk: partial?.modelKey ?? "", $samp: JSON.stringify(partial?.sampling ?? {}), $expert: partial?.expertMode ? 1 : 0, $tp: partial?.toolPolicy ?? null, $kind: partial?.kind ?? "chat", $now: now });
|
|
4081
|
+
this.db.prepare(`INSERT INTO conversations (id,title,system_prompt,model_key,sampling,expert_mode,tool_policy,kind,folder_id,created_at,updated_at) VALUES ($id,$title,$sp,$mk,$samp,$expert,$tp,$kind,$fid,$now,$now)`).run({ $id: id, $title: partial?.title ?? "New chat", $sp: partial?.systemPrompt ?? "", $mk: partial?.modelKey ?? "", $samp: JSON.stringify(partial?.sampling ?? {}), $expert: partial?.expertMode ? 1 : 0, $tp: partial?.toolPolicy ?? null, $kind: partial?.kind ?? "chat", $fid: partial?.folderId ?? null, $now: now });
|
|
3985
4082
|
return this.getConversation(id);
|
|
3986
4083
|
}
|
|
3987
4084
|
getConversation(id, withMessages = false) {
|
|
@@ -4092,6 +4189,41 @@ var ConversationStore = class {
|
|
|
4092
4189
|
const row = this.db.prepare(`SELECT * FROM messages WHERE conv_id = $id ORDER BY seq DESC LIMIT 1`).get({ $id: convId });
|
|
4093
4190
|
return row ? rowToMsg(row) : null;
|
|
4094
4191
|
}
|
|
4192
|
+
// ── Folder methods (v10 migration) ────────────────────────────────────────
|
|
4193
|
+
/** All folders in insertion order (sort_order asc, then created_at). */
|
|
4194
|
+
listFolders() {
|
|
4195
|
+
return this.db.prepare(`SELECT * FROM folders ORDER BY sort_order ASC, created_at ASC`).all().map(rowToFolder);
|
|
4196
|
+
}
|
|
4197
|
+
getFolder(id) {
|
|
4198
|
+
const row = this.db.prepare(`SELECT * FROM folders WHERE id = $id`).get({ $id: id });
|
|
4199
|
+
return row ? rowToFolder(row) : null;
|
|
4200
|
+
}
|
|
4201
|
+
createFolder(name) {
|
|
4202
|
+
const id = randomUUID3();
|
|
4203
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4204
|
+
this.db.prepare(`INSERT INTO folders (id,name,sort_order,created_at,updated_at) VALUES ($id,$name,0,$now,$now)`).run({ $id: id, $name: name, $now: now });
|
|
4205
|
+
return this.getFolder(id);
|
|
4206
|
+
}
|
|
4207
|
+
renameFolder(id, name) {
|
|
4208
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4209
|
+
return this.db.prepare(`UPDATE folders SET name = $name, updated_at = $now WHERE id = $id`).run({ $id: id, $name: name, $now: now }).changes > 0;
|
|
4210
|
+
}
|
|
4211
|
+
/** Delete a folder. Member conversations are UNASSIGNED (folder_id → NULL), never
|
|
4212
|
+
* cascade-deleted. Returns false if the folder did not exist. */
|
|
4213
|
+
deleteFolder(id) {
|
|
4214
|
+
this.db.prepare(`UPDATE conversations SET folder_id = NULL WHERE folder_id = $id`).run({ $id: id });
|
|
4215
|
+
return this.db.prepare(`DELETE FROM folders WHERE id = $id`).run({ $id: id }).changes > 0;
|
|
4216
|
+
}
|
|
4217
|
+
/** Move a conversation into a folder (or out of any folder when folderId is null).
|
|
4218
|
+
* When folderId is non-null the folder must exist. Returns:
|
|
4219
|
+
* - { ok: true } on success,
|
|
4220
|
+
* - { ok: false, reason } when the conversation or the target folder is missing. */
|
|
4221
|
+
moveConversationToFolder(convId, folderId) {
|
|
4222
|
+
if (folderId !== null && !this.getFolder(folderId)) return { ok: false, reason: "folder_not_found" };
|
|
4223
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4224
|
+
const changed = this.db.prepare(`UPDATE conversations SET folder_id = $fid, updated_at = $now WHERE id = $id`).run({ $id: convId, $fid: folderId, $now: now }).changes > 0;
|
|
4225
|
+
return changed ? { ok: true } : { ok: false, reason: "conversation_not_found" };
|
|
4226
|
+
}
|
|
4095
4227
|
// ── Agent run methods (v8 migration) ──────────────────────────────────────
|
|
4096
4228
|
createAgentRun(params) {
|
|
4097
4229
|
const id = randomUUID3();
|
|
@@ -4303,6 +4435,31 @@ var HfClient = class {
|
|
|
4303
4435
|
fetchModelCard(repo) {
|
|
4304
4436
|
return this.getCard(repo, CARD_EXTRACT_MAX);
|
|
4305
4437
|
}
|
|
4438
|
+
/** Fetch a repo's structured generation-params sidecar, if it has one — some quantizers
|
|
4439
|
+
* (e.g. unsloth) publish a root-level `params` JSON file with the exact recommended
|
|
4440
|
+
* sampling values (`{"temperature":1.0,"top_k":0,"top_p":1.0,"min_p":0.0,"stop":[…]}`)
|
|
4441
|
+
* alongside the GGUF shards. When present this is a strictly better signal than the
|
|
4442
|
+
* README heuristic/LLM extraction (exact values, no parsing/inference needed) — auto-tune
|
|
4443
|
+
* tries it first. Tries `params` then the more standard `generation_config.json` name.
|
|
4444
|
+
* Best-effort: '' when neither exists or the repo is unreachable, never throws. */
|
|
4445
|
+
async fetchGenerationParams(repo) {
|
|
4446
|
+
for (const name of ["params", "generation_config.json"]) {
|
|
4447
|
+
const url = `${BASE}/${repo}/raw/main/${name}`;
|
|
4448
|
+
const now = Date.now();
|
|
4449
|
+
const hit = this.cache.get(url);
|
|
4450
|
+
if (hit && now - hit.at < CACHE_TTL_MS) return hit.value;
|
|
4451
|
+
try {
|
|
4452
|
+
const res = await fetch(url, { headers: this.authHeaders(), redirect: "follow" });
|
|
4453
|
+
if (!res.ok) continue;
|
|
4454
|
+
const raw = await res.text();
|
|
4455
|
+
this.cache.set(url, { at: now, value: raw });
|
|
4456
|
+
return raw;
|
|
4457
|
+
} catch {
|
|
4458
|
+
continue;
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
return "";
|
|
4462
|
+
}
|
|
4306
4463
|
/** Resolve the upstream base model for a repo (ADR-099 base-model fallback): the `base_model`
|
|
4307
4464
|
* declared in the repo's HF card metadata. Most local GGUFs are third-party requants
|
|
4308
4465
|
* (lmstudio-community / unsloth / noctrex / …) whose card omits the author's recommended
|
|
@@ -4959,6 +5116,29 @@ function parseCardSampling(card) {
|
|
|
4959
5116
|
function hasAnySampling(s) {
|
|
4960
5117
|
return s.temp != null || s.topP != null || s.topK != null || s.minP != null;
|
|
4961
5118
|
}
|
|
5119
|
+
function parseGenerationParams(raw) {
|
|
5120
|
+
if (!raw) return {};
|
|
5121
|
+
let obj;
|
|
5122
|
+
try {
|
|
5123
|
+
obj = JSON.parse(raw);
|
|
5124
|
+
} catch {
|
|
5125
|
+
return {};
|
|
5126
|
+
}
|
|
5127
|
+
if (typeof obj !== "object" || obj === null) return {};
|
|
5128
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
5129
|
+
const out = {};
|
|
5130
|
+
const map = [
|
|
5131
|
+
["temperature", "temp"],
|
|
5132
|
+
["top_p", "topP"],
|
|
5133
|
+
["top_k", "topK"],
|
|
5134
|
+
["min_p", "minP"]
|
|
5135
|
+
];
|
|
5136
|
+
for (const [jsonKey, field] of map) {
|
|
5137
|
+
const v = num(obj[jsonKey]);
|
|
5138
|
+
if (v !== null) out[field] = v;
|
|
5139
|
+
}
|
|
5140
|
+
return clampCardSampling(out);
|
|
5141
|
+
}
|
|
4962
5142
|
function relevantCardExcerpt(card, maxLen = 8e3) {
|
|
4963
5143
|
if (card.length <= maxLen) return card;
|
|
4964
5144
|
const strong = /\b(?:recommended\s+(?:settings|sampling|parameters)|sampling\s+(?:settings|parameters|params)|best\s+practices?|generation\s+config)\b/i.exec(card);
|
|
@@ -5092,7 +5272,7 @@ var BenchRunner = class {
|
|
|
5092
5272
|
saveResult() {
|
|
5093
5273
|
const w = this.winning;
|
|
5094
5274
|
if (!w) return false;
|
|
5095
|
-
const record = this.persistBest(w.modelKey, w.profile, w.cand);
|
|
5275
|
+
const record = this.persistBest(w.modelKey, w.profile, w.cand, w.engineId);
|
|
5096
5276
|
this.queueTelemetry(record, w.entry, w.sys, this.version, w.engineVersion);
|
|
5097
5277
|
this.winning = null;
|
|
5098
5278
|
this.state = { ...this.state, result: void 0 };
|
|
@@ -5161,7 +5341,7 @@ var BenchRunner = class {
|
|
|
5161
5341
|
this.runLogMeta = { modelKey, startedAt: (/* @__PURE__ */ new Date()).toISOString(), sys };
|
|
5162
5342
|
const active = this.registry.active();
|
|
5163
5343
|
const caps = active?.capabilities ?? { flags: [], kvTypes: [] };
|
|
5164
|
-
const saved = this.store.snapshot()
|
|
5344
|
+
const saved = getModelProfile(this.store.snapshot(), modelKey, active?.id ?? "*");
|
|
5165
5345
|
const defaults = this.store.snapshot().modelDefaults;
|
|
5166
5346
|
const resolved = resolveProfile(entry, sys, saved, base2, defaults);
|
|
5167
5347
|
const baseProfile = { ...resolved, speculative: "off", mtpHeadPath: "", draftModelPath: "" };
|
|
@@ -5197,7 +5377,7 @@ var BenchRunner = class {
|
|
|
5197
5377
|
return;
|
|
5198
5378
|
}
|
|
5199
5379
|
const profile = recommended && hasAnySampling(recommended) ? { ...best.profile, sampling: { ...best.profile.sampling, ...recommended } } : best.profile;
|
|
5200
|
-
this.winning = { modelKey, profile, cand: best.cand, entry, sys, engineVersion: active?.version ?? "" };
|
|
5380
|
+
this.winning = { modelKey, profile, cand: best.cand, entry, sys, engineVersion: active?.version ?? "", engineId: active?.id ?? "" };
|
|
5201
5381
|
this.logWinner = { params: best.cand.params, tps: best.cand.tps ?? 0, prefillTps: best.cand.prefillTps, ttftMs: best.cand.ttftMs ?? 0, vramMb: best.cand.vramMb };
|
|
5202
5382
|
this.state = {
|
|
5203
5383
|
running: false,
|
|
@@ -5663,6 +5843,9 @@ var BenchRunner = class {
|
|
|
5663
5843
|
const repo = inferRepoFromPath(entry.path, this.store.snapshot().modelDirs);
|
|
5664
5844
|
if (!repo) return void 0;
|
|
5665
5845
|
this.state = { ...this.state, step: "Reading model-card recommendations\u2026" };
|
|
5846
|
+
const genParams = await this.hf.fetchGenerationParams(repo).catch(() => "");
|
|
5847
|
+
const paramsH = parseGenerationParams(genParams);
|
|
5848
|
+
if (hasAnySampling(paramsH)) return paramsH;
|
|
5666
5849
|
const localCard = await this.hf.fetchModelCard(repo).catch(() => "");
|
|
5667
5850
|
const localH = parseCardSampling(localCard);
|
|
5668
5851
|
if (hasAnySampling(localH)) return localH;
|
|
@@ -5744,7 +5927,7 @@ var BenchRunner = class {
|
|
|
5744
5927
|
}
|
|
5745
5928
|
/** Save the winning profile as the model's saved profile (tunedBy:'bench') and
|
|
5746
5929
|
* persist a benchResults row. Both via the same ConfigStore the route uses. */
|
|
5747
|
-
persistBest(modelKey, profile, cand) {
|
|
5930
|
+
persistBest(modelKey, profile, cand, engineId) {
|
|
5748
5931
|
const record = {
|
|
5749
5932
|
modelKey,
|
|
5750
5933
|
tps: cand.tps ?? 0,
|
|
@@ -5755,7 +5938,7 @@ var BenchRunner = class {
|
|
|
5755
5938
|
};
|
|
5756
5939
|
const tuned = { ...profile, tunedBy: "bench" };
|
|
5757
5940
|
this.store.update((cfg2) => {
|
|
5758
|
-
cfg2
|
|
5941
|
+
setModelProfile(cfg2, modelKey, engineId || "*", tuned);
|
|
5759
5942
|
cfg2.benchResults[modelKey] = record;
|
|
5760
5943
|
});
|
|
5761
5944
|
return record;
|
|
@@ -6075,7 +6258,7 @@ var ModelRouter = class {
|
|
|
6075
6258
|
const cfg2 = this.store.snapshot();
|
|
6076
6259
|
const sys = getSysInfo();
|
|
6077
6260
|
if (entry.format !== "gguf") {
|
|
6078
|
-
const savedProfile = cfg2
|
|
6261
|
+
const savedProfile = getModelProfile(cfg2, entry.key, engine.id);
|
|
6079
6262
|
return {
|
|
6080
6263
|
engine,
|
|
6081
6264
|
model: { key: entry.key, name: entry.name, quant: entry.quant, ctx: entry.nativeCtx, vision: false },
|
|
@@ -6085,7 +6268,7 @@ var ModelRouter = class {
|
|
|
6085
6268
|
tensorParallelSize: savedProfile?.gpu?.tensorParallelSize
|
|
6086
6269
|
};
|
|
6087
6270
|
}
|
|
6088
|
-
const saved = cfg2
|
|
6271
|
+
const saved = getModelProfile(cfg2, entry.key, engine.id);
|
|
6089
6272
|
const profile = resolveProfile(entry, sys, saved, void 0, cfg2.modelDefaults);
|
|
6090
6273
|
const extraArgs = engine.kind === "koboldcpp" ? koboldcppProfileToArgs(profile, sys.gpus[0]?.vendor ?? "unknown", sys.gpus.length > 0) : profileToArgs(profile, entry, engine.capabilities, sys.cores);
|
|
6091
6274
|
return {
|
|
@@ -8383,6 +8566,28 @@ async function body(c) {
|
|
|
8383
8566
|
}
|
|
8384
8567
|
function registerChatRoutes(app2, d) {
|
|
8385
8568
|
const { db: db2 } = d;
|
|
8569
|
+
app2.get("/api/v1/folders", (c) => {
|
|
8570
|
+
return c.json({ folders: db2.listFolders() });
|
|
8571
|
+
});
|
|
8572
|
+
app2.post("/api/v1/folders", async (c) => {
|
|
8573
|
+
const b = await body(c);
|
|
8574
|
+
const name = (b.name ?? "").trim();
|
|
8575
|
+
if (!name) return err(c, 400, "invalid_input", "Folder name required.");
|
|
8576
|
+
return c.json(db2.createFolder(name), 201);
|
|
8577
|
+
});
|
|
8578
|
+
app2.patch("/api/v1/folders/:id", async (c) => {
|
|
8579
|
+
const b = await body(c);
|
|
8580
|
+
const name = (b.name ?? "").trim();
|
|
8581
|
+
if (!name) return err(c, 400, "invalid_input", "Folder name required.");
|
|
8582
|
+
const ok = db2.renameFolder(c.req.param("id"), name);
|
|
8583
|
+
if (!ok) return err(c, 404, "not_found", "Folder not found.");
|
|
8584
|
+
return c.json(db2.getFolder(c.req.param("id")));
|
|
8585
|
+
});
|
|
8586
|
+
app2.delete("/api/v1/folders/:id", (c) => {
|
|
8587
|
+
const ok = db2.deleteFolder(c.req.param("id"));
|
|
8588
|
+
if (!ok) return err(c, 404, "not_found", "Folder not found.");
|
|
8589
|
+
return c.json({ ok: true });
|
|
8590
|
+
});
|
|
8386
8591
|
app2.get("/api/v1/conversations", (c) => {
|
|
8387
8592
|
const q = c.req.query("q");
|
|
8388
8593
|
return c.json({ conversations: db2.listConversations(q, "chat") });
|
|
@@ -8408,6 +8613,16 @@ function registerChatRoutes(app2, d) {
|
|
|
8408
8613
|
if (!ok) return err(c, 404, "not_found", "Conversation not found.");
|
|
8409
8614
|
return c.json({ ok: true });
|
|
8410
8615
|
});
|
|
8616
|
+
app2.patch("/api/v1/conversations/:id/folder", async (c) => {
|
|
8617
|
+
const b = await body(c);
|
|
8618
|
+
const folderId = b.folderId ?? null;
|
|
8619
|
+
const res = db2.moveConversationToFolder(c.req.param("id"), folderId);
|
|
8620
|
+
if (!res.ok) {
|
|
8621
|
+
if (res.reason === "folder_not_found") return err(c, 404, "not_found", "Folder not found.");
|
|
8622
|
+
return err(c, 404, "not_found", "Conversation not found.");
|
|
8623
|
+
}
|
|
8624
|
+
return c.json(db2.getConversation(c.req.param("id")));
|
|
8625
|
+
});
|
|
8411
8626
|
app2.post("/api/v1/conversations/:id/messages", async (c) => {
|
|
8412
8627
|
const convId = c.req.param("id");
|
|
8413
8628
|
const b = await body(c);
|
|
@@ -9561,10 +9776,11 @@ import { promisify as promisify5 } from "util";
|
|
|
9561
9776
|
var execFileP5 = promisify5(execFile8);
|
|
9562
9777
|
function buildEnv(toolchainDirs = []) {
|
|
9563
9778
|
const dirs = toolchainDirs.map((d) => d.trim()).filter(Boolean);
|
|
9564
|
-
if (dirs.length === 0) return { ...process.env };
|
|
9565
9779
|
const env = { ...process.env };
|
|
9566
9780
|
const key = Object.keys(env).find((k) => k.toLowerCase() === "path") ?? "PATH";
|
|
9567
|
-
|
|
9781
|
+
const existing = (env[key] ?? "").split(delimiter);
|
|
9782
|
+
const segments = [...dirs, ...existing].map((s) => s.trim()).filter(Boolean);
|
|
9783
|
+
if (env[key] !== void 0 || dirs.length > 0) env[key] = segments.join(delimiter);
|
|
9568
9784
|
return env;
|
|
9569
9785
|
}
|
|
9570
9786
|
var INSTALL_URLS = {
|
|
@@ -9856,6 +10072,9 @@ async function findVcvarsall() {
|
|
|
9856
10072
|
return null;
|
|
9857
10073
|
}
|
|
9858
10074
|
}
|
|
10075
|
+
function winComSpec() {
|
|
10076
|
+
return process.env.ComSpec || "cmd.exe";
|
|
10077
|
+
}
|
|
9859
10078
|
function runStep(cmd, args, opts) {
|
|
9860
10079
|
return new Promise((resolve2, reject) => {
|
|
9861
10080
|
const child = spawn4(cmd, args, {
|
|
@@ -10006,7 +10225,7 @@ async function runBuild(req, hooks, signal) {
|
|
|
10006
10225
|
if (isWindows) {
|
|
10007
10226
|
const configureBat = join19(buildRoot, "_tllm_configure.bat");
|
|
10008
10227
|
writeFileSync8(configureBat, vcvarsBatch(vcvars, configureArgs));
|
|
10009
|
-
await runStep(
|
|
10228
|
+
await runStep(winComSpec(), ["/c", configureBat], { cwd: buildRoot, env, signal, onLine: hooks.log });
|
|
10010
10229
|
} else {
|
|
10011
10230
|
await runStep("cmake", configureArgs, { cwd: buildRoot, env, signal, onLine: hooks.log });
|
|
10012
10231
|
}
|
|
@@ -10015,7 +10234,7 @@ async function runBuild(req, hooks, signal) {
|
|
|
10015
10234
|
if (isWindows) {
|
|
10016
10235
|
const compileBat = join19(buildRoot, "_tllm_build.bat");
|
|
10017
10236
|
writeFileSync8(compileBat, vcvarsBatch(vcvars, compileArgs));
|
|
10018
|
-
await runStep(
|
|
10237
|
+
await runStep(winComSpec(), ["/c", compileBat], { cwd: buildRoot, env, signal, onLine: hooks.log });
|
|
10019
10238
|
} else {
|
|
10020
10239
|
await runStep("cmake", compileArgs, { cwd: buildRoot, env, signal, onLine: hooks.log });
|
|
10021
10240
|
}
|
|
@@ -10151,7 +10370,7 @@ function detectHardware(info = getSysInfo()) {
|
|
|
10151
10370
|
(best, g) => !best || g.vramMb > best.vramMb ? g : best,
|
|
10152
10371
|
void 0
|
|
10153
10372
|
);
|
|
10154
|
-
const vramMb =
|
|
10373
|
+
const vramMb = ofVendor.reduce((sum, g) => sum + g.vramMb, 0);
|
|
10155
10374
|
return {
|
|
10156
10375
|
platform: process.platform,
|
|
10157
10376
|
arch: arch2,
|
|
@@ -11078,7 +11297,7 @@ function registerApi(app2, d) {
|
|
|
11078
11297
|
}
|
|
11079
11298
|
let opts2;
|
|
11080
11299
|
if (entry.format !== "gguf") {
|
|
11081
|
-
const savedProfile = cfg2
|
|
11300
|
+
const savedProfile = getModelProfile(cfg2, entry.key, active.id);
|
|
11082
11301
|
const extraArgs = active.kind === "mlx" ? mlxSamplingArgs(savedProfile?.sampling) : active.kind === "vllm" ? vllmProfileToArgs(resolveProfile(entry, sys, savedProfile, b.profileOverrides, cfg2.modelDefaults)) : [];
|
|
11083
11302
|
opts2 = {
|
|
11084
11303
|
engine: active,
|
|
@@ -11088,7 +11307,7 @@ function registerApi(app2, d) {
|
|
|
11088
11307
|
tensorParallelSize: savedProfile?.gpu?.tensorParallelSize
|
|
11089
11308
|
};
|
|
11090
11309
|
} else {
|
|
11091
|
-
const saved = cfg2
|
|
11310
|
+
const saved = getModelProfile(cfg2, entry.key, active.id);
|
|
11092
11311
|
const profile = resolveProfile(entry, sys, saved, b.profileOverrides, cfg2.modelDefaults);
|
|
11093
11312
|
const extraArgs = active.kind === "koboldcpp" ? koboldcppProfileToArgs(profile, primaryVendor(sys), sys.gpus.length > 0) : profileToArgs(profile, entry, active.capabilities, sys.cores);
|
|
11094
11313
|
opts2 = {
|
|
@@ -11279,7 +11498,8 @@ function registerApi(app2, d) {
|
|
|
11279
11498
|
if (!e) return err2(c, 404, "no_such_model", "No model with that key.");
|
|
11280
11499
|
const sys = getSysInfo();
|
|
11281
11500
|
const snap = d.store.snapshot();
|
|
11282
|
-
const
|
|
11501
|
+
const engineId = c.req.query("engine") || d.registry.active()?.id || "*";
|
|
11502
|
+
const saved = getModelProfile(snap, e.key, engineId);
|
|
11283
11503
|
const profile = resolveProfile(e, sys, saved, void 0, snap.modelDefaults);
|
|
11284
11504
|
return c.json({ ...overlayModel(e, d), profile, vramFit: estimateVram(profile, e, sys), gpu: sys.gpus[0] ?? null, gpus: sys.gpus, cores: sys.cores });
|
|
11285
11505
|
});
|
|
@@ -11306,15 +11526,17 @@ function registerApi(app2, d) {
|
|
|
11306
11526
|
return err2(c, 400, "invalid_profile_value", "gpu.tensorParallelSize must be an integer \u2265 1.");
|
|
11307
11527
|
}
|
|
11308
11528
|
}
|
|
11529
|
+
const engineId = c.req.query("engine") || d.registry.active()?.id || "*";
|
|
11309
11530
|
d.store.update((cfg2) => {
|
|
11310
|
-
cfg2
|
|
11531
|
+
setModelProfile(cfg2, key, engineId, p);
|
|
11311
11532
|
});
|
|
11312
11533
|
return c.json(p);
|
|
11313
11534
|
});
|
|
11314
11535
|
app2.post("/api/v1/models/:key/profile/reset", (c) => {
|
|
11315
11536
|
const key = decodeURIComponent(c.req.param("key"));
|
|
11537
|
+
const engineId = c.req.query("engine") || d.registry.active()?.id || "*";
|
|
11316
11538
|
d.store.update((cfg2) => {
|
|
11317
|
-
|
|
11539
|
+
deleteModelProfile(cfg2, key, engineId);
|
|
11318
11540
|
});
|
|
11319
11541
|
return c.json({ ok: true });
|
|
11320
11542
|
});
|
|
@@ -11716,7 +11938,8 @@ function overlayModel(e, d, lastTpsMap) {
|
|
|
11716
11938
|
const compatibleWithActiveEngine = active ? engineAcceptsFormat(active.kind, e.format) : true;
|
|
11717
11939
|
const provRepo = d.downloads.provenance().find((p) => p.dest === e.path && p.repo)?.repo;
|
|
11718
11940
|
const sourceRepo = provRepo ?? inferRepoFromPath(e.path, snap.modelDirs);
|
|
11719
|
-
|
|
11941
|
+
const hasProfile = !!profiles[e.key] && Object.keys(profiles[e.key]).length > 0;
|
|
11942
|
+
return { ...e, loaded, hasProfile, lastTps, liveTps, benchTps, compatibleWithActiveEngine, sourceRepo };
|
|
11720
11943
|
}
|
|
11721
11944
|
function formatMismatchMessage(engineKind, format) {
|
|
11722
11945
|
if (engineKind === "mlx")
|
|
@@ -13053,7 +13276,7 @@ void (async () => {
|
|
|
13053
13276
|
extraArgs: []
|
|
13054
13277
|
};
|
|
13055
13278
|
} else {
|
|
13056
|
-
const saved = cfg
|
|
13279
|
+
const saved = getModelProfile(cfg, entry.key, active.id);
|
|
13057
13280
|
const profile = resolveProfile(entry, sys, saved, void 0, cfg.modelDefaults);
|
|
13058
13281
|
opts = {
|
|
13059
13282
|
engine: active,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{j as e}from"./radix-
|
|
1
|
+
import{j as e}from"./radix-BY-fjzwm.js";import{r as u}from"./vendor-W5GIuFcv.js";import{c as w,d as k,A as T,u as v,L as h,e as S,a as b,f as C,g as q,X as A}from"./index-oSSYX1Lk.js";import{P as N}from"./plus-C8MMVqVA.js";import{C as y}from"./circle-x-caNFmCHm.js";import{C as O}from"./circle-check-DXOwCXEz.js";import{W as E}from"./wrench-BotIMKYs.js";const _=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}]],D=w("circle-dot",_);const L=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 6v6l4 2",key:"mmk7yg"}]],P=w("clock",L),p={all:["agent-runs"],list:()=>[...p.all,"list"],detail:t=>[...p.all,"detail",t]};async function g(t,a){const l={Accept:"application/json",...k(),...a?.headers??{}},i=await fetch(t,{...a,headers:l});if(i.status===204)return;const r=await i.text(),o=r?JSON.parse(r):void 0;if(!i.ok){const d=o;throw new T(d?.error?.code??"http_error",d?.error?.message??`Request failed with status ${i.status}.`,i.status)}return o}async function $(){return g("/api/v1/agents/runs")}async function I(t){return g(`/api/v1/agents/runs/${t}`)}async function Q(t){return g("/api/v1/agents/runs",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function K(t){await g(`/api/v1/agents/runs/${t}`,{method:"DELETE"})}async function*F(t,a=0,l){const i=`/api/v1/agents/runs/${t}/stream?fromSeq=${a}`,r=await fetch(i,{headers:{Accept:"text/event-stream",...k()},signal:l});if(!r.ok||!r.body)throw new Error(`Stream error: ${r.status}`);const o=r.body.getReader(),d=new TextDecoder;let m="";try{for(;;){const{done:s,value:x}=await o.read();if(s)break;m+=d.decode(x,{stream:!0});const c=m.split(`
|
|
2
2
|
`);m=c.pop()??"";let n="message";for(const f of c){if(f.startsWith("event: ")){n=f.slice(7).trim();continue}if(f.startsWith("data: ")){const j=f.slice(6).trim();if(j==="[DONE]")return;try{const R=JSON.parse(j);if(yield{event:n,data:R},n==="done"||n==="error")return}catch{}n="message"}}}}finally{o.cancel()}}const J={queued:e.jsx(P,{size:12}),running:e.jsx(h,{size:12,className:"animate-spin"}),done:e.jsx(O,{size:12}),failed:e.jsx(y,{size:12}),cancelled:e.jsx(y,{size:12}),interrupted:e.jsx(y,{size:12})},M={queued:"text-muted",running:"text-accent",done:"text-green-500",failed:"text-red-500",cancelled:"text-muted",interrupted:"text-orange-500"};function z({status:t}){return e.jsxs("span",{className:b("flex items-center gap-1 text-[11px] capitalize",M[t]),children:[J[t],t]})}function W(t){const a=Date.now()-new Date(t).getTime();return a<6e4?"just now":a<36e5?`${Math.floor(a/6e4)}m ago`:a<864e5?`${Math.floor(a/36e5)}h ago`:new Date(t).toLocaleDateString()}const U=[{id:"web_search",label:"Web search",hint:"Search the web (needs a provider configured in Settings)"},{id:"fetch_url",label:"Fetch URL",hint:"Read the contents of a web page"},{id:"run_code",label:"Run code",hint:"Execute JS in a sandbox — no mid-run confirmation, opt in only if you trust the task"}];function B({onClose:t,onCreated:a}){const[l,i]=u.useState(""),[r,o]=u.useState(""),[d,m]=u.useState({web_search:!0,fetch_url:!0,run_code:!1}),s=C(),x=n=>m(f=>({...f,[n]:!f[n]})),c=q({mutationFn:()=>Q({title:l.slice(0,60)||"Agent run",systemPrompt:r||void 0,userMessage:l,allowedTools:Object.entries(d).filter(([,n])=>n).map(([n])=>n)}),onSuccess:n=>{s.invalidateQueries({queryKey:p.list()}),a(n.id),t()}});return e.jsx("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/40",onClick:t,children:e.jsxs("div",{className:"w-full max-w-lg rounded-xl border border-border bg-panel p-5 shadow-xl",onClick:n=>n.stopPropagation(),children:[e.jsxs("div",{className:"mb-4 flex items-center justify-between",children:[e.jsx("h2",{className:"text-[15px] font-semibold",children:"New agent run"}),e.jsx("button",{type:"button",onClick:t,className:"text-faint hover:text-ink",children:e.jsx(A,{size:16})})]}),e.jsx("label",{className:"mb-1 block text-[12px] text-muted",children:"Task"}),e.jsx("textarea",{autoFocus:!0,value:l,onChange:n=>i(n.target.value),placeholder:"Describe what the agent should do…",rows:4,className:"mb-3 w-full resize-none rounded-lg border border-border bg-input px-3 py-2 text-[13px] text-ink placeholder:text-faint focus:border-accent focus:outline-none"}),e.jsxs("label",{className:"mb-1 block text-[12px] text-muted",children:["System prompt ",e.jsx("span",{className:"text-faint",children:"(optional)"})]}),e.jsx("textarea",{value:r,onChange:n=>o(n.target.value),placeholder:"You are a helpful assistant…",rows:2,className:"mb-4 w-full resize-none rounded-lg border border-border bg-input px-3 py-2 text-[13px] text-ink placeholder:text-faint focus:border-accent focus:outline-none"}),e.jsx("label",{className:"mb-1.5 block text-[12px] text-muted",children:"Tools"}),e.jsx("div",{className:"mb-4 space-y-1.5",children:U.map(n=>e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 rounded-lg border border-border px-2.5 py-2 hover:bg-panel-2",children:[e.jsx("input",{type:"checkbox",checked:d[n.id],onChange:()=>x(n.id),className:"mt-0.5 accent-[var(--accent)]"}),e.jsxs("span",{className:"min-w-0",children:[e.jsx("span",{className:"block text-[13px] font-medium text-ink",children:n.label}),e.jsx("span",{className:"block text-[11px] leading-snug text-faint",children:n.hint})]})]},n.id))}),e.jsxs("div",{className:"flex items-center justify-end gap-2",children:[e.jsx("button",{type:"button",onClick:t,className:"rounded-lg px-3 py-1.5 text-[13px] text-muted hover:text-ink",children:"Cancel"}),e.jsxs("button",{type:"button",disabled:!l.trim()||c.isPending,onClick:()=>c.mutate(),className:"flex items-center gap-1.5 rounded-lg bg-accent px-3 py-1.5 text-[13px] font-medium text-white disabled:opacity-50",children:[c.isPending?e.jsx(h,{size:13,className:"animate-spin"}):e.jsx(S,{size:13}),"Run agent"]})]}),c.isError&&e.jsx("p",{className:"mt-2 text-[12px]",style:{color:"var(--err)"},children:c.error.message})]})})}function X({call:t}){const[a,l]=u.useState(!1),i=typeof t.args?.query=="string"?t.args.query:typeof t.args?.url=="string"?t.args.url:JSON.stringify(t.args??{});return e.jsxs("div",{className:"w-full max-w-[85%] rounded-lg border border-border bg-panel px-2.5 py-1.5 text-[12px]",children:[e.jsxs("button",{type:"button",onClick:()=>l(r=>!r),className:"flex w-full items-center gap-1.5 text-left text-muted hover:text-ink",children:[e.jsx(E,{size:12,className:"shrink-0 text-accent"}),e.jsx("span",{className:"font-medium text-ink",children:t.name}),e.jsx("span",{className:"truncate text-faint",children:i})]}),a&&e.jsx("pre",{className:"mt-1.5 max-h-48 overflow-auto whitespace-pre-wrap break-words rounded bg-panel-2 p-2 text-[11px] text-muted",children:t.result})]})}function H({run:t}){const a=C(),l=u.useRef(null),[i,r]=u.useState(""),o=u.useRef(null),d=q({mutationFn:()=>K(t.id),onSuccess:()=>{a.invalidateQueries({queryKey:p.list()})}});u.useEffect(()=>{if(t.status!=="running"&&t.status!=="queued")return;const s=new AbortController;l.current=s;let x="";return(async()=>{try{for await(const c of F(t.id,0,s.signal))if(c.event==="delta")x+=c.data.delta,r(x);else if(c.event==="done"||c.event==="error"){r(""),a.invalidateQueries({queryKey:p.detail(t.id)}),a.invalidateQueries({queryKey:p.list()});break}}catch{}})(),()=>{s.abort(),l.current=null}},[t.id,t.status,a]),u.useEffect(()=>{o.current?.scrollIntoView({behavior:"smooth"})},[i,t.messages?.length]);const m=t.messages??[];return e.jsxs("div",{className:"flex h-full flex-col",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-border px-4 py-2.5",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-[14px] font-medium text-ink",children:t.title}),e.jsx(z,{status:t.status})]}),(t.status==="running"||t.status==="queued")&&e.jsx("button",{type:"button",onClick:()=>d.mutate(),className:"rounded-lg border border-border px-3 py-1 text-[12px] text-muted transition-colors hover:border-red-400 hover:text-red-500",children:d.isPending?e.jsx(h,{size:12,className:"inline animate-spin"}):"Cancel"})]}),e.jsxs("div",{className:"flex-1 space-y-3 overflow-y-auto p-4",children:[m.map(s=>e.jsxs("div",{className:b("flex flex-col gap-1.5",s.role==="user"?"items-end":"items-start"),children:[s.toolCalls?.map(x=>e.jsx(X,{call:x},x.id)),(s.content||s.role==="user"||t.status==="running")&&e.jsx("div",{className:b("max-w-[85%] rounded-xl px-3 py-2 text-[13px] leading-relaxed",s.role==="user"?"bg-accent/15 text-ink":"bg-panel-2 text-ink"),children:e.jsx("p",{className:"whitespace-pre-wrap break-words",children:s.content||(s.role==="assistant"&&t.status==="running"?"…":"")})})]},s.id)),i&&e.jsx("div",{className:"flex gap-2",children:e.jsx("div",{className:"max-w-[85%] rounded-xl bg-panel-2 px-3 py-2 text-[13px] leading-relaxed text-ink",children:e.jsx("p",{className:"whitespace-pre-wrap break-words",children:i})})}),t.error&&e.jsxs("div",{className:"rounded-lg bg-red-500/10 px-3 py-2 text-[12px] text-red-500",children:["Error: ",t.error]}),e.jsx("div",{ref:o})]})]})}function ne(){const[t,a]=u.useState(null),[l,i]=u.useState(!1),r=v({queryKey:p.list(),queryFn:$,refetchInterval:s=>(s.state.data??[]).some(n=>n.status==="queued"||n.status==="running")?3e3:!1}),o=v({queryKey:p.detail(t??""),queryFn:()=>I(t),enabled:!!t,refetchInterval:s=>{const x=s.state.data;return x?.status==="queued"||x?.status==="running"?5e3:!1}}),d=r.data??[],m=o.data;return e.jsxs("div",{className:"flex h-full",children:[e.jsxs("div",{className:"flex w-64 shrink-0 flex-col border-r border-border",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-border px-3 py-2.5",children:[e.jsx("span",{className:"text-[13px] font-semibold text-ink",children:"Agents"}),e.jsxs("button",{type:"button",title:"New agent run",onClick:()=>i(!0),className:"flex items-center gap-1 rounded-lg bg-accent px-2 py-1 text-[11px] font-medium text-white transition-opacity hover:opacity-90",children:[e.jsx(N,{size:11})," New"]})]}),e.jsxs("div",{className:"flex-1 overflow-y-auto",children:[r.isLoading&&e.jsx("div",{className:"flex h-20 items-center justify-center",children:e.jsx(h,{size:16,className:"animate-spin text-faint"})}),d.length===0&&!r.isLoading&&e.jsxs("div",{className:"px-4 py-8 text-center",children:[e.jsx(S,{size:28,className:"mx-auto mb-2 text-faint"}),e.jsx("p",{className:"text-[12px] text-muted",children:"No agent runs yet."}),e.jsx("p",{className:"mt-0.5 text-[11px] text-faint",children:"Click “New” to start one."})]}),d.map(s=>e.jsxs("button",{type:"button",onClick:()=>a(s.id),className:b("w-full border-b border-border px-3 py-2.5 text-left transition-colors hover:bg-panel-2",t===s.id&&"bg-panel-2"),children:[e.jsx("p",{className:"mb-0.5 truncate text-[13px] font-medium text-ink",children:s.title}),e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx(z,{status:s.status}),e.jsx("span",{className:"text-[10px] text-faint",children:W(s.createdAt)})]})]},s.id))]})]}),e.jsx("div",{className:"flex min-w-0 flex-1 flex-col",children:t?o.isLoading?e.jsx("div",{className:"flex h-full items-center justify-center",children:e.jsx(h,{size:20,className:"animate-spin text-faint"})}):m?e.jsx(H,{run:m}):null:e.jsxs("div",{className:"flex h-full flex-col items-center justify-center text-center",children:[e.jsx(D,{size:32,className:"mb-3 text-faint"}),e.jsx("p",{className:"text-[14px] font-medium text-ink",children:"Select a run"}),e.jsx("p",{className:"mt-1 text-[12px] text-muted",children:"or create a new agent run to get started"}),e.jsxs("button",{type:"button",onClick:()=>i(!0),className:"mt-4 flex items-center gap-1.5 rounded-lg bg-accent px-3 py-1.5 text-[13px] font-medium text-white hover:opacity-90",children:[e.jsx(N,{size:13})," New agent run"]})]})}),l&&e.jsx(B,{onClose:()=>i(!1),onCreated:s=>{a(s),r.refetch()}})]})}export{ne as AgentsScreen};
|