turbollm 1.5.0 → 1.5.2

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 (91) hide show
  1. package/README.md +9 -0
  2. package/dist/cli.js +71 -43
  3. package/dist/webdist/assets/AgentsScreen-BISmGjbo.js +2 -0
  4. package/dist/webdist/assets/ChatScreen-CdGovrD_.js +57 -0
  5. package/dist/webdist/assets/{CustomizeScreen-BvwLXcaF.js → CustomizeScreen-vCdaeaYm.js} +1 -1
  6. package/dist/webdist/assets/{DeveloperScreen-Cf_BjtjZ.js → DeveloperScreen-DTDECTfQ.js} +1 -1
  7. package/dist/webdist/assets/{EnginesScreen-C-ny7PFc.js → EnginesScreen-BLqB4B4s.js} +3 -3
  8. package/dist/webdist/assets/ModelDetailDialog-CmcauyUd.js +1 -0
  9. package/dist/webdist/assets/{ModelDirs-V6R_2wmI.js → ModelDirs-CVl421kH.js} +1 -1
  10. package/dist/webdist/assets/ModelsScreen-BsjQ-rXT.js +1 -0
  11. package/dist/webdist/assets/SettingsScreen-DZQ1lIIa.js +1 -0
  12. package/dist/webdist/assets/{arc-C-BB3uXs.js → arc-BOg2ZUVv.js} +1 -1
  13. package/dist/webdist/assets/{architectureDiagram-3BPJPVTR-CcJ0ZOqZ.js → architectureDiagram-3BPJPVTR-JA6U-k6V.js} +1 -1
  14. package/dist/webdist/assets/{blockDiagram-GPEHLZMM-ALJWfOwg.js → blockDiagram-GPEHLZMM-CeFKl5gt.js} +1 -1
  15. package/dist/webdist/assets/{c4Diagram-AAUBKEIU-BaIxyDOO.js → c4Diagram-AAUBKEIU-NAYIvLue.js} +1 -1
  16. package/dist/webdist/assets/channel-R-Cc4MxS.js +1 -0
  17. package/dist/webdist/assets/check-BEf12PVV.js +1 -0
  18. package/dist/webdist/assets/{chunk-2J33WTMH-EWXF5El7.js → chunk-2J33WTMH-CZAFUB1n.js} +1 -1
  19. package/dist/webdist/assets/{chunk-4BX2VUAB-B01XIIl5.js → chunk-4BX2VUAB-BwDeI7tm.js} +1 -1
  20. package/dist/webdist/assets/{chunk-55IACEB6-CAORK4LB.js → chunk-55IACEB6-CrMcwFFf.js} +1 -1
  21. package/dist/webdist/assets/{chunk-727SXJPM-Bow98gCW.js → chunk-727SXJPM-B8pvS-2b.js} +1 -1
  22. package/dist/webdist/assets/{chunk-AQP2D5EJ-D3ihkUfL.js → chunk-AQP2D5EJ-4nG2xg8B.js} +1 -1
  23. package/dist/webdist/assets/{chunk-FMBD7UC4-B3pZV5W8.js → chunk-FMBD7UC4-4dS81TVH.js} +1 -1
  24. package/dist/webdist/assets/{chunk-ND2GUHAM-Bpfzzi3w.js → chunk-ND2GUHAM-BOND_2fr.js} +1 -1
  25. package/dist/webdist/assets/{chunk-QZHKN3VN-BAr49pW1.js → chunk-QZHKN3VN-uo5AhgZ_.js} +1 -1
  26. package/dist/webdist/assets/{circle-check-Bxq6YJMS.js → circle-check-BM1U1LBh.js} +1 -1
  27. package/dist/webdist/assets/{circle-x-DEBqweG-.js → circle-x-BQVgQDWt.js} +1 -1
  28. package/dist/webdist/assets/classDiagram-4FO5ZUOK-Bo30oFs_.js +1 -0
  29. package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-Bo30oFs_.js +1 -0
  30. package/dist/webdist/assets/{common-BSLr1MvR.js → common-CREyTFTu.js} +1 -1
  31. package/dist/webdist/assets/{copy-button-SltJUQtH.js → copy-button-CCE6xN9X.js} +1 -1
  32. package/dist/webdist/assets/{cose-bilkent-S5V4N54A-BEX523vE.js → cose-bilkent-S5V4N54A-C2Hznw_T.js} +1 -1
  33. package/dist/webdist/assets/{dagre-BM42HDAG-De5xizkB.js → dagre-BM42HDAG-DfgRJcxi.js} +1 -1
  34. package/dist/webdist/assets/{diagram-2AECGRRQ-Dlcd7uUE.js → diagram-2AECGRRQ-B3NKQXs-.js} +1 -1
  35. package/dist/webdist/assets/{diagram-5GNKFQAL-CflK9di5.js → diagram-5GNKFQAL-CsddCSIK.js} +1 -1
  36. package/dist/webdist/assets/{diagram-KO2AKTUF-Fbl7G2bU.js → diagram-KO2AKTUF-B-0Scyhf.js} +1 -1
  37. package/dist/webdist/assets/{diagram-LMA3HP47-BC3FH92a.js → diagram-LMA3HP47-zZMoAxon.js} +1 -1
  38. package/dist/webdist/assets/{diagram-OG6HWLK6-DkL3gfhn.js → diagram-OG6HWLK6-DkhKqVoh.js} +1 -1
  39. package/dist/webdist/assets/{dialog-D1-Zlj1l.js → dialog-Bu_dNoGl.js} +1 -1
  40. package/dist/webdist/assets/{erDiagram-TEJ5UH35-DGD3ClXg.js → erDiagram-TEJ5UH35-N3kX36Fb.js} +1 -1
  41. package/dist/webdist/assets/{flowDiagram-I6XJVG4X-CHzMVGFr.js → flowDiagram-I6XJVG4X-MBmSjZLW.js} +1 -1
  42. package/dist/webdist/assets/{ganttDiagram-6RSMTGT7-CAB-ravM.js → ganttDiagram-6RSMTGT7-BemcAo3u.js} +1 -1
  43. package/dist/webdist/assets/{gitGraphDiagram-PVQCEYII-DmhgBW8a.js → gitGraphDiagram-PVQCEYII-C3Z2MeCR.js} +1 -1
  44. package/dist/webdist/assets/html2canvas.esm-DXEQVQnt.js +5 -0
  45. package/dist/webdist/assets/{index-B7LTDUDR.js → index-C07fQlaV.js} +3 -3
  46. package/dist/webdist/assets/index-FmTuHPCZ.css +1 -0
  47. package/dist/webdist/assets/{infoDiagram-5YYISTIA-BswUlelU.js → infoDiagram-5YYISTIA-CJ9bQKM4.js} +1 -1
  48. package/dist/webdist/assets/{ishikawaDiagram-YF4QCWOH-DZENhuS5.js → ishikawaDiagram-YF4QCWOH-DAU8qHuy.js} +1 -1
  49. package/dist/webdist/assets/{journeyDiagram-JHISSGLW-D4BKn3fd.js → journeyDiagram-JHISSGLW-DYWLdObC.js} +1 -1
  50. package/dist/webdist/assets/{kanban-definition-UN3LZRKU-C4uVyQVS.js → kanban-definition-UN3LZRKU-SRRfAaYQ.js} +1 -1
  51. package/dist/webdist/assets/{linear-BwE47XoP.js → linear-Cn3E4T4R.js} +1 -1
  52. package/dist/webdist/assets/{mermaid.core-DdEuSeXx.js → mermaid.core-D0LNV0aO.js} +4 -4
  53. package/dist/webdist/assets/{mindmap-definition-RKZ34NQL-DxPkmqjP.js → mindmap-definition-RKZ34NQL-4OzvUsMs.js} +1 -1
  54. package/dist/webdist/assets/{pencil-BavbRBL9.js → pencil-Ds4UYumb.js} +1 -1
  55. package/dist/webdist/assets/personas-C4oTeImA.js +280 -0
  56. package/dist/webdist/assets/{pieDiagram-4H26LBE5-CUdk_VXR.js → pieDiagram-4H26LBE5-DnUMRqxT.js} +1 -1
  57. package/dist/webdist/assets/{plus-BEY4GU1K.js → plus-3UMxyIEk.js} +1 -1
  58. package/dist/webdist/assets/{quadrantDiagram-W4KKPZXB-MT8mFTQj.js → quadrantDiagram-W4KKPZXB-CuGEtBna.js} +1 -1
  59. package/dist/webdist/assets/{radix-DX0vbZxT.js → radix-4XwKfE88.js} +1 -1
  60. package/dist/webdist/assets/{refresh-cw-B2qwQZOz.js → refresh-cw-kd3J_RQJ.js} +1 -1
  61. package/dist/webdist/assets/{requirementDiagram-4Y6WPE33-C5lcbiaL.js → requirementDiagram-4Y6WPE33-D6JrM-I-.js} +1 -1
  62. package/dist/webdist/assets/{sankeyDiagram-5OEKKPKP-gCtNl8Ay.js → sankeyDiagram-5OEKKPKP-7SVXztDZ.js} +1 -1
  63. package/dist/webdist/assets/{save-B5yXIJpT.js → save-CTNv_c8L.js} +1 -1
  64. package/dist/webdist/assets/{sequenceDiagram-3UESZ5HK-CZm2Qqlo.js → sequenceDiagram-3UESZ5HK-DBPkd_Mz.js} +1 -1
  65. package/dist/webdist/assets/skeleton-D4pn0jcp.js +1 -0
  66. package/dist/webdist/assets/{stateDiagram-AJRCARHV-BBf-D1_B.js → stateDiagram-AJRCARHV-CeJn57NH.js} +1 -1
  67. package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-64xpB2-o.js +1 -0
  68. package/dist/webdist/assets/{timeline-definition-PNZ67QCA-pZHclsB5.js → timeline-definition-PNZ67QCA-4wGzSbT-.js} +1 -1
  69. package/dist/webdist/assets/{trash-2-BOk5wJqV.js → trash-2-Ct_313d6.js} +1 -1
  70. package/dist/webdist/assets/{vendor-Bhj2wMgn.js → vendor-CKtmMAvG.js} +1 -1
  71. package/dist/webdist/assets/{vennDiagram-CIIHVFJN-D0G0bNDg.js → vennDiagram-CIIHVFJN-C07Iks1t.js} +1 -1
  72. package/dist/webdist/assets/{wardley-L42UT6IY-CVfbre9P.js → wardley-L42UT6IY-CkjQxAi3.js} +1 -1
  73. package/dist/webdist/assets/{wardleyDiagram-YWT4CUSO-DpJ-M6Rq.js → wardleyDiagram-YWT4CUSO-CrfLbu_T.js} +1 -1
  74. package/dist/webdist/assets/{wrench-B_UtGGtF.js → wrench-CBGqccQu.js} +1 -1
  75. package/dist/webdist/assets/{xychartDiagram-2RQKCTM6-D1Ye9Jsh.js → xychartDiagram-2RQKCTM6-VlKQgaXq.js} +1 -1
  76. package/dist/webdist/index.html +4 -4
  77. package/package.json +1 -1
  78. package/dist/webdist/assets/AgentsScreen-C8ijrgPm.js +0 -2
  79. package/dist/webdist/assets/ChatScreen-BLAaAAkO.js +0 -48
  80. package/dist/webdist/assets/ModelDetailDialog-64XpCDO6.js +0 -1
  81. package/dist/webdist/assets/ModelsScreen-D8EkblWt.js +0 -1
  82. package/dist/webdist/assets/SettingsScreen-khqKELRg.js +0 -1
  83. package/dist/webdist/assets/channel-xWwBhwfN.js +0 -1
  84. package/dist/webdist/assets/check-Dg0H4Og5.js +0 -1
  85. package/dist/webdist/assets/classDiagram-4FO5ZUOK-CWFRccJj.js +0 -1
  86. package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-CWFRccJj.js +0 -1
  87. package/dist/webdist/assets/index-DnCwNfT4.css +0 -1
  88. package/dist/webdist/assets/personas-uLVxW5Bm.js +0 -77
  89. package/dist/webdist/assets/skeleton-CrqR3g7t.js +0 -1
  90. package/dist/webdist/assets/sparkles-DlKKkTEa.js +0 -1
  91. package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-DG-bUrT1.js +0 -1
package/README.md CHANGED
@@ -17,6 +17,8 @@
17
17
  <img src="https://img.shields.io/badge/node-%E2%89%A522-3c873a.svg" alt="node >= 22" />
18
18
  <img src="https://img.shields.io/badge/license-FSL--1.1--ALv2-blue.svg" alt="license" />
19
19
  <img src="https://img.shields.io/badge/platform-Windows%20%C2%B7%20macOS%20%C2%B7%20Linux-555.svg" alt="platforms" />
20
+ <a href="https://ko-fi.com/mohitsoni"><img src="https://img.shields.io/badge/Ko--fi-support%20us-FF5E5B?logo=kofi&logoColor=white" alt="Ko-fi" /></a>
21
+ <a href="https://discord.gg/v6kRbV7nC"><img src="https://img.shields.io/badge/Discord-join%20chat-5865F2?logo=discord&logoColor=white" alt="Discord" /></a>
20
22
  </p>
21
23
 
22
24
  <!-- Brand: shipped app icon web/public/brand/turbollm-icon-512.jpeg · high-res masters web/brand-assets/ (unshipped) · in-app mark web/src/components/Logo.tsx · favicon web/public/favicon.svg -->
@@ -51,6 +53,7 @@ LLMs** — built for people who today hand-compile forks and hunt forums for the
51
53
  - [How TurboLLM compares](#how-turbollm-compares)
52
54
  - [Troubleshooting](#troubleshooting)
53
55
  - [Develop from source](#develop-from-source)
56
+ - [Community](#community)
54
57
  - [License](#license)
55
58
 
56
59
  ---
@@ -482,6 +485,12 @@ shadcn/ui frontend. One TypeScript codebase, shipped as an npm package.
482
485
 
483
486
  ---
484
487
 
488
+ ## Community
489
+
490
+ Questions, ideas, and show-and-tell — join the [Discord](https://discord.gg/v6kRbV7nC).
491
+
492
+ ---
493
+
485
494
  ## License
486
495
 
487
496
  Source-available under the **Functional Source License 1.1 (Apache-2.0 future grant)** — SPDX
package/dist/cli.js CHANGED
@@ -658,9 +658,24 @@ async function ensureMlxEnv(root, onProgress, upgrade = false) {
658
658
  const uv = await ensureUv(root, onProgress);
659
659
  const envDir = join3(root, "mlx", "venv");
660
660
  const py = venvPython(envDir);
661
+ if (existsSync3(py)) {
662
+ let compatible = false;
663
+ try {
664
+ const { stdout } = await execFileP2(py, ["--version"], { timeout: 5e3 });
665
+ const m = stdout.match(/Python 3\.(\d+)/);
666
+ if (m) {
667
+ const minor = Number(m[1]);
668
+ compatible = minor >= 10 && minor <= 13;
669
+ }
670
+ } catch {
671
+ }
672
+ if (!compatible) rmSync2(envDir, { recursive: true, force: true });
673
+ }
661
674
  if (!existsSync3(py)) {
662
675
  onProgress?.({ phase: "extracting", pct: -1 });
663
- await execFileP2(uv, ["venv", envDir], { cwd: root });
676
+ const PINNED_PYTHON = "cpython-3.12-macos-aarch64";
677
+ const venvArgs = ["venv", "--python", PINNED_PYTHON, ...existsSync3(envDir) ? ["--clear"] : [], envDir];
678
+ await execFileP2(uv, venvArgs, { cwd: root });
664
679
  }
665
680
  onProgress?.({ phase: "extracting", pct: -1 });
666
681
  const installArgs = ["pip", "install", "--python", py, ...upgrade ? ["--upgrade"] : [], "mlx-lm"];
@@ -3661,6 +3676,8 @@ function profileToArgs(p, m, caps, cores = 0) {
3661
3676
  const threads = p.threads > 0 ? p.threads : cores > 0 ? Math.max(1, Math.floor(cores / 2)) : 0;
3662
3677
  if (threads > 0) a.push("--threads", String(threads));
3663
3678
  if (p.threadsBatch > 0) a.push("--threads-batch", String(p.threadsBatch));
3679
+ if (p.batchSize && p.batchSize > 0 && has("--batch-size")) a.push("--batch-size", String(p.batchSize));
3680
+ if (p.uBatchSize && p.uBatchSize > 0 && has("--ubatch-size")) a.push("--ubatch-size", String(p.uBatchSize));
3664
3681
  if (m.vision && p.useMmproj && m.mmprojPath) a.push("--mmproj", m.mmprojPath);
3665
3682
  if (m.vision && p.useMmproj && !p.mmprojGpu && has("--no-mmproj-offload")) a.push("--no-mmproj-offload");
3666
3683
  if (p.imageMaxTokens > 0 && has("--image-max-tokens")) a.push("--image-max-tokens", String(p.imageMaxTokens));
@@ -4324,7 +4341,7 @@ var DownloadManager = class {
4324
4341
  const u = input.url.trim();
4325
4342
  if (!/^https?:\/\//i.test(u)) throw new DownloadError("invalid_url", "URL must start with http:// or https://.");
4326
4343
  const path = safePathname(u);
4327
- if (!subdir && !/\.gguf$/i.test(path) && !HF_BLOB_RE.test(u)) {
4344
+ if (!subdir && !/\.gguf$/i.test(path) && !HF_BLOB_RE.test(u.split("?")[0])) {
4328
4345
  throw new DownloadError("invalid_url", "URL must point to a .gguf file.");
4329
4346
  }
4330
4347
  filename = basename2(path);
@@ -4767,6 +4784,9 @@ var BenchRunner = class {
4767
4784
  // stop/restart/load (kill switches) interrupts auto-tune immediately rather than
4768
4785
  // waiting out the current candidate's request.
4769
4786
  abort = null;
4787
+ runLog = [];
4788
+ runLogMeta = null;
4789
+ logWinner = null;
4770
4790
  // The finished run's winning candidate, held (not persisted) until the user clicks Save.
4771
4791
  winning = null;
4772
4792
  /** Live state for GET /status. */
@@ -4805,6 +4825,24 @@ var BenchRunner = class {
4805
4825
  const deadline = Date.now() + timeoutMs;
4806
4826
  while (this.state.running && Date.now() < deadline) await sleep2(150);
4807
4827
  }
4828
+ getLog() {
4829
+ if (!this.runLogMeta) return null;
4830
+ const { modelKey, startedAt: startedAt2, sys } = this.runLogMeta;
4831
+ return {
4832
+ modelKey,
4833
+ startedAt: startedAt2,
4834
+ hardware: {
4835
+ gpus: sys.gpus.map((g) => ({ name: g.name, vramMb: g.vramMb })),
4836
+ ramMb: sys.ramMB,
4837
+ os: sys.os
4838
+ },
4839
+ entries: [...this.runLog],
4840
+ winner: this.logWinner
4841
+ };
4842
+ }
4843
+ emit(step, candidate) {
4844
+ this.runLog.push({ ts: (/* @__PURE__ */ new Date()).toISOString(), step, ...candidate ? { candidate } : {} });
4845
+ }
4808
4846
  /** Start a run for `modelKey`. Rejects (throws BenchError) when a run is already
4809
4847
  * active, the engine is busy, or the model isn't a benchmarkable GGUF. The run
4810
4848
  * itself proceeds in the background; callers get 202 + poll /status. */
@@ -4826,6 +4864,9 @@ var BenchRunner = class {
4826
4864
  this.cancelled = false;
4827
4865
  this.abort = new AbortController();
4828
4866
  this.winning = null;
4867
+ this.runLog = [];
4868
+ this.runLogMeta = null;
4869
+ this.logWinner = null;
4829
4870
  this.deadline = Date.now() + TOTAL_BUDGET_MS;
4830
4871
  this.state = { running: true, modelKey, step: "Preparing\u2026", candidates: [] };
4831
4872
  void this.run(modelKey, entry, base2).catch((e) => {
@@ -4837,6 +4878,7 @@ var BenchRunner = class {
4837
4878
  // ---- the run ------------------------------------------------------------
4838
4879
  async run(modelKey, entry, base2) {
4839
4880
  const sys = getSysInfo();
4881
+ this.runLogMeta = { modelKey, startedAt: (/* @__PURE__ */ new Date()).toISOString(), sys };
4840
4882
  const active = this.registry.active();
4841
4883
  const caps = active?.capabilities ?? { flags: [], kvTypes: [] };
4842
4884
  const saved = this.store.snapshot().modelProfiles[modelKey];
@@ -4852,6 +4894,7 @@ var BenchRunner = class {
4852
4894
  kvToTune = decideKvToBench(spread, kvCandidates);
4853
4895
  const swing = spread < 0 ? "unknown" : spread >= Number.MAX_SAFE_INTEGER ? "huge" : `${Math.round(spread)} MB`;
4854
4896
  this.state = { ...this.state, step: `KV cache swing ${swing} \u2192 tuning ${kvToTune.join(" + ")}`, candidates: results };
4897
+ this.emit(`KV spread ${swing} \u2192 tuning ${kvToTune.join(" + ")}`);
4855
4898
  }
4856
4899
  for (let i = 0; i < kvToTune.length && !this.cancelled && Date.now() <= this.deadline; i++) {
4857
4900
  const kv = kvToTune[i];
@@ -4875,6 +4918,7 @@ var BenchRunner = class {
4875
4918
  }
4876
4919
  const profile = recommended && hasAnySampling(recommended) ? { ...best.profile, sampling: { ...best.profile.sampling, ...recommended } } : best.profile;
4877
4920
  this.winning = { modelKey, profile, cand: best.cand, entry, sys, engineVersion: active?.version ?? "" };
4921
+ this.logWinner = { params: best.cand.params, tps: best.cand.tps ?? 0, prefillTps: best.cand.prefillTps, ttftMs: best.cand.ttftMs ?? 0, vramMb: best.cand.vramMb };
4878
4922
  this.state = {
4879
4923
  running: false,
4880
4924
  modelKey,
@@ -5021,7 +5065,7 @@ var BenchRunner = class {
5021
5065
  /** Record a VRAM-probe trial in the candidate list (tps/prefill are null — nothing was generated;
5022
5066
  * only the load outcome and absolute VRAM are known). */
5023
5067
  pushProbe(results, base2, knob, value, probe2) {
5024
- results.push({
5068
+ const probeCand = {
5025
5069
  label: `probe ${knob}=${value}`,
5026
5070
  params: {
5027
5071
  ctx: base2.ctx,
@@ -5037,7 +5081,9 @@ var BenchRunner = class {
5037
5081
  ttftMs: null,
5038
5082
  vramMb: null,
5039
5083
  vramAbsMb: probe2.vramAbsMb
5040
- });
5084
+ };
5085
+ results.push(probeCand);
5086
+ this.emit(`${probeCand.label} \u2192 ${probe2.outcome}${probe2.vramAbsMb != null ? ` (${probe2.vramAbsMb} MB)` : ""}`, probeCand);
5041
5087
  this.state = { ...this.state, candidates: results };
5042
5088
  }
5043
5089
  /** Phase 2: the single full prefill + t/s benchmark, at the offload the VRAM probe chose. Pushes
@@ -5046,6 +5092,7 @@ var BenchRunner = class {
5046
5092
  this.state = { ...this.state, step: `KV ${profile.kvTypeK}: measuring best (${label})\u2026`, candidates: results };
5047
5093
  const cand = await this.measure(entry, sys, profile, caps, label, `Measuring ${label}`);
5048
5094
  results.push(cand);
5095
+ this.emit(`bench ${label} \u2192 ${cand.outcome}${cand.tps != null ? ` (${cand.tps.toFixed(1)} tok/s)` : ""}`, cand);
5049
5096
  this.state = { ...this.state, candidates: results, bestTps: cand.tps ?? this.state.bestTps };
5050
5097
  await this.settleGpu();
5051
5098
  return cand.outcome === "ok" && cand.tps !== null ? { cand, profile } : null;
@@ -7363,6 +7410,7 @@ var AgentRunner = class {
7363
7410
  acs = /* @__PURE__ */ new Map();
7364
7411
  queue = [];
7365
7412
  active = null;
7413
+ interruptedIds = /* @__PURE__ */ new Set();
7366
7414
  constructor(d) {
7367
7415
  this.d = d;
7368
7416
  }
@@ -7440,11 +7488,13 @@ var AgentRunner = class {
7440
7488
  this.d.db.updateAgentRun?.(pending.id, { status: "done", endedAt: (/* @__PURE__ */ new Date()).toISOString() });
7441
7489
  } catch (e) {
7442
7490
  const isAbort = e?.name === "AbortError";
7443
- this.d.db.updateAgentRun?.(pending.id, {
7444
- status: isAbort ? "cancelled" : "failed",
7445
- error: isAbort ? void 0 : e.message,
7446
- endedAt: (/* @__PURE__ */ new Date()).toISOString()
7447
- });
7491
+ if (!this.interruptedIds.delete(pending.id)) {
7492
+ this.d.db.updateAgentRun?.(pending.id, {
7493
+ status: isAbort ? "cancelled" : "failed",
7494
+ error: isAbort ? void 0 : e.message,
7495
+ endedAt: (/* @__PURE__ */ new Date()).toISOString()
7496
+ });
7497
+ }
7448
7498
  } finally {
7449
7499
  this.finish(pending.id);
7450
7500
  }
@@ -7453,6 +7503,10 @@ var AgentRunner = class {
7453
7503
  this.emitters.get(id)?.emit("done");
7454
7504
  this.acs.delete(id);
7455
7505
  this.active = null;
7506
+ setTimeout(() => {
7507
+ this.buffers.delete(id);
7508
+ this.emitters.delete(id);
7509
+ }, 5e3);
7456
7510
  void this.processNext();
7457
7511
  }
7458
7512
  cancel(id) {
@@ -7473,6 +7527,7 @@ var AgentRunner = class {
7473
7527
  /** Called by the engine manager when the active model is evicted. */
7474
7528
  interruptActive() {
7475
7529
  if (!this.active) return;
7530
+ this.interruptedIds.add(this.active);
7476
7531
  this.acs.get(this.active)?.abort();
7477
7532
  this.d.db.updateAgentRun?.(this.active, { status: "interrupted", endedAt: (/* @__PURE__ */ new Date()).toISOString() });
7478
7533
  }
@@ -8021,30 +8076,6 @@ function abortAllInFlightChats() {
8021
8076
  inflight.clear();
8022
8077
  return n;
8023
8078
  }
8024
- var EXPERT_SYSTEM_PROMPT = `You are the TurboLLM in-app expert assistant \u2014 a knowledgeable, friendly guide built into TurboLLM, a local-first desktop app for running large language models on the user's own machine.
8025
-
8026
- Your job is to help the user get the most out of TurboLLM:
8027
- - Explain what TurboLLM features do and how to use them: chatting with local models, the Models screen (discover, download, load, and tune models), the Engines screen (install and manage inference backends like llama.cpp), and Settings (idle timeout, model defaults such as context length and GPU layers, auto-load, theme, network/LAN exposure, and privacy/telemetry).
8028
- - Help the user configure things: picking and loading a model, adjusting sampling (temperature, top-p, top-k, min-p), setting context length and GPU offload, and managing per-thread system prompts and sampling overrides.
8029
- - Troubleshoot common problems: a model that won't load, slow generation, running out of context, missing or failed engine installs, and GPU/CPU offload questions. Reason from the symptoms the user describes and the hardware they mention.
8030
-
8031
- Guidelines:
8032
- - Keep answers practical, concise, and actionable. Prefer concrete steps ("Open the Models screen, click \u2026") over abstract advice.
8033
- - When something depends on the user's hardware or which model is loaded, say so and ask a brief clarifying question if needed.
8034
- - Everything runs locally and offline; never suggest sending the user's data to external services.
8035
- - If you are unsure or a feature may not exist, say so honestly rather than inventing details.`;
8036
- function buildExpertPrompt() {
8037
- const sys = getSysInfo();
8038
- const ramGb = Math.round(sys.ramMB / 1024);
8039
- const gpuLines = sys.gpus.length ? sys.gpus.map((g) => `- GPU: ${g.name}${g.vramMb ? ` (${Math.round(g.vramMb / 1024)} GB VRAM)` : ""}`).join("\n") : "- GPU: none detected";
8040
- const hw = [
8041
- "\n\n## User's hardware",
8042
- `- CPU: ${sys.cpu}${sys.cores ? ` (${sys.cores} cores)` : ""}`,
8043
- `- RAM: ${ramGb} GB`,
8044
- gpuLines
8045
- ].join("\n");
8046
- return EXPERT_SYSTEM_PROMPT + hw;
8047
- }
8048
8079
  function err(c, s, code, msg2) {
8049
8080
  return c.json({ error: { code, message: msg2 } }, s);
8050
8081
  }
@@ -8066,16 +8097,6 @@ function registerChatRoutes(app2, d) {
8066
8097
  const conv = db2.createConversation({ title: b.title, systemPrompt: b.systemPrompt, modelKey: b.modelKey, toolPolicy: b.toolPolicy });
8067
8098
  return c.json(conv, 201);
8068
8099
  });
8069
- app2.post("/api/v1/conversations/expert", async (c) => {
8070
- const ms = d.manager.status();
8071
- const conv = db2.createConversation({
8072
- title: "TurboLLM Expert",
8073
- systemPrompt: buildExpertPrompt(),
8074
- modelKey: ms.model?.key ?? "",
8075
- expertMode: true
8076
- });
8077
- return c.json(conv, 201);
8078
- });
8079
8100
  app2.get("/api/v1/conversations/:id", (c) => {
8080
8101
  const conv = db2.getConversation(c.req.param("id"), true);
8081
8102
  if (!conv) return err(c, 404, "not_found", "Conversation not found.");
@@ -10650,6 +10671,13 @@ function registerApi(app2, d) {
10650
10671
  if (!saved) return err2(c, 409, "no_bench_result", "No auto-tune result to save.");
10651
10672
  return c.json({ ok: true });
10652
10673
  });
10674
+ app2.get("/api/v1/bench/log", (c) => {
10675
+ const log = d.bench.getLog();
10676
+ if (!log) return err2(c, 404, "no_bench_log", "No auto-tune log available.");
10677
+ const slug = log.modelKey.replace(/[^a-z0-9]/gi, "-").replace(/-+/g, "-").slice(0, 60);
10678
+ c.header("Content-Disposition", `attachment; filename="autotune-${slug}-${log.startedAt.slice(0, 10)}.json"`);
10679
+ return c.json(log);
10680
+ });
10653
10681
  app2.get("/api/v1/models", (c) => {
10654
10682
  const { models, scanning, lastScanAt } = d.scanner.list();
10655
10683
  const lastTps = d.db.lastGenTpsByModel();
@@ -0,0 +1,2 @@
1
+ import{j as e}from"./radix-4XwKfE88.js";import{r as u}from"./vendor-CKtmMAvG.js";import{c as w,d as k,A as T,e as v,L as h,f as S,b,g as C,h as q,X as A}from"./index-C07fQlaV.js";import{P as N}from"./plus-3UMxyIEk.js";import{C as y}from"./circle-x-BQVgQDWt.js";import{C as O}from"./circle-check-BM1U1LBh.js";import{W as E}from"./wrench-CBGqccQu.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
+ `);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};