research-copilot 0.2.8 → 0.2.9

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 (68) hide show
  1. package/app/out/main/index.mjs +6126 -2485
  2. package/app/out/preload/index.js +30 -0
  3. package/app/out/renderer/assets/{MilkdownMarkdownEditor-wtbKgEbm.js → MilkdownMarkdownEditor-CK0d0F6d.js} +50 -50
  4. package/app/out/renderer/assets/{arc-BqLtjO5D.js → arc-CgrgxBD6.js} +1 -1
  5. package/app/out/renderer/assets/{blockDiagram-c4efeb88-CHtvWbUe.js → blockDiagram-c4efeb88-BqZGW4Le.js} +8 -8
  6. package/app/out/renderer/assets/{c4Diagram-c83219d4-CGorl4p_.js → c4Diagram-c83219d4-gNbl9eSr.js} +3 -3
  7. package/app/out/renderer/assets/{channel-C4dnKIdE.js → channel-vUNI1iNi.js} +1 -1
  8. package/app/out/renderer/assets/{classDiagram-beda092f-CcVq8zgL.js → classDiagram-beda092f-jMkGJ9Ey.js} +6 -6
  9. package/app/out/renderer/assets/{classDiagram-v2-2358418a-C1T7WzEm.js → classDiagram-v2-2358418a-CNuFkLzW.js} +10 -10
  10. package/app/out/renderer/assets/{clone-DxlJpy1a.js → clone-rSI1fs-P.js} +1 -1
  11. package/app/out/renderer/assets/{createText-1719965b-C0d2kD0r.js → createText-1719965b-BXIo6gGa.js} +2 -2
  12. package/app/out/renderer/assets/{edges-96097737-CLaP5xIP.js → edges-96097737-nhmklBMU.js} +3 -3
  13. package/app/out/renderer/assets/{erDiagram-0228fc6a-MUP9rxkZ.js → erDiagram-0228fc6a-DAEu9LLq.js} +5 -5
  14. package/app/out/renderer/assets/{flowDb-c6c81e3f-BPAie9_m.js → flowDb-c6c81e3f-BCxc52Bt.js} +1 -1
  15. package/app/out/renderer/assets/{flowDiagram-50d868cf-wPPnXbEC.js → flowDiagram-50d868cf-DAeMm-oV.js} +12 -12
  16. package/app/out/renderer/assets/{flowDiagram-v2-4f6560a1-Bla11wXJ.js → flowDiagram-v2-4f6560a1-B3DiSW7H.js} +12 -12
  17. package/app/out/renderer/assets/{flowchart-elk-definition-6af322e1-D05H4IzP.js → flowchart-elk-definition-6af322e1-C3U45hIj.js} +6 -6
  18. package/app/out/renderer/assets/{ganttDiagram-a2739b55-_HXvgcdj.js → ganttDiagram-a2739b55-DH_BJl1v.js} +3 -3
  19. package/app/out/renderer/assets/{gitGraphDiagram-82fe8481-qK3Q1wq5.js → gitGraphDiagram-82fe8481-BQYI6jwt.js} +2 -2
  20. package/app/out/renderer/assets/{graph-C5ywDmPQ.js → graph-Cbmv2S9S.js} +1 -1
  21. package/app/out/renderer/assets/{index-eO4trtHi.js → index--rks7CK0.js} +3 -3
  22. package/app/out/renderer/assets/{index-5325376f-D3a5ZZZz.js → index-5325376f-tk-JBjUy.js} +6 -6
  23. package/app/out/renderer/assets/{index-CAV5KrKz.js → index-B05MKQl-.js} +3 -3
  24. package/app/out/renderer/assets/{index-DkxwQySh.js → index-BTNmK-qR.js} +3 -3
  25. package/app/out/renderer/assets/{index-MnlXVp99.js → index-BaHR_2Nj.js} +3 -3
  26. package/app/out/renderer/assets/{index-ByTHdZ7D.js → index-BdSZkB2P.js} +4 -4
  27. package/app/out/renderer/assets/{index-BD3NNxzl.js → index-Bg6-UTvh.js} +3 -3
  28. package/app/out/renderer/assets/{index-DD3Ny6Vp.js → index-BgoqHomD.js} +6 -6
  29. package/app/out/renderer/assets/{index-CsIY4rv7.js → index-BkmP_G4w.js} +3 -3
  30. package/app/out/renderer/assets/{index-0RdJfTuz.js → index-ByH1hCUC.js} +4 -4
  31. package/app/out/renderer/assets/{index-Ct5-4eln.js → index-C4WNjCL0.js} +6 -6
  32. package/app/out/renderer/assets/{index-CTYjTyUA.js → index-CdhMP7aL.js} +3 -3
  33. package/app/out/renderer/assets/{index-CsvJ1nbq.js → index-CqAzD5Mv.js} +6 -6
  34. package/app/out/renderer/assets/{index-DXSYlARx.js → index-DCdUaSNI.js} +3 -3
  35. package/app/out/renderer/assets/{index-BWngX05Q.js → index-DK2BzNnx.js} +3 -3
  36. package/app/out/renderer/assets/{index-NTc0rY_G.js → index-DU4yTtXH.js} +3 -3
  37. package/app/out/renderer/assets/{index-CviTrzVQ.js → index-DbWCHQ2E.js} +3 -3
  38. package/app/out/renderer/assets/{index-Bnopm9R6.js → index-DcOi0itd.js} +2076 -657
  39. package/app/out/renderer/assets/{index-Cvn9cLQy.js → index-Dk6CqgIG.js} +5 -5
  40. package/app/out/renderer/assets/{index-CJfHLqJM.js → index-Do8kanBG.js} +1 -1
  41. package/app/out/renderer/assets/{index-CUhOSCxv.js → index-HX-NQu3g.js} +6 -6
  42. package/app/out/renderer/assets/{index-BcyuyYWv.css → index-LbAr_1fx.css} +310 -75
  43. package/app/out/renderer/assets/{index-BPbilNff.js → index-QjeQ3sgb.js} +3 -3
  44. package/app/out/renderer/assets/{index-UKpTDcKF.js → index-hDvDO954.js} +6 -6
  45. package/app/out/renderer/assets/{index-BlphXQkB.js → index-xqpIz9Nc.js} +6 -6
  46. package/app/out/renderer/assets/{infoDiagram-8eee0895-Y5yOIBzh.js → infoDiagram-8eee0895-B7stNUSw.js} +2 -2
  47. package/app/out/renderer/assets/{journeyDiagram-c64418c1-nxv9KGN2.js → journeyDiagram-c64418c1-BmjjCYfG.js} +4 -4
  48. package/app/out/renderer/assets/{layout-QkNm3OqP.js → layout-BwUXWB3n.js} +2 -2
  49. package/app/out/renderer/assets/{line-CcMLwlNB.js → line-DAwxPIOb.js} +1 -1
  50. package/app/out/renderer/assets/{linear-DZFvfKB-.js → linear-DF3tF_pi.js} +1 -1
  51. package/app/out/renderer/assets/{mindmap-definition-8da855dc-BLROjqLP.js → mindmap-definition-8da855dc-Dcm6UXzX.js} +3 -3
  52. package/app/out/renderer/assets/{pieDiagram-a8764435-CEwkf3-w.js → pieDiagram-a8764435-DkGcoK7i.js} +3 -3
  53. package/app/out/renderer/assets/{quadrantDiagram-1e28029f-Wtu-X7yj.js → quadrantDiagram-1e28029f-C1Q8r814.js} +3 -3
  54. package/app/out/renderer/assets/{requirementDiagram-08caed73-BmHzmnoA.js → requirementDiagram-08caed73-C83u7Zho.js} +5 -5
  55. package/app/out/renderer/assets/{sankeyDiagram-a04cb91d-DR-ZBDLL.js → sankeyDiagram-a04cb91d-CmjgGu4H.js} +2 -2
  56. package/app/out/renderer/assets/{sequenceDiagram-c5b8d532-CvKwzJJS.js → sequenceDiagram-c5b8d532-B0tN0kuE.js} +3 -3
  57. package/app/out/renderer/assets/{stateDiagram-1ecb1508-D9DTeGP_.js → stateDiagram-1ecb1508-CvChhiL2.js} +6 -6
  58. package/app/out/renderer/assets/{stateDiagram-v2-c2b004d7-CFD8lymK.js → stateDiagram-v2-c2b004d7-DuV4iLKk.js} +10 -10
  59. package/app/out/renderer/assets/{styles-b4e223ce-rY8jQLYQ.js → styles-b4e223ce-CYC1lyng.js} +1 -1
  60. package/app/out/renderer/assets/{styles-ca3715f6-DkM1AvDm.js → styles-ca3715f6-wbpEm70M.js} +1 -1
  61. package/app/out/renderer/assets/{styles-d45a18b0-Cg53i9fc.js → styles-d45a18b0-dtPSe0gc.js} +4 -4
  62. package/app/out/renderer/assets/{svgDrawCommon-b86b1483-Dcml1adX.js → svgDrawCommon-b86b1483-DeFjfiKP.js} +1 -1
  63. package/app/out/renderer/assets/{timeline-definition-faaaa080-Dn53tAu-.js → timeline-definition-faaaa080-WkpCNoL6.js} +3 -3
  64. package/app/out/renderer/assets/{xychartDiagram-f5964ef8-Dur19n8N.js → xychartDiagram-f5964ef8-DhjmJVco.js} +5 -5
  65. package/app/out/renderer/index.html +2 -2
  66. package/app/package.json +3 -1
  67. package/lib/skills/builtin/marp-slides/SKILL.md +642 -0
  68. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./MilkdownMarkdownEditor-wtbKgEbm.js","./MilkdownMarkdownEditor-tTNRIB2K.css"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./MilkdownMarkdownEditor-CK0d0F6d.js","./MilkdownMarkdownEditor-tTNRIB2K.css"])))=>i.map(i=>d[i]);
2
2
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
3
3
  function getDefaultExportFromCjs(x) {
4
4
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
@@ -12524,6 +12524,16 @@ const createLucideIcon = (iconName, iconNode) => {
12524
12524
  Component2.displayName = `${iconName}`;
12525
12525
  return Component2;
12526
12526
  };
12527
+ /**
12528
+ * @license lucide-react v0.469.0 - ISC
12529
+ *
12530
+ * This source code is licensed under the ISC license.
12531
+ * See the LICENSE file in the root directory of this source tree.
12532
+ */
12533
+ const ArrowLeft = createLucideIcon("ArrowLeft", [
12534
+ ["path", { d: "m12 19-7-7 7-7", key: "1l729n" }],
12535
+ ["path", { d: "M19 12H5", key: "x3x0zl" }]
12536
+ ]);
12527
12537
  /**
12528
12538
  * @license lucide-react v0.469.0 - ISC
12529
12539
  *
@@ -12609,6 +12619,17 @@ const ChartColumn = createLucideIcon("ChartColumn", [
12609
12619
  ["path", { d: "M13 17V5", key: "1frdt8" }],
12610
12620
  ["path", { d: "M8 17v-3", key: "17ska0" }]
12611
12621
  ]);
12622
+ /**
12623
+ * @license lucide-react v0.469.0 - ISC
12624
+ *
12625
+ * This source code is licensed under the ISC license.
12626
+ * See the LICENSE file in the root directory of this source tree.
12627
+ */
12628
+ const ChartNoAxesColumn = createLucideIcon("ChartNoAxesColumn", [
12629
+ ["line", { x1: "18", x2: "18", y1: "20", y2: "10", key: "1xfpm4" }],
12630
+ ["line", { x1: "12", x2: "12", y1: "20", y2: "4", key: "be30l9" }],
12631
+ ["line", { x1: "6", x2: "6", y1: "20", y2: "14", key: "1r4le6" }]
12632
+ ]);
12612
12633
  /**
12613
12634
  * @license lucide-react v0.469.0 - ISC
12614
12635
  *
@@ -12673,6 +12694,16 @@ const CircleCheck = createLucideIcon("CircleCheck", [
12673
12694
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
12674
12695
  ["path", { d: "m9 12 2 2 4-4", key: "dzmm74" }]
12675
12696
  ]);
12697
+ /**
12698
+ * @license lucide-react v0.469.0 - ISC
12699
+ *
12700
+ * This source code is licensed under the ISC license.
12701
+ * See the LICENSE file in the root directory of this source tree.
12702
+ */
12703
+ const CircleDot = createLucideIcon("CircleDot", [
12704
+ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
12705
+ ["circle", { cx: "12", cy: "12", r: "1", key: "41hilf" }]
12706
+ ]);
12676
12707
  /**
12677
12708
  * @license lucide-react v0.469.0 - ISC
12678
12709
  *
@@ -13018,6 +13049,19 @@ const Monitor = createLucideIcon("Monitor", [
13018
13049
  const Moon = createLucideIcon("Moon", [
13019
13050
  ["path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z", key: "a7tn18" }]
13020
13051
  ]);
13052
+ /**
13053
+ * @license lucide-react v0.469.0 - ISC
13054
+ *
13055
+ * This source code is licensed under the ISC license.
13056
+ * See the LICENSE file in the root directory of this source tree.
13057
+ */
13058
+ const Network = createLucideIcon("Network", [
13059
+ ["rect", { x: "16", y: "16", width: "6", height: "6", rx: "1", key: "4q2zg0" }],
13060
+ ["rect", { x: "2", y: "16", width: "6", height: "6", rx: "1", key: "8cvhb9" }],
13061
+ ["rect", { x: "9", y: "2", width: "6", height: "6", rx: "1", key: "1egb70" }],
13062
+ ["path", { d: "M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3", key: "1jsf9p" }],
13063
+ ["path", { d: "M12 12V8", key: "2874zd" }]
13064
+ ]);
13021
13065
  /**
13022
13066
  * @license lucide-react v0.469.0 - ISC
13023
13067
  *
@@ -13125,22 +13169,6 @@ const Send = createLucideIcon("Send", [
13125
13169
  ],
13126
13170
  ["path", { d: "m21.854 2.147-10.94 10.939", key: "12cjpa" }]
13127
13171
  ]);
13128
- /**
13129
- * @license lucide-react v0.469.0 - ISC
13130
- *
13131
- * This source code is licensed under the ISC license.
13132
- * See the LICENSE file in the root directory of this source tree.
13133
- */
13134
- const Settings = createLucideIcon("Settings", [
13135
- [
13136
- "path",
13137
- {
13138
- d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z",
13139
- key: "1qme2f"
13140
- }
13141
- ],
13142
- ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
13143
- ]);
13144
13172
  /**
13145
13173
  * @license lucide-react v0.469.0 - ISC
13146
13174
  *
@@ -13302,7 +13330,7 @@ const Zap = createLucideIcon("Zap", [
13302
13330
  }
13303
13331
  ]
13304
13332
  ]);
13305
- const api$b = window.api;
13333
+ const api$g = window.api;
13306
13334
  const KEY_FIELDS = [
13307
13335
  {
13308
13336
  name: "ANTHROPIC_API_KEY",
@@ -13337,7 +13365,62 @@ const KEY_FIELDS = [
13337
13365
  required: false
13338
13366
  }
13339
13367
  ];
13340
- function ApiKeySetup({ onComplete }) {
13368
+ function UpdateBanner() {
13369
+ const [update, setUpdate] = reactExports.useState(null);
13370
+ const [dismissed, setDismissed] = reactExports.useState(false);
13371
+ const [copied, setCopied] = reactExports.useState(false);
13372
+ reactExports.useEffect(() => {
13373
+ api$g.checkForUpdate().then((info) => {
13374
+ if (info.hasUpdate) setUpdate(info);
13375
+ }).catch(() => {
13376
+ });
13377
+ }, []);
13378
+ if (!update || dismissed) return null;
13379
+ const command = "npm update -g research-copilot";
13380
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4 relative rounded-lg border t-border-subtle t-bg-elevated overflow-hidden", children: [
13381
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { "aria-hidden": true, className: "absolute left-0 top-0 bottom-0 w-[2px] t-bg-accent-soft" }),
13382
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-2.5 flex items-start gap-2.5", children: [
13383
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CircleArrowUp, { size: 15, className: "t-text-accent-soft mt-0.5 shrink-0" }),
13384
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
13385
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs font-medium t-text", children: [
13386
+ "v",
13387
+ update.latest,
13388
+ " available",
13389
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "t-text-secondary font-normal ml-1.5", children: [
13390
+ "(current: v",
13391
+ update.current,
13392
+ ")"
13393
+ ] })
13394
+ ] }),
13395
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1.5 flex items-center gap-1.5", children: [
13396
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "flex-1 text-[10px] font-mono px-2 py-1 rounded t-bg-surface t-text select-all", children: command }),
13397
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
13398
+ "button",
13399
+ {
13400
+ onClick: () => {
13401
+ navigator.clipboard.writeText(command);
13402
+ setCopied(true);
13403
+ setTimeout(() => setCopied(false), 2e3);
13404
+ },
13405
+ className: "shrink-0 p-1 rounded t-bg-surface hover:opacity-80 transition-opacity",
13406
+ title: "Copy command",
13407
+ children: copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 12, className: "t-text-success" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { size: 12, className: "t-text-secondary" })
13408
+ }
13409
+ )
13410
+ ] })
13411
+ ] }),
13412
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
13413
+ "button",
13414
+ {
13415
+ onClick: () => setDismissed(true),
13416
+ className: "shrink-0 p-0.5 rounded hover:t-bg-surface transition-colors t-text-secondary hover:t-text",
13417
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(X$1, { size: 12 })
13418
+ }
13419
+ )
13420
+ ] })
13421
+ ] });
13422
+ }
13423
+ function ApiKeysSettings({ showSaveButton, onSaved }) {
13341
13424
  const [values, setValues] = reactExports.useState({});
13342
13425
  const [status, setStatus] = reactExports.useState({});
13343
13426
  const [visible, setVisible] = reactExports.useState({});
@@ -13346,14 +13429,14 @@ function ApiKeySetup({ onComplete }) {
13346
13429
  const [codexStatus, setCodexStatus] = reactExports.useState(null);
13347
13430
  const [codexLoggingIn, setCodexLoggingIn] = reactExports.useState(false);
13348
13431
  reactExports.useEffect(() => {
13349
- api$b.getApiKeyStatus().then((s15) => setStatus(s15));
13350
- api$b.getOpenAICodexStatus?.().then((s15) => setCodexStatus(s15)).catch(() => {
13432
+ api$g.getApiKeyStatus().then((s15) => setStatus(s15));
13433
+ api$g.getOpenAICodexStatus?.().then((s15) => setCodexStatus(s15)).catch(() => {
13351
13434
  });
13352
13435
  }, []);
13353
13436
  const handleCodexLogin = async () => {
13354
13437
  setCodexLoggingIn(true);
13355
13438
  try {
13356
- const result = await api$b.openaiCodexLogin?.();
13439
+ const result = await api$g.openaiCodexLogin?.();
13357
13440
  if (result?.success) {
13358
13441
  setCodexStatus({ isLoggedIn: true });
13359
13442
  } else {
@@ -13365,148 +13448,632 @@ function ApiKeySetup({ onComplete }) {
13365
13448
  setCodexLoggingIn(false);
13366
13449
  }
13367
13450
  };
13368
- const hasAnyLlmKey = status.ANTHROPIC_API_KEY || status.OPENAI_API_KEY || codexStatus?.isLoggedIn || !!(values.ANTHROPIC_API_KEY || "").trim() || !!(values.OPENAI_API_KEY || "").trim();
13369
13451
  const handleSave = async () => {
13370
13452
  const entries = Object.entries(values).filter(([, v3]) => v3.trim());
13371
13453
  if (entries.length === 0 && !status.ANTHROPIC_API_KEY && !status.OPENAI_API_KEY && !codexStatus?.isLoggedIn) {
13372
- setError("Please enter at least one API key or sign in with ChatGPT to continue.");
13454
+ setError("Please enter at least one LLM API key or sign in with ChatGPT.");
13373
13455
  return;
13374
13456
  }
13375
13457
  setSaving(true);
13376
13458
  setError(null);
13377
13459
  try {
13378
13460
  for (const [key, val] of entries) {
13379
- await api$b.saveApiKey(key, val);
13461
+ await api$g.saveApiKey(key, val);
13380
13462
  }
13381
- onComplete();
13463
+ const newStatus = await api$g.getApiKeyStatus();
13464
+ setStatus(newStatus);
13465
+ setValues({});
13466
+ onSaved?.();
13382
13467
  } catch (err) {
13383
13468
  setError(err.message || "Failed to save keys");
13384
13469
  } finally {
13385
13470
  setSaving(false);
13386
13471
  }
13387
13472
  };
13388
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex h-screen w-screen t-bg-base t-text items-center justify-center", children: [
13389
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "drag-region fixed top-0 left-0 right-0 h-8 z-50" }),
13390
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full max-w-lg px-8", children: [
13391
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center mb-8", children: [
13392
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "relative mx-auto mb-6 w-fit", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
13393
- "div",
13394
- {
13395
- className: "w-14 h-14 rounded-2xl flex items-center justify-center t-gradient-accent t-gradient-accent-shadow-lg",
13396
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(Key, { className: "text-white", size: 22 })
13397
- }
13398
- ) }),
13399
- /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-xl font-semibold tracking-tight mb-1", children: "Configure API Keys" }),
13400
- /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "t-text-secondary text-[13px] leading-relaxed", children: [
13401
- "At least one LLM key (Anthropic or OpenAI) is required.",
13402
- /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
13403
- "Keys are stored locally in ",
13404
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "px-1 py-0.5 rounded t-bg-surface text-xs font-mono", children: "~/.research-copilot/config.json" })
13405
- ] })
13406
- ] }),
13407
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3 mb-6", children: KEY_FIELDS.map((field) => {
13408
- const alreadySet = status[field.name];
13409
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border t-border t-bg-surface/50 p-3", children: [
13410
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-1.5", children: [
13411
- /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-xs font-medium t-text flex items-center gap-1.5", children: [
13412
- field.label,
13413
- field.required && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] t-text-muted", children: "(required*)" }),
13414
- !field.required && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] t-text-muted", children: "(optional)" }),
13415
- alreadySet && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-0.5 text-[10px] text-green-500", children: [
13416
- /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 10 }),
13417
- " configured"
13418
- ] })
13419
- ] }),
13420
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
13421
- "a",
13422
- {
13423
- href: field.url,
13424
- target: "_blank",
13425
- rel: "noreferrer",
13426
- className: "text-[10px] t-text-muted hover:t-text flex items-center gap-0.5",
13427
- onClick: (e) => {
13428
- e.preventDefault();
13429
- window.open(field.url, "_blank");
13430
- },
13431
- children: [
13432
- "Get key ",
13433
- /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { size: 9 })
13434
- ]
13435
- }
13436
- )
13437
- ] }),
13438
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
13439
- /* @__PURE__ */ jsxRuntimeExports.jsx(
13440
- "input",
13441
- {
13442
- type: visible[field.name] ? "text" : "password",
13443
- className: "w-full text-xs px-2.5 py-1.5 rounded-md border t-border t-bg-base t-text font-mono pr-8\n focus:outline-none focus:ring-1 focus:ring-[var(--color-accent)]",
13444
- placeholder: alreadySet ? "•••••••• (already set — leave blank to keep)" : field.placeholder,
13445
- value: values[field.name] || "",
13446
- onChange: (e) => setValues((prev) => ({ ...prev, [field.name]: e.target.value })),
13447
- "aria-label": `${field.label} API key`
13448
- }
13449
- ),
13450
- /* @__PURE__ */ jsxRuntimeExports.jsx(
13451
- "button",
13452
- {
13453
- type: "button",
13454
- className: "absolute right-2 top-1/2 -translate-y-1/2 t-text-muted hover:t-text",
13455
- onClick: () => setVisible((prev) => ({ ...prev, [field.name]: !prev[field.name] })),
13456
- tabIndex: -1,
13457
- children: visible[field.name] ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { size: 13 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { size: 13 })
13458
- }
13459
- )
13473
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
13474
+ /* @__PURE__ */ jsxRuntimeExports.jsx(UpdateBanner, {}),
13475
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3", children: KEY_FIELDS.map((field) => {
13476
+ const alreadySet = status[field.name];
13477
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border t-border t-bg-surface/50 p-3", children: [
13478
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-1.5", children: [
13479
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-xs font-medium t-text flex items-center gap-1.5", children: [
13480
+ field.label,
13481
+ field.required && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] t-text-muted", children: "(required*)" }),
13482
+ !field.required && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] t-text-muted", children: "(optional)" }),
13483
+ alreadySet && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-0.5 text-[10px] text-green-500", children: [
13484
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 10 }),
13485
+ " configured"
13486
+ ] })
13460
13487
  ] }),
13461
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mt-1", children: field.hint })
13462
- ] }, field.name);
13463
- }) }),
13464
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border t-border t-bg-surface/50 p-3", children: [
13465
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-between mb-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-xs font-medium t-text flex items-center gap-1.5", children: [
13466
- "ChatGPT Subscription",
13467
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] t-text-muted", children: "(alternative to OpenAI API key)" }),
13468
- codexStatus?.isLoggedIn && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-0.5 text-[10px] text-green-500", children: [
13469
- /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 10 }),
13470
- " signed in"
13471
- ] })
13472
- ] }) }),
13473
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
13474
- "button",
13475
- {
13476
- onClick: handleCodexLogin,
13477
- disabled: codexLoggingIn || codexStatus?.isLoggedIn,
13478
- className: "flex items-center gap-1.5 px-3 py-1.5 rounded-md border t-border text-xs t-text-secondary hover:t-text t-bg-hover disabled:opacity-50",
13479
- children: [
13480
- /* @__PURE__ */ jsxRuntimeExports.jsx(LogIn, { size: 12 }),
13481
- codexLoggingIn ? "Signing in..." : codexStatus?.isLoggedIn ? "Already signed in" : "Sign in with ChatGPT"
13482
- ]
13483
- }
13484
- ),
13485
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mt-1", children: "Use your ChatGPT Plus/Pro subscription instead of an API key. No per-token billing." })
13486
- ] }),
13487
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mb-1", children: "* You need at least one of Anthropic or OpenAI (API key or ChatGPT subscription)." }),
13488
- error && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-400 mb-3", role: "alert", children: error }),
13488
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
13489
+ "a",
13490
+ {
13491
+ href: field.url,
13492
+ target: "_blank",
13493
+ rel: "noreferrer",
13494
+ className: "text-[10px] t-text-muted hover:t-text flex items-center gap-0.5",
13495
+ onClick: (e) => {
13496
+ e.preventDefault();
13497
+ window.open(field.url, "_blank");
13498
+ },
13499
+ children: [
13500
+ "Get key ",
13501
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { size: 9 })
13502
+ ]
13503
+ }
13504
+ )
13505
+ ] }),
13506
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
13507
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
13508
+ "input",
13509
+ {
13510
+ type: visible[field.name] ? "text" : "password",
13511
+ className: "w-full text-xs px-2.5 py-1.5 rounded-md border t-border t-bg-base t-text font-mono pr-8\n focus:outline-none focus:ring-1 focus:ring-[var(--color-accent)]",
13512
+ placeholder: alreadySet ? "•••••••• (already set leave blank to keep)" : field.placeholder,
13513
+ value: values[field.name] || "",
13514
+ onChange: (e) => setValues((prev) => ({ ...prev, [field.name]: e.target.value })),
13515
+ "aria-label": `${field.label} API key`
13516
+ }
13517
+ ),
13518
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
13519
+ "button",
13520
+ {
13521
+ type: "button",
13522
+ className: "absolute right-2 top-1/2 -translate-y-1/2 t-text-muted hover:t-text",
13523
+ onClick: () => setVisible((prev) => ({ ...prev, [field.name]: !prev[field.name] })),
13524
+ tabIndex: -1,
13525
+ children: visible[field.name] ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { size: 13 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { size: 13 })
13526
+ }
13527
+ )
13528
+ ] }),
13529
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mt-1", children: field.hint })
13530
+ ] }, field.name);
13531
+ }) }),
13532
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border t-border t-bg-surface/50 p-3 mt-3", children: [
13533
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-between mb-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-xs font-medium t-text flex items-center gap-1.5", children: [
13534
+ "ChatGPT Subscription",
13535
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] t-text-muted", children: "(alternative to OpenAI API key)" }),
13536
+ codexStatus?.isLoggedIn && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-0.5 text-[10px] text-green-500", children: [
13537
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 10 }),
13538
+ " signed in"
13539
+ ] })
13540
+ ] }) }),
13541
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
13542
+ "button",
13543
+ {
13544
+ onClick: handleCodexLogin,
13545
+ disabled: codexLoggingIn || codexStatus?.isLoggedIn,
13546
+ className: "flex items-center gap-1.5 px-3 py-1.5 rounded-md border t-border text-xs t-text-secondary hover:t-text t-bg-hover disabled:opacity-50",
13547
+ children: [
13548
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LogIn, { size: 12 }),
13549
+ codexLoggingIn ? "Signing in..." : codexStatus?.isLoggedIn ? "Already signed in" : "Sign in with ChatGPT"
13550
+ ]
13551
+ }
13552
+ ),
13553
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mt-1", children: "Use your ChatGPT Plus/Pro subscription instead of an API key. No per-token billing." })
13554
+ ] }),
13555
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mt-2", children: "* At least one of Anthropic or OpenAI (API key or ChatGPT subscription) is required." }),
13556
+ error && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-400 mt-2", role: "alert", children: error }),
13557
+ showSaveButton && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-4 flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
13558
+ "button",
13559
+ {
13560
+ onClick: handleSave,
13561
+ disabled: saving,
13562
+ className: "px-4 py-1.5 rounded-md text-white text-[13px] font-medium hover:brightness-110 transition-[filter] duration-150 disabled:opacity-50 bg-[var(--color-accent)]",
13563
+ children: saving ? "Saving…" : "Save & Continue"
13564
+ }
13565
+ ) })
13566
+ ] });
13567
+ }
13568
+ function SegmentedControl({ options, value, onChange }) {
13569
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex rounded-lg border t-border overflow-hidden", children: options.map((opt) => {
13570
+ const active = opt.value === value;
13571
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
13572
+ "button",
13573
+ {
13574
+ onClick: () => onChange(opt.value),
13575
+ className: `flex-1 px-3 py-1.5 text-xs font-medium transition-colors
13576
+ ${active ? "t-text-accent bg-[var(--color-accent)]/10" : "t-text-secondary hover:t-text hover:t-bg-hover"}
13577
+ `,
13578
+ children: opt.label
13579
+ },
13580
+ opt.value
13581
+ );
13582
+ }) });
13583
+ }
13584
+ function ResearchSettings({
13585
+ researchIntensity,
13586
+ webSearchDepth,
13587
+ autoSaveSensitivity,
13588
+ onChangeIntensity,
13589
+ onChangeWebDepth,
13590
+ onChangeAutoSave
13591
+ }) {
13592
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
13593
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
13594
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-semibold t-text mb-1.5", children: "Literature Search Intensity" }),
13595
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mb-2.5", children: "Controls how many papers are fetched per source and how thoroughly results are reviewed." }),
13596
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
13597
+ SegmentedControl,
13598
+ {
13599
+ options: [
13600
+ { label: "Low", value: "low" },
13601
+ { label: "Medium", value: "medium" },
13602
+ { label: "High", value: "high" }
13603
+ ],
13604
+ value: researchIntensity,
13605
+ onChange: onChangeIntensity
13606
+ }
13607
+ ),
13608
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] t-text-muted mt-1.5", children: [
13609
+ researchIntensity === "low" && "Faster searches, fewer papers. Good for quick checks.",
13610
+ researchIntensity === "medium" && "Balanced coverage. Suitable for most research tasks.",
13611
+ researchIntensity === "high" && "Thorough searches with more papers per source. Best for comprehensive reviews."
13612
+ ] })
13613
+ ] }),
13614
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
13615
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-semibold t-text mb-1.5", children: "Web Search Depth" }),
13616
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mb-2.5", children: "Controls the number of results and how much content is fetched from each page." }),
13617
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
13618
+ SegmentedControl,
13619
+ {
13620
+ options: [
13621
+ { label: "Quick", value: "quick" },
13622
+ { label: "Standard", value: "standard" },
13623
+ { label: "Thorough", value: "thorough" }
13624
+ ],
13625
+ value: webSearchDepth,
13626
+ onChange: onChangeWebDepth
13627
+ }
13628
+ ),
13629
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] t-text-muted mt-1.5", children: [
13630
+ webSearchDepth === "quick" && "Fewer results, smaller page fetches. Good for simple lookups.",
13631
+ webSearchDepth === "standard" && "Balanced results. Suitable for most searches.",
13632
+ webSearchDepth === "thorough" && "More results and larger page fetches. Best for deep research."
13633
+ ] })
13634
+ ] }),
13635
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
13636
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-semibold t-text mb-1.5", children: "Auto-Save Sensitivity" }),
13637
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mb-2.5", children: "How aggressively papers are auto-saved to your library based on relevance scores." }),
13638
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
13639
+ SegmentedControl,
13640
+ {
13641
+ options: [
13642
+ { label: "Conservative", value: "conservative" },
13643
+ { label: "Balanced", value: "balanced" },
13644
+ { label: "Aggressive", value: "aggressive" }
13645
+ ],
13646
+ value: autoSaveSensitivity,
13647
+ onChange: onChangeAutoSave
13648
+ }
13649
+ ),
13650
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] t-text-muted mt-1.5", children: [
13651
+ autoSaveSensitivity === "conservative" && "Only saves highly relevant papers. Keeps your library focused.",
13652
+ autoSaveSensitivity === "balanced" && "Saves papers with good relevance. A sensible default.",
13653
+ autoSaveSensitivity === "aggressive" && "Saves more papers for broader coverage. May include tangential results."
13654
+ ] })
13655
+ ] })
13656
+ ] });
13657
+ }
13658
+ function DataAnalysisSettings({ executionTimeLimit, onChange }) {
13659
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
13660
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-semibold t-text mb-1.5", children: "Execution Time Limit" }),
13661
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mb-2.5", children: "Maximum time allowed for Python data analysis scripts to run before timeout." }),
13662
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
13663
+ SegmentedControl,
13664
+ {
13665
+ options: [
13666
+ { label: "1 min", value: "short" },
13667
+ { label: "2 min", value: "standard" },
13668
+ { label: "5 min", value: "extended" },
13669
+ { label: "10 min", value: "long" }
13670
+ ],
13671
+ value: executionTimeLimit,
13672
+ onChange
13673
+ }
13674
+ ),
13675
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] t-text-muted mt-1.5", children: [
13676
+ executionTimeLimit === "short" && "Quick timeout for simple analyses.",
13677
+ executionTimeLimit === "standard" && "Suitable for most data analysis tasks.",
13678
+ executionTimeLimit === "extended" && "For larger datasets or complex computations.",
13679
+ executionTimeLimit === "long" && "For very large datasets or intensive modeling tasks."
13680
+ ] })
13681
+ ] }) });
13682
+ }
13683
+ const REASONING_MODELS = [
13684
+ "openai:gpt-5.4",
13685
+ "openai:gpt-5.4-mini",
13686
+ "openai:gpt-5.4-nano",
13687
+ "openai:gpt-5.4-pro",
13688
+ "openai-codex:gpt-5.4",
13689
+ "openai-codex:gpt-5.4-mini",
13690
+ "anthropic:claude-opus-4-6",
13691
+ "anthropic-sub:claude-opus-4-6"
13692
+ ];
13693
+ const SUPPORTED_MODELS = [
13694
+ // OpenAI (API key)
13695
+ { id: "openai:gpt-5.4", label: "GPT-5.4", provider: "OpenAI" },
13696
+ { id: "openai:gpt-5.4-mini", label: "GPT-5.4 Mini", provider: "OpenAI" },
13697
+ { id: "openai:gpt-5.4-nano", label: "GPT-5.4 Nano", provider: "OpenAI" },
13698
+ { id: "openai:gpt-4o", label: "GPT-4o", provider: "OpenAI" },
13699
+ { id: "openai:gpt-5.4-pro", label: "GPT-5.4 Pro", provider: "OpenAI" },
13700
+ // ChatGPT Subscription (OAuth) — only models registered in pi-ai's openai-codex provider
13701
+ { id: "openai-codex:gpt-5.4", label: "GPT-5.4", provider: "ChatGPT Subscription" },
13702
+ { id: "openai-codex:gpt-5.4-mini", label: "GPT-5.4 Mini", provider: "ChatGPT Subscription" },
13703
+ // Anthropic (API key)
13704
+ { id: "anthropic:claude-opus-4-6", label: "Claude Opus 4.6", provider: "Anthropic" },
13705
+ { id: "anthropic:claude-opus-4-5-20251101", label: "Claude Opus 4.5", provider: "Anthropic" },
13706
+ { id: "anthropic:claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5", provider: "Anthropic" },
13707
+ { id: "anthropic:claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", provider: "Anthropic" },
13708
+ // Claude Subscription (OAuth) — gated behind ENABLE_CLAUDE_SUB=1
13709
+ { id: "anthropic-sub:claude-opus-4-6", label: "Claude Opus 4.6", provider: "Claude Subscription" },
13710
+ { id: "anthropic-sub:claude-opus-4-5-20251101", label: "Claude Opus 4.5", provider: "Claude Subscription" },
13711
+ { id: "anthropic-sub:claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5", provider: "Claude Subscription" },
13712
+ { id: "anthropic-sub:claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", provider: "Claude Subscription" }
13713
+ ];
13714
+ const DEFAULT_MODEL = "openai:gpt-5.4";
13715
+ const api$f = window.api;
13716
+ const SPEED_OPTIONS = [
13717
+ { label: "Slow", value: "slow", desc: "Minimal resource usage. Best for subscription plans with tight limits." },
13718
+ { label: "Medium", value: "medium", desc: "Balanced processing. Suitable for most users." },
13719
+ { label: "Fast", value: "fast", desc: "Processes papers quickly when idle. Uses more API calls." }
13720
+ ];
13721
+ const MODEL_GROUPS = (() => {
13722
+ const groups = /* @__PURE__ */ new Map();
13723
+ for (const m of SUPPORTED_MODELS) {
13724
+ const list2 = groups.get(m.provider) || [];
13725
+ list2.push(m);
13726
+ groups.set(m.provider, list2);
13727
+ }
13728
+ return groups;
13729
+ })();
13730
+ function WikiAgentSettings({ model, speed, onChangeModel, onChangeSpeed }) {
13731
+ const [status, setStatus] = reactExports.useState(null);
13732
+ const [stats, setStats] = reactExports.useState(null);
13733
+ const [recentLog, setRecentLog] = reactExports.useState([]);
13734
+ reactExports.useEffect(() => {
13735
+ api$f.wikiGetStatus?.().then((s15) => setStatus(s15)).catch(() => {
13736
+ });
13737
+ api$f.wikiGetStats?.().then((s15) => setStats(s15)).catch(() => {
13738
+ });
13739
+ api$f.wikiGetLog?.().then((l2) => setRecentLog(l2 || [])).catch(() => {
13740
+ });
13741
+ const unsub = api$f.onWikiStatus?.((s15) => setStatus(s15));
13742
+ return () => unsub?.();
13743
+ }, []);
13744
+ const enabled = model !== "none";
13745
+ const speedDesc = SPEED_OPTIONS.find((o2) => o2.value === speed)?.desc || "";
13746
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
13747
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
13748
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-semibold t-text mb-1.5", children: "Wiki Agent Model" }),
13749
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mb-2.5", children: "Select a model to power the background Paper Wiki agent. The wiki accumulates LLM-generated summaries of papers across all your projects. A smaller model is recommended for background processing." }),
13750
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
13751
+ "select",
13752
+ {
13753
+ value: model,
13754
+ onChange: (e) => onChangeModel(e.target.value),
13755
+ className: "w-full px-3 py-1.5 rounded-lg border t-border t-bg-base t-text text-xs",
13756
+ children: [
13757
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "none", children: "None (disabled)" }),
13758
+ Array.from(MODEL_GROUPS.entries()).map(([provider, models]) => /* @__PURE__ */ jsxRuntimeExports.jsx("optgroup", { label: provider, children: models.map((m) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: m.id, children: m.label }, m.id)) }, provider))
13759
+ ]
13760
+ }
13761
+ ),
13762
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] t-text-muted mt-1.5 italic", children: "Changes take effect after app restart." })
13763
+ ] }),
13764
+ enabled && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
13765
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-semibold t-text mb-1.5", children: "Processing Speed" }),
13766
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
13767
+ SegmentedControl,
13768
+ {
13769
+ options: SPEED_OPTIONS,
13770
+ value: speed,
13771
+ onChange: onChangeSpeed
13772
+ }
13773
+ ),
13774
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mt-2", children: speedDesc })
13775
+ ] }),
13776
+ enabled && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border t-border t-bg-surface/50 p-3 space-y-3", children: [
13489
13777
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
13490
- (status.ANTHROPIC_API_KEY || status.OPENAI_API_KEY || codexStatus?.isLoggedIn) && /* @__PURE__ */ jsxRuntimeExports.jsx(
13491
- "button",
13492
- {
13493
- onClick: onComplete,
13494
- className: "text-xs t-text-secondary hover:t-text transition-colors",
13495
- children: "Skip — keys already configured"
13496
- }
13497
- ),
13498
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1" }),
13499
- /* @__PURE__ */ jsxRuntimeExports.jsx(
13778
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-semibold t-text", children: "Status" }),
13779
+ status?.state && status.state !== "disabled" && /* @__PURE__ */ jsxRuntimeExports.jsx(
13500
13780
  "button",
13501
13781
  {
13502
- onClick: handleSave,
13503
- disabled: saving,
13504
- className: "px-5 py-2 rounded-lg text-white text-sm font-medium hover:opacity-90 transition-all duration-200 disabled:opacity-50 t-gradient-accent t-gradient-accent-shadow",
13505
- children: saving ? "Saving..." : hasAnyLlmKey ? "Save & Continue" : "Save & Continue"
13782
+ onClick: () => {
13783
+ if (status.state === "paused") {
13784
+ api$f.wikiResume?.();
13785
+ } else {
13786
+ api$f.wikiPause?.();
13787
+ }
13788
+ },
13789
+ className: "px-2.5 py-1 rounded-md border t-border t-bg-base t-text text-[11px] font-medium hover:t-bg-hover transition-colors",
13790
+ children: status.state === "paused" ? "Resume" : "Pause"
13506
13791
  }
13507
13792
  )
13793
+ ] }),
13794
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 text-[11px]", children: [
13795
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted", children: "State:" }),
13796
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "t-text flex items-center gap-1.5", children: [
13797
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `inline-block w-1.5 h-1.5 rounded-full ${status?.state === "processing" ? "bg-blue-500" : status?.state === "idle" ? "bg-green-500" : status?.state === "paused" ? "bg-yellow-500" : "bg-gray-400"}` }),
13798
+ status?.state === "processing" ? `Processing (${status?.pending ?? 0} pending)` : status?.state ?? "Disabled"
13799
+ ] }),
13800
+ stats && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
13801
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted", children: "Papers:" }),
13802
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "t-text", children: [
13803
+ stats.papers,
13804
+ " in wiki (",
13805
+ stats.fulltext,
13806
+ " fulltext, ",
13807
+ stats.abstractOnly,
13808
+ " abstract)"
13809
+ ] }),
13810
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted", children: "Concepts:" }),
13811
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "t-text", children: [
13812
+ stats.concepts,
13813
+ " pages"
13814
+ ] })
13815
+ ] }),
13816
+ status?.lastRunAt && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
13817
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted", children: "Last run:" }),
13818
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text", children: formatRelativeTime(status.lastRunAt) })
13819
+ ] })
13820
+ ] }),
13821
+ recentLog.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
13822
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h5", { className: "text-[11px] font-medium t-text-muted mb-1", children: "Recent Activity" }),
13823
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-0.5 max-h-32 overflow-y-auto", children: recentLog.slice(0, 10).map((entry, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] t-text-muted font-mono leading-relaxed truncate", children: entry }, i)) })
13508
13824
  ] })
13509
- ] })
13825
+ ] }),
13826
+ !enabled && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-lg border t-border t-bg-surface/50 p-3", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] t-text-muted", children: [
13827
+ "The wiki agent is disabled. Select a model above to enable cross-project paper memory. Papers from all your projects are accumulated into a searchable local memory accessible via ",
13828
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "font-mono", children: "wiki_search" }),
13829
+ ",",
13830
+ " ",
13831
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "font-mono", children: "wiki_get" }),
13832
+ ", ",
13833
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "font-mono", children: "wiki_coverage" }),
13834
+ ", and ",
13835
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "font-mono", children: "wiki_source" }),
13836
+ " — the wiki is a research memory layer, not a fact oracle."
13837
+ ] }) })
13838
+ ] });
13839
+ }
13840
+ function formatRelativeTime(isoString) {
13841
+ const diff = Date.now() - new Date(isoString).getTime();
13842
+ const mins = Math.floor(diff / 6e4);
13843
+ if (mins < 1) return "just now";
13844
+ if (mins < 60) return `${mins} minute${mins > 1 ? "s" : ""} ago`;
13845
+ const hours = Math.floor(mins / 60);
13846
+ if (hours < 24) return `${hours} hour${hours > 1 ? "s" : ""} ago`;
13847
+ return new Date(isoString).toLocaleDateString();
13848
+ }
13849
+ const DEFAULT_SETTINGS = {
13850
+ research: {
13851
+ researchIntensity: "medium",
13852
+ webSearchDepth: "standard",
13853
+ autoSaveSensitivity: "balanced"
13854
+ },
13855
+ dataAnalysis: {
13856
+ executionTimeLimit: "standard"
13857
+ },
13858
+ wikiAgent: {
13859
+ model: "none",
13860
+ speed: "medium"
13861
+ }
13862
+ };
13863
+ const api$e = window.api;
13864
+ const TABS = [
13865
+ { id: "api-keys", label: "API Keys", icon: Key },
13866
+ { id: "research", label: "Research", icon: BookOpen },
13867
+ { id: "data-analysis", label: "Data Analysis", icon: ChartNoAxesColumn },
13868
+ { id: "paper-wiki", label: "Paper Wiki", icon: BookMarked }
13869
+ ];
13870
+ const FOCUSABLE_SELECTOR = [
13871
+ "button:not([disabled])",
13872
+ "[href]",
13873
+ "input:not([disabled])",
13874
+ "select:not([disabled])",
13875
+ "textarea:not([disabled])",
13876
+ '[tabindex]:not([tabindex="-1"])'
13877
+ ].join(",");
13878
+ function getVisibleFocusable(root2) {
13879
+ return Array.from(root2.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el2) => el2.offsetParent !== null);
13880
+ }
13881
+ function SettingsModal({ open, onClose, initialTab }) {
13882
+ const [activeTab, setActiveTab] = reactExports.useState(initialTab ?? "api-keys");
13883
+ const [settings, setSettings] = reactExports.useState(DEFAULT_SETTINGS);
13884
+ const [loaded, setLoaded] = reactExports.useState(false);
13885
+ const [dirty, setDirty] = reactExports.useState(false);
13886
+ const panelRef = reactExports.useRef(null);
13887
+ reactExports.useEffect(() => {
13888
+ if (open && initialTab) {
13889
+ setActiveTab(initialTab);
13890
+ }
13891
+ }, [open, initialTab]);
13892
+ reactExports.useEffect(() => {
13893
+ if (!open) return;
13894
+ api$e.loadSettings?.().then((s15) => {
13895
+ if (s15) setSettings(s15);
13896
+ setLoaded(true);
13897
+ setDirty(false);
13898
+ }).catch(() => setLoaded(true));
13899
+ }, [open]);
13900
+ reactExports.useEffect(() => {
13901
+ if (!open) return;
13902
+ const trigger = document.activeElement;
13903
+ const frame = requestAnimationFrame(() => {
13904
+ const panel = panelRef.current;
13905
+ if (!panel) return;
13906
+ const focusable = getVisibleFocusable(panel);
13907
+ const first = focusable[0] ?? panel;
13908
+ first.focus();
13909
+ });
13910
+ return () => {
13911
+ cancelAnimationFrame(frame);
13912
+ if (trigger && document.contains(trigger)) {
13913
+ trigger.focus?.();
13914
+ }
13915
+ };
13916
+ }, [open]);
13917
+ reactExports.useEffect(() => {
13918
+ if (!open) return;
13919
+ const handleKey = (e) => {
13920
+ if (e.key === "Escape") {
13921
+ e.preventDefault();
13922
+ e.stopPropagation();
13923
+ onClose();
13924
+ return;
13925
+ }
13926
+ if (e.key !== "Tab") return;
13927
+ const panel = panelRef.current;
13928
+ if (!panel) return;
13929
+ const focusable = getVisibleFocusable(panel);
13930
+ if (focusable.length === 0) {
13931
+ e.preventDefault();
13932
+ panel.focus();
13933
+ return;
13934
+ }
13935
+ const first = focusable[0];
13936
+ const last = focusable[focusable.length - 1];
13937
+ const active = document.activeElement;
13938
+ if (!active || !panel.contains(active)) {
13939
+ e.preventDefault();
13940
+ first.focus();
13941
+ return;
13942
+ }
13943
+ if (e.shiftKey && active === first) {
13944
+ e.preventDefault();
13945
+ last.focus();
13946
+ } else if (!e.shiftKey && active === last) {
13947
+ e.preventDefault();
13948
+ first.focus();
13949
+ }
13950
+ };
13951
+ window.addEventListener("keydown", handleKey, true);
13952
+ return () => window.removeEventListener("keydown", handleKey, true);
13953
+ }, [open, onClose]);
13954
+ const updateSettings = (patch2) => {
13955
+ setSettings((prev) => {
13956
+ const next = { ...prev, ...patch2 };
13957
+ if (patch2.research) next.research = { ...prev.research, ...patch2.research };
13958
+ if (patch2.dataAnalysis) next.dataAnalysis = { ...prev.dataAnalysis, ...patch2.dataAnalysis };
13959
+ if (patch2.wikiAgent) next.wikiAgent = { ...prev.wikiAgent, ...patch2.wikiAgent };
13960
+ return next;
13961
+ });
13962
+ setDirty(true);
13963
+ };
13964
+ reactExports.useEffect(() => {
13965
+ if (!dirty || !loaded) return;
13966
+ const timer = setTimeout(() => {
13967
+ api$e.saveSettings?.(settings).catch(() => {
13968
+ });
13969
+ setDirty(false);
13970
+ }, 300);
13971
+ return () => clearTimeout(timer);
13972
+ }, [settings, dirty, loaded]);
13973
+ if (!open) return null;
13974
+ const activeLabel = TABS.find((t) => t.id === activeTab)?.label ?? "Settings";
13975
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[60] flex items-center justify-center", children: [
13976
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/40", "aria-hidden": "true" }),
13977
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
13978
+ "div",
13979
+ {
13980
+ ref: panelRef,
13981
+ role: "dialog",
13982
+ "aria-modal": "true",
13983
+ "aria-labelledby": "settings-dialog-title",
13984
+ tabIndex: -1,
13985
+ className: "relative w-full max-w-2xl h-[520px] rounded-xl border t-border t-bg-surface shadow-xl flex overflow-hidden outline-none",
13986
+ children: [
13987
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
13988
+ "nav",
13989
+ {
13990
+ "aria-label": "Settings categories",
13991
+ className: "w-48 shrink-0 border-r t-border t-bg-base flex flex-col py-4 px-2",
13992
+ children: [
13993
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
13994
+ "h1",
13995
+ {
13996
+ id: "settings-dialog-title",
13997
+ className: "px-3 mb-3 text-sm font-semibold t-text tracking-tight",
13998
+ children: "Settings"
13999
+ }
14000
+ ),
14001
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-0.5", children: TABS.map((tab2) => {
14002
+ const Icon2 = tab2.icon;
14003
+ const active = activeTab === tab2.id;
14004
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
14005
+ "button",
14006
+ {
14007
+ onClick: () => setActiveTab(tab2.id),
14008
+ "aria-current": active ? "page" : void 0,
14009
+ className: `w-full flex items-center gap-2 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors
14010
+ ${active ? "t-text-accent bg-[var(--color-accent)]/10" : "t-text-secondary hover:t-text hover:t-bg-hover"}
14011
+ `,
14012
+ children: [
14013
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Icon2, { size: 14 }),
14014
+ tab2.label
14015
+ ]
14016
+ },
14017
+ tab2.id
14018
+ );
14019
+ }) }),
14020
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1" }),
14021
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "px-3 text-[10px] t-text-muted leading-relaxed", children: [
14022
+ "Keys are stored in",
14023
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
14024
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "font-mono", children: "~/.research-copilot/" })
14025
+ ] })
14026
+ ]
14027
+ }
14028
+ ),
14029
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col min-w-0", children: [
14030
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-6 pt-4 pb-2", children: [
14031
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold t-text", children: activeLabel }),
14032
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
14033
+ "button",
14034
+ {
14035
+ onClick: onClose,
14036
+ className: "p-1.5 rounded-lg t-text-muted hover:t-text hover:t-bg-hover transition-colors",
14037
+ "aria-label": "Close settings",
14038
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(X$1, { size: 16 })
14039
+ }
14040
+ )
14041
+ ] }),
14042
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto px-6 pb-4", children: [
14043
+ activeTab === "api-keys" && /* @__PURE__ */ jsxRuntimeExports.jsx(ApiKeysSettings, {}),
14044
+ activeTab === "research" && loaded && /* @__PURE__ */ jsxRuntimeExports.jsx(
14045
+ ResearchSettings,
14046
+ {
14047
+ researchIntensity: settings.research.researchIntensity,
14048
+ webSearchDepth: settings.research.webSearchDepth,
14049
+ autoSaveSensitivity: settings.research.autoSaveSensitivity,
14050
+ onChangeIntensity: (v3) => updateSettings({ research: { ...settings.research, researchIntensity: v3 } }),
14051
+ onChangeWebDepth: (v3) => updateSettings({ research: { ...settings.research, webSearchDepth: v3 } }),
14052
+ onChangeAutoSave: (v3) => updateSettings({ research: { ...settings.research, autoSaveSensitivity: v3 } })
14053
+ }
14054
+ ),
14055
+ activeTab === "data-analysis" && loaded && /* @__PURE__ */ jsxRuntimeExports.jsx(
14056
+ DataAnalysisSettings,
14057
+ {
14058
+ executionTimeLimit: settings.dataAnalysis.executionTimeLimit,
14059
+ onChange: (v3) => updateSettings({ dataAnalysis: { executionTimeLimit: v3 } })
14060
+ }
14061
+ ),
14062
+ activeTab === "paper-wiki" && loaded && /* @__PURE__ */ jsxRuntimeExports.jsx(
14063
+ WikiAgentSettings,
14064
+ {
14065
+ model: settings.wikiAgent?.model ?? "none",
14066
+ speed: settings.wikiAgent?.speed ?? "medium",
14067
+ onChangeModel: (v3) => updateSettings({ wikiAgent: { ...settings.wikiAgent, model: v3 } }),
14068
+ onChangeSpeed: (v3) => updateSettings({ wikiAgent: { ...settings.wikiAgent, speed: v3 } })
14069
+ }
14070
+ )
14071
+ ] }),
14072
+ activeTab !== "api-keys" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-6 py-2.5 border-t t-border-subtle", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] t-text-muted", children: "Settings are saved automatically. Changes to research and analysis settings take effect for new agent sessions. Existing sessions require an app restart." }) })
14073
+ ] })
14074
+ ]
14075
+ }
14076
+ )
13510
14077
  ] });
13511
14078
  }
13512
14079
  const createStoreImpl = (createState2) => {
@@ -13548,32 +14115,6 @@ const createImpl = (createState2) => {
13548
14115
  return useBoundStore;
13549
14116
  };
13550
14117
  const create$1 = (createState2) => createState2 ? createImpl(createState2) : createImpl;
13551
- const REASONING_MODELS = [
13552
- "openai:gpt-5.4",
13553
- "openai:gpt-5.4-mini",
13554
- "openai:gpt-5.4-nano",
13555
- "openai:gpt-5.4-pro",
13556
- "openai-codex:gpt-5.4",
13557
- "openai-codex:gpt-5.4-mini",
13558
- "anthropic:claude-opus-4-6"
13559
- ];
13560
- const SUPPORTED_MODELS = [
13561
- // OpenAI (API key)
13562
- { id: "openai:gpt-5.4", label: "GPT-5.4", provider: "OpenAI" },
13563
- { id: "openai:gpt-5.4-mini", label: "GPT-5.4 Mini", provider: "OpenAI" },
13564
- { id: "openai:gpt-5.4-nano", label: "GPT-5.4 Nano", provider: "OpenAI" },
13565
- { id: "openai:gpt-4o", label: "GPT-4o", provider: "OpenAI" },
13566
- { id: "openai:gpt-5.4-pro", label: "GPT-5.4 Pro", provider: "OpenAI" },
13567
- // ChatGPT Subscription (OAuth) — only models registered in pi-ai's openai-codex provider
13568
- { id: "openai-codex:gpt-5.4", label: "GPT-5.4", provider: "ChatGPT Subscription" },
13569
- { id: "openai-codex:gpt-5.4-mini", label: "GPT-5.4 Mini", provider: "ChatGPT Subscription" },
13570
- // Anthropic (API key)
13571
- { id: "anthropic:claude-opus-4-6", label: "Claude Opus 4.6", provider: "Anthropic" },
13572
- { id: "anthropic:claude-opus-4-5-20251101", label: "Claude Opus 4.5", provider: "Anthropic" },
13573
- { id: "anthropic:claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5", provider: "Anthropic" },
13574
- { id: "anthropic:claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", provider: "Anthropic" }
13575
- ];
13576
- const DEFAULT_MODEL = "openai:gpt-5.4";
13577
14118
  function parseModelKey(key) {
13578
14119
  const i = key.indexOf(":");
13579
14120
  if (i > 0) return { provider: key.slice(0, i), modelId: key.slice(i + 1) };
@@ -13581,8 +14122,38 @@ function parseModelKey(key) {
13581
14122
  if (key.startsWith("gemini-")) return { provider: "google", modelId: key };
13582
14123
  return { provider: "openai", modelId: key };
13583
14124
  }
14125
+ const STORAGE_KEY = "rp-theme";
14126
+ function getInitialTheme() {
14127
+ if (typeof window === "undefined") return "dark";
14128
+ try {
14129
+ const stored = window.localStorage.getItem(STORAGE_KEY);
14130
+ if (stored === "light" || stored === "dark") return stored;
14131
+ } catch {
14132
+ }
14133
+ return "dark";
14134
+ }
14135
+ function persistTheme(theme) {
14136
+ if (typeof window === "undefined") return;
14137
+ try {
14138
+ window.localStorage.setItem(STORAGE_KEY, theme);
14139
+ } catch {
14140
+ }
14141
+ }
14142
+ function applyThemeClass(theme) {
14143
+ if (typeof document === "undefined") return;
14144
+ document.documentElement.classList.remove("dark", "light");
14145
+ document.documentElement.classList.add(theme);
14146
+ }
14147
+ function bootTheme() {
14148
+ const theme = getInitialTheme();
14149
+ applyThemeClass(theme);
14150
+ return theme;
14151
+ }
13584
14152
  const useUIStore = create$1((set) => ({
13585
- theme: "light",
14153
+ // Theme hydrates from localStorage (or OS preference) at module init so
14154
+ // the zustand state matches the <html> class applied by bootTheme() in
14155
+ // main.tsx. Both ends derive from getInitialTheme() — they stay in sync.
14156
+ theme: getInitialTheme(),
13586
14157
  leftTab: "files",
13587
14158
  centerView: "chat",
13588
14159
  selectedModel: DEFAULT_MODEL,
@@ -13605,6 +14176,17 @@ const useUIStore = create$1((set) => ({
13605
14176
  source: null,
13606
14177
  round: null
13607
14178
  },
14179
+ wikiReaderSlug: null,
14180
+ wikiReaderHistory: [],
14181
+ setWikiReaderSlug: (slug) => set((s15) => ({
14182
+ wikiReaderHistory: s15.wikiReaderSlug ? [...s15.wikiReaderHistory, s15.wikiReaderSlug] : s15.wikiReaderHistory,
14183
+ wikiReaderSlug: slug
14184
+ })),
14185
+ wikiReaderBack: () => set((s15) => {
14186
+ const history = [...s15.wikiReaderHistory];
14187
+ const prev = history.pop() ?? null;
14188
+ return { wikiReaderSlug: prev, wikiReaderHistory: history };
14189
+ }),
13608
14190
  setReasoningEffort: (reasoningEffort) => {
13609
14191
  set({ reasoningEffort });
13610
14192
  const api2 = window.api;
@@ -13613,8 +14195,8 @@ const useUIStore = create$1((set) => ({
13613
14195
  },
13614
14196
  setTheme: (theme) => {
13615
14197
  set({ theme });
13616
- const api2 = window.api;
13617
- api2?.savePreferences?.({ theme });
14198
+ persistTheme(theme);
14199
+ applyThemeClass(theme);
13618
14200
  },
13619
14201
  toggleTheme: () => {
13620
14202
  const newTheme = useUIStore.getState().theme === "dark" ? "light" : "dark";
@@ -13680,7 +14262,9 @@ const useUIStore = create$1((set) => ({
13680
14262
  previewEntity: null,
13681
14263
  previewSourceTab: null,
13682
14264
  previewEditorFocused: false,
13683
- literatureFilter: { search: "", subTopic: null, sortBy: "year", sortDir: "desc", minScore: 0, source: null, round: null }
14265
+ literatureFilter: { search: "", subTopic: null, sortBy: "year", sortDir: "desc", minScore: 0, source: null, round: null },
14266
+ wikiReaderSlug: null,
14267
+ wikiReaderHistory: []
13684
14268
  }),
13685
14269
  openPreview: (entity) => set((s15) => ({ previewEntity: entity, previewSourceTab: s15.leftTab, leftSidebarCollapsed: true, previewEditorFocused: false })),
13686
14270
  closePreview: () => set({ previewEntity: null, previewSourceTab: null, leftSidebarCollapsed: false, previewEditorFocused: false }),
@@ -13701,7 +14285,6 @@ async function hydratePreferences() {
13701
14285
  }
13702
14286
  }
13703
14287
  if (prefs.reasoningEffort) updates.reasoningEffort = prefs.reasoningEffort;
13704
- if (prefs.theme) updates.theme = prefs.theme;
13705
14288
  if (Object.keys(updates).length > 0) useUIStore.setState(updates);
13706
14289
  }
13707
14290
  const uiStore = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
@@ -13846,7 +14429,7 @@ const useToolEventsStore = create$1((set, get) => ({
13846
14429
  clearRun: () => set({ currentRunEvents: [] })
13847
14430
  }));
13848
14431
  const PAGE_SIZE = 20;
13849
- const api$a = window.api;
14432
+ const api$d = window.api;
13850
14433
  let _sessionId = "";
13851
14434
  const useChatStore = create$1((set, get) => ({
13852
14435
  messages: [],
@@ -13874,7 +14457,7 @@ const useChatStore = create$1((set, get) => ({
13874
14457
  isStreaming: true
13875
14458
  }));
13876
14459
  if (_sessionId) {
13877
- api$a.saveMessage(_sessionId, userMsg).catch(() => {
14460
+ api$d.saveMessage(_sessionId, userMsg).catch(() => {
13878
14461
  });
13879
14462
  }
13880
14463
  const { useUsageStore: useUsageStore2 } = await __vitePreload(async () => {
@@ -13888,12 +14471,12 @@ const useChatStore = create$1((set, get) => ({
13888
14471
  return { useUIStore: useUIStore3 };
13889
14472
  }, true ? void 0 : void 0, import.meta.url);
13890
14473
  const model = useUIStore2.getState().selectedModel;
13891
- await api$a.sendMessage(text2, void 0, model, images);
14474
+ await api$d.sendMessage(text2, void 0, model, images);
13892
14475
  } catch {
13893
14476
  }
13894
14477
  },
13895
14478
  stop: async () => {
13896
- await api$a.stopAgent();
14479
+ await api$d.stopAgent();
13897
14480
  },
13898
14481
  appendChunk: (chunk) => {
13899
14482
  set((s15) => ({ streamingText: s15.streamingText + chunk }));
@@ -13922,7 +14505,7 @@ const useChatStore = create$1((set, get) => ({
13922
14505
  };
13923
14506
  });
13924
14507
  if (_sessionId) {
13925
- api$a.saveMessage(_sessionId, assistantMsg).catch(() => {
14508
+ api$d.saveMessage(_sessionId, assistantMsg).catch(() => {
13926
14509
  });
13927
14510
  }
13928
14511
  },
@@ -13948,7 +14531,7 @@ const useChatStore = create$1((set, get) => ({
13948
14531
  return { savedMessageIds: next };
13949
14532
  });
13950
14533
  if (_sessionId) {
13951
- api$a.markMessageSaved(_sessionId, messageId).catch(() => {
14534
+ api$d.markMessageSaved(_sessionId, messageId).catch(() => {
13952
14535
  });
13953
14536
  }
13954
14537
  },
@@ -13965,9 +14548,9 @@ const useChatStore = create$1((set, get) => ({
13965
14548
  _sessionId = sessionId;
13966
14549
  try {
13967
14550
  const [count, messages, savedIds] = await Promise.all([
13968
- api$a.getMessageCount(sessionId),
13969
- api$a.loadMessages(sessionId, 0, PAGE_SIZE),
13970
- api$a.loadSavedMessageIds(sessionId)
14551
+ api$d.getMessageCount(sessionId),
14552
+ api$d.loadMessages(sessionId, 0, PAGE_SIZE),
14553
+ api$d.loadSavedMessageIds(sessionId)
13971
14554
  ]);
13972
14555
  set({
13973
14556
  messages,
@@ -13984,8 +14567,8 @@ const useChatStore = create$1((set, get) => ({
13984
14567
  set({ isLoadingHistory: true });
13985
14568
  try {
13986
14569
  const [count, older] = await Promise.all([
13987
- api$a.getMessageCount(_sessionId),
13988
- api$a.loadMessages(_sessionId, _offset, PAGE_SIZE)
14570
+ api$d.getMessageCount(_sessionId),
14571
+ api$d.loadMessages(_sessionId, _offset, PAGE_SIZE)
13989
14572
  ]);
13990
14573
  if (older.length > 0) {
13991
14574
  set((s15) => ({
@@ -14063,7 +14646,7 @@ function merge(definitions, space2) {
14063
14646
  }
14064
14647
  return new Schema(property, normal, space2);
14065
14648
  }
14066
- function normalize$1(value) {
14649
+ function normalize$2(value) {
14067
14650
  return value.toLowerCase();
14068
14651
  }
14069
14652
  class Info {
@@ -14163,8 +14746,8 @@ function create(definition2) {
14163
14746
  info.mustUseProperty = true;
14164
14747
  }
14165
14748
  properties[property] = info;
14166
- normals[normalize$1(property)] = property;
14167
- normals[normalize$1(info.attribute)] = property;
14749
+ normals[normalize$2(property)] = property;
14750
+ normals[normalize$2(info.attribute)] = property;
14168
14751
  }
14169
14752
  return new Schema(properties, normals, definition2.space);
14170
14753
  }
@@ -15203,7 +15786,7 @@ const cap$1 = /[A-Z]/g;
15203
15786
  const dash = /-[a-z]/g;
15204
15787
  const valid = /^data[-\w.:]+$/i;
15205
15788
  function find(schema, value) {
15206
- const normal = normalize$1(value);
15789
+ const normal = normalize$2(value);
15207
15790
  let property = value;
15208
15791
  let Type = Info;
15209
15792
  if (normal in schema.normal) {
@@ -22223,9 +22806,9 @@ function join(...segments) {
22223
22806
  joined = joined === void 0 ? segments[index2] : joined + "/" + segments[index2];
22224
22807
  }
22225
22808
  }
22226
- return joined === void 0 ? "." : normalize(joined);
22809
+ return joined === void 0 ? "." : normalize$1(joined);
22227
22810
  }
22228
- function normalize(path2) {
22811
+ function normalize$1(path2) {
22229
22812
  assertPath$1(path2);
22230
22813
  const absolute = path2.codePointAt(0) === 47;
22231
22814
  let value = normalizeString(path2, !absolute);
@@ -26549,7 +27132,7 @@ function remarkGfm(options) {
26549
27132
  fromMarkdownExtensions.push(gfmFromMarkdown());
26550
27133
  toMarkdownExtensions.push(gfmToMarkdown(settings));
26551
27134
  }
26552
- const api$9 = window.api;
27135
+ const api$c = window.api;
26553
27136
  function stamp(items, type) {
26554
27137
  return (items || []).map((i) => ({
26555
27138
  ...i,
@@ -26589,9 +27172,9 @@ const useEntityStore = create$1((set, get) => ({
26589
27172
  }),
26590
27173
  refreshAll: async () => {
26591
27174
  const [notesRaw, papersRaw, dataRaw] = await Promise.all([
26592
- api$9.listNotes(),
26593
- api$9.listLiterature(),
26594
- api$9.listData()
27175
+ api$c.listNotes(),
27176
+ api$c.listLiterature(),
27177
+ api$c.listData()
26595
27178
  ]);
26596
27179
  const notes = stamp(notesRaw, "note");
26597
27180
  notes.sort((a, b2) => a.id === "agent-md" ? -1 : b2.id === "agent-md" ? 1 : 0);
@@ -26604,7 +27187,7 @@ const useEntityStore = create$1((set, get) => ({
26604
27187
  });
26605
27188
  },
26606
27189
  deleteEntity: async (id) => {
26607
- await api$9.deleteEntity(id);
27190
+ await api$c.deleteEntity(id);
26608
27191
  await get().refreshAll();
26609
27192
  }
26610
27193
  }));
@@ -26660,10 +27243,10 @@ function findLastIndex(arr, pred) {
26660
27243
  }
26661
27244
  return -1;
26662
27245
  }
26663
- const api$8 = window.api;
27246
+ const api$b = window.api;
26664
27247
  async function loadFromFramework() {
26665
27248
  try {
26666
- const data = await api$8?.getUsageTotals?.();
27249
+ const data = await api$b?.getUsageTotals?.();
26667
27250
  return data ?? null;
26668
27251
  } catch (e) {
26669
27252
  console.warn("[usage-store] Failed to load persisted totals:", e);
@@ -26779,7 +27362,7 @@ const useUsageStore = create$1((set, get) => {
26779
27362
  },
26780
27363
  // Reset all-time totals (user-initiated)
26781
27364
  resetAllTime: () => {
26782
- api$8?.resetUsageTotals?.().catch?.(() => {
27365
+ api$b?.resetUsageTotals?.().catch?.(() => {
26783
27366
  });
26784
27367
  set({
26785
27368
  runPromptTokens: 0,
@@ -26810,13 +27393,31 @@ const usageStore = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePr
26810
27393
  __proto__: null,
26811
27394
  useUsageStore
26812
27395
  }, Symbol.toStringTag, { value: "Module" }));
26813
- const api$7 = window.api;
27396
+ const api$a = window.api;
27397
+ async function applyOpenResult(set, result) {
27398
+ if (!result) return false;
27399
+ useChatStore.getState().clear();
27400
+ useProgressStore.getState().clear();
27401
+ useActivityStore.getState().clear();
27402
+ useUIStore.getState().reset();
27403
+ useEntityStore.getState().reset();
27404
+ useUsageStore.getState().resetSession();
27405
+ set({ hasProject: false, sessionId: "", projectPath: "" });
27406
+ await new Promise((r) => setTimeout(r, 0));
27407
+ set({
27408
+ sessionId: result.sessionId,
27409
+ projectPath: result.projectPath,
27410
+ hasProject: true
27411
+ });
27412
+ await hydratePreferences();
27413
+ return true;
27414
+ }
26814
27415
  const useSessionStore = create$1((set) => ({
26815
27416
  sessionId: "",
26816
27417
  projectPath: "",
26817
27418
  hasProject: false,
26818
27419
  init: async () => {
26819
- const session = await api$7.getCurrentSession();
27420
+ const session = await api$a.getCurrentSession();
26820
27421
  set({
26821
27422
  sessionId: session.sessionId,
26822
27423
  projectPath: session.projectPath,
@@ -26827,28 +27428,15 @@ const useSessionStore = create$1((set) => ({
26827
27428
  }
26828
27429
  },
26829
27430
  pickFolder: async () => {
26830
- const result = await api$7.pickFolder();
26831
- if (result) {
26832
- useChatStore.getState().clear();
26833
- useProgressStore.getState().clear();
26834
- useActivityStore.getState().clear();
26835
- useUIStore.getState().reset();
26836
- useEntityStore.getState().reset();
26837
- useUsageStore.getState().resetSession();
26838
- set({ hasProject: false, sessionId: "", projectPath: "" });
26839
- await new Promise((r) => setTimeout(r, 0));
26840
- set({
26841
- sessionId: result.sessionId,
26842
- projectPath: result.projectPath,
26843
- hasProject: true
26844
- });
26845
- await hydratePreferences();
26846
- return true;
26847
- }
26848
- return false;
27431
+ const result = await api$a.pickFolder();
27432
+ return applyOpenResult(set, result);
27433
+ },
27434
+ openPath: async (projectPath) => {
27435
+ const result = await api$a.openProjectPath(projectPath);
27436
+ return applyOpenResult(set, result);
26849
27437
  },
26850
27438
  closeProject: async () => {
26851
- await api$7.closeProject();
27439
+ await api$a.closeProject();
26852
27440
  useChatStore.getState().clear();
26853
27441
  useProgressStore.getState().clear();
26854
27442
  useActivityStore.getState().clear();
@@ -26858,7 +27446,7 @@ const useSessionStore = create$1((set) => ({
26858
27446
  set({ sessionId: "", projectPath: "", hasProject: false });
26859
27447
  }
26860
27448
  }));
26861
- const api$6 = window.api;
27449
+ const api$9 = window.api;
26862
27450
  const ROW_HEIGHT = 28;
26863
27451
  const OVERSCAN_ROWS = 10;
26864
27452
  const MAX_DROP_FILE_SIZE = 100 * 1024 * 1024;
@@ -26957,7 +27545,7 @@ function WorkspaceTree() {
26957
27545
  const parentKey = toKey(relativePath);
26958
27546
  setParentLoading(parentKey, true);
26959
27547
  try {
26960
- const children = await api$6.listTree({
27548
+ const children = await api$9.listTree({
26961
27549
  relativePath,
26962
27550
  showIgnored,
26963
27551
  limit: 2e3
@@ -26982,8 +27570,8 @@ function WorkspaceTree() {
26982
27570
  void Promise.all(dirs.map((dir) => loadChildren(dir)));
26983
27571
  }, 500);
26984
27572
  };
26985
- const unsubFileCreated = api$6.onFileCreated(scheduleRefresh);
26986
- const unsubAgentDone = api$6.onAgentDone(scheduleRefresh);
27573
+ const unsubFileCreated = api$9.onFileCreated(scheduleRefresh);
27574
+ const unsubAgentDone = api$9.onAgentDone(scheduleRefresh);
26987
27575
  return () => {
26988
27576
  if (debounceTimer) clearTimeout(debounceTimer);
26989
27577
  unsubFileCreated();
@@ -27023,7 +27611,7 @@ function WorkspaceTree() {
27023
27611
  }
27024
27612
  setSearching(true);
27025
27613
  try {
27026
- const results = await api$6.searchTree(query.trim(), { showIgnored, maxResults: 4e3 });
27614
+ const results = await api$9.searchTree(query.trim(), { showIgnored, maxResults: 4e3 });
27027
27615
  setSearchResults(results);
27028
27616
  } finally {
27029
27617
  setSearching(false);
@@ -27067,7 +27655,7 @@ function WorkspaceTree() {
27067
27655
  if (node2.type !== "file") return;
27068
27656
  const ext = (node2.name.split(".").pop() || "").toLowerCase();
27069
27657
  if (!TEXT_EXTENSIONS.has(ext)) {
27070
- api$6.openFile(node2.path);
27658
+ api$9.openFile(node2.path);
27071
27659
  return;
27072
27660
  }
27073
27661
  const normalizedNodePath = normalizePath(node2.path);
@@ -27092,14 +27680,14 @@ function WorkspaceTree() {
27092
27680
  });
27093
27681
  }, [data, openPreview]);
27094
27682
  const createArtifact = reactExports.useCallback(async (node2) => {
27095
- await api$6.createArtifactFromFile(node2.path);
27683
+ await api$9.createArtifactFromFile(node2.path);
27096
27684
  await refreshEntities();
27097
27685
  }, [refreshEntities]);
27098
27686
  const handleTrashClick = reactExports.useCallback(async (node2) => {
27099
27687
  if (confirmTrashPath === node2.relativePath) {
27100
27688
  if (confirmTimerRef.current) clearTimeout(confirmTimerRef.current);
27101
27689
  setConfirmTrashPath(null);
27102
- const result = await api$6.trashFile(node2.path);
27690
+ const result = await api$9.trashFile(node2.path);
27103
27691
  if (result.success) {
27104
27692
  const parentRelPath = node2.relativePath.includes("/") ? node2.relativePath.slice(0, node2.relativePath.lastIndexOf("/")) : "";
27105
27693
  await loadChildren(parentRelPath);
@@ -27148,7 +27736,7 @@ function WorkspaceTree() {
27148
27736
  return;
27149
27737
  }
27150
27738
  const relPath = creating.parentDir ? `${creating.parentDir}/${createValue.trim()}` : createValue.trim();
27151
- const result = creating.type === "file" ? await api$6.createFile(relPath) : await api$6.createDir(relPath);
27739
+ const result = creating.type === "file" ? await api$9.createFile(relPath) : await api$9.createDir(relPath);
27152
27740
  if (result.success) {
27153
27741
  await loadChildren(creating.parentDir);
27154
27742
  }
@@ -27175,7 +27763,7 @@ function WorkspaceTree() {
27175
27763
  const parentDir = renaming.includes("/") ? renaming.slice(0, renaming.lastIndexOf("/")) : "";
27176
27764
  const newRelPath = parentDir ? `${parentDir}/${renameValue.trim()}` : renameValue.trim();
27177
27765
  if (newRelPath !== renaming) {
27178
- await api$6.renameFile(renaming, newRelPath);
27766
+ await api$9.renameFile(renaming, newRelPath);
27179
27767
  await loadChildren(parentDir);
27180
27768
  }
27181
27769
  setRenaming(null);
@@ -27217,7 +27805,7 @@ function WorkspaceTree() {
27217
27805
  const base64 = btoa(
27218
27806
  new Uint8Array(buffer).reduce((data2, byte) => data2 + String.fromCharCode(byte), "")
27219
27807
  );
27220
- await api$6.dropToDir(file.name, base64, targetRelPath);
27808
+ await api$9.dropToDir(file.name, base64, targetRelPath);
27221
27809
  }
27222
27810
  await loadChildren(targetRelPath);
27223
27811
  }, [getDropDir, loadChildren]);
@@ -27243,7 +27831,7 @@ function WorkspaceTree() {
27243
27831
  const base64 = btoa(
27244
27832
  new Uint8Array(buffer).reduce((data2, byte) => data2 + String.fromCharCode(byte), "")
27245
27833
  );
27246
- await api$6.dropToDir(file.name, base64, "");
27834
+ await api$9.dropToDir(file.name, base64, "");
27247
27835
  }
27248
27836
  await loadChildren("");
27249
27837
  }, [loadChildren]);
@@ -27318,7 +27906,7 @@ function WorkspaceTree() {
27318
27906
  onKeyDown: handleCreateKeyDown,
27319
27907
  onBlur: () => void commitCreate(),
27320
27908
  placeholder: creating.type === "file" ? "filename.ext" : "folder name",
27321
- className: "flex-1 bg-transparent outline-none border-b border-[var(--color-accent-soft)] text-xs t-text"
27909
+ className: "flex-1 bg-transparent outline-none t-focus-ring border-b border-[var(--color-accent-soft)] text-xs t-text"
27322
27910
  }
27323
27911
  )
27324
27912
  ]
@@ -27637,7 +28225,7 @@ const useSkillStore = create$1((set, get) => ({
27637
28225
  return result;
27638
28226
  }
27639
28227
  }));
27640
- const remarkPlugins$2 = [remarkGfm];
28228
+ const remarkPlugins$3 = [remarkGfm];
27641
28229
  function HoverPreview({
27642
28230
  entity,
27643
28231
  anchorRect,
@@ -27679,7 +28267,7 @@ function HoverPreview({
27679
28267
  }
27680
28268
  ) })
27681
28269
  ] }),
27682
- content2 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "md-prose text-xs", style: { color: "var(--color-text-secondary)" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { remarkPlugins: remarkPlugins$2, children: content2.length > 600 ? content2.slice(0, 600) + "..." : content2 }) }) })
28270
+ content2 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "md-prose text-xs", style: { color: "var(--color-text-secondary)" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { remarkPlugins: remarkPlugins$3, children: content2.length > 600 ? content2.slice(0, 600) + "..." : content2 }) }) })
27683
28271
  ]
27684
28272
  }
27685
28273
  );
@@ -28140,6 +28728,35 @@ function EntityTabs() {
28140
28728
  )
28141
28729
  ] });
28142
28730
  }
28731
+ const api$8 = window.api;
28732
+ function ConceptsList() {
28733
+ const [concepts, setConcepts] = reactExports.useState([]);
28734
+ const setSlug = useUIStore((s15) => s15.setWikiReaderSlug);
28735
+ const activeSlug = useUIStore((s15) => s15.wikiReaderSlug);
28736
+ reactExports.useEffect(() => {
28737
+ api$8.wikiListPages?.().then((result) => {
28738
+ if (result?.concepts) setConcepts(result.concepts);
28739
+ }).catch(() => {
28740
+ });
28741
+ }, []);
28742
+ if (concepts.length === 0) return null;
28743
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
28744
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] t-text-accent-soft uppercase tracking-wider font-medium flex items-center gap-1.5", children: [
28745
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Network, { size: 10 }),
28746
+ "Concepts"
28747
+ ] }),
28748
+ concepts.map((c) => /* @__PURE__ */ jsxRuntimeExports.jsx(
28749
+ "button",
28750
+ {
28751
+ onClick: () => setSlug(c.slug),
28752
+ className: `w-full text-left flex items-center justify-between px-1 py-0.5 rounded transition-colors ${activeSlug === c.slug ? "bg-[var(--color-accent-soft)]/10 t-text-accent" : "t-text-secondary hover:t-bg-hover"}`,
28753
+ title: c.title,
28754
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] truncate", children: c.title })
28755
+ },
28756
+ c.slug
28757
+ ))
28758
+ ] });
28759
+ }
28143
28760
  function QuickAction({
28144
28761
  icon: Icon2,
28145
28762
  label,
@@ -28163,45 +28780,6 @@ function QuickAction({
28163
28780
  }
28164
28781
  );
28165
28782
  }
28166
- function SourceBreakdown({ papers }) {
28167
- const sources = reactExports.useMemo(() => {
28168
- const counts = /* @__PURE__ */ new Map();
28169
- for (const p of papers) {
28170
- const src = p.externalSource || "unknown";
28171
- counts.set(src, (counts.get(src) || 0) + 1);
28172
- }
28173
- return Array.from(counts.entries()).sort((a, b2) => b2[1] - a[1]);
28174
- }, [papers]);
28175
- if (sources.length === 0) return null;
28176
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
28177
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] t-text-muted uppercase tracking-wider font-medium", children: "Sources" }),
28178
- sources.map(([src, count]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-1", children: [
28179
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] t-text-secondary capitalize", children: src.replace(/_/g, " ") }),
28180
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] t-text-muted tabular-nums", children: count })
28181
- ] }, src))
28182
- ] });
28183
- }
28184
- function RoundHistory({ papers }) {
28185
- const rounds = reactExports.useMemo(() => {
28186
- const map2 = /* @__PURE__ */ new Map();
28187
- for (const p of papers) {
28188
- const round = p.addedInRound || "";
28189
- if (round) map2.set(round, (map2.get(round) || 0) + 1);
28190
- }
28191
- return Array.from(map2.entries()).sort();
28192
- }, [papers]);
28193
- if (rounds.length === 0) return null;
28194
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
28195
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] t-text-muted uppercase tracking-wider font-medium", children: "Search Rounds" }),
28196
- rounds.map(([round, count]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-1", children: [
28197
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] t-text-secondary", children: round }),
28198
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] t-text-muted tabular-nums", children: [
28199
- count,
28200
- " papers"
28201
- ] })
28202
- ] }, round))
28203
- ] });
28204
- }
28205
28783
  function GapAlerts({ papers }) {
28206
28784
  const gaps = reactExports.useMemo(() => {
28207
28785
  const topicScores = /* @__PURE__ */ new Map();
@@ -28312,8 +28890,7 @@ function LiteratureSidebar() {
28312
28890
  ] }),
28313
28891
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-3 border-t t-border" }),
28314
28892
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto px-3 py-3 space-y-4", children: [
28315
- /* @__PURE__ */ jsxRuntimeExports.jsx(SourceBreakdown, { papers }),
28316
- /* @__PURE__ */ jsxRuntimeExports.jsx(RoundHistory, { papers }),
28893
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ConceptsList, {}),
28317
28894
  /* @__PURE__ */ jsxRuntimeExports.jsx(GapAlerts, { papers }),
28318
28895
  papers.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center py-6", children: [
28319
28896
  /* @__PURE__ */ jsxRuntimeExports.jsx(ChartColumn, { size: 24, className: "mx-auto mb-2 t-text-muted opacity-30" }),
@@ -28505,20 +29082,25 @@ function UserProfile() {
28505
29082
  }
28506
29083
  );
28507
29084
  }
28508
- const providers = [...new Set(SUPPORTED_MODELS.map((m) => m.provider))];
28509
- const groupedModels = {};
28510
- for (const p of providers) {
28511
- groupedModels[p] = SUPPORTED_MODELS.filter((m) => m.provider === p);
29085
+ const allProviders = [...new Set(SUPPORTED_MODELS.map((m) => m.provider))];
29086
+ const allGroupedModels = {};
29087
+ for (const p of allProviders) {
29088
+ allGroupedModels[p] = SUPPORTED_MODELS.filter((m) => m.provider === p);
28512
29089
  }
28513
29090
  function ModelSelector$1({ selectedModel, onSelectModel }) {
28514
29091
  const [open, setOpen] = reactExports.useState(false);
28515
29092
  const [anthropicStatus, setAnthropicStatus] = reactExports.useState(null);
28516
29093
  const [codexStatus, setCodexStatus] = reactExports.useState(null);
28517
29094
  const [codexLoggingIn, setCodexLoggingIn] = reactExports.useState(false);
29095
+ const [anthropicSubStatus, setAnthropicSubStatus] = reactExports.useState(null);
29096
+ const [anthropicSubLoggingIn, setAnthropicSubLoggingIn] = reactExports.useState(false);
28518
29097
  const [showAnthropicDialog, setShowAnthropicDialog] = reactExports.useState(false);
28519
29098
  const [showOpenAIDialog, setShowOpenAIDialog] = reactExports.useState(false);
28520
29099
  const ref = reactExports.useRef(null);
28521
29100
  const api2 = window.api;
29101
+ const claudeSubEnabled = api2?.isClaudeSubEnabled?.() ?? false;
29102
+ const providers = claudeSubEnabled ? allProviders : allProviders.filter((p) => p !== "Claude Subscription");
29103
+ const groupedModels = claudeSubEnabled ? allGroupedModels : Object.fromEntries(Object.entries(allGroupedModels).filter(([p]) => p !== "Claude Subscription"));
28522
29104
  const current = SUPPORTED_MODELS.find((m) => m.id === selectedModel);
28523
29105
  const refreshAnthropicStatus = reactExports.useCallback(async () => {
28524
29106
  try {
@@ -28536,6 +29118,14 @@ function ModelSelector$1({ selectedModel, onSelectModel }) {
28536
29118
  setCodexStatus(null);
28537
29119
  }
28538
29120
  }, [api2]);
29121
+ const refreshAnthropicSubStatus = reactExports.useCallback(async () => {
29122
+ try {
29123
+ const status = await api2?.getAnthropicSubStatus?.();
29124
+ setAnthropicSubStatus(status ?? null);
29125
+ } catch {
29126
+ setAnthropicSubStatus(null);
29127
+ }
29128
+ }, [api2]);
28539
29129
  reactExports.useEffect(() => {
28540
29130
  if (!open) return;
28541
29131
  const handler = (e) => {
@@ -28557,11 +29147,12 @@ function ModelSelector$1({ selectedModel, onSelectModel }) {
28557
29147
  reactExports.useEffect(() => {
28558
29148
  refreshAnthropicStatus();
28559
29149
  refreshCodexStatus();
29150
+ if (claudeSubEnabled) refreshAnthropicSubStatus();
28560
29151
  const unsub = api2?.onAnthropicAuthStatus?.((status) => setAnthropicStatus(status));
28561
29152
  return () => {
28562
29153
  if (typeof unsub === "function") unsub();
28563
29154
  };
28564
- }, [api2, refreshAnthropicStatus, refreshCodexStatus]);
29155
+ }, [api2, refreshAnthropicStatus, refreshCodexStatus, refreshAnthropicSubStatus, claudeSubEnabled]);
28565
29156
  const handleCodexLogin = async () => {
28566
29157
  setCodexLoggingIn(true);
28567
29158
  try {
@@ -28581,6 +29172,25 @@ function ModelSelector$1({ selectedModel, onSelectModel }) {
28581
29172
  await api2?.openaiCodexLogout?.();
28582
29173
  await refreshCodexStatus();
28583
29174
  };
29175
+ const handleAnthropicSubLogin = async () => {
29176
+ setAnthropicSubLoggingIn(true);
29177
+ try {
29178
+ const result = await api2?.anthropicSubLogin?.();
29179
+ if (result?.success) {
29180
+ await refreshAnthropicSubStatus();
29181
+ } else {
29182
+ console.error("[ModelSelector] Anthropic sub login failed:", result?.error);
29183
+ }
29184
+ } catch (err) {
29185
+ console.error("[ModelSelector] Anthropic sub login error:", err);
29186
+ } finally {
29187
+ setAnthropicSubLoggingIn(false);
29188
+ }
29189
+ };
29190
+ const handleAnthropicSubLogout = async () => {
29191
+ await api2?.anthropicSubLogout?.();
29192
+ await refreshAnthropicSubStatus();
29193
+ };
28584
29194
  const handleModelSelect = async (model) => {
28585
29195
  const { provider } = parseModelKey(model.id);
28586
29196
  if (provider === "openai-codex") {
@@ -28596,6 +29206,19 @@ function ModelSelector$1({ selectedModel, onSelectModel }) {
28596
29206
  setOpen(false);
28597
29207
  return;
28598
29208
  }
29209
+ if (provider === "anthropic-sub") {
29210
+ if (!anthropicSubStatus?.isLoggedIn) {
29211
+ await handleAnthropicSubLogin();
29212
+ const status = await api2?.getAnthropicSubStatus?.();
29213
+ if (!status?.isLoggedIn) {
29214
+ setOpen(false);
29215
+ return;
29216
+ }
29217
+ }
29218
+ onSelectModel(model.id);
29219
+ setOpen(false);
29220
+ return;
29221
+ }
28599
29222
  if (provider === "openai") {
28600
29223
  const status = await api2?.getOpenAIAuthStatus?.();
28601
29224
  if (!status?.hasApiKey) {
@@ -28617,7 +29240,7 @@ function ModelSelector$1({ selectedModel, onSelectModel }) {
28617
29240
  refreshAnthropicStatus();
28618
29241
  };
28619
29242
  const { provider: currentProvider } = current ? parseModelKey(current.id) : { provider: "" };
28620
- const authBadge = currentProvider === "anthropic" ? anthropicStatus?.authMode === "api-key" ? "api" : "auth" : currentProvider === "openai-codex" ? "sub" : currentProvider === "openai" ? "api" : null;
29243
+ const authBadge = currentProvider === "anthropic" ? anthropicStatus?.authMode === "api-key" ? "api" : "auth" : currentProvider === "anthropic-sub" ? "sub" : currentProvider === "openai-codex" ? "sub" : currentProvider === "openai" ? "api" : null;
28621
29244
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref, className: "relative", children: [
28622
29245
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
28623
29246
  "button",
@@ -28638,7 +29261,7 @@ function ModelSelector$1({ selectedModel, onSelectModel }) {
28638
29261
  }
28639
29262
  ),
28640
29263
  /* @__PURE__ */ jsxRuntimeExports.jsx(Cpu, { size: 14 }),
28641
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate max-w-[100px]", children: current?.label || selectedModel }),
29264
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate max-w-[72px]", children: current?.label || selectedModel }),
28642
29265
  authBadge && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] px-1 rounded border t-border t-text-muted uppercase", children: authBadge }),
28643
29266
  /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 12, className: `transition-transform ${open ? "rotate-180" : ""}` })
28644
29267
  ]
@@ -28678,6 +29301,38 @@ function ModelSelector$1({ selectedModel, onSelectModel }) {
28678
29301
  }
28679
29302
  )
28680
29303
  ] }),
29304
+ provider === "Claude Subscription" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 pb-1 flex items-center justify-between", children: [
29305
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] t-text-muted", children: anthropicSubStatus?.isLoggedIn ? "Signed in" : "OAuth required" }),
29306
+ anthropicSubStatus?.isLoggedIn ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
29307
+ "button",
29308
+ {
29309
+ onClick: (e) => {
29310
+ e.stopPropagation();
29311
+ handleAnthropicSubLogout();
29312
+ },
29313
+ className: "text-[10px] t-text-muted hover:t-text flex items-center gap-0.5",
29314
+ children: [
29315
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LogOut, { size: 10 }),
29316
+ " Sign out"
29317
+ ]
29318
+ }
29319
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
29320
+ "button",
29321
+ {
29322
+ onClick: (e) => {
29323
+ e.stopPropagation();
29324
+ handleAnthropicSubLogin();
29325
+ },
29326
+ disabled: anthropicSubLoggingIn,
29327
+ className: "text-[10px] t-text-accent flex items-center gap-0.5 hover:opacity-80 disabled:opacity-50",
29328
+ children: [
29329
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LogIn, { size: 10 }),
29330
+ " ",
29331
+ anthropicSubLoggingIn ? "Signing in..." : "Sign in"
29332
+ ]
29333
+ }
29334
+ )
29335
+ ] }),
28681
29336
  (provider === "OpenAI" || provider === "Anthropic") && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 pb-1 text-[11px] t-text-muted", children: "API Key" }),
28682
29337
  groupedModels[provider].map((model) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
28683
29338
  "button",
@@ -28961,75 +29616,119 @@ function LeftSidebar() {
28961
29616
  useChatStore.getState().insertContextReset();
28962
29617
  noContextShownRef.current = false;
28963
29618
  }, []);
28964
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("aside", { className: "w-80 flex flex-col border-r t-border t-bg-base pt-10", children: [
28965
- /* @__PURE__ */ jsxRuntimeExports.jsxs("nav", { "aria-label": "Sidebar tools", className: "px-4 pb-3 flex items-center justify-between", children: [
28966
- /* @__PURE__ */ jsxRuntimeExports.jsx(ModelSelector, {}),
28967
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
28968
- /* @__PURE__ */ jsxRuntimeExports.jsx(ReasoningToggle, {}),
28969
- /* @__PURE__ */ jsxRuntimeExports.jsx(
28970
- ToolbarButton,
28971
- {
28972
- onClick: handleResetContext,
28973
- tooltip: "Reset AI context",
28974
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 16 })
28975
- }
28976
- ),
29619
+ return (
29620
+ // Narrow windows (≤1279px) get w-80 (320px) so the center panel keeps
29621
+ // room to breathe; wider windows (≥1280px) get w-[22rem] (352px) so the
29622
+ // toolbar has comfortable slack. Below ~1024px the ModelSelector label
29623
+ // is the first thing to truncate — see ModelSelector for the shrink
29624
+ // pattern introduced in commit 95312df.
29625
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("aside", { className: "w-80 xl:w-[22rem] flex flex-col border-r t-border t-bg-base pt-10", children: [
29626
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("nav", { "aria-label": "Sidebar tools", className: "px-4 pb-3 flex items-center justify-between", children: [
29627
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ModelSelector, {}),
29628
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
29629
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ReasoningToggle, {}),
29630
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
29631
+ ToolbarButton,
29632
+ {
29633
+ onClick: handleResetContext,
29634
+ tooltip: "Reset AI context",
29635
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 16 })
29636
+ }
29637
+ ),
29638
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
29639
+ ToolbarButton,
29640
+ {
29641
+ onClick: toggleTheme,
29642
+ tooltip: `${theme === "dark" ? "Light" : "Dark"} mode`,
29643
+ children: theme === "dark" ? /* @__PURE__ */ jsxRuntimeExports.jsx(Sun, { size: 16 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Moon, { size: 16 })
29644
+ }
29645
+ ),
29646
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
29647
+ ToolbarButton,
29648
+ {
29649
+ onClick: () => useUIStore.getState().toggleTerminal(),
29650
+ tooltip: "Terminal ⌘`",
29651
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { size: 16 })
29652
+ }
29653
+ )
29654
+ ] })
29655
+ ] }),
29656
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: centerView === "literature" ? /* @__PURE__ */ jsxRuntimeExports.jsx(LiteratureSidebar, {}) : centerView === "compute" && window.api?.isComputeEnabled?.() ? /* @__PURE__ */ jsxRuntimeExports.jsx(ComputeSidebar, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(EntityTabs, {}) }),
29657
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t t-border p-4", children: /* @__PURE__ */ jsxRuntimeExports.jsx(UserProfile, {}) })
29658
+ ] })
29659
+ );
29660
+ }
29661
+ const STARTERS = [
29662
+ {
29663
+ label: "Understand this folder",
29664
+ description: "Scan files, existing artifacts, and recent work to get oriented."
29665
+ },
29666
+ {
29667
+ label: "Start a literature review on …",
29668
+ description: "Plan sub-topics, search multiple sources, score and summarize."
29669
+ },
29670
+ {
29671
+ label: "Capture a research note about …",
29672
+ description: "Draft a note artifact from your description."
29673
+ }
29674
+ ];
29675
+ function focusInput() {
29676
+ const input = document.querySelector("[data-chat-input]");
29677
+ if (!input) return;
29678
+ input.focus();
29679
+ const end = input.value.length;
29680
+ try {
29681
+ input.setSelectionRange(end, end);
29682
+ } catch {
29683
+ }
29684
+ }
29685
+ function prefillFromLabel(label) {
29686
+ return label.replace(/…\s*$/, "").replace(/\s+$/, "") + (label.endsWith("…") ? " " : "");
29687
+ }
29688
+ function StarterRow({ starter, onActivate }) {
29689
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
29690
+ "button",
29691
+ {
29692
+ type: "button",
29693
+ onClick: onActivate,
29694
+ className: "group relative w-full text-left flex flex-col gap-0.5 py-2.5 pl-4 pr-2 rounded-sm t-bg-hover transition-colors",
29695
+ children: [
28977
29696
  /* @__PURE__ */ jsxRuntimeExports.jsx(
28978
- ToolbarButton,
29697
+ "span",
28979
29698
  {
28980
- onClick: toggleTheme,
28981
- tooltip: `${theme === "dark" ? "Light" : "Dark"} mode`,
28982
- children: theme === "dark" ? /* @__PURE__ */ jsxRuntimeExports.jsx(Sun, { size: 16 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Moon, { size: 16 })
29699
+ "aria-hidden": true,
29700
+ className: "absolute left-0 top-1.5 bottom-1.5 w-[2px] rounded-full bg-transparent group-hover:t-bg-accent-soft transition-colors"
28983
29701
  }
28984
29702
  ),
28985
- /* @__PURE__ */ jsxRuntimeExports.jsx(
28986
- ToolbarButton,
28987
- {
28988
- onClick: () => useUIStore.getState().toggleTerminal(),
28989
- tooltip: "Terminal ⌘`",
28990
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { size: 16 })
28991
- }
28992
- )
28993
- ] })
28994
- ] }),
28995
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: centerView === "literature" ? /* @__PURE__ */ jsxRuntimeExports.jsx(LiteratureSidebar, {}) : centerView === "compute" && window.api?.isComputeEnabled?.() ? /* @__PURE__ */ jsxRuntimeExports.jsx(ComputeSidebar, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(EntityTabs, {}) }),
28996
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t t-border p-4", children: /* @__PURE__ */ jsxRuntimeExports.jsx(UserProfile, {}) })
28997
- ] });
29703
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[13px] t-text-secondary group-hover:t-text transition-colors leading-snug", children: starter.label }),
29704
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] t-text-muted leading-snug", children: starter.description })
29705
+ ]
29706
+ }
29707
+ );
28998
29708
  }
28999
- const suggestions = [
29000
- { icon: FileText, label: "Draft a research note", prompt: "Help me draft a research note about " },
29001
- { icon: BookOpen, label: "Search literature", prompt: "Search for recent papers on " },
29002
- { icon: ChartColumn, label: "Analyze data", prompt: "Analyze the data in " },
29003
- { icon: Sparkles, label: "Brainstorm ideas", prompt: "Help me brainstorm ideas for " }
29004
- ];
29005
29709
  function HeroIdle() {
29006
29710
  const setDraftText = useChatStore((s15) => s15.setDraftText);
29007
- const handleSuggestion = (prompt) => {
29008
- setDraftText(prompt);
29009
- const input = document.querySelector("[data-chat-input]");
29010
- input?.focus();
29711
+ const handleStarter = (label) => {
29712
+ const prefill = prefillFromLabel(label);
29713
+ setDraftText(prefill);
29714
+ requestAnimationFrame(focusInput);
29011
29715
  };
29012
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-10 w-full max-w-lg px-8", children: [
29013
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-center space-y-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
29014
- "h1",
29015
- {
29016
- className: "t-text-secondary tracking-tight font-medium",
29017
- style: { fontSize: "var(--text-xl)" },
29018
- children: "What would you like to do?"
29019
- }
29020
- ) }),
29021
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-2 gap-3 w-full", children: suggestions.map(({ icon: Icon2, label, prompt }) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
29022
- "button",
29023
- {
29024
- onClick: () => handleSuggestion(prompt),
29025
- className: "group flex items-center gap-2.5 px-3.5 py-2.5 rounded-lg t-bg-surface border t-border\n chip-hover hover:shadow-sm transition-all duration-200 text-left",
29026
- children: [
29027
- /* @__PURE__ */ jsxRuntimeExports.jsx(Icon2, { size: 14, className: "shrink-0 t-text-muted group-hover:t-text-accent-2 transition-colors" }),
29028
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-secondary group-hover:t-text font-medium transition-colors", style: { fontSize: "var(--text-sm)" }, children: label })
29029
- ]
29030
- },
29031
- label
29032
- )) })
29716
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full max-w-lg px-8", children: [
29717
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-4 mb-2 text-[10px] uppercase tracking-wider t-text-muted font-medium", children: "Start" }),
29718
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col", children: STARTERS.map((s15) => /* @__PURE__ */ jsxRuntimeExports.jsx(StarterRow, { starter: s15, onActivate: () => handleStarter(s15.label) }, s15.label)) }),
29719
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-8 pl-4 flex flex-col gap-1.5 text-[10px] t-text-muted", children: [
29720
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
29721
+ /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "inline-flex items-center px-1 py-0 rounded border t-border-subtle t-bg-elevated text-[9.5px] font-mono t-text-secondary leading-[1.4]", children: "@" }),
29722
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "mention a note, paper, or file" })
29723
+ ] }),
29724
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
29725
+ /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "inline-flex items-center px-1 py-0 rounded border t-border-subtle t-bg-elevated text-[9.5px] font-mono t-text-secondary leading-[1.4]", children: "↵" }),
29726
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "send" }),
29727
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "opacity-50", "aria-hidden": true, children: "·" }),
29728
+ /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "inline-flex items-center px-1 py-0 rounded border t-border-subtle t-bg-elevated text-[9.5px] font-mono t-text-secondary leading-[1.4]", children: "⇧↵" }),
29729
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "newline" })
29730
+ ] })
29731
+ ] })
29033
29732
  ] });
29034
29733
  }
29035
29734
  function getFileName(path2) {
@@ -29656,8 +30355,8 @@ function ToolUseStream({ events: propEvents }) {
29656
30355
  running.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 mt-1", children: running.map((event) => /* @__PURE__ */ jsxRuntimeExports.jsx(ToolUseCard, { event }, event.id)) })
29657
30356
  ] });
29658
30357
  }
29659
- const api$5 = window.api;
29660
- const remarkPlugins$1 = [remarkGfm];
30358
+ const api$7 = window.api;
30359
+ const remarkPlugins$2 = [remarkGfm];
29661
30360
  function formatMessageTime(ts2) {
29662
30361
  const d = new Date(ts2);
29663
30362
  const now2 = /* @__PURE__ */ new Date();
@@ -29731,7 +30430,7 @@ function SelectionBookmark() {
29731
30430
  try {
29732
30431
  const first = selectedText.split(/[.!?\n]/)[0].trim();
29733
30432
  const title = first.length > 60 ? first.slice(0, 57) + "…" : first || "Untitled selection";
29734
- const created = await api$5.artifactCreate({
30433
+ const created = await api$7.artifactCreate({
29735
30434
  type: "note",
29736
30435
  title,
29737
30436
  content: selectedText,
@@ -29796,7 +30495,7 @@ const MessageBubble = React$2.memo(function MessageBubble2({ msg, isSaved }) {
29796
30495
  try {
29797
30496
  const first = msg.content.replace(/^#+\s*/, "").split(/[.!?\n]/)[0].trim();
29798
30497
  const title = first.length > 60 ? first.slice(0, 57) + "…" : first || "Untitled note";
29799
- const created = await api$5.artifactCreate({
30498
+ const created = await api$7.artifactCreate({
29800
30499
  type: "note",
29801
30500
  title,
29802
30501
  content: msg.content,
@@ -29842,7 +30541,7 @@ const MessageBubble = React$2.memo(function MessageBubble2({ msg, isSaved }) {
29842
30541
  },
29843
30542
  i
29844
30543
  )) }),
29845
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "md-prose", style: { color: "var(--color-text)" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { remarkPlugins: remarkPlugins$1, children: msg.content }) }),
30544
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "md-prose", style: { color: "var(--color-text)" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { remarkPlugins: remarkPlugins$2, children: msg.content }) }),
29846
30545
  msg.timestamp > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `mt-1.5 text-[10px] t-text-muted select-none ${isUser ? "text-right" : "text-left"}`, children: formatMessageTime(msg.timestamp) }),
29847
30546
  !isUser && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute -right-8 top-2 flex flex-col gap-1.5", children: [
29848
30547
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -29872,11 +30571,18 @@ const MessageBubble = React$2.memo(function MessageBubble2({ msg, isSaved }) {
29872
30571
  ) });
29873
30572
  });
29874
30573
  function ThinkingIndicator() {
29875
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-1.5 mt-3 ml-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex gap-[3px] items-center", "aria-label": "Thinking", children: [
29876
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-[5px] h-[5px] rounded-full t-bg-accent-soft animate-bounce animation-delay-0" }),
29877
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-[5px] h-[5px] rounded-full t-bg-accent-soft animate-bounce animation-delay-150" }),
29878
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-[5px] h-[5px] rounded-full t-bg-accent-soft animate-bounce animation-delay-300" })
29879
- ] }) });
30574
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
30575
+ "div",
30576
+ {
30577
+ role: "status",
30578
+ "aria-live": "polite",
30579
+ className: "flex items-center gap-2 mt-3 ml-2 text-[11px] t-text-muted",
30580
+ children: [
30581
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size: 11, className: "t-text-accent-soft animate-spin shrink-0", "aria-hidden": true }),
30582
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Thinking…" })
30583
+ ]
30584
+ }
30585
+ );
29880
30586
  }
29881
30587
  function StreamingBubble() {
29882
30588
  const text2 = useChatStore((s15) => s15.streamingText);
@@ -29887,7 +30593,7 @@ function StreamingBubble() {
29887
30593
  className: "max-w-[90%] rounded-2xl px-4 py-3 text-sm t-text assistant-bubble",
29888
30594
  style: { background: "var(--color-bubble-assistant)" },
29889
30595
  children: [
29890
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "md-prose", style: { color: "var(--color-text)" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { remarkPlugins: remarkPlugins$1, children: text2 }) }),
30596
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "md-prose", style: { color: "var(--color-text)" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { remarkPlugins: remarkPlugins$2, children: text2 }) }),
29891
30597
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-block w-1.5 h-4 t-bg-accent-soft animate-pulse ml-0.5 align-text-bottom" })
29892
30598
  ]
29893
30599
  }
@@ -30052,7 +30758,7 @@ function ChatTimeline({ messages, scrollContainerRef }) {
30052
30758
  borderRadius: 1,
30053
30759
  background: "var(--color-accent-soft)",
30054
30760
  opacity: isActive ? 1 : 0.35,
30055
- transition: "width 80ms, height 80ms, opacity 80ms"
30761
+ transition: "opacity 80ms"
30056
30762
  }
30057
30763
  },
30058
30764
  node2.msgId
@@ -30196,7 +30902,7 @@ function ChatMessages() {
30196
30902
  )
30197
30903
  ] });
30198
30904
  }
30199
- const api$4 = window.api;
30905
+ const api$6 = window.api;
30200
30906
  const typeIcons$1 = {
30201
30907
  note: /* @__PURE__ */ jsxRuntimeExports.jsx(StickyNote, { size: 13, className: "t-text-warning" }),
30202
30908
  paper: /* @__PURE__ */ jsxRuntimeExports.jsx(BookOpen, { size: 13, className: "t-text-info" }),
@@ -30255,7 +30961,7 @@ function MentionPopover({ query, onSelect, onClose }) {
30255
30961
  }
30256
30962
  }
30257
30963
  debounceRef.current = setTimeout(() => {
30258
- api$4.getCandidates(search2, type).then((result) => {
30964
+ api$6.getCandidates(search2, type).then((result) => {
30259
30965
  if (stale) return;
30260
30966
  setCandidates(result || []);
30261
30967
  setSelectedIdx(0);
@@ -30448,7 +31154,7 @@ const SLASH_COMMANDS = [
30448
31154
  { name: "/delete", description: "Delete an entity", args: "<id>" },
30449
31155
  { name: "/help", description: "Show available commands" }
30450
31156
  ];
30451
- const api$3 = window.api;
31157
+ const api$5 = window.api;
30452
31158
  const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
30453
31159
  const MAX_IMAGES = 5;
30454
31160
  const ACCEPTED_DOC_EXTENSIONS = [".pdf", ".csv", ".md", ".txt", ".json", ".xml", ".html", ".docx"];
@@ -30569,7 +31275,7 @@ function ChatInput() {
30569
31275
  const dataUrl = reader.result;
30570
31276
  const base64 = dataUrl.split(",")[1];
30571
31277
  try {
30572
- const result = await api$3.convertFileToText(file.name, base64);
31278
+ const result = await api$5.convertFileToText(file.name, base64);
30573
31279
  if (result?.success && result.content) {
30574
31280
  setPendingFiles((p) => p.map(
30575
31281
  (f) => f.name === file.name ? { ...f, content: result.content, loading: false } : f
@@ -30686,19 +31392,19 @@ ${fileBlocks}`;
30686
31392
  try {
30687
31393
  switch (cmd) {
30688
31394
  case "/notes": {
30689
- const notes = await api$3.listNotes();
31395
+ const notes = await api$5.listNotes();
30690
31396
  result = notes?.length ? `**Notes (${notes.length}):**
30691
31397
  ` + notes.map((n) => `- ${n.title} \`${n.id.slice(0, 8)}\``).join("\n") : "No notes yet.";
30692
31398
  break;
30693
31399
  }
30694
31400
  case "/papers": {
30695
- const papers = await api$3.listLiterature();
31401
+ const papers = await api$5.listLiterature();
30696
31402
  result = papers?.length ? `**Papers (${papers.length}):**
30697
31403
  ` + papers.map((p) => `- ${p.title} \`${p.citeKey}\``).join("\n") : "No papers yet.";
30698
31404
  break;
30699
31405
  }
30700
31406
  case "/data": {
30701
- const data = await api$3.listData();
31407
+ const data = await api$5.listData();
30702
31408
  result = data?.length ? `**Data (${data.length}):**
30703
31409
  ` + data.map((d) => `- ${d.name} \`${d.id.slice(0, 8)}\``).join("\n") : "No data attachments yet.";
30704
31410
  break;
@@ -30708,7 +31414,7 @@ ${fileBlocks}`;
30708
31414
  result = "Usage: `/search <query>`";
30709
31415
  break;
30710
31416
  }
30711
- const results = await api$3.search(rest);
31417
+ const results = await api$5.search(rest);
30712
31418
  result = results?.length ? `**Search results for "${rest}":**
30713
31419
  ` + results.map((r) => `- [${r.type}] ${r.title} \`${r.id.slice(0, 8)}\``).join("\n") : `No results for "${rest}".`;
30714
31420
  break;
@@ -30718,7 +31424,7 @@ ${fileBlocks}`;
30718
31424
  result = "Usage: `/note <title>`";
30719
31425
  break;
30720
31426
  }
30721
- const r = await api$3.artifactCreate({ type: "note", title: rest, content: "" });
31427
+ const r = await api$5.artifactCreate({ type: "note", title: rest, content: "" });
30722
31428
  result = r?.success ? `Note saved: **${rest}**` : `Failed: ${r?.error || "unknown error"}`;
30723
31429
  refreshEntities();
30724
31430
  break;
@@ -30741,7 +31447,7 @@ ${fileBlocks}`;
30741
31447
  const bibtex = flags.bibtex || `@article{${citeKey},
30742
31448
  title = {${cleaned}}
30743
31449
  }`;
30744
- const r = await api$3.artifactCreate({
31450
+ const r = await api$5.artifactCreate({
30745
31451
  type: "paper",
30746
31452
  title: cleaned,
30747
31453
  authors,
@@ -30768,7 +31474,7 @@ ${fileBlocks}`;
30768
31474
  result = "Usage: `/save-data <name> --path <file>`";
30769
31475
  break;
30770
31476
  }
30771
- const r = await api$3.artifactCreate({
31477
+ const r = await api$5.artifactCreate({
30772
31478
  type: "data",
30773
31479
  title: cleaned,
30774
31480
  filePath: flags.path,
@@ -30779,7 +31485,7 @@ ${fileBlocks}`;
30779
31485
  break;
30780
31486
  }
30781
31487
  case "/summary": {
30782
- const summaryResult = await api$3.sessionSummaryGet();
31488
+ const summaryResult = await api$5.sessionSummaryGet();
30783
31489
  if (!summaryResult?.success || !summaryResult?.summary) {
30784
31490
  result = "No session summary available yet.";
30785
31491
  break;
@@ -30806,7 +31512,7 @@ ${s15.openQuestions.map((q2) => `- ${q2}`).join("\n")}`;
30806
31512
  result = "Usage: `/delete <id>`";
30807
31513
  break;
30808
31514
  }
30809
- const r = await api$3.deleteEntity(rest);
31515
+ const r = await api$5.deleteEntity(rest);
30810
31516
  result = r?.success ? `Deleted \`${rest}\`` : `Failed: ${r?.error || "not found"}`;
30811
31517
  refreshEntities();
30812
31518
  break;
@@ -31025,49 +31731,245 @@ ${s15.openQuestions.map((q2) => `- ${q2}`).join("\n")}`;
31025
31731
  )
31026
31732
  ] });
31027
31733
  }
31028
- function TopicTree({
31029
- papers,
31030
- selectedTopic,
31031
- onSelect
31032
- }) {
31033
- const topicCounts = reactExports.useMemo(() => {
31034
- const counts = /* @__PURE__ */ new Map();
31035
- for (const p of papers) {
31036
- const topic = p.subTopic || "Uncategorized";
31037
- counts.set(topic, (counts.get(topic) || 0) + 1);
31734
+ const api$4 = window.api;
31735
+ const remarkPlugins$1 = [remarkGfm];
31736
+ function PaperFallback({ paper }) {
31737
+ const authors = paper.authors || [];
31738
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
31739
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-base font-semibold t-text leading-tight", children: paper.title }),
31740
+ authors.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs t-text-muted", children: authors.join(", ") }),
31741
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-1.5", children: [
31742
+ paper.year && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1.5 py-0.5 text-[10px] rounded t-bg-elevated t-text-muted", children: paper.year }),
31743
+ paper.venue && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1.5 py-0.5 text-[10px] rounded t-bg-elevated t-text-muted", children: paper.venue }),
31744
+ paper.doi && !paper.doi.startsWith("unknown:") && /* @__PURE__ */ jsxRuntimeExports.jsxs(
31745
+ "a",
31746
+ {
31747
+ href: `https://doi.org/${paper.doi}`,
31748
+ target: "_blank",
31749
+ rel: "noopener noreferrer",
31750
+ className: "inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10px] rounded t-bg-elevated t-text-accent hover:underline",
31751
+ children: [
31752
+ "DOI ",
31753
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { size: 8 })
31754
+ ]
31755
+ }
31756
+ ),
31757
+ paper.relevanceScore != null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-1.5 py-0.5 text-[10px] rounded t-bg-elevated t-text-accent", children: [
31758
+ paper.relevanceScore,
31759
+ "/10"
31760
+ ] })
31761
+ ] }),
31762
+ paper.abstract && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
31763
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] t-text-muted uppercase tracking-wider mb-1 font-medium", children: "Abstract" }),
31764
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs t-text-secondary leading-relaxed", children: paper.abstract })
31765
+ ] }),
31766
+ paper.relevanceJustification && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
31767
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] t-text-muted uppercase tracking-wider mb-1 font-medium", children: "Relevance" }),
31768
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs t-text-secondary italic leading-relaxed", children: paper.relevanceJustification })
31769
+ ] }),
31770
+ (paper.keyFindings || []).length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
31771
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] t-text-muted uppercase tracking-wider mb-1 font-medium", children: "Key Findings" }),
31772
+ /* @__PURE__ */ jsxRuntimeExports.jsx("ul", { className: "space-y-0.5", children: paper.keyFindings.map((f, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "text-xs t-text-secondary flex items-start gap-1.5", children: [
31773
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 mt-1 w-1 h-1 rounded-full bg-[var(--color-accent-soft)]" }),
31774
+ f
31775
+ ] }, i)) })
31776
+ ] }),
31777
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] t-text-muted italic pt-2", children: "No wiki page available for this paper yet." })
31778
+ ] });
31779
+ }
31780
+ function WikiReaderPanel() {
31781
+ const slug = useUIStore((s15) => s15.wikiReaderSlug);
31782
+ const setSlug = useUIStore((s15) => s15.setWikiReaderSlug);
31783
+ const goBack = useUIStore((s15) => s15.wikiReaderBack);
31784
+ const history = useUIStore((s15) => s15.wikiReaderHistory);
31785
+ const papers = useEntityStore((s15) => s15.papers);
31786
+ const [content2, setContent] = reactExports.useState(null);
31787
+ const [loading, setLoading] = reactExports.useState(false);
31788
+ const isPaperFallback = slug?.startsWith("paper:");
31789
+ const fallbackPaper = isPaperFallback ? papers.find((p) => p.id === slug.replace("paper:", "")) : null;
31790
+ reactExports.useEffect(() => {
31791
+ if (!slug || isPaperFallback) {
31792
+ setContent(null);
31793
+ return;
31038
31794
  }
31039
- return Array.from(counts.entries()).sort((a, b2) => b2[1] - a[1]);
31040
- }, [papers]);
31041
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-48 shrink-0 border-r t-border overflow-y-auto py-2", children: [
31042
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
31043
- "button",
31044
- {
31045
- onClick: () => onSelect(null),
31046
- className: `w-full text-left px-3 py-1.5 text-xs transition-colors ${selectedTopic === null ? "t-text-accent font-medium bg-[var(--color-accent-soft)]/10" : "t-text-secondary hover:t-bg-hover"}`,
31047
- children: [
31048
- "All Topics (",
31049
- papers.length,
31050
- ")"
31051
- ]
31795
+ let cancelled = false;
31796
+ setLoading(true);
31797
+ api$4.wikiReadPage?.(slug).then((md) => {
31798
+ if (!cancelled) {
31799
+ setContent(md);
31800
+ setLoading(false);
31052
31801
  }
31053
- ),
31054
- topicCounts.map(([topic, count]) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
31055
- "button",
31056
- {
31057
- onClick: () => onSelect(selectedTopic === topic ? null : topic),
31058
- className: `w-full text-left px-3 py-1.5 text-xs transition-colors truncate ${selectedTopic === topic ? "t-text-accent font-medium bg-[var(--color-accent-soft)]/10" : "t-text-secondary hover:t-bg-hover"}`,
31059
- title: topic,
31060
- children: [
31061
- topic,
31062
- " (",
31063
- count,
31064
- ")"
31065
- ]
31066
- },
31067
- topic
31068
- ))
31802
+ }).catch(() => {
31803
+ if (!cancelled) {
31804
+ setContent(null);
31805
+ setLoading(false);
31806
+ }
31807
+ });
31808
+ return () => {
31809
+ cancelled = true;
31810
+ };
31811
+ }, [slug, isPaperFallback]);
31812
+ const processedContent = reactExports.useMemo(() => {
31813
+ if (!content2) return null;
31814
+ return content2.replace(
31815
+ /<!--\s*paper:(\S+)\s*-->([\s\S]*?)<!--\s*\/paper:\1\s*-->/g,
31816
+ (_match, paperSlug, body) => {
31817
+ const firstLine = body.trim().split("\n")[0] || "";
31818
+ const titleMatch = firstLine.match(/\*{1,2}([^*]+)\*{1,2}/) || firstLine.match(/"([^"]+)"/);
31819
+ const title = titleMatch ? titleMatch[1] : paperSlug;
31820
+ return `
31821
+ ---
31822
+ ### [${title}](#wiki:${paperSlug})
31823
+ ${body.trim()}
31824
+ `;
31825
+ }
31826
+ ).replace(/<!--[\s\S]*?-->/g, "").replace(
31827
+ /\[\[([^\]]+)\]\]/g,
31828
+ (_match, innerSlug) => `[${innerSlug}](#wiki:${innerSlug})`
31829
+ );
31830
+ }, [content2]);
31831
+ const handleLinkClick = reactExports.useCallback((e) => {
31832
+ const target = e.target;
31833
+ const anchor = target.closest("a");
31834
+ if (!anchor) return;
31835
+ const href = anchor.getAttribute("href");
31836
+ if (href?.startsWith("#wiki:")) {
31837
+ e.preventDefault();
31838
+ setSlug(href.replace("#wiki:", ""));
31839
+ }
31840
+ }, [setSlug]);
31841
+ if (!slug) {
31842
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full text-center px-6", children: [
31843
+ /* @__PURE__ */ jsxRuntimeExports.jsx(BookOpen, { size: 28, className: "t-text-muted mb-2.5 opacity-30" }),
31844
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs t-text-muted", children: "Select a paper or concept to view details." })
31845
+ ] });
31846
+ }
31847
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full min-h-0", children: [
31848
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 px-3 py-1.5 border-b t-border t-bg-surface shrink-0", children: [
31849
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
31850
+ "button",
31851
+ {
31852
+ onClick: goBack,
31853
+ disabled: history.length === 0,
31854
+ className: "p-1 rounded t-text-muted hover:t-text transition-colors disabled:opacity-30",
31855
+ title: "Back",
31856
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowLeft, { size: 14 })
31857
+ }
31858
+ ),
31859
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 text-[11px] t-text-muted truncate font-mono", children: isPaperFallback ? fallbackPaper?.title || slug : slug }),
31860
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
31861
+ "button",
31862
+ {
31863
+ onClick: () => setSlug(null),
31864
+ className: "p-1 rounded t-text-muted hover:t-text transition-colors",
31865
+ title: "Close",
31866
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(X$1, { size: 13 })
31867
+ }
31868
+ )
31869
+ ] }),
31870
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto px-4 py-3", onClick: handleLinkClick, children: loading ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs t-text-muted animate-pulse", children: "Loading..." }) : isPaperFallback && fallbackPaper ? /* @__PURE__ */ jsxRuntimeExports.jsx(PaperFallback, { paper: fallbackPaper }) : processedContent ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "md-prose text-sm", style: { color: "var(--color-text)" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { remarkPlugins: remarkPlugins$1, children: processedContent }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs t-text-muted", children: "No wiki page found for this slug." }) })
31069
31871
  ] });
31070
31872
  }
31873
+ function normalize(raw) {
31874
+ return raw.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
31875
+ }
31876
+ function splitWords(s15) {
31877
+ const out = [];
31878
+ for (const w2 of s15.split(" ")) if (w2) out.push(w2);
31879
+ return out;
31880
+ }
31881
+ function firstAndLastWords(s15) {
31882
+ const words = splitWords(s15);
31883
+ if (words.length === 0) return [];
31884
+ if (words.length === 1) return [words[0]];
31885
+ return words[0] === words[words.length - 1] ? [words[0]] : [words[0], words[words.length - 1]];
31886
+ }
31887
+ function makeSearchable(p) {
31888
+ const normTitle = normalize(p.title || "");
31889
+ const titleWords = splitWords(normTitle);
31890
+ const normAuthors = (p.authors || []).map((a) => normalize(a)).filter(Boolean);
31891
+ const prefixTokens = /* @__PURE__ */ new Set();
31892
+ for (const a of normAuthors) for (const t of firstAndLastWords(a)) prefixTokens.add(t);
31893
+ return {
31894
+ normTitle,
31895
+ titleWords,
31896
+ titleWordSet: new Set(titleWords),
31897
+ normAuthors,
31898
+ authorPrefixTokens: Array.from(prefixTokens),
31899
+ normVenue: p.venue ? normalize(p.venue) : void 0,
31900
+ normTldr: p.tldr ? normalize(p.tldr) : void 0,
31901
+ normAbstract: p.abstract ? normalize(p.abstract) : void 0
31902
+ };
31903
+ }
31904
+ function tokenizeQuery(query) {
31905
+ const seen2 = /* @__PURE__ */ new Set();
31906
+ const out = [];
31907
+ for (const w2 of normalize(query).split(" ")) {
31908
+ if (w2.length < 2) continue;
31909
+ if (seen2.has(w2)) continue;
31910
+ seen2.add(w2);
31911
+ out.push(w2);
31912
+ }
31913
+ return out;
31914
+ }
31915
+ function isSubsequence(needle, hay) {
31916
+ if (needle.length === 0) return true;
31917
+ if (needle.length > hay.length) return false;
31918
+ let i = 0;
31919
+ for (let j2 = 0; j2 < hay.length && i < needle.length; j2++) {
31920
+ if (hay.charCodeAt(j2) === needle.charCodeAt(i)) i++;
31921
+ }
31922
+ return i === needle.length;
31923
+ }
31924
+ function scoreToken(token, p) {
31925
+ let best = 0;
31926
+ if (p.titleWordSet.has(token)) {
31927
+ best = Math.max(best, 20);
31928
+ } else if (p.normTitle.includes(token)) {
31929
+ best = Math.max(best, 10);
31930
+ } else if (token.length >= 4) {
31931
+ for (const word of p.titleWords) {
31932
+ if (word.length >= token.length && isSubsequence(token, word)) {
31933
+ best = Math.max(best, 4);
31934
+ break;
31935
+ }
31936
+ }
31937
+ }
31938
+ for (const pref of p.authorPrefixTokens) {
31939
+ if (pref.startsWith(token)) {
31940
+ best = Math.max(best, 10);
31941
+ break;
31942
+ }
31943
+ }
31944
+ if (best < 8) {
31945
+ for (const full of p.normAuthors) {
31946
+ if (full.includes(token)) {
31947
+ best = Math.max(best, 8);
31948
+ break;
31949
+ }
31950
+ }
31951
+ }
31952
+ if (p.normVenue && p.normVenue.includes(token)) best = Math.max(best, 3);
31953
+ if (p.normTldr && p.normTldr.includes(token)) best = Math.max(best, 3);
31954
+ if (p.normAbstract && p.normAbstract.includes(token)) best = Math.max(best, 2);
31955
+ return best;
31956
+ }
31957
+ function scorePaper(tokens, p) {
31958
+ if (tokens.length === 0) return 0;
31959
+ let total = 0;
31960
+ let hits = 0;
31961
+ for (const t of tokens) {
31962
+ const s15 = scoreToken(t, p);
31963
+ if (s15 > 0) {
31964
+ hits++;
31965
+ total += s15;
31966
+ }
31967
+ }
31968
+ const required = tokens.length <= 3 ? tokens.length : Math.ceil(0.7 * tokens.length);
31969
+ if (hits < required) return null;
31970
+ return total;
31971
+ }
31972
+ const api$3 = window.api;
31071
31973
  function ScoreBadge({ score }) {
31072
31974
  if (score == null) return null;
31073
31975
  const color2 = score >= 8 ? "text-emerald-500" : score >= 6 ? "text-amber-500" : "t-text-muted";
@@ -31101,36 +32003,49 @@ function PaperRow({
31101
32003
  paper,
31102
32004
  expanded,
31103
32005
  onToggle,
31104
- onPreview
32006
+ wikiSlug,
32007
+ isActive,
32008
+ source = "project"
31105
32009
  }) {
32010
+ const setWikiSlug = useUIStore((s15) => s15.setWikiReaderSlug);
31106
32011
  const authors = paper.authors || [];
31107
32012
  const authorStr = authors.length <= 3 ? authors.join(", ") : `${authors.slice(0, 2).join(", ")} et al.`;
31108
32013
  const keyFindings = paper.keyFindings || [];
31109
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-b t-border last:border-b-0", children: [
32014
+ const isWiki = source === "wiki";
32015
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `border-b t-border last:border-b-0 ${isActive ? "bg-[var(--color-accent-soft)]/8" : ""} ${isWiki ? "border-l-2 border-l-[var(--color-accent-soft)]" : ""}`, children: [
31110
32016
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
31111
32017
  "div",
31112
32018
  {
31113
- className: "flex items-center gap-3 px-3 py-2 hover:bg-[var(--color-accent-soft)]/5 transition-colors cursor-pointer",
32019
+ className: `flex items-center gap-3 px-3 py-2 transition-colors cursor-pointer ${isActive ? "bg-[var(--color-accent-soft)]/10" : "hover:bg-[var(--color-accent-soft)]/5"}`,
31114
32020
  onClick: onToggle,
31115
32021
  children: [
31116
32022
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "shrink-0 t-text-muted", children: expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 }) }),
31117
32023
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
31118
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[13px] t-text font-medium truncate leading-tight", children: paper.title }),
32024
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 min-w-0", children: [
32025
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[13px] t-text font-medium truncate leading-tight", children: paper.title }),
32026
+ isWiki && /* @__PURE__ */ jsxRuntimeExports.jsx(
32027
+ "span",
32028
+ {
32029
+ className: "shrink-0 px-1.5 py-0 text-[9px] uppercase tracking-wider rounded t-text-accent bg-[var(--color-accent-soft)]/15 border border-[var(--color-accent-soft)]/30",
32030
+ title: "From paper wiki — not in this project",
32031
+ children: "Wiki"
32032
+ }
32033
+ )
32034
+ ] }),
31119
32035
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted truncate mt-0.5", children: authorStr })
31120
32036
  ] }),
31121
32037
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-[11px] t-text-muted tabular-nums w-10 text-right", children: paper.year || "—" }),
31122
32038
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0 w-10 text-right", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScoreBadge, { score: paper.relevanceScore }) }),
31123
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-[11px] t-text-muted tabular-nums w-12 text-right", children: paper.citationCount != null ? paper.citationCount : "—" }),
31124
32039
  /* @__PURE__ */ jsxRuntimeExports.jsx(
31125
32040
  "button",
31126
32041
  {
31127
32042
  onClick: (e) => {
31128
32043
  e.stopPropagation();
31129
- onPreview();
32044
+ setWikiSlug(wikiSlug || `paper:${paper.id}`);
31130
32045
  },
31131
- className: "shrink-0 p-1 rounded t-text-muted hover:t-text-accent-soft transition-colors",
31132
- title: "Open detail",
31133
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(BookOpen, { size: 14 })
32046
+ className: `shrink-0 p-1 rounded transition-colors ${wikiSlug ? "t-text-accent-soft hover:t-text-accent" : "t-text-muted hover:t-text-accent-soft"}`,
32047
+ title: wikiSlug ? "View wiki page" : "View paper details",
32048
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(BookOpen, { size: 13 })
31134
32049
  }
31135
32050
  )
31136
32051
  ]
@@ -31140,8 +32055,6 @@ function PaperRow({
31140
32055
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-1.5", children: [
31141
32056
  paper.venue && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1.5 py-0.5 text-[10px] rounded t-bg-elevated t-text-muted", children: paper.venue }),
31142
32057
  paper.subTopic && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1.5 py-0.5 text-[10px] rounded bg-[var(--color-accent-soft)]/10 t-text-accent", children: paper.subTopic }),
31143
- paper.externalSource && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1.5 py-0.5 text-[10px] rounded t-bg-elevated t-text-muted", children: paper.externalSource }),
31144
- paper.addedInRound && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1.5 py-0.5 text-[10px] rounded t-bg-elevated t-text-muted", children: paper.addedInRound }),
31145
32058
  paper.doi && !paper.doi.startsWith("unknown:") && /* @__PURE__ */ jsxRuntimeExports.jsxs(
31146
32059
  "a",
31147
32060
  {
@@ -31186,6 +32099,46 @@ function PaperRow({
31186
32099
  ] })
31187
32100
  ] });
31188
32101
  }
32102
+ function WikiStatusPill() {
32103
+ const [status, setStatus] = reactExports.useState(null);
32104
+ reactExports.useEffect(() => {
32105
+ let cancelled = false;
32106
+ api$3.wikiGetStatus?.().then((s15) => {
32107
+ if (!cancelled) setStatus(s15);
32108
+ }).catch(() => {
32109
+ });
32110
+ const unsub = api$3.onWikiStatus?.((s15) => setStatus(s15));
32111
+ return () => {
32112
+ cancelled = true;
32113
+ unsub?.();
32114
+ };
32115
+ }, []);
32116
+ if (!status || status.state === "disabled") return null;
32117
+ const isProcessing = status.state === "processing";
32118
+ const isPaused = status.state === "paused";
32119
+ const totalInBatch = status.processed + status.pending;
32120
+ const pct = totalInBatch > 0 ? Math.round(status.processed / totalInBatch * 100) : 0;
32121
+ const dotClass = isProcessing ? "bg-blue-500 animate-pulse" : isPaused ? "bg-yellow-500" : "bg-emerald-500";
32122
+ const label = isProcessing ? `Wiki · processing${totalInBatch > 0 ? ` ${status.processed}/${totalInBatch}` : ""}` : isPaused ? "Wiki · paused" : `Wiki · idle${status.totalInWiki > 0 ? ` · ${status.totalInWiki} pages` : ""}`;
32123
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
32124
+ "div",
32125
+ {
32126
+ className: "ml-auto flex items-center gap-2 shrink-0",
32127
+ title: status.lastRunAt ? `Last tick: ${new Date(status.lastRunAt).toLocaleString()}` : void 0,
32128
+ children: [
32129
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `inline-block w-1.5 h-1.5 rounded-full ${dotClass}` }),
32130
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted tabular-nums", children: label }),
32131
+ isProcessing && totalInBatch > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-16 h-1 rounded-full t-bg-elevated overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32132
+ "div",
32133
+ {
32134
+ className: "h-full bg-blue-500 transition-[width] duration-500 ease-out",
32135
+ style: { width: `${pct}%` }
32136
+ }
32137
+ ) })
32138
+ ]
32139
+ }
32140
+ );
32141
+ }
31189
32142
  function CoverageBar$1({ papers }) {
31190
32143
  const topicCounts = reactExports.useMemo(() => {
31191
32144
  const counts = /* @__PURE__ */ new Map();
@@ -31215,7 +32168,7 @@ function CoverageBar$1({ papers }) {
31215
32168
  highRelevance,
31216
32169
  " highly relevant"
31217
32170
  ] }),
31218
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 max-w-48", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-1.5 rounded-full t-bg-elevated overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32171
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "max-w-48 w-48", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-1.5 rounded-full t-bg-elevated overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
31219
32172
  "div",
31220
32173
  {
31221
32174
  className: "h-full rounded-full t-gradient-accent-h",
@@ -31223,10 +32176,11 @@ function CoverageBar$1({ papers }) {
31223
32176
  width: `${Math.min(100, highRelevance / Math.max(papers.length, 1) * 100)}%`
31224
32177
  }
31225
32178
  }
31226
- ) }) })
32179
+ ) }) }),
32180
+ /* @__PURE__ */ jsxRuntimeExports.jsx(WikiStatusPill, {})
31227
32181
  ] });
31228
32182
  }
31229
- function FilterBar$1() {
32183
+ function FilterBar$1({ topics }) {
31230
32184
  const filter = useUIStore((s15) => s15.literatureFilter);
31231
32185
  const setFilter = useUIStore((s15) => s15.setLiteratureFilter);
31232
32186
  const [showFilters, setShowFilters] = reactExports.useState(false);
@@ -31241,7 +32195,7 @@ function FilterBar$1() {
31241
32195
  type: "text",
31242
32196
  value: filter.search,
31243
32197
  onChange: (e) => setFilter({ search: e.target.value }),
31244
- placeholder: "Search papers by title, author, abstract...",
32198
+ placeholder: "Search papers...",
31245
32199
  className: "w-full pl-8 pr-3 py-1.5 text-xs rounded-lg border t-border t-bg-surface t-text focus:outline-none focus:border-[var(--color-accent-soft)]"
31246
32200
  }
31247
32201
  ),
@@ -31254,6 +32208,18 @@ function FilterBar$1() {
31254
32208
  }
31255
32209
  )
31256
32210
  ] }),
32211
+ topics.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
32212
+ "select",
32213
+ {
32214
+ value: filter.subTopic || "",
32215
+ onChange: (e) => setFilter({ subTopic: e.target.value || null }),
32216
+ className: "px-2 py-1.5 text-xs rounded-lg border t-border t-bg-surface t-text max-w-40",
32217
+ children: [
32218
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "All topics" }),
32219
+ topics.map((t) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: t, children: t }, t))
32220
+ ]
32221
+ }
32222
+ ),
31257
32223
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
31258
32224
  "button",
31259
32225
  {
@@ -31261,7 +32227,6 @@ function FilterBar$1() {
31261
32227
  className: `flex items-center gap-1 px-2 py-1.5 text-xs rounded-lg border t-border transition-colors ${hasActiveFilters ? "border-[var(--color-accent-soft)] t-text-accent" : "t-text-muted hover:t-text-secondary"}`,
31262
32228
  children: [
31263
32229
  /* @__PURE__ */ jsxRuntimeExports.jsx(Filter, { size: 12 }),
31264
- "Filters",
31265
32230
  hasActiveFilters && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-[var(--color-accent-soft)]" })
31266
32231
  ]
31267
32232
  }
@@ -31293,12 +32258,47 @@ function FilterBar$1() {
31293
32258
  ] })
31294
32259
  ] });
31295
32260
  }
32261
+ function wikiMetaToEntityItem(meta) {
32262
+ return {
32263
+ id: `wiki:${meta.slug}`,
32264
+ type: "paper",
32265
+ title: meta.title,
32266
+ authors: meta.authors,
32267
+ year: meta.year,
32268
+ venue: meta.venue,
32269
+ abstract: meta.tldr
32270
+ // show tldr as abstract preview for wiki rows
32271
+ };
32272
+ }
31296
32273
  function LiteratureView() {
31297
32274
  const papers = useEntityStore((s15) => s15.papers);
31298
32275
  const filter = useUIStore((s15) => s15.literatureFilter);
31299
32276
  const setFilter = useUIStore((s15) => s15.setLiteratureFilter);
31300
- const openPreview = useUIStore((s15) => s15.openPreview);
31301
- const [expandedId, setExpandedId] = reactExports.useState(null);
32277
+ const wikiReaderSlug = useUIStore((s15) => s15.wikiReaderSlug);
32278
+ const setWikiSlug = useUIStore((s15) => s15.setWikiReaderSlug);
32279
+ const [expandedKey, setExpandedKey] = reactExports.useState(null);
32280
+ const [wikiSlugs, setWikiSlugs] = reactExports.useState({});
32281
+ const [wikiMeta, setWikiMeta] = reactExports.useState([]);
32282
+ reactExports.useEffect(() => {
32283
+ let cancelled = false;
32284
+ api$3.wikiPaperSlugMap?.().then((map2) => {
32285
+ if (!cancelled && map2) setWikiSlugs(map2);
32286
+ }).catch(() => {
32287
+ });
32288
+ return () => {
32289
+ cancelled = true;
32290
+ };
32291
+ }, [papers]);
32292
+ reactExports.useEffect(() => {
32293
+ let cancelled = false;
32294
+ api$3.wikiListPaperMeta?.().then((list2) => {
32295
+ if (!cancelled && Array.isArray(list2)) setWikiMeta(list2);
32296
+ }).catch(() => {
32297
+ });
32298
+ return () => {
32299
+ cancelled = true;
32300
+ };
32301
+ }, []);
31302
32302
  const handleSort = reactExports.useCallback(
31303
32303
  (key) => {
31304
32304
  if (filter.sortBy === key) {
@@ -31309,73 +32309,121 @@ function LiteratureView() {
31309
32309
  },
31310
32310
  [filter.sortBy, filter.sortDir, setFilter]
31311
32311
  );
31312
- const filtered = reactExports.useMemo(() => {
31313
- let result = [...papers];
31314
- if (filter.search.trim()) {
31315
- const q2 = filter.search.toLowerCase();
31316
- result = result.filter((p) => {
31317
- const title = (p.title || "").toLowerCase();
31318
- const abstract = (p.abstract || "").toLowerCase();
31319
- const authors = (p.authors || []).join(" ").toLowerCase();
31320
- return title.includes(q2) || abstract.includes(q2) || authors.includes(q2);
31321
- });
31322
- }
31323
- if (filter.subTopic) {
31324
- result = result.filter(
31325
- (p) => (p.subTopic || "Uncategorized") === filter.subTopic
31326
- );
31327
- }
31328
- if (filter.minScore > 0) {
31329
- result = result.filter((p) => (p.relevanceScore || 0) >= filter.minScore);
32312
+ const topics = reactExports.useMemo(() => {
32313
+ const counts = /* @__PURE__ */ new Map();
32314
+ for (const p of papers) {
32315
+ const topic = p.subTopic;
32316
+ if (topic) counts.set(topic, (counts.get(topic) || 0) + 1);
31330
32317
  }
31331
- if (filter.source) {
31332
- result = result.filter((p) => p.externalSource === filter.source);
32318
+ return Array.from(counts.entries()).sort((a, b2) => b2[1] - a[1]).map(([t]) => t);
32319
+ }, [papers]);
32320
+ const projectRows = reactExports.useMemo(() => {
32321
+ return papers.map((p) => ({
32322
+ key: `project:${p.id}`,
32323
+ source: "project",
32324
+ paper: p,
32325
+ wikiSlug: wikiSlugs[p.id],
32326
+ searchable: makeSearchable({
32327
+ title: p.title || "",
32328
+ authors: p.authors || [],
32329
+ venue: p.venue,
32330
+ tldr: void 0,
32331
+ abstract: p.abstract
32332
+ })
32333
+ }));
32334
+ }, [papers, wikiSlugs]);
32335
+ const wikiOnlyRows = reactExports.useMemo(() => {
32336
+ const usedSlugs = new Set(Object.values(wikiSlugs));
32337
+ return wikiMeta.filter((m) => !usedSlugs.has(m.slug)).map((m) => ({
32338
+ key: `wiki:${m.slug}`,
32339
+ source: "wiki",
32340
+ paper: wikiMetaToEntityItem(m),
32341
+ wikiSlug: m.slug,
32342
+ searchable: makeSearchable({
32343
+ title: m.title,
32344
+ authors: m.authors,
32345
+ venue: m.venue,
32346
+ tldr: m.tldr
32347
+ })
32348
+ }));
32349
+ }, [wikiMeta, papers, wikiSlugs]);
32350
+ const filteredRows = reactExports.useMemo(() => {
32351
+ const query = filter.search.trim();
32352
+ const tokens = query ? tokenizeQuery(query) : [];
32353
+ const searching = tokens.length > 0;
32354
+ const passesProjectFilters = (p) => {
32355
+ if (filter.subTopic && (p.subTopic || "Uncategorized") !== filter.subTopic) return false;
32356
+ if (filter.minScore > 0 && (p.relevanceScore || 0) < filter.minScore) return false;
32357
+ if (filter.source && p.externalSource !== filter.source) return false;
32358
+ if (filter.round && p.addedInRound !== filter.round) return false;
32359
+ return true;
32360
+ };
32361
+ const scored = [];
32362
+ for (const row of projectRows) {
32363
+ if (!passesProjectFilters(row.paper)) continue;
32364
+ if (searching) {
32365
+ const s15 = scorePaper(tokens, row.searchable);
32366
+ if (s15 == null) continue;
32367
+ scored.push({ row, score: s15 });
32368
+ } else {
32369
+ scored.push({ row, score: 0 });
32370
+ }
31333
32371
  }
31334
- if (filter.round) {
31335
- result = result.filter((p) => p.addedInRound === filter.round);
32372
+ if (searching) {
32373
+ for (const row of wikiOnlyRows) {
32374
+ const s15 = scorePaper(tokens, row.searchable);
32375
+ if (s15 == null) continue;
32376
+ scored.push({ row, score: s15 });
32377
+ }
31336
32378
  }
31337
- result.sort((a, b2) => {
31338
- const dir = filter.sortDir === "desc" ? -1 : 1;
32379
+ const dir = filter.sortDir === "desc" ? -1 : 1;
32380
+ const compareSortKey = (a, b2) => {
31339
32381
  switch (filter.sortBy) {
31340
- case "year": {
31341
- const ay = a.year || 0;
31342
- const by = b2.year || 0;
31343
- return (ay - by) * dir;
31344
- }
31345
- case "relevance": {
31346
- const as2 = a.relevanceScore || 0;
31347
- const bs2 = b2.relevanceScore || 0;
31348
- return (as2 - bs2) * dir;
31349
- }
31350
- case "citations": {
31351
- const ac2 = a.citationCount || 0;
31352
- const bc2 = b2.citationCount || 0;
31353
- return (ac2 - bc2) * dir;
31354
- }
32382
+ case "year":
32383
+ return ((a.year || 0) - (b2.year || 0)) * dir;
32384
+ case "relevance":
32385
+ return ((a.relevanceScore || 0) - (b2.relevanceScore || 0)) * dir;
32386
+ case "citations":
32387
+ return ((a.citationCount || 0) - (b2.citationCount || 0)) * dir;
31355
32388
  case "title":
31356
32389
  return a.title.localeCompare(b2.title) * dir;
31357
32390
  default:
31358
32391
  return 0;
31359
32392
  }
32393
+ };
32394
+ scored.sort((a, b2) => {
32395
+ if (searching) {
32396
+ if (a.score !== b2.score) return b2.score - a.score;
32397
+ }
32398
+ return compareSortKey(a.row.paper, b2.row.paper);
31360
32399
  });
31361
- return result;
31362
- }, [papers, filter]);
31363
- const hasTopics = papers.some((p) => p.subTopic);
32400
+ return scored.map((s15) => s15.row);
32401
+ }, [projectRows, wikiOnlyRows, filter]);
32402
+ reactExports.useEffect(() => {
32403
+ if (wikiReaderSlug || filteredRows.length === 0) return;
32404
+ const first = filteredRows[0];
32405
+ if (first.source === "wiki") {
32406
+ setWikiSlug(first.wikiSlug);
32407
+ } else {
32408
+ setWikiSlug(first.wikiSlug || `paper:${first.paper.id}`);
32409
+ }
32410
+ }, [filteredRows, wikiReaderSlug, setWikiSlug]);
32411
+ const activeRowKey = reactExports.useMemo(() => {
32412
+ if (!wikiReaderSlug) return null;
32413
+ if (wikiReaderSlug.startsWith("paper:")) {
32414
+ return `project:${wikiReaderSlug.slice("paper:".length)}`;
32415
+ }
32416
+ for (const [paperId, slug] of Object.entries(wikiSlugs)) {
32417
+ if (slug === wikiReaderSlug) return `project:${paperId}`;
32418
+ }
32419
+ return `wiki:${wikiReaderSlug}`;
32420
+ }, [wikiReaderSlug, wikiSlugs]);
31364
32421
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col min-h-0", children: [
31365
- /* @__PURE__ */ jsxRuntimeExports.jsx(FilterBar$1, {}),
32422
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FilterBar$1, { topics }),
31366
32423
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex min-h-0", children: [
31367
- hasTopics && /* @__PURE__ */ jsxRuntimeExports.jsx(
31368
- TopicTree,
31369
- {
31370
- papers,
31371
- selectedTopic: filter.subTopic,
31372
- onSelect: (topic) => setFilter({ subTopic: topic })
31373
- }
31374
- ),
31375
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col min-w-0", children: [
32424
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-1/2 flex flex-col min-w-0 border-r t-border", children: [
31376
32425
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 px-3 py-1.5 border-b t-border t-bg-surface", children: [
31377
32426
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-5" }),
31378
- " ",
31379
32427
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
31380
32428
  SortHeader,
31381
32429
  {
@@ -31408,36 +32456,34 @@ function LiteratureView() {
31408
32456
  className: "w-10 justify-end"
31409
32457
  }
31410
32458
  ),
31411
- /* @__PURE__ */ jsxRuntimeExports.jsx(
31412
- SortHeader,
31413
- {
31414
- label: "Cites",
31415
- sortKey: "citations",
31416
- currentSort: filter.sortBy,
31417
- currentDir: filter.sortDir,
31418
- onSort: handleSort,
31419
- className: "w-12 justify-end"
31420
- }
31421
- ),
31422
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-8" }),
31423
- " "
32459
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-8" })
31424
32460
  ] }),
31425
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto", children: filtered.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full text-center px-8", children: [
32461
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto", children: filteredRows.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full text-center px-8", children: [
31426
32462
  /* @__PURE__ */ jsxRuntimeExports.jsx(BookOpen, { size: 32, className: "t-text-muted mb-3 opacity-40" }),
31427
32463
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm t-text-muted", children: papers.length === 0 ? "No papers yet. Use the chat to search for literature." : "No papers match your filters." })
31428
- ] }) : filtered.map((paper) => /* @__PURE__ */ jsxRuntimeExports.jsx(
32464
+ ] }) : filteredRows.map((row) => /* @__PURE__ */ jsxRuntimeExports.jsx(
31429
32465
  PaperRow,
31430
32466
  {
31431
- paper,
31432
- expanded: expandedId === paper.id,
31433
- onToggle: () => setExpandedId(expandedId === paper.id ? null : paper.id),
31434
- onPreview: () => openPreview(paper)
32467
+ paper: row.paper,
32468
+ source: row.source,
32469
+ expanded: expandedKey === row.key,
32470
+ isActive: activeRowKey === row.key,
32471
+ onToggle: () => {
32472
+ setExpandedKey(expandedKey === row.key ? null : row.key);
32473
+ if (row.source === "wiki") {
32474
+ setWikiSlug(row.wikiSlug);
32475
+ } else {
32476
+ setWikiSlug(row.wikiSlug || `paper:${row.paper.id}`);
32477
+ }
32478
+ },
32479
+ wikiSlug: row.wikiSlug
31435
32480
  },
31436
- paper.id
32481
+ row.key
31437
32482
  )) })
31438
- ] })
32483
+ ] }),
32484
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-1/2 min-w-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(WikiReaderPanel, {}) })
31439
32485
  ] }),
31440
- /* @__PURE__ */ jsxRuntimeExports.jsx(CoverageBar$1, { papers: filtered })
32486
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CoverageBar$1, { papers: filteredRows.filter((r) => r.source === "project").map((r) => r.paper) })
31441
32487
  ] });
31442
32488
  }
31443
32489
  function formatDuration(seconds) {
@@ -31514,7 +32560,16 @@ function RunRow({
31514
32560
  className: "flex items-center gap-3 px-3 py-2 hover:bg-[var(--color-accent-soft)]/5 transition-colors cursor-pointer",
31515
32561
  onClick: onToggle,
31516
32562
  children: [
31517
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "shrink-0 t-text-muted", children: expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 }) }),
32563
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
32564
+ "button",
32565
+ {
32566
+ type: "button",
32567
+ "aria-label": expanded ? "Collapse run details" : "Expand run details",
32568
+ "aria-expanded": expanded,
32569
+ className: "shrink-0 t-text-muted",
32570
+ children: expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 })
32571
+ }
32572
+ ),
31518
32573
  /* @__PURE__ */ jsxRuntimeExports.jsx(StatusDot, { status: run.status }),
31519
32574
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
31520
32575
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[13px] t-text font-medium truncate leading-tight font-mono", children: run.command }),
@@ -31647,7 +32702,9 @@ function FilterBar({
31647
32702
  search2 && /* @__PURE__ */ jsxRuntimeExports.jsx(
31648
32703
  "button",
31649
32704
  {
32705
+ type: "button",
31650
32706
  onClick: () => onSearchChange(""),
32707
+ "aria-label": "Clear search",
31651
32708
  className: "absolute right-2 top-1/2 -translate-y-1/2 t-text-muted hover:t-text",
31652
32709
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(X$1, { size: 12 })
31653
32710
  }
@@ -31790,22 +32847,56 @@ function ViewSwitcher() {
31790
32847
  const setCenterView = useUIStore((s15) => s15.setCenterView);
31791
32848
  const paperCount = useEntityStore((s15) => s15.papers.length);
31792
32849
  const activeComputeRuns = useActiveRunCount();
31793
- return /* @__PURE__ */ jsxRuntimeExports.jsx("nav", { "aria-label": "View switcher", className: "flex items-center gap-0.5 px-4 pt-10 pb-1", children: viewTabs.map(({ key, label, icon: Icon2, shortcut }) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
31794
- "button",
31795
- {
31796
- onClick: () => setCenterView(key),
31797
- className: `no-drag flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg transition-colors ${centerView === key ? "t-text-accent bg-[var(--color-accent-soft)]/10" : "t-text-muted hover:t-text-secondary hover:t-bg-hover"}`,
31798
- title: `${label} (${shortcut})`,
31799
- children: [
31800
- /* @__PURE__ */ jsxRuntimeExports.jsx(Icon2, { size: 13 }),
31801
- label,
31802
- key === "literature" && paperCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-0.5 px-1 py-px text-[9px] rounded-full t-bg-elevated t-text-muted tabular-nums", children: paperCount }),
31803
- key === "compute" && activeComputeRuns > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-0.5 px-1 py-px text-[9px] rounded-full bg-[var(--color-accent-soft)]/10 t-text-accent tabular-nums", children: activeComputeRuns }),
31804
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] t-text-muted opacity-40 ml-0.5", children: shortcut })
31805
- ]
31806
- },
31807
- key
31808
- )) });
32850
+ return (
32851
+ // Bottom hairline gives the nav a real anchor — without it, the tabs
32852
+ // float in a gray area between the drag region and the content.
32853
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
32854
+ "nav",
32855
+ {
32856
+ "aria-label": "View switcher",
32857
+ className: "flex items-stretch gap-1 px-4 pt-10 border-b t-border",
32858
+ children: viewTabs.map(({ key, label, icon: Icon2, shortcut }) => {
32859
+ const isActive = centerView === key;
32860
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
32861
+ "button",
32862
+ {
32863
+ onClick: () => setCenterView(key),
32864
+ "aria-current": isActive ? "page" : void 0,
32865
+ title: `${label} (${shortcut})`,
32866
+ className: `no-drag relative group flex items-center gap-2 px-3 pt-1.5 pb-2 text-[13px] font-medium transition-colors ${isActive ? "t-text" : "t-text-muted hover:t-text-secondary"}`,
32867
+ children: [
32868
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Icon2, { size: 14, className: isActive ? "t-text-accent" : "" }),
32869
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: label }),
32870
+ key === "literature" && paperCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
32871
+ "span",
32872
+ {
32873
+ className: `px-1.5 py-px text-[10px] rounded-full tabular-nums ${isActive ? "t-bg-accent/15 t-text-accent" : "t-bg-elevated t-text-muted"}`,
32874
+ children: paperCount
32875
+ }
32876
+ ),
32877
+ key === "compute" && activeComputeRuns > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1.5 py-px text-[10px] rounded-full tabular-nums t-bg-accent/15 t-text-accent", children: activeComputeRuns }),
32878
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
32879
+ "kbd",
32880
+ {
32881
+ className: `inline-flex items-center px-1 py-0 rounded border text-[9.5px] font-mono leading-[1.4] transition-colors ${isActive ? "t-border-accent-soft t-text-accent-soft bg-transparent" : "t-border-subtle t-bg-elevated t-text-muted group-hover:t-text-secondary"}`,
32882
+ children: shortcut
32883
+ }
32884
+ ),
32885
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
32886
+ "span",
32887
+ {
32888
+ "aria-hidden": true,
32889
+ className: `pointer-events-none absolute left-0 right-0 -bottom-px h-[2px] transition-colors ${isActive ? "t-bg-accent" : "bg-transparent"}`
32890
+ }
32891
+ )
32892
+ ]
32893
+ },
32894
+ key
32895
+ );
32896
+ })
32897
+ }
32898
+ )
32899
+ );
31809
32900
  }
31810
32901
  function CenterPanel() {
31811
32902
  const centerView = useUIStore((s15) => s15.centerView);
@@ -31832,7 +32923,7 @@ function CenterPanel() {
31832
32923
  }
31833
32924
  const remarkPlugins = [remarkGfm];
31834
32925
  const LazyMilkdownMarkdownEditor = reactExports.lazy(async () => {
31835
- const mod = await __vitePreload(() => import("./MilkdownMarkdownEditor-wtbKgEbm.js").then((n) => n.bL), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
32926
+ const mod = await __vitePreload(() => import("./MilkdownMarkdownEditor-CK0d0F6d.js").then((n) => n.bL), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
31836
32927
  return { default: mod.MilkdownMarkdownEditor };
31837
32928
  });
31838
32929
  const typeIcons = {
@@ -32458,14 +33549,21 @@ function StatusBar() {
32458
33549
  const hasRunUsage = runTokens > 0;
32459
33550
  const hasProjectUsage = allTimeTokens > 0;
32460
33551
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-7 flex items-center px-4 gap-5 border-t t-border t-bg-surface text-[11px] t-text-secondary select-none shrink-0", children: [
32461
- hasSkills && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-2 overflow-hidden", children: activeSkills.map((name2) => /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 px-1.5 py-0.5 rounded t-bg-accent/10 t-text-accent whitespace-nowrap", children: [
32462
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px]", children: "⚡" }),
32463
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: name2 })
32464
- ] }, name2)) }),
32465
- hasActivity && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-3 overflow-hidden", children: toolSummary.map((t) => /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 whitespace-nowrap", children: [
32466
- t.pending > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-warning", children: "⟳" }) : t.failed > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-error", children: "✗" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-success", children: "✓" }),
33552
+ hasSkills && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-2 overflow-hidden", children: activeSkills.map((name2) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
33553
+ "span",
33554
+ {
33555
+ className: "flex items-center gap-1.5 px-1.5 py-0.5 rounded t-bg-accent/10 t-text-accent whitespace-nowrap",
33556
+ children: [
33557
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CircleDot, { size: 10, className: "shrink-0", "aria-hidden": true }),
33558
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: name2 })
33559
+ ]
33560
+ },
33561
+ name2
33562
+ )) }),
33563
+ hasActivity && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-3 overflow-hidden", children: toolSummary.map((t) => /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1.5 whitespace-nowrap", children: [
33564
+ t.pending > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size: 11, className: "t-text-warning shrink-0 animate-spin", "aria-label": "In progress" }) : t.failed > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx(X$1, { size: 11, className: "t-text-error shrink-0", "aria-label": "Failed" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 11, className: "t-text-success shrink-0", "aria-label": "Done" }),
32467
33565
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "capitalize", children: t.name }),
32468
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "t-text-muted", children: [
33566
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "t-text-muted tabular-nums", children: [
32469
33567
  "×",
32470
33568
  t.total
32471
33569
  ] })
@@ -32483,7 +33581,7 @@ function StatusBar() {
32483
33581
  "% cache"
32484
33582
  ] })
32485
33583
  ] }),
32486
- hasRunUsage && hasProjectUsage && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted", children: "" }),
33584
+ hasRunUsage && hasProjectUsage && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted opacity-50", "aria-hidden": true, children: "·" }),
32487
33585
  hasProjectUsage && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "t-text-muted", title: "Project total", children: [
32488
33586
  formatTokens(allTimeTokens),
32489
33587
  " / ",
@@ -41277,134 +42375,437 @@ const useToolProgressStore = create$1((set) => ({
41277
42375
  clearAll: () => set({ inFlight: /* @__PURE__ */ new Map() })
41278
42376
  }));
41279
42377
  const api = window.api;
41280
- function UpdateBanner() {
41281
- const [update, setUpdate] = reactExports.useState(null);
41282
- const [dismissed, setDismissed] = reactExports.useState(false);
41283
- const [copied, setCopied] = reactExports.useState(false);
41284
- reactExports.useEffect(() => {
41285
- api.checkForUpdate().then((info) => {
41286
- if (info.hasUpdate) setUpdate(info);
41287
- }).catch(() => {
41288
- });
41289
- }, []);
41290
- if (!update || dismissed) return null;
41291
- const command = "npm update -g research-copilot";
41292
- const handleCopy = () => {
41293
- navigator.clipboard.writeText(command);
41294
- setCopied(true);
41295
- setTimeout(() => setCopied(false), 2e3);
41296
- };
41297
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
41298
- "div",
42378
+ function relativeTime(iso) {
42379
+ const then = new Date(iso).getTime();
42380
+ if (!Number.isFinite(then)) return "";
42381
+ const diffMs = Date.now() - then;
42382
+ const mins = Math.floor(diffMs / 6e4);
42383
+ if (mins < 1) return "just now";
42384
+ if (mins < 60) return `${mins}m ago`;
42385
+ const hours = Math.floor(mins / 60);
42386
+ if (hours < 24) return `${hours}h ago`;
42387
+ const days = Math.floor(hours / 24);
42388
+ if (days < 7) return `${days}d ago`;
42389
+ const weeks = Math.floor(days / 7);
42390
+ if (weeks < 5) return `${weeks}w ago`;
42391
+ return new Date(iso).toLocaleDateString(void 0, { month: "short", day: "numeric" });
42392
+ }
42393
+ function splitPath(p) {
42394
+ const clean = p.replace(/\/+$/, "");
42395
+ const idx = clean.lastIndexOf("/");
42396
+ if (idx < 0) return { name: clean, parent: "" };
42397
+ return { name: clean.slice(idx + 1), parent: clean.slice(0, idx) };
42398
+ }
42399
+ function Kbd({ children }) {
42400
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "inline-flex items-center px-1 py-0 rounded border t-border-subtle t-bg-elevated text-[9.5px] font-mono t-text-secondary leading-[1.4]", children });
42401
+ }
42402
+ function RecentRow({
42403
+ entry,
42404
+ active,
42405
+ confirmRemove,
42406
+ stats,
42407
+ onActivate,
42408
+ onHover
42409
+ }) {
42410
+ const { name: name2, parent } = splitPath(entry.path);
42411
+ const totalArtifacts = stats ? stats.papers + stats.notes + stats.data : 0;
42412
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
42413
+ "button",
41299
42414
  {
41300
- className: "w-full max-w-md mb-6 rounded-xl border t-border-subtle overflow-hidden",
41301
- style: { background: "linear-gradient(135deg, rgba(99,102,241,0.12), rgba(168,85,247,0.10))" },
41302
- children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 flex items-start gap-3", children: [
41303
- /* @__PURE__ */ jsxRuntimeExports.jsx(CircleArrowUp, { size: 18, className: "t-text-accent-soft mt-0.5 shrink-0" }),
41304
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
41305
- /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[13px] font-medium t-text", children: [
41306
- "v",
41307
- update.latest,
41308
- " available",
41309
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "t-text-secondary font-normal ml-1.5", children: [
41310
- "(current: v",
41311
- update.current,
41312
- ")"
41313
- ] })
41314
- ] }),
41315
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 flex items-center gap-2", children: [
41316
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "flex-1 text-xs font-mono px-2.5 py-1.5 rounded-md t-bg-surface t-text select-all", children: command }),
41317
- /* @__PURE__ */ jsxRuntimeExports.jsx(
41318
- "button",
41319
- {
41320
- onClick: handleCopy,
41321
- className: "shrink-0 p-1.5 rounded-md t-bg-surface hover:opacity-80 transition-opacity",
41322
- title: "Copy command",
41323
- children: copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 14, className: "t-text-success" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { size: 14, className: "t-text-secondary" })
41324
- }
41325
- )
41326
- ] })
41327
- ] }),
42415
+ type: "button",
42416
+ onClick: onActivate,
42417
+ onMouseEnter: onHover,
42418
+ className: `group relative w-full text-left flex items-baseline gap-4 py-2 pl-4 pr-3 rounded-sm transition-colors ${active ? "t-bg-hover" : ""}`,
42419
+ children: [
41328
42420
  /* @__PURE__ */ jsxRuntimeExports.jsx(
41329
- "button",
42421
+ "span",
41330
42422
  {
41331
- onClick: () => setDismissed(true),
41332
- className: "shrink-0 p-1 rounded-md hover:t-bg-surface transition-colors t-text-secondary hover:t-text",
41333
- title: "Dismiss",
41334
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(X$1, { size: 14 })
42423
+ "aria-hidden": true,
42424
+ className: `absolute left-0 top-1 bottom-1 w-[2px] rounded-full transition-colors ${active ? "t-bg-accent" : "bg-transparent group-hover:t-bg-accent-soft"}`
41335
42425
  }
41336
- )
41337
- ] })
42426
+ ),
42427
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
42428
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
42429
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[13px] font-medium truncate ${active ? "t-text" : "t-text-secondary group-hover:t-text"}`, children: name2 }),
42430
+ entry.pinned && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] uppercase tracking-wider t-text-muted", children: "pinned" }),
42431
+ stats && !stats.initialized && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] uppercase tracking-wider t-text-muted", title: "No .research-pilot directory yet", children: "new" })
42432
+ ] }),
42433
+ parent && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[11px] t-text-muted truncate font-mono mt-0.5", children: parent })
42434
+ ] }),
42435
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0 flex flex-col items-end gap-0.5 tabular-nums text-[10px] t-text-muted", children: confirmRemove ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-error-soft", children: "press ⌫ again" }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
42436
+ stats && totalArtifacts > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1.5 t-text-secondary", children: [
42437
+ stats.papers > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
42438
+ stats.papers,
42439
+ "p"
42440
+ ] }),
42441
+ stats.notes > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
42442
+ stats.notes,
42443
+ "n"
42444
+ ] }),
42445
+ stats.data > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
42446
+ stats.data,
42447
+ "d"
42448
+ ] })
42449
+ ] }),
42450
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: relativeTime(entry.openedAt) })
42451
+ ] }) })
42452
+ ]
41338
42453
  }
41339
42454
  );
41340
42455
  }
42456
+ function WikiPanel({ onOpenSettings }) {
42457
+ const [stats, setStats] = reactExports.useState(null);
42458
+ const [recentPapers, setRecentPapers] = reactExports.useState([]);
42459
+ const [status, setStatus] = reactExports.useState(null);
42460
+ const [loaded, setLoaded] = reactExports.useState(false);
42461
+ reactExports.useEffect(() => {
42462
+ let cancelled = false;
42463
+ Promise.all([
42464
+ api.wikiGetStats?.().catch(() => null),
42465
+ api.wikiListPaperMeta?.().catch(() => null),
42466
+ api.wikiGetStatus?.().catch(() => null)
42467
+ ]).then(([s15, list2, st2]) => {
42468
+ if (cancelled) return;
42469
+ if (s15) setStats(s15);
42470
+ if (Array.isArray(list2)) {
42471
+ const sorted = [...list2].sort((a, b2) => (b2.updatedAt || "").localeCompare(a.updatedAt || ""));
42472
+ setRecentPapers(sorted.slice(0, 3));
42473
+ }
42474
+ if (st2) setStatus(st2);
42475
+ setLoaded(true);
42476
+ });
42477
+ const unsub = api.onWikiStatus?.((s15) => setStatus(s15));
42478
+ return () => {
42479
+ cancelled = true;
42480
+ unsub?.();
42481
+ };
42482
+ }, []);
42483
+ if (!loaded) return null;
42484
+ const isDisabled = status?.state === "disabled";
42485
+ const isEmpty = (stats?.papers ?? 0) === 0;
42486
+ const header = /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-baseline justify-between mb-3", children: [
42487
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider t-text-muted font-medium", children: "Paper wiki" }),
42488
+ !isEmpty && stats && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] t-text-muted tabular-nums", children: stats.papers }),
42489
+ isDisabled && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider t-text-muted", children: "off" })
42490
+ ] });
42491
+ if (isDisabled) {
42492
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { "aria-labelledby": "wiki-panel-heading", children: [
42493
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { id: "wiki-panel-heading", className: "sr-only", children: "Paper wiki" }),
42494
+ header,
42495
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[12px] t-text-secondary leading-relaxed", children: "Turn the wiki agent on in settings to build a cross-project summary of every paper you save. It runs quietly in the background." }),
42496
+ onOpenSettings && /* @__PURE__ */ jsxRuntimeExports.jsx(
42497
+ "button",
42498
+ {
42499
+ onClick: onOpenSettings,
42500
+ className: "mt-3 inline-flex items-center gap-1.5 text-[11px] t-text-accent-soft hover:t-text-accent transition-colors",
42501
+ children: "Open settings →"
42502
+ }
42503
+ )
42504
+ ] });
42505
+ }
42506
+ if (isEmpty) {
42507
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { "aria-labelledby": "wiki-panel-heading", children: [
42508
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { id: "wiki-panel-heading", className: "sr-only", children: "Paper wiki" }),
42509
+ header,
42510
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[12px] t-text-secondary leading-relaxed", children: "Your cross-project library. As you save papers in any project, the agent summarizes each one here — visible from every project afterwards." }),
42511
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-3 text-[11px] t-text-muted leading-relaxed", children: "Nothing here yet. Open a project and add your first paper." })
42512
+ ] });
42513
+ }
42514
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { "aria-labelledby": "wiki-panel-heading", children: [
42515
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { id: "wiki-panel-heading", className: "sr-only", children: "Paper wiki" }),
42516
+ header,
42517
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-baseline gap-3 text-[11px] t-text-secondary tabular-nums mb-6", children: [
42518
+ (stats?.fulltext ?? 0) > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
42519
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text font-medium", children: stats.fulltext }),
42520
+ " ",
42521
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted", children: "fulltext" })
42522
+ ] }),
42523
+ (stats?.abstractOnly ?? 0) > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
42524
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted opacity-50", "aria-hidden": true, children: "·" }),
42525
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
42526
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text font-medium", children: stats.abstractOnly }),
42527
+ " ",
42528
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted", children: "abstract" })
42529
+ ] })
42530
+ ] }),
42531
+ (stats?.concepts ?? 0) > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
42532
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted opacity-50", "aria-hidden": true, children: "·" }),
42533
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
42534
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text font-medium", children: stats.concepts }),
42535
+ " ",
42536
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted", children: "concepts" })
42537
+ ] })
42538
+ ] })
42539
+ ] }),
42540
+ recentPapers.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-6", children: [
42541
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase tracking-wider t-text-muted font-medium mb-2", children: "Recently added" }),
42542
+ /* @__PURE__ */ jsxRuntimeExports.jsx("ul", { className: "flex flex-col gap-1.5", children: recentPapers.map((p) => /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex items-baseline gap-2", children: [
42543
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-[10px] t-text-muted mt-1", children: "·" }),
42544
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0 flex-1", children: [
42545
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[12px] t-text-secondary truncate leading-snug", children: p.title }),
42546
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[10px] t-text-muted font-mono truncate", children: [
42547
+ p.slug,
42548
+ p.updatedAt && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
42549
+ " · ",
42550
+ relativeTime(p.updatedAt)
42551
+ ] })
42552
+ ] })
42553
+ ] })
42554
+ ] }, p.slug)) })
42555
+ ] }),
42556
+ status && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 text-[10px] t-text-muted", children: [
42557
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42558
+ "span",
42559
+ {
42560
+ className: `inline-block w-1.5 h-1.5 rounded-full ${status.state === "processing" ? "bg-blue-500 animate-pulse" : status.state === "paused" ? "bg-yellow-500" : "bg-emerald-500"}`,
42561
+ "aria-hidden": true
42562
+ }
42563
+ ),
42564
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "capitalize", children: status.state }),
42565
+ status.lastRunAt && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
42566
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "opacity-50", "aria-hidden": true, children: "·" }),
42567
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
42568
+ "last tick ",
42569
+ relativeTime(status.lastRunAt)
42570
+ ] })
42571
+ ] })
42572
+ ] })
42573
+ ] });
42574
+ }
42575
+ function TipsBlock() {
42576
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { "aria-label": "Tips", children: [
42577
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase tracking-wider t-text-muted font-medium mb-2", children: "Tips" }),
42578
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "flex flex-col gap-1.5 text-[11px] t-text-secondary", children: [
42579
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex items-center gap-2", children: [
42580
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Kbd, { children: "/" }),
42581
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted", children: "or" }),
42582
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Kbd, { children: "⌘K" }),
42583
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "open the command palette" })
42584
+ ] }),
42585
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex items-center gap-2", children: [
42586
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Kbd, { children: "@" }),
42587
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "mention a note, paper, or file" })
42588
+ ] }),
42589
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex items-center gap-2", children: [
42590
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Kbd, { children: "⌘1" }),
42591
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Kbd, { children: "⌘2" }),
42592
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "switch between chat and literature" })
42593
+ ] })
42594
+ ] })
42595
+ ] });
42596
+ }
41341
42597
  function FolderGate({ onOpenSettings }) {
41342
42598
  const pickFolder = useSessionStore((s15) => s15.pickFolder);
42599
+ const openPath = useSessionStore((s15) => s15.openPath);
41343
42600
  const refreshEntities = useEntityStore((s15) => s15.refreshAll);
41344
- const handlePick = async () => {
41345
- const picked = await pickFolder();
41346
- if (picked) {
41347
- await refreshEntities();
42601
+ const [recents, setRecents] = reactExports.useState([]);
42602
+ const [projectStats, setProjectStats] = reactExports.useState({});
42603
+ const [activeIndex, setActiveIndex] = reactExports.useState(0);
42604
+ const [confirmRemove, setConfirmRemove] = reactExports.useState(null);
42605
+ const [opening, setOpening] = reactExports.useState(false);
42606
+ reactExports.useEffect(() => {
42607
+ let cancelled = false;
42608
+ api.listRecentProjects?.().then((list2) => {
42609
+ if (!cancelled && Array.isArray(list2)) setRecents(list2);
42610
+ }).catch(() => {
42611
+ });
42612
+ return () => {
42613
+ cancelled = true;
42614
+ };
42615
+ }, []);
42616
+ reactExports.useEffect(() => {
42617
+ if (recents.length === 0) {
42618
+ setProjectStats({});
42619
+ return;
41348
42620
  }
41349
- };
41350
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex h-screen w-screen t-bg-base t-text items-center justify-center", children: [
41351
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "drag-region fixed top-0 left-0 right-0 h-8 z-50" }),
41352
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center max-w-sm px-8", children: [
41353
- /* @__PURE__ */ jsxRuntimeExports.jsx(UpdateBanner, {}),
41354
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center", children: [
41355
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative mx-auto mb-8 w-fit", children: [
41356
- /* @__PURE__ */ jsxRuntimeExports.jsx(
41357
- "div",
41358
- {
41359
- className: "w-14 h-14 rounded-2xl flex items-center justify-center t-gradient-accent t-gradient-accent-shadow-lg",
41360
- children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-white text-xl font-bold tracking-tight", children: "P" })
41361
- }
41362
- ),
41363
- /* @__PURE__ */ jsxRuntimeExports.jsx(
41364
- "div",
41365
- {
41366
- className: "absolute -inset-2 rounded-3xl opacity-15 blur-xl -z-10 t-gradient-accent"
41367
- }
41368
- )
41369
- ] }),
41370
- /* @__PURE__ */ jsxRuntimeExports.jsx(
41371
- "h1",
41372
- {
41373
- className: "text-2xl font-semibold mb-2 tracking-tight",
41374
- children: "Research Pilot"
41375
- }
41376
- ),
41377
- /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "t-text-secondary text-[13px] mb-8 leading-relaxed", children: [
41378
- "Open a project folder to begin. Your notes, papers, and data will live in a ",
41379
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "px-1 py-0.5 rounded t-bg-surface text-xs font-mono", children: ".research-pilot" }),
41380
- " directory."
41381
- ] }),
41382
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-3", children: [
41383
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
41384
- "button",
41385
- {
41386
- onClick: handlePick,
41387
- className: "inline-flex items-center gap-2 px-5 py-2.5 rounded-lg text-white text-sm font-medium\n hover:opacity-90 transition-all duration-200 t-gradient-accent t-gradient-accent-shadow",
41388
- children: [
41389
- /* @__PURE__ */ jsxRuntimeExports.jsx(FolderOpen, { size: 16 }),
41390
- "Open Project Folder"
41391
- ]
41392
- }
41393
- ),
41394
- onOpenSettings && /* @__PURE__ */ jsxRuntimeExports.jsxs(
41395
- "button",
41396
- {
41397
- onClick: onOpenSettings,
41398
- className: "inline-flex items-center gap-1.5 text-xs t-text-secondary hover:t-text transition-colors",
41399
- children: [
41400
- /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { size: 13 }),
41401
- "API Keys & Settings"
41402
- ]
41403
- }
41404
- )
42621
+ let cancelled = false;
42622
+ api.projectStatsBatch?.(recents.map((r) => r.path)).then(
42623
+ (map2) => {
42624
+ if (!cancelled && map2) setProjectStats(map2);
42625
+ }
42626
+ ).catch(() => {
42627
+ });
42628
+ return () => {
42629
+ cancelled = true;
42630
+ };
42631
+ }, [recents]);
42632
+ const handleOpen = reactExports.useCallback(async (path2) => {
42633
+ if (opening) return;
42634
+ setOpening(true);
42635
+ try {
42636
+ const ok2 = await openPath(path2);
42637
+ if (ok2) await refreshEntities();
42638
+ } finally {
42639
+ setOpening(false);
42640
+ }
42641
+ }, [openPath, refreshEntities, opening]);
42642
+ const handlePickNew = reactExports.useCallback(async () => {
42643
+ if (opening) return;
42644
+ setOpening(true);
42645
+ try {
42646
+ const ok2 = await pickFolder();
42647
+ if (ok2) await refreshEntities();
42648
+ } finally {
42649
+ setOpening(false);
42650
+ }
42651
+ }, [pickFolder, refreshEntities, opening]);
42652
+ const handleRemove = reactExports.useCallback(async (path2) => {
42653
+ await api.removeRecentProject?.(path2);
42654
+ setRecents((prev) => {
42655
+ const next = prev.filter((e) => e.path !== path2);
42656
+ setActiveIndex((i) => Math.min(Math.max(0, i), Math.max(0, next.length - 1)));
42657
+ return next;
42658
+ });
42659
+ setConfirmRemove(null);
42660
+ }, []);
42661
+ reactExports.useEffect(() => {
42662
+ const handler = (e) => {
42663
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "o") {
42664
+ e.preventDefault();
42665
+ handlePickNew();
42666
+ return;
42667
+ }
42668
+ if (e.key === "ArrowDown") {
42669
+ if (recents.length === 0) return;
42670
+ e.preventDefault();
42671
+ setActiveIndex((i) => (i + 1) % recents.length);
42672
+ setConfirmRemove(null);
42673
+ } else if (e.key === "ArrowUp") {
42674
+ if (recents.length === 0) return;
42675
+ e.preventDefault();
42676
+ setActiveIndex((i) => (i - 1 + recents.length) % recents.length);
42677
+ setConfirmRemove(null);
42678
+ } else if (e.key === "Enter") {
42679
+ if (recents.length === 0) {
42680
+ e.preventDefault();
42681
+ handlePickNew();
42682
+ return;
42683
+ }
42684
+ const target = recents[activeIndex];
42685
+ if (target) {
42686
+ e.preventDefault();
42687
+ handleOpen(target.path);
42688
+ }
42689
+ } else if (e.key === "Backspace" || e.key === "Delete") {
42690
+ if (recents.length === 0) return;
42691
+ const target = recents[activeIndex];
42692
+ if (!target) return;
42693
+ e.preventDefault();
42694
+ if (confirmRemove === target.path) {
42695
+ handleRemove(target.path);
42696
+ } else {
42697
+ setConfirmRemove(target.path);
42698
+ }
42699
+ } else if (e.key === "Escape") {
42700
+ setConfirmRemove(null);
42701
+ }
42702
+ };
42703
+ window.addEventListener("keydown", handler);
42704
+ return () => window.removeEventListener("keydown", handler);
42705
+ }, [recents, activeIndex, confirmRemove, handleOpen, handlePickNew, handleRemove]);
42706
+ const hasRecents = recents.length > 0;
42707
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-screen w-screen t-bg-base t-text overflow-hidden", children: [
42708
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "drag-region fixed top-0 left-0 right-0 h-10 z-50" }),
42709
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42710
+ "main",
42711
+ {
42712
+ id: "main-content",
42713
+ className: "flex-1 overflow-y-auto pt-[12vh] px-[8vw] pb-6",
42714
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mx-auto w-full max-w-6xl", children: [
42715
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-12 pl-1", children: [
42716
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-[16px] font-semibold t-text tracking-tight leading-none", children: "Research Pilot" }),
42717
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[11px] t-text-muted mt-1.5 leading-none", children: "A research workflow, not a chat window." })
42718
+ ] }),
42719
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 xl:grid-cols-[minmax(0,1fr)_20rem] gap-12 xl:gap-16 items-start", children: [
42720
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { "aria-labelledby": "recents-heading", children: [
42721
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { id: "recents-heading", className: "sr-only", children: "Recent projects" }),
42722
+ hasRecents ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
42723
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-baseline justify-between mb-2 pl-4", children: [
42724
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider t-text-muted font-medium", children: "Recent projects" }),
42725
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] t-text-muted tabular-nums", children: recents.length })
42726
+ ] }),
42727
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col mb-6", children: recents.map((entry, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(
42728
+ RecentRow,
42729
+ {
42730
+ entry,
42731
+ stats: projectStats[entry.path],
42732
+ active: i === activeIndex,
42733
+ confirmRemove: confirmRemove === entry.path,
42734
+ onActivate: () => handleOpen(entry.path),
42735
+ onHover: () => {
42736
+ setActiveIndex(i);
42737
+ if (confirmRemove !== entry.path) setConfirmRemove(null);
42738
+ }
42739
+ },
42740
+ entry.path
42741
+ )) })
42742
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-6 pl-4", children: [
42743
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-baseline justify-between mb-3", children: [
42744
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider t-text-muted font-medium", children: "Recent projects" }),
42745
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] t-text-muted", children: "—" })
42746
+ ] }),
42747
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[13px] t-text-secondary leading-relaxed mb-1", children: "No projects yet." }),
42748
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[12px] t-text-muted leading-relaxed max-w-md", children: [
42749
+ "Pick a folder to begin — we'll create a",
42750
+ " ",
42751
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "px-1 py-0.5 rounded t-bg-elevated text-[10.5px] font-mono t-text-secondary", children: ".research-pilot" }),
42752
+ " ",
42753
+ "directory beside it for your notes, papers, and data. Everything stays on disk; nothing goes to the cloud."
42754
+ ] })
42755
+ ] }),
42756
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
42757
+ "button",
42758
+ {
42759
+ onClick: handlePickNew,
42760
+ disabled: opening,
42761
+ className: "inline-flex items-center gap-2.5 px-3 py-1.5 rounded-md border t-border t-text-secondary hover:t-text hover:t-border-accent-soft text-[12px] transition-colors disabled:opacity-50",
42762
+ children: [
42763
+ hasRecents ? "Open another folder…" : "Choose a folder to begin",
42764
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Kbd, { children: "⌘O" })
42765
+ ]
42766
+ }
42767
+ ) })
42768
+ ] }),
42769
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("aside", { "aria-label": "Global status", className: "flex flex-col gap-10 xl:pl-4 xl:border-l t-border-subtle", children: [
42770
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "xl:pl-8", children: /* @__PURE__ */ jsxRuntimeExports.jsx(WikiPanel, { onOpenSettings }) }),
42771
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "xl:pl-8", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TipsBlock, {}) })
42772
+ ] })
42773
+ ] }),
42774
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-16 pl-1 flex items-center gap-5 text-[10px] t-text-muted flex-wrap", children: [
42775
+ hasRecents && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
42776
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
42777
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Kbd, { children: "↑↓" }),
42778
+ " navigate"
42779
+ ] }),
42780
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
42781
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Kbd, { children: "↵" }),
42782
+ " open"
42783
+ ] }),
42784
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
42785
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Kbd, { children: "⌫" }),
42786
+ " remove"
42787
+ ] })
42788
+ ] }),
42789
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
42790
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Kbd, { children: "⌘O" }),
42791
+ " new folder"
42792
+ ] }),
42793
+ onOpenSettings && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
42794
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Kbd, { children: "⌘," }),
42795
+ " settings"
42796
+ ] }),
42797
+ onOpenSettings && /* @__PURE__ */ jsxRuntimeExports.jsx(
42798
+ "button",
42799
+ {
42800
+ onClick: onOpenSettings,
42801
+ className: "ml-auto text-[11px] t-text-muted hover:t-text-secondary transition-colors",
42802
+ children: "API keys & settings →"
42803
+ }
42804
+ )
42805
+ ] })
41405
42806
  ] })
41406
- ] })
41407
- ] })
42807
+ }
42808
+ )
41408
42809
  ] });
41409
42810
  }
41410
42811
  function App() {
@@ -41419,16 +42820,17 @@ function App() {
41419
42820
  const terminalVisible = useUIStore((s15) => s15.terminalVisible);
41420
42821
  const terminalAlive = useUIStore((s15) => s15.terminalAlive);
41421
42822
  const theme = useUIStore((s15) => s15.theme);
41422
- const [apiKeyChecked, setApiKeyChecked] = reactExports.useState(false);
41423
- const [needsApiKeySetup, setNeedsApiKeySetup] = reactExports.useState(false);
41424
- const [showApiKeySetup, setShowApiKeySetup] = reactExports.useState(false);
42823
+ const [settingsOpen, setSettingsOpen] = reactExports.useState(false);
42824
+ const [settingsTab, setSettingsTab] = reactExports.useState("api-keys");
42825
+ const [authChecked, setAuthChecked] = reactExports.useState(false);
41425
42826
  reactExports.useEffect(() => {
41426
- api.getApiKeyStatus().then((status) => {
41427
- const hasLlmKey = status.ANTHROPIC_API_KEY || status.OPENAI_API_KEY;
41428
- setNeedsApiKeySetup(!hasLlmKey);
41429
- setShowApiKeySetup(!hasLlmKey);
41430
- setApiKeyChecked(true);
41431
- }).catch(() => setApiKeyChecked(true));
42827
+ api.hasLlmAuth?.().then((hasAuth) => {
42828
+ if (!hasAuth) {
42829
+ setSettingsOpen(true);
42830
+ setSettingsTab("api-keys");
42831
+ }
42832
+ setAuthChecked(true);
42833
+ }).catch(() => setAuthChecked(true));
41432
42834
  }, []);
41433
42835
  reactExports.useEffect(() => {
41434
42836
  document.documentElement.classList.remove("dark", "light");
@@ -41622,18 +43024,33 @@ function App() {
41622
43024
  e.preventDefault();
41623
43025
  useUIStore.getState().toggleTerminal();
41624
43026
  }
43027
+ if ((e.metaKey || e.ctrlKey) && e.key === ",") {
43028
+ e.preventDefault();
43029
+ setSettingsOpen((prev) => !prev);
43030
+ }
41625
43031
  };
41626
43032
  window.addEventListener("keydown", handler);
41627
43033
  return () => window.removeEventListener("keydown", handler);
41628
43034
  }, [previewEntity, previewEditorFocused]);
41629
- if (!apiKeyChecked) {
43035
+ if (!authChecked) {
41630
43036
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex h-screen w-screen t-bg-base t-text items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "drag-region fixed top-0 left-0 right-0 h-8 z-50" }) });
41631
43037
  }
41632
- if (showApiKeySetup) {
41633
- return /* @__PURE__ */ jsxRuntimeExports.jsx(ApiKeySetup, { onComplete: () => setShowApiKeySetup(false) });
41634
- }
43038
+ const settingsModal = /* @__PURE__ */ jsxRuntimeExports.jsx(
43039
+ SettingsModal,
43040
+ {
43041
+ open: settingsOpen,
43042
+ onClose: () => setSettingsOpen(false),
43043
+ initialTab: settingsTab
43044
+ }
43045
+ );
41635
43046
  if (!hasProject) {
41636
- return /* @__PURE__ */ jsxRuntimeExports.jsx(FolderGate, { onOpenSettings: () => setShowApiKeySetup(true) });
43047
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
43048
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FolderGate, { onOpenSettings: () => {
43049
+ setSettingsTab("api-keys");
43050
+ setSettingsOpen(true);
43051
+ } }),
43052
+ settingsModal
43053
+ ] });
41637
43054
  }
41638
43055
  return /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorBoundary, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-screen w-screen t-bg-base t-text", children: [
41639
43056
  /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: "#main-content", className: "skip-link", children: "Skip to content" }),
@@ -41659,9 +43076,11 @@ function App() {
41659
43076
  )
41660
43077
  ] })
41661
43078
  ] }),
41662
- /* @__PURE__ */ jsxRuntimeExports.jsx(StatusBar, {})
43079
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StatusBar, {}),
43080
+ settingsModal
41663
43081
  ] }) });
41664
43082
  }
43083
+ bootTheme();
41665
43084
  ReactDOM.createRoot(document.getElementById("root")).render(
41666
43085
  /* @__PURE__ */ jsxRuntimeExports.jsx(React$2.StrictMode, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(App, {}) })
41667
43086
  );