turbollm 1.6.4 → 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.
Files changed (99) hide show
  1. package/README.md +8 -4
  2. package/dist/cli.js +140 -20
  3. package/dist/webdist/assets/{AgentsScreen-z7V9yJTT.js → AgentsScreen-D0J8ZEtf.js} +1 -1
  4. package/dist/webdist/assets/ChatScreen-7bIHB35Z.js +85 -0
  5. package/dist/webdist/assets/{CustomizeScreen-CAQemkBC.js → CustomizeScreen-z5568762.js} +1 -1
  6. package/dist/webdist/assets/{DeveloperScreen-BfW2INcc.js → DeveloperScreen-C5opqGrE.js} +1 -1
  7. package/dist/webdist/assets/{EnginesScreen-DeVQWBY-.js → EnginesScreen-NqAMc4rS.js} +6 -6
  8. package/dist/webdist/assets/{ModelDetailDialog-CQxlHshX.js → ModelDetailDialog-CtjNu2Qc.js} +19 -19
  9. package/dist/webdist/assets/ModelDirs-C-riHLWK.js +1 -0
  10. package/dist/webdist/assets/{ModelsScreen-CVyA8shA.js → ModelsScreen-DnASH2bF.js} +2 -2
  11. package/dist/webdist/assets/{SettingsScreen-DVJQTfi8.js → SettingsScreen-v-LAgJFH.js} +1 -1
  12. package/dist/webdist/assets/{arc-BMLeTMlL.js → arc-OPogq9vx.js} +1 -1
  13. package/dist/webdist/assets/{architectureDiagram-3BPJPVTR-CG46ZIfD.js → architectureDiagram-3BPJPVTR-DI3zlQZC.js} +1 -1
  14. package/dist/webdist/assets/{arrow-left-ClFIxfDf.js → arrow-left-DWtpbvUs.js} +1 -1
  15. package/dist/webdist/assets/{blockDiagram-GPEHLZMM-Ct5vmWMX.js → blockDiagram-GPEHLZMM-bbqbctKR.js} +1 -1
  16. package/dist/webdist/assets/{c4Diagram-AAUBKEIU-CAC8CrWx.js → c4Diagram-AAUBKEIU-CGj5BFj-.js} +1 -1
  17. package/dist/webdist/assets/channel-aq4Rip4D.js +1 -0
  18. package/dist/webdist/assets/check-CMGnr_Ph.js +1 -0
  19. package/dist/webdist/assets/chevron-right-CIKvJUaG.js +1 -0
  20. package/dist/webdist/assets/{chunk-2J33WTMH-Boq1pvZ0.js → chunk-2J33WTMH-CZKSYaHn.js} +1 -1
  21. package/dist/webdist/assets/{chunk-4BX2VUAB-BxOCNoyH.js → chunk-4BX2VUAB-D3SQCGwz.js} +1 -1
  22. package/dist/webdist/assets/{chunk-55IACEB6-xrXYZmka.js → chunk-55IACEB6-DDmZTA0L.js} +1 -1
  23. package/dist/webdist/assets/{chunk-727SXJPM-BFEC_Ow4.js → chunk-727SXJPM-DRutYNQM.js} +1 -1
  24. package/dist/webdist/assets/{chunk-AQP2D5EJ-DFTdPrur.js → chunk-AQP2D5EJ-B_u0ow9I.js} +1 -1
  25. package/dist/webdist/assets/{chunk-FMBD7UC4-D41UJ-1d.js → chunk-FMBD7UC4-Cu73-Koh.js} +1 -1
  26. package/dist/webdist/assets/{chunk-ND2GUHAM-BeavMz7M.js → chunk-ND2GUHAM-Z71l0U4D.js} +1 -1
  27. package/dist/webdist/assets/{chunk-QZHKN3VN-CUHX28cH.js → chunk-QZHKN3VN-D2PeraEx.js} +1 -1
  28. package/dist/webdist/assets/circle-check-DXOwCXEz.js +1 -0
  29. package/dist/webdist/assets/circle-x-caNFmCHm.js +1 -0
  30. package/dist/webdist/assets/classDiagram-4FO5ZUOK-C5KHEB9t.js +1 -0
  31. package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-C5KHEB9t.js +1 -0
  32. package/dist/webdist/assets/common-CQQGhjlM.js +1 -0
  33. package/dist/webdist/assets/{copy-button-CTWXbfiP.js → copy-button-Dt4Afcgb.js} +1 -1
  34. package/dist/webdist/assets/{cose-bilkent-S5V4N54A-Bx_lty5d.js → cose-bilkent-S5V4N54A-D3rxBv0J.js} +1 -1
  35. package/dist/webdist/assets/{dagre-BM42HDAG-0sn0yk1w.js → dagre-BM42HDAG--G2XvGhI.js} +1 -1
  36. package/dist/webdist/assets/{diagram-2AECGRRQ-B7dlDz_q.js → diagram-2AECGRRQ-Te1rwFql.js} +1 -1
  37. package/dist/webdist/assets/{diagram-5GNKFQAL--tcSUKb3.js → diagram-5GNKFQAL-B5vALx3-.js} +1 -1
  38. package/dist/webdist/assets/{diagram-KO2AKTUF-DWmLxntr.js → diagram-KO2AKTUF-CBxjZi0b.js} +1 -1
  39. package/dist/webdist/assets/{diagram-LMA3HP47-DBOpUcpv.js → diagram-LMA3HP47-BGiirNkw.js} +1 -1
  40. package/dist/webdist/assets/{diagram-OG6HWLK6-CdRckAFu.js → diagram-OG6HWLK6-BVgMIJkt.js} +1 -1
  41. package/dist/webdist/assets/dialog-coezioYj.js +1 -0
  42. package/dist/webdist/assets/dropdown-menu-Ce842M-W.js +1 -0
  43. package/dist/webdist/assets/{erDiagram-TEJ5UH35-Bg-nfhtr.js → erDiagram-TEJ5UH35-DowgN2O9.js} +1 -1
  44. package/dist/webdist/assets/{flowDiagram-I6XJVG4X-CghDUz8j.js → flowDiagram-I6XJVG4X-BD0xs8q_.js} +1 -1
  45. package/dist/webdist/assets/{ganttDiagram-6RSMTGT7-B76rXmmo.js → ganttDiagram-6RSMTGT7-BHHhhU_7.js} +1 -1
  46. package/dist/webdist/assets/{gitGraphDiagram-PVQCEYII-DYwR4vbe.js → gitGraphDiagram-PVQCEYII-Brchy1Vz.js} +1 -1
  47. package/dist/webdist/assets/index-CVyS_zSS.css +1 -0
  48. package/dist/webdist/assets/{index-DxM70nEl.js → index-oSSYX1Lk.js} +4 -4
  49. package/dist/webdist/assets/{infoDiagram-5YYISTIA-BvoZYlup.js → infoDiagram-5YYISTIA-D4HdfOcs.js} +1 -1
  50. package/dist/webdist/assets/{ishikawaDiagram-YF4QCWOH-D5TRR4WP.js → ishikawaDiagram-YF4QCWOH-wmMjgjbp.js} +1 -1
  51. package/dist/webdist/assets/{journeyDiagram-JHISSGLW-2aK1csut.js → journeyDiagram-JHISSGLW-CaF09KKI.js} +1 -1
  52. package/dist/webdist/assets/{kanban-definition-UN3LZRKU-CWEMWeUg.js → kanban-definition-UN3LZRKU-BuBI9QG2.js} +1 -1
  53. package/dist/webdist/assets/{linear-D7FhiD9R.js → linear-DHVGKuTX.js} +1 -1
  54. package/dist/webdist/assets/{mermaid.core-DQIxkS1K.js → mermaid.core-CrU-xSQY.js} +4 -4
  55. package/dist/webdist/assets/{mindmap-definition-RKZ34NQL-DaZEBIU3.js → mindmap-definition-RKZ34NQL-cBLMFEJ9.js} +1 -1
  56. package/dist/webdist/assets/pdf.worker.min-DEtVeC4l.mjs +29 -0
  57. package/dist/webdist/assets/pencil-BBioAa4x.js +1 -0
  58. package/dist/webdist/assets/{personas-DbG2zH5T.js → personas-DaUrwq7m.js} +7 -3
  59. package/dist/webdist/assets/{pieDiagram-4H26LBE5-C6AqueQK.js → pieDiagram-4H26LBE5-BJnfOXlL.js} +1 -1
  60. package/dist/webdist/assets/plus-C8MMVqVA.js +1 -0
  61. package/dist/webdist/assets/{quadrantDiagram-W4KKPZXB-C3ufHf4n.js → quadrantDiagram-W4KKPZXB-Cm-qlQtE.js} +1 -1
  62. package/dist/webdist/assets/{radix-jAkiiept.js → radix-BY-fjzwm.js} +1 -1
  63. package/dist/webdist/assets/refresh-cw-DTqN58WZ.js +1 -0
  64. package/dist/webdist/assets/{requirementDiagram-4Y6WPE33-DKjCoTy-.js → requirementDiagram-4Y6WPE33-DHY3RUt1.js} +1 -1
  65. package/dist/webdist/assets/{sankeyDiagram-5OEKKPKP-D3Lhx_3A.js → sankeyDiagram-5OEKKPKP-DQkDAi3z.js} +1 -1
  66. package/dist/webdist/assets/save-PN9--sf1.js +1 -0
  67. package/dist/webdist/assets/{sequenceDiagram-3UESZ5HK-Bg7qUaiJ.js → sequenceDiagram-3UESZ5HK-OJf-ZzIm.js} +1 -1
  68. package/dist/webdist/assets/skeleton-Dpg8YusE.js +1 -0
  69. package/dist/webdist/assets/{stateDiagram-AJRCARHV-CEe0Lqmt.js → stateDiagram-AJRCARHV-QupYS2FV.js} +1 -1
  70. package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-DPP0kwbC.js +1 -0
  71. package/dist/webdist/assets/{timeline-definition-PNZ67QCA-ILud0hYG.js → timeline-definition-PNZ67QCA-B7kdKKeO.js} +1 -1
  72. package/dist/webdist/assets/trash-2-C5mszq8p.js +1 -0
  73. package/dist/webdist/assets/{vennDiagram-CIIHVFJN-UmbDOfzC.js → vennDiagram-CIIHVFJN-DCckmGsZ.js} +1 -1
  74. package/dist/webdist/assets/{wardley-L42UT6IY-CJPgfZGE.js → wardley-L42UT6IY-C66hVI6t.js} +1 -1
  75. package/dist/webdist/assets/{wardleyDiagram-YWT4CUSO-COswOSfT.js → wardleyDiagram-YWT4CUSO-0WD2JYDy.js} +1 -1
  76. package/dist/webdist/assets/{wrench-BtDcuPD1.js → wrench-BotIMKYs.js} +1 -1
  77. package/dist/webdist/assets/{xychartDiagram-2RQKCTM6-pBHtZvWZ.js → xychartDiagram-2RQKCTM6-Dc0w49oN.js} +1 -1
  78. package/dist/webdist/index.html +3 -3
  79. package/package.json +1 -1
  80. package/dist/webdist/assets/ChatScreen-7b_l3eo1.js +0 -29
  81. package/dist/webdist/assets/ModelDirs-D_Pl9O4e.js +0 -1
  82. package/dist/webdist/assets/channel-3lZ_p1cA.js +0 -1
  83. package/dist/webdist/assets/check-DQ6z92nS.js +0 -1
  84. package/dist/webdist/assets/chevron-right-DGMGRuqq.js +0 -1
  85. package/dist/webdist/assets/circle-check-B6TnDVnx.js +0 -1
  86. package/dist/webdist/assets/circle-x-zQ-pYHWJ.js +0 -1
  87. package/dist/webdist/assets/classDiagram-4FO5ZUOK-qxOwHzBq.js +0 -1
  88. package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-qxOwHzBq.js +0 -1
  89. package/dist/webdist/assets/common-stymjhMS.js +0 -1
  90. package/dist/webdist/assets/dialog-D-C0Tc3g.js +0 -1
  91. package/dist/webdist/assets/dropdown-menu-Co-guMgc.js +0 -1
  92. package/dist/webdist/assets/index-CT5lTdUf.css +0 -1
  93. package/dist/webdist/assets/pencil-zupSY7XP.js +0 -1
  94. package/dist/webdist/assets/plus-Ct45XaAU.js +0 -1
  95. package/dist/webdist/assets/refresh-cw-CUUblWFA.js +0 -1
  96. package/dist/webdist/assets/save-Cu00OCua.js +0 -1
  97. package/dist/webdist/assets/skeleton-DAp10ou3.js +0 -1
  98. package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-BSX42oue.js +0 -1
  99. package/dist/webdist/assets/trash-2-Dxm0bLnm.js +0 -1
package/README.md CHANGED
@@ -174,7 +174,9 @@ the detail:
174
174
  carry a draft head, threads auto — safely gated to what your engine actually accepts.
175
175
  - **Multi-GPU, per model** — split a model across cards (layer/row split + main-GPU pick on
176
176
  llama.cpp, tensor-parallel on vLLM). Defaults are no-ops, so single-GPU rigs are untouched.
177
- - **Saved per-model profiles** — tune once, and it loads that way every time.
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.
178
180
 
179
181
  </details>
180
182
 
@@ -196,11 +198,13 @@ the detail:
196
198
  Creative · Research) per conversation, no prompt-wrangling required. The **Designer** persona
197
199
  produces polished, self-contained, previewable designs by default.
198
200
  - **Edit, regenerate, delete, copy** any message; **persistent, searchable conversations**
199
- 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.
200
203
  - **Per-chat system prompt** and **per-chat sampling** overrides — temperature, top-p/k, min-p,
201
204
  repeat/presence/frequency penalties, and **stop strings**.
202
- - **Image input** for vision models, and **TurboLLM Expert** — a built-in assistant that knows
203
- the app and your hardware for onboarding and troubleshooting without leaving the UI.
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.
204
208
  - **Agentic tools** — built-in `web_search` (Tavily), `fetch_url`, and sandboxed `run_code`, plus
205
209
  an **MCP marketplace** in Customize: one-click connect for hosted MCPs (GitHub, Linear, Stripe,
206
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 = 2;
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 (!(k in cfg2) && k !== "engine" && k !== "host" && k !== "port") {
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;
@@ -3843,16 +3869,24 @@ function profileToArgs(p, m, caps, cores = 0) {
3843
3869
  if (p.chatTemplateFile && has("--chat-template-file")) a.push("--chat-template-file", p.chatTemplateFile);
3844
3870
  const specType = has("--spec-type");
3845
3871
  const specAccepts = (v) => caps.flags.length === 0 || caps.flags.includes(`spec-type:${v}`);
3872
+ let specActive = false;
3846
3873
  if (p.speculative === "mtp" && p.mtpHeadPath && has("--mtp-head")) {
3847
3874
  if (specType) a.push("--spec-type", "mtp");
3848
3875
  a.push("--mtp-head", p.mtpHeadPath);
3876
+ specActive = true;
3849
3877
  } else if (p.speculative === "nextn" && specType && has("--model-draft")) {
3850
3878
  const nextnVal = ["nextn", "draft-mtp"].find((v) => specAccepts(v));
3851
- if (nextnVal) a.push("--spec-type", nextnVal, "--model-draft", m.path);
3879
+ if (nextnVal) {
3880
+ a.push("--spec-type", nextnVal, "--model-draft", m.path);
3881
+ specActive = true;
3882
+ }
3852
3883
  } else if (p.speculative === "draft" && p.draftModelPath && has("--model-draft")) {
3853
3884
  if (specType) a.push("--spec-type", "draft");
3854
- a.push("--model-draft", p.draftModelPath, "--draft-max", String(p.draftMax ?? 16), "--draft-min", String(p.draftMin ?? 1));
3885
+ a.push("--model-draft", p.draftModelPath);
3886
+ specActive = true;
3855
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));
3856
3890
  if (p.sampling.temp !== 0.8 && has("--temp")) a.push("--temp", String(p.sampling.temp));
3857
3891
  if (p.sampling.topP !== 0.95 && has("--top-p")) a.push("--top-p", String(p.sampling.topP));
3858
3892
  if (p.sampling.topK !== 40 && has("--top-k")) a.push("--top-k", String(p.sampling.topK));
@@ -3899,7 +3933,10 @@ function safeJson(s) {
3899
3933
  }
3900
3934
  }
3901
3935
  function rowToConv(r) {
3902
- 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 };
3903
3940
  }
3904
3941
  function rowToAgentRun(r) {
3905
3942
  return {
@@ -4008,6 +4045,18 @@ var ConversationStore = class {
4008
4045
  PRAGMA user_version = 9;
4009
4046
  `);
4010
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
+ }
4011
4060
  }
4012
4061
  listConversations(q, kind = "all") {
4013
4062
  if (q) {
@@ -4029,7 +4078,7 @@ var ConversationStore = class {
4029
4078
  createConversation(partial) {
4030
4079
  const now = (/* @__PURE__ */ new Date()).toISOString();
4031
4080
  const id = randomUUID3();
4032
- 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 });
4033
4082
  return this.getConversation(id);
4034
4083
  }
4035
4084
  getConversation(id, withMessages = false) {
@@ -4140,6 +4189,41 @@ var ConversationStore = class {
4140
4189
  const row = this.db.prepare(`SELECT * FROM messages WHERE conv_id = $id ORDER BY seq DESC LIMIT 1`).get({ $id: convId });
4141
4190
  return row ? rowToMsg(row) : null;
4142
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
+ }
4143
4227
  // ── Agent run methods (v8 migration) ──────────────────────────────────────
4144
4228
  createAgentRun(params) {
4145
4229
  const id = randomUUID3();
@@ -5188,7 +5272,7 @@ var BenchRunner = class {
5188
5272
  saveResult() {
5189
5273
  const w = this.winning;
5190
5274
  if (!w) return false;
5191
- const record = this.persistBest(w.modelKey, w.profile, w.cand);
5275
+ const record = this.persistBest(w.modelKey, w.profile, w.cand, w.engineId);
5192
5276
  this.queueTelemetry(record, w.entry, w.sys, this.version, w.engineVersion);
5193
5277
  this.winning = null;
5194
5278
  this.state = { ...this.state, result: void 0 };
@@ -5257,7 +5341,7 @@ var BenchRunner = class {
5257
5341
  this.runLogMeta = { modelKey, startedAt: (/* @__PURE__ */ new Date()).toISOString(), sys };
5258
5342
  const active = this.registry.active();
5259
5343
  const caps = active?.capabilities ?? { flags: [], kvTypes: [] };
5260
- const saved = this.store.snapshot().modelProfiles[modelKey];
5344
+ const saved = getModelProfile(this.store.snapshot(), modelKey, active?.id ?? "*");
5261
5345
  const defaults = this.store.snapshot().modelDefaults;
5262
5346
  const resolved = resolveProfile(entry, sys, saved, base2, defaults);
5263
5347
  const baseProfile = { ...resolved, speculative: "off", mtpHeadPath: "", draftModelPath: "" };
@@ -5293,7 +5377,7 @@ var BenchRunner = class {
5293
5377
  return;
5294
5378
  }
5295
5379
  const profile = recommended && hasAnySampling(recommended) ? { ...best.profile, sampling: { ...best.profile.sampling, ...recommended } } : best.profile;
5296
- 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 ?? "" };
5297
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 };
5298
5382
  this.state = {
5299
5383
  running: false,
@@ -5843,7 +5927,7 @@ var BenchRunner = class {
5843
5927
  }
5844
5928
  /** Save the winning profile as the model's saved profile (tunedBy:'bench') and
5845
5929
  * persist a benchResults row. Both via the same ConfigStore the route uses. */
5846
- persistBest(modelKey, profile, cand) {
5930
+ persistBest(modelKey, profile, cand, engineId) {
5847
5931
  const record = {
5848
5932
  modelKey,
5849
5933
  tps: cand.tps ?? 0,
@@ -5854,7 +5938,7 @@ var BenchRunner = class {
5854
5938
  };
5855
5939
  const tuned = { ...profile, tunedBy: "bench" };
5856
5940
  this.store.update((cfg2) => {
5857
- cfg2.modelProfiles[modelKey] = tuned;
5941
+ setModelProfile(cfg2, modelKey, engineId || "*", tuned);
5858
5942
  cfg2.benchResults[modelKey] = record;
5859
5943
  });
5860
5944
  return record;
@@ -6174,7 +6258,7 @@ var ModelRouter = class {
6174
6258
  const cfg2 = this.store.snapshot();
6175
6259
  const sys = getSysInfo();
6176
6260
  if (entry.format !== "gguf") {
6177
- const savedProfile = cfg2.modelProfiles[entry.key];
6261
+ const savedProfile = getModelProfile(cfg2, entry.key, engine.id);
6178
6262
  return {
6179
6263
  engine,
6180
6264
  model: { key: entry.key, name: entry.name, quant: entry.quant, ctx: entry.nativeCtx, vision: false },
@@ -6184,7 +6268,7 @@ var ModelRouter = class {
6184
6268
  tensorParallelSize: savedProfile?.gpu?.tensorParallelSize
6185
6269
  };
6186
6270
  }
6187
- const saved = cfg2.modelProfiles[entry.key];
6271
+ const saved = getModelProfile(cfg2, entry.key, engine.id);
6188
6272
  const profile = resolveProfile(entry, sys, saved, void 0, cfg2.modelDefaults);
6189
6273
  const extraArgs = engine.kind === "koboldcpp" ? koboldcppProfileToArgs(profile, sys.gpus[0]?.vendor ?? "unknown", sys.gpus.length > 0) : profileToArgs(profile, entry, engine.capabilities, sys.cores);
6190
6274
  return {
@@ -8482,6 +8566,28 @@ async function body(c) {
8482
8566
  }
8483
8567
  function registerChatRoutes(app2, d) {
8484
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
+ });
8485
8591
  app2.get("/api/v1/conversations", (c) => {
8486
8592
  const q = c.req.query("q");
8487
8593
  return c.json({ conversations: db2.listConversations(q, "chat") });
@@ -8507,6 +8613,16 @@ function registerChatRoutes(app2, d) {
8507
8613
  if (!ok) return err(c, 404, "not_found", "Conversation not found.");
8508
8614
  return c.json({ ok: true });
8509
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
+ });
8510
8626
  app2.post("/api/v1/conversations/:id/messages", async (c) => {
8511
8627
  const convId = c.req.param("id");
8512
8628
  const b = await body(c);
@@ -11181,7 +11297,7 @@ function registerApi(app2, d) {
11181
11297
  }
11182
11298
  let opts2;
11183
11299
  if (entry.format !== "gguf") {
11184
- const savedProfile = cfg2.modelProfiles[entry.key];
11300
+ const savedProfile = getModelProfile(cfg2, entry.key, active.id);
11185
11301
  const extraArgs = active.kind === "mlx" ? mlxSamplingArgs(savedProfile?.sampling) : active.kind === "vllm" ? vllmProfileToArgs(resolveProfile(entry, sys, savedProfile, b.profileOverrides, cfg2.modelDefaults)) : [];
11186
11302
  opts2 = {
11187
11303
  engine: active,
@@ -11191,7 +11307,7 @@ function registerApi(app2, d) {
11191
11307
  tensorParallelSize: savedProfile?.gpu?.tensorParallelSize
11192
11308
  };
11193
11309
  } else {
11194
- const saved = cfg2.modelProfiles[entry.key];
11310
+ const saved = getModelProfile(cfg2, entry.key, active.id);
11195
11311
  const profile = resolveProfile(entry, sys, saved, b.profileOverrides, cfg2.modelDefaults);
11196
11312
  const extraArgs = active.kind === "koboldcpp" ? koboldcppProfileToArgs(profile, primaryVendor(sys), sys.gpus.length > 0) : profileToArgs(profile, entry, active.capabilities, sys.cores);
11197
11313
  opts2 = {
@@ -11382,7 +11498,8 @@ function registerApi(app2, d) {
11382
11498
  if (!e) return err2(c, 404, "no_such_model", "No model with that key.");
11383
11499
  const sys = getSysInfo();
11384
11500
  const snap = d.store.snapshot();
11385
- const saved = snap.modelProfiles[e.key];
11501
+ const engineId = c.req.query("engine") || d.registry.active()?.id || "*";
11502
+ const saved = getModelProfile(snap, e.key, engineId);
11386
11503
  const profile = resolveProfile(e, sys, saved, void 0, snap.modelDefaults);
11387
11504
  return c.json({ ...overlayModel(e, d), profile, vramFit: estimateVram(profile, e, sys), gpu: sys.gpus[0] ?? null, gpus: sys.gpus, cores: sys.cores });
11388
11505
  });
@@ -11409,15 +11526,17 @@ function registerApi(app2, d) {
11409
11526
  return err2(c, 400, "invalid_profile_value", "gpu.tensorParallelSize must be an integer \u2265 1.");
11410
11527
  }
11411
11528
  }
11529
+ const engineId = c.req.query("engine") || d.registry.active()?.id || "*";
11412
11530
  d.store.update((cfg2) => {
11413
- cfg2.modelProfiles[key] = p;
11531
+ setModelProfile(cfg2, key, engineId, p);
11414
11532
  });
11415
11533
  return c.json(p);
11416
11534
  });
11417
11535
  app2.post("/api/v1/models/:key/profile/reset", (c) => {
11418
11536
  const key = decodeURIComponent(c.req.param("key"));
11537
+ const engineId = c.req.query("engine") || d.registry.active()?.id || "*";
11419
11538
  d.store.update((cfg2) => {
11420
- delete cfg2.modelProfiles[key];
11539
+ deleteModelProfile(cfg2, key, engineId);
11421
11540
  });
11422
11541
  return c.json({ ok: true });
11423
11542
  });
@@ -11819,7 +11938,8 @@ function overlayModel(e, d, lastTpsMap) {
11819
11938
  const compatibleWithActiveEngine = active ? engineAcceptsFormat(active.kind, e.format) : true;
11820
11939
  const provRepo = d.downloads.provenance().find((p) => p.dest === e.path && p.repo)?.repo;
11821
11940
  const sourceRepo = provRepo ?? inferRepoFromPath(e.path, snap.modelDirs);
11822
- return { ...e, loaded, hasProfile: e.key in profiles, lastTps, liveTps, benchTps, compatibleWithActiveEngine, sourceRepo };
11941
+ const hasProfile = !!profiles[e.key] && Object.keys(profiles[e.key]).length > 0;
11942
+ return { ...e, loaded, hasProfile, lastTps, liveTps, benchTps, compatibleWithActiveEngine, sourceRepo };
11823
11943
  }
11824
11944
  function formatMismatchMessage(engineKind, format) {
11825
11945
  if (engineKind === "mlx")
@@ -13156,7 +13276,7 @@ void (async () => {
13156
13276
  extraArgs: []
13157
13277
  };
13158
13278
  } else {
13159
- const saved = cfg.modelProfiles[entry.key];
13279
+ const saved = getModelProfile(cfg, entry.key, active.id);
13160
13280
  const profile = resolveProfile(entry, sys, saved, void 0, cfg.modelDefaults);
13161
13281
  opts = {
13162
13282
  engine: active,
@@ -1,2 +1,2 @@
1
- import{j as e}from"./radix-jAkiiept.js";import{r as u}from"./vendor-W5GIuFcv.js";import{a as w,d as k,A as T,u as v,L as h,e as S,c as b,f as C,g as q,X as A}from"./index-DxM70nEl.js";import{P as N}from"./plus-Ct45XaAU.js";import{C as y}from"./circle-x-zQ-pYHWJ.js";import{C as O}from"./circle-check-B6TnDVnx.js";import{W as E}from"./wrench-BtDcuPD1.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(`
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};