reasonix 0.39.0 → 0.40.0

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 (115) hide show
  1. package/dashboard/app.css +580 -0
  2. package/dashboard/dist/app.js +1827 -32
  3. package/dashboard/dist/app.js.map +1 -1
  4. package/dist/cli/chat-G7CUW4ZI.js +45 -0
  5. package/dist/cli/{chunk-TPK2CHWR.js → chunk-26UDIXLD.js} +1222 -1209
  6. package/dist/cli/chunk-26UDIXLD.js.map +1 -0
  7. package/dist/cli/{chunk-SUZRC4NC.js → chunk-4X3NY5ZM.js} +2 -2
  8. package/dist/cli/{chunk-5ZCRXN7S.js → chunk-4YV2GBYG.js} +2237 -2211
  9. package/dist/cli/chunk-4YV2GBYG.js.map +1 -0
  10. package/dist/cli/{chunk-6NMWJSES.js → chunk-5GKJLNP2.js} +2 -2
  11. package/dist/cli/{chunk-6DR4F3MC.js → chunk-7DLHHBGN.js} +49 -18
  12. package/dist/cli/chunk-7DLHHBGN.js.map +1 -0
  13. package/dist/cli/{chunk-NLV2YORE.js → chunk-A5LSGEEK.js} +43 -3
  14. package/dist/cli/chunk-A5LSGEEK.js.map +1 -0
  15. package/dist/cli/{code-3BBVXXY6.js → chunk-AVB3WZWU.js} +51 -132
  16. package/dist/cli/chunk-AVB3WZWU.js.map +1 -0
  17. package/dist/cli/{chunk-4D662BWT.js → chunk-CLAN6PVH.js} +4 -95
  18. package/dist/cli/chunk-CLAN6PVH.js.map +1 -0
  19. package/dist/cli/chunk-CPTZ5OHX.js +18 -0
  20. package/dist/cli/chunk-CPTZ5OHX.js.map +1 -0
  21. package/dist/cli/{chunk-SWLIVNTP.js → chunk-CZSJILQP.js} +84 -1
  22. package/dist/cli/chunk-CZSJILQP.js.map +1 -0
  23. package/dist/cli/{chunk-MHDNZXJJ.js → chunk-E46ECXJD.js} +7 -2
  24. package/dist/cli/{chunk-MHDNZXJJ.js.map → chunk-E46ECXJD.js.map} +1 -1
  25. package/dist/cli/{chunk-AKDDHHE6.js → chunk-FFNOMR32.js} +2 -2
  26. package/dist/cli/{chunk-AJGLCSZS.js → chunk-H7PHYVPM.js} +138 -40
  27. package/dist/cli/chunk-H7PHYVPM.js.map +1 -0
  28. package/dist/cli/{chunk-SJNIIH5W.js → chunk-HCC42PEI.js} +5 -1
  29. package/dist/cli/{chunk-SJNIIH5W.js.map → chunk-HCC42PEI.js.map} +1 -1
  30. package/dist/cli/chunk-IYF36OCJ.js +45 -0
  31. package/dist/cli/chunk-IYF36OCJ.js.map +1 -0
  32. package/dist/cli/{chunk-BQR5TTNY.js → chunk-JWCTX5S4.js} +2 -2
  33. package/dist/cli/{chunk-DDA76P44.js → chunk-R4YTW7PR.js} +5 -21
  34. package/dist/cli/chunk-R4YTW7PR.js.map +1 -0
  35. package/dist/cli/{chunk-7G3SESEU.js → chunk-RFX7TYVV.js} +2 -15
  36. package/dist/cli/chunk-RFX7TYVV.js.map +1 -0
  37. package/dist/cli/{chunk-NTVW2TWO.js → chunk-SZH34P45.js} +186 -12
  38. package/dist/cli/chunk-SZH34P45.js.map +1 -0
  39. package/dist/cli/chunk-UCMTWZKU.js +100 -0
  40. package/dist/cli/chunk-UCMTWZKU.js.map +1 -0
  41. package/dist/cli/{chunk-V5D77TFD.js → chunk-ULBW7DYL.js} +2 -2
  42. package/dist/cli/{chunk-TPDWAMG6.js → chunk-UVRXTSK3.js} +195 -20
  43. package/dist/cli/chunk-UVRXTSK3.js.map +1 -0
  44. package/dist/cli/chunk-VLNRQMCI.js +413 -0
  45. package/dist/cli/chunk-VLNRQMCI.js.map +1 -0
  46. package/dist/cli/{chunk-TGO7X47P.js → chunk-WKOMCPXP.js} +9 -7
  47. package/dist/cli/{chunk-TGO7X47P.js.map → chunk-WKOMCPXP.js.map} +1 -1
  48. package/dist/cli/{chunk-6CXT5JRM.js → chunk-XST7BSZJ.js} +2 -1
  49. package/dist/cli/{chunk-6CXT5JRM.js.map → chunk-XST7BSZJ.js.map} +1 -1
  50. package/dist/cli/code-YQGVLIT2.js +147 -0
  51. package/dist/cli/code-YQGVLIT2.js.map +1 -0
  52. package/dist/cli/{commands-PJMHSP3Z.js → commands-FQZOBLLZ.js} +6 -4
  53. package/dist/cli/{commands-PJMHSP3Z.js.map → commands-FQZOBLLZ.js.map} +1 -1
  54. package/dist/cli/{commit-R6SC44W5.js → commit-ZS24SHPG.js} +2 -2
  55. package/dist/cli/desktop-6OLENOOO.js +807 -0
  56. package/dist/cli/desktop-6OLENOOO.js.map +1 -0
  57. package/dist/cli/{diff-LXBBKOZA.js → diff-2VUKNGEI.js} +4 -4
  58. package/dist/cli/{doctor-ZBUEBRXP.js → doctor-JO2WNN6C.js} +8 -7
  59. package/dist/cli/{events-SQXPVV7B.js → events-APSVNROZ.js} +3 -3
  60. package/dist/cli/index.js +93 -33
  61. package/dist/cli/index.js.map +1 -1
  62. package/dist/cli/{mcp-RABKZDX4.js → mcp-DCKOE5RF.js} +2 -2
  63. package/dist/cli/{mcp-browse-H6O73SHN.js → mcp-browse-D6GBP5RQ.js} +2 -2
  64. package/dist/cli/{mcp-inspect-XWBO52H6.js → mcp-inspect-KFGFPJ3E.js} +42 -5
  65. package/dist/cli/mcp-inspect-KFGFPJ3E.js.map +1 -0
  66. package/dist/cli/{prompt-CZSOFYK6.js → prompt-PKCCLLAD.js} +3 -3
  67. package/dist/cli/{prune-sessions-FCFOYCBP.js → prune-sessions-LV33R47N.js} +2 -2
  68. package/dist/cli/{replay-TWTUIUUB.js → replay-WFCYX7XF.js} +5 -5
  69. package/dist/cli/{run-RWBLIICY.js → run-IUJYEPMT.js} +18 -15
  70. package/dist/cli/run-IUJYEPMT.js.map +1 -0
  71. package/dist/cli/{server-EPU4QONU.js → server-CN4QPPVJ.js} +531 -88
  72. package/dist/cli/server-CN4QPPVJ.js.map +1 -0
  73. package/dist/cli/{sessions-TWUFHOUX.js → sessions-F5GPGTJN.js} +10 -10
  74. package/dist/cli/{setup-WHXXHIZV.js → setup-WWMDBPSB.js} +6 -6
  75. package/dist/cli/{version-RAMBOIYL.js → version-KQUPV6T5.js} +10 -10
  76. package/dist/index.d.ts +43 -0
  77. package/dist/index.js +440 -127
  78. package/dist/index.js.map +1 -1
  79. package/package.json +1 -3
  80. package/dist/cli/chat-QCY6CH7O.js +0 -42
  81. package/dist/cli/chunk-4D662BWT.js.map +0 -1
  82. package/dist/cli/chunk-5ZCRXN7S.js.map +0 -1
  83. package/dist/cli/chunk-6DR4F3MC.js.map +0 -1
  84. package/dist/cli/chunk-7G3SESEU.js.map +0 -1
  85. package/dist/cli/chunk-AJGLCSZS.js.map +0 -1
  86. package/dist/cli/chunk-BQNUJJN7.js +0 -42
  87. package/dist/cli/chunk-BQNUJJN7.js.map +0 -1
  88. package/dist/cli/chunk-DDA76P44.js.map +0 -1
  89. package/dist/cli/chunk-NLV2YORE.js.map +0 -1
  90. package/dist/cli/chunk-NTVW2TWO.js.map +0 -1
  91. package/dist/cli/chunk-SWLIVNTP.js.map +0 -1
  92. package/dist/cli/chunk-TPDWAMG6.js.map +0 -1
  93. package/dist/cli/chunk-TPK2CHWR.js.map +0 -1
  94. package/dist/cli/code-3BBVXXY6.js.map +0 -1
  95. package/dist/cli/mcp-inspect-XWBO52H6.js.map +0 -1
  96. package/dist/cli/run-RWBLIICY.js.map +0 -1
  97. package/dist/cli/server-EPU4QONU.js.map +0 -1
  98. /package/dist/cli/{chat-QCY6CH7O.js.map → chat-G7CUW4ZI.js.map} +0 -0
  99. /package/dist/cli/{chunk-SUZRC4NC.js.map → chunk-4X3NY5ZM.js.map} +0 -0
  100. /package/dist/cli/{chunk-6NMWJSES.js.map → chunk-5GKJLNP2.js.map} +0 -0
  101. /package/dist/cli/{chunk-AKDDHHE6.js.map → chunk-FFNOMR32.js.map} +0 -0
  102. /package/dist/cli/{chunk-BQR5TTNY.js.map → chunk-JWCTX5S4.js.map} +0 -0
  103. /package/dist/cli/{chunk-V5D77TFD.js.map → chunk-ULBW7DYL.js.map} +0 -0
  104. /package/dist/cli/{commit-R6SC44W5.js.map → commit-ZS24SHPG.js.map} +0 -0
  105. /package/dist/cli/{diff-LXBBKOZA.js.map → diff-2VUKNGEI.js.map} +0 -0
  106. /package/dist/cli/{doctor-ZBUEBRXP.js.map → doctor-JO2WNN6C.js.map} +0 -0
  107. /package/dist/cli/{events-SQXPVV7B.js.map → events-APSVNROZ.js.map} +0 -0
  108. /package/dist/cli/{mcp-RABKZDX4.js.map → mcp-DCKOE5RF.js.map} +0 -0
  109. /package/dist/cli/{mcp-browse-H6O73SHN.js.map → mcp-browse-D6GBP5RQ.js.map} +0 -0
  110. /package/dist/cli/{prompt-CZSOFYK6.js.map → prompt-PKCCLLAD.js.map} +0 -0
  111. /package/dist/cli/{prune-sessions-FCFOYCBP.js.map → prune-sessions-LV33R47N.js.map} +0 -0
  112. /package/dist/cli/{replay-TWTUIUUB.js.map → replay-WFCYX7XF.js.map} +0 -0
  113. /package/dist/cli/{sessions-TWUFHOUX.js.map → sessions-F5GPGTJN.js.map} +0 -0
  114. /package/dist/cli/{setup-WHXXHIZV.js.map → setup-WWMDBPSB.js.map} +0 -0
  115. /package/dist/cli/{version-RAMBOIYL.js.map → version-KQUPV6T5.js.map} +0 -0
@@ -922,10 +922,10 @@ var require_core = __commonJS({
922
922
  }
923
923
  var version = "11.11.1";
924
924
  var HTMLInjectionError = class extends Error {
925
- constructor(reason, html6) {
925
+ constructor(reason, html8) {
926
926
  super(reason);
927
927
  this.name = "HTMLInjectionError";
928
- this.html = html6;
928
+ this.html = html8;
929
929
  }
930
930
  };
931
931
  var escape3 = escapeHTML;
@@ -959,14 +959,14 @@ var require_core = __commonJS({
959
959
  classes += block2.parentNode ? block2.parentNode.className : "";
960
960
  const match = options2.languageDetectRe.exec(classes);
961
961
  if (match) {
962
- const language = getLanguage(match[1]);
962
+ const language = getLanguage2(match[1]);
963
963
  if (!language) {
964
964
  warn(LANGUAGE_NOT_FOUND.replace("{}", match[1]));
965
965
  warn("Falling back to no-highlight mode for this block.", block2);
966
966
  }
967
967
  return language ? match[1] : "no-highlight";
968
968
  }
969
- return classes.split(/\s+/).find((_class) => shouldNotHighlight(_class) || getLanguage(_class));
969
+ return classes.split(/\s+/).find((_class) => shouldNotHighlight(_class) || getLanguage2(_class));
970
970
  }
971
971
  function highlight2(codeOrLanguageName, optionsOrCode, ignoreIllegals) {
972
972
  let code = "";
@@ -1244,7 +1244,7 @@ var require_core = __commonJS({
1244
1244
  modeBuffer += lexeme;
1245
1245
  return lexeme.length;
1246
1246
  }
1247
- const language = getLanguage(languageName);
1247
+ const language = getLanguage2(languageName);
1248
1248
  if (!language) {
1249
1249
  error(LANGUAGE_NOT_FOUND.replace("{}", languageName));
1250
1250
  throw new Error('Unknown language: "' + languageName + '"');
@@ -1336,16 +1336,16 @@ var require_core = __commonJS({
1336
1336
  function highlightAuto(code, languageSubset) {
1337
1337
  languageSubset = languageSubset || options2.languages || Object.keys(languages);
1338
1338
  const plaintext = justTextHighlightResult(code);
1339
- const results = languageSubset.filter(getLanguage).filter(autoDetection).map(
1339
+ const results = languageSubset.filter(getLanguage2).filter(autoDetection).map(
1340
1340
  (name) => _highlight(name, code, false)
1341
1341
  );
1342
1342
  results.unshift(plaintext);
1343
1343
  const sorted = results.sort((a3, b2) => {
1344
1344
  if (a3.relevance !== b2.relevance) return b2.relevance - a3.relevance;
1345
1345
  if (a3.language && b2.language) {
1346
- if (getLanguage(a3.language).supersetOf === b2.language) {
1346
+ if (getLanguage2(a3.language).supersetOf === b2.language) {
1347
1347
  return 1;
1348
- } else if (getLanguage(b2.language).supersetOf === a3.language) {
1348
+ } else if (getLanguage2(b2.language).supersetOf === a3.language) {
1349
1349
  return -1;
1350
1350
  }
1351
1351
  }
@@ -1465,7 +1465,7 @@ var require_core = __commonJS({
1465
1465
  function listLanguages() {
1466
1466
  return Object.keys(languages);
1467
1467
  }
1468
- function getLanguage(name) {
1468
+ function getLanguage2(name) {
1469
1469
  name = (name || "").toLowerCase();
1470
1470
  return languages[name] || languages[aliases[name]];
1471
1471
  }
@@ -1478,7 +1478,7 @@ var require_core = __commonJS({
1478
1478
  });
1479
1479
  }
1480
1480
  function autoDetection(name) {
1481
- const lang = getLanguage(name);
1481
+ const lang = getLanguage2(name);
1482
1482
  return lang && !lang.disableAutodetect;
1483
1483
  }
1484
1484
  function upgradePluginAPI(plugin) {
@@ -1533,7 +1533,7 @@ var require_core = __commonJS({
1533
1533
  registerLanguage,
1534
1534
  unregisterLanguage,
1535
1535
  listLanguages,
1536
- getLanguage,
1536
+ getLanguage: getLanguage2,
1537
1537
  registerAliases,
1538
1538
  autoDetection,
1539
1539
  inherit,
@@ -19205,8 +19205,58 @@ var en = {
19205
19205
  tabMemory: "Memory",
19206
19206
  tabHooks: "Hooks",
19207
19207
  tabSettings: "Settings",
19208
+ sectionChanges: "Changes",
19209
+ tabChanges: "Changes",
19208
19210
  footer: "127.0.0.1 only \xB7 token-gated"
19209
19211
  },
19212
+ changes: {
19213
+ chatPlaceholder: "Ask about your code...",
19214
+ chatWelcome: "Changes \u2014 ask questions about your project files.",
19215
+ chatSend: "Send",
19216
+ viewerPlaceholder: "Select a file to view",
19217
+ treeEmpty: "(empty)",
19218
+ tabClose: "Close tab",
19219
+ newConversation: "New",
19220
+ clearConversation: "Clear",
19221
+ newTitle: "/new \u2014 wipe conversation context",
19222
+ clearTitle: "/clear \u2014 wipe visible scrollback",
19223
+ newConfirmBusy: "A turn is in flight. Abort and start a new conversation?",
19224
+ newConfirm: "Clear current conversation and start fresh?",
19225
+ newToast: "new conversation",
19226
+ clearToast: "scrollback cleared",
19227
+ newFailed: "/new failed: {error}",
19228
+ clearFailed: "/clear failed: {error}",
19229
+ chatSendBtn: "Send",
19230
+ fileTreeTitle: "Files",
19231
+ codeViewerTitle: "Code Viewer",
19232
+ chatPanelTitle: "Chat",
19233
+ loadingFiles: "Loading project files\u2026",
19234
+ review: "Review",
19235
+ allFiles: "All Files",
19236
+ changes: "changes",
19237
+ commentLabel: "Commenting on line",
19238
+ commentPlaceholder: "Add a comment\u2026",
19239
+ commentCancel: "Cancel",
19240
+ commentSubmit: "Comment",
19241
+ commentLine: "Line",
19242
+ commentEdit: "Edit",
19243
+ commentDelete: "Delete",
19244
+ diffSourceGit: "Git changes",
19245
+ diffSourceSession: "Previous session",
19246
+ diffSourceCheckpoint: "Checkpoint",
19247
+ checkpointEmpty: "No checkpoints in this workspace yet.",
19248
+ restoreBtn: "Restore",
19249
+ restoreConfirm: 'Restore "{name}"? This will overwrite current files.',
19250
+ deleteBtn: "Delete",
19251
+ deleteConfirm: 'Delete checkpoint "{name}"? Snapshot will be removed, files stay unchanged.',
19252
+ createBtn: "Snapshot",
19253
+ createPlaceholder: "name for snapshot\u2026",
19254
+ backToList: "back to list",
19255
+ diffStyleUnified: "Unified",
19256
+ diffStyleSplit: "Split",
19257
+ expandAll: "Expand all",
19258
+ collapseAll: "Collapse all"
19259
+ },
19210
19260
  common: {
19211
19261
  loading: "loading\u2026",
19212
19262
  loadingFailed: "{name} failed: {error}",
@@ -19786,8 +19836,50 @@ var zhCN = {
19786
19836
  tabMemory: "\u8BB0\u5FC6",
19787
19837
  tabHooks: "\u94A9\u5B50",
19788
19838
  tabSettings: "\u8BBE\u7F6E",
19839
+ sectionChanges: "\u53D8\u66F4",
19840
+ tabChanges: "\u53D8\u66F4",
19789
19841
  footer: "\u4EC5 127.0.0.1 \xB7 Token \u4FDD\u62A4"
19790
19842
  },
19843
+ changes: {
19844
+ chatPlaceholder: "\u8BE2\u95EE\u4EE3\u7801\u95EE\u9898\u2026",
19845
+ chatWelcome: "\u53D8\u66F4 \u2014 \u8BE2\u95EE\u9879\u76EE\u6587\u4EF6\u76F8\u5173\u95EE\u9898\u3002",
19846
+ chatSend: "\u53D1\u9001",
19847
+ viewerPlaceholder: "\u9009\u62E9\u4E00\u4E2A\u6587\u4EF6\u67E5\u770B",
19848
+ treeEmpty: "\uFF08\u7A7A\uFF09",
19849
+ tabClose: "\u5173\u95ED\u6807\u7B7E",
19850
+ newConversation: "\u65B0\u5EFA",
19851
+ clearConversation: "\u6E05\u9664",
19852
+ newTitle: "/new \u2014 \u6E05\u9664\u5BF9\u8BDD\u4E0A\u4E0B\u6587",
19853
+ clearTitle: "/clear \u2014 \u4EC5\u6E05\u9664\u53EF\u89C1\u7684\u6EDA\u52A8\u56DE\u653E",
19854
+ newConfirmBusy: "\u6709\u8F6E\u6B21\u6B63\u5728\u6267\u884C\u3002\u4E2D\u6B62\u5E76\u5F00\u59CB\u65B0\u5BF9\u8BDD\uFF1F",
19855
+ newConfirm: "\u6E05\u9664\u5F53\u524D\u5BF9\u8BDD\u5E76\u91CD\u65B0\u5F00\u59CB\uFF1F",
19856
+ newToast: "\u65B0\u5BF9\u8BDD",
19857
+ clearToast: "\u6EDA\u52A8\u56DE\u653E\u5DF2\u6E05\u9664",
19858
+ newFailed: "/new \u5931\u8D25\uFF1A{error}",
19859
+ clearFailed: "/clear \u5931\u8D25\uFF1A{error}",
19860
+ chatSendBtn: "\u53D1\u9001",
19861
+ fileTreeTitle: "\u6587\u4EF6",
19862
+ codeViewerTitle: "\u4EE3\u7801\u67E5\u770B\u5668",
19863
+ chatPanelTitle: "\u5BF9\u8BDD",
19864
+ loadingFiles: "\u6B63\u5728\u52A0\u8F7D\u9879\u76EE\u6587\u4EF6\u2026",
19865
+ review: "\u5BA1\u67E5",
19866
+ allFiles: "\u6240\u6709\u6587\u4EF6",
19867
+ changes: "\u66F4\u6539",
19868
+ commentLabel: "\u6B63\u5728\u8BC4\u8BBA \u7B2C",
19869
+ commentPlaceholder: "\u6DFB\u52A0\u8BC4\u8BBA\u2026",
19870
+ commentCancel: "\u53D6\u6D88",
19871
+ commentSubmit: "\u8BC4\u8BBA",
19872
+ commentLine: "\u7B2C",
19873
+ commentEdit: "\u7F16\u8F91",
19874
+ commentDelete: "\u5220\u9664",
19875
+ reviewEmpty: "\u6682\u65E0\u53EF\u5BA1\u67E5\u7684\u66F4\u6539",
19876
+ diffSourceGit: "Git \u53D8\u66F4",
19877
+ diffSourceSession: "\u4E0A\u4E00\u8F6E\u53D8\u66F4",
19878
+ diffStyleUnified: "\u7EDF\u4E00\u89C6\u56FE",
19879
+ diffStyleSplit: "\u5206\u680F\u89C6\u56FE",
19880
+ expandAll: "\u5168\u90E8\u5C55\u5F00",
19881
+ collapseAll: "\u5168\u90E8\u6298\u53E0"
19882
+ },
19791
19883
  common: {
19792
19884
  loading: "\u52A0\u8F7D\u4E2D\u2026",
19793
19885
  loadingFailed: "{name}\u5931\u8D25\uFF1A{error}",
@@ -27767,37 +27859,1740 @@ function UsagePanel() {
27767
27859
  `;
27768
27860
  }
27769
27861
 
27770
- // dashboard/app.js
27862
+ // dashboard/src/lib/file-tree.ts
27771
27863
  var html5 = htm_module_default.bind(k);
27772
- function tabSections() {
27864
+ var EXT_ICONS = {
27865
+ ts: "TS",
27866
+ tsx: "TS",
27867
+ js: "JS",
27868
+ jsx: "JS",
27869
+ json: "{}",
27870
+ css: "#",
27871
+ scss: "#",
27872
+ html: "<>",
27873
+ md: "MD",
27874
+ py: "PY",
27875
+ rs: "RS",
27876
+ go: "GO",
27877
+ yaml: "Y",
27878
+ yml: "Y",
27879
+ toml: "T",
27880
+ xml: "<>",
27881
+ svg: "<>",
27882
+ png: "[]",
27883
+ jpg: "[]",
27884
+ ico: "[]",
27885
+ sh: "$",
27886
+ bash: "$",
27887
+ ps1: "$",
27888
+ bat: "$",
27889
+ sql: "DB",
27890
+ graphql: "GQ",
27891
+ proto: "PB",
27892
+ dockerfile: "D",
27893
+ makefile: "MK"
27894
+ };
27895
+ var EXT_LANG = {
27896
+ ts: "typescript",
27897
+ tsx: "typescript",
27898
+ js: "javascript",
27899
+ jsx: "javascript",
27900
+ json: "json",
27901
+ css: "css",
27902
+ scss: "scss",
27903
+ html: "html",
27904
+ md: "markdown",
27905
+ py: "python",
27906
+ rs: "rust",
27907
+ go: "go",
27908
+ yaml: "yaml",
27909
+ yml: "yaml",
27910
+ toml: "toml",
27911
+ xml: "xml",
27912
+ sh: "bash",
27913
+ bash: "bash",
27914
+ ps1: "powershell",
27915
+ bat: "batch",
27916
+ sql: "sql",
27917
+ graphql: "graphql",
27918
+ proto: "protobuf",
27919
+ dockerfile: "dockerfile",
27920
+ makefile: "makefile"
27921
+ };
27922
+ function getFileIcon(name) {
27923
+ const ext = name.split(".").pop()?.toLowerCase() ?? "";
27924
+ const icon = EXT_ICONS[ext] ?? "\xB7";
27925
+ const cls = ext || "file";
27926
+ return { icon, cls };
27927
+ }
27928
+ function getLanguage(name) {
27929
+ const ext = name.split(".").pop()?.toLowerCase() ?? "";
27930
+ return EXT_LANG[ext] ?? ext;
27931
+ }
27932
+ function isBinaryExt(name) {
27933
+ const ext = name.split(".").pop()?.toLowerCase() ?? "";
27934
+ const binary = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "ico", "svg", "woff", "woff2", "ttf", "eot", "mp4", "webm", "mp3", "wav", "zip", "tar", "gz", "7z", "pdf"]);
27935
+ return binary.has(ext);
27936
+ }
27937
+ function useProjectTree() {
27938
+ const [tree, setTree] = d2([]);
27939
+ const [loading, setLoading] = d2(true);
27940
+ const [error, setError] = d2(null);
27941
+ y2(() => {
27942
+ if (MODE === "standalone") {
27943
+ setLoading(false);
27944
+ setTree(createDemoTree());
27945
+ return;
27946
+ }
27947
+ let cancelled = false;
27948
+ api("/project-tree").then((r3) => {
27949
+ if (!cancelled) {
27950
+ setTree(r3.tree);
27951
+ setLoading(false);
27952
+ }
27953
+ }).catch((err) => {
27954
+ if (!cancelled) {
27955
+ setError(err.message);
27956
+ setLoading(false);
27957
+ setTree(createDemoTree());
27958
+ }
27959
+ });
27960
+ return () => {
27961
+ cancelled = true;
27962
+ };
27963
+ }, []);
27964
+ return { tree, loading, error };
27965
+ }
27966
+ function useFileTreeState(initialTree) {
27967
+ const [expanded, setExpanded] = d2(/* @__PURE__ */ new Set());
27968
+ const [openFiles, setOpenFiles] = d2([]);
27969
+ const [activeFilePath, setActiveFilePath] = d2(null);
27970
+ const [loadingFiles, setLoadingFiles] = d2({});
27971
+ const toggleExpand = q2((path) => {
27972
+ setExpanded((prev) => {
27973
+ const next = new Set(prev);
27974
+ if (next.has(path)) next.delete(path);
27975
+ else next.add(path);
27976
+ return next;
27977
+ });
27978
+ }, []);
27979
+ const openFile = q2(async (node) => {
27980
+ if (node.isDir) {
27981
+ toggleExpand(node.path);
27982
+ return;
27983
+ }
27984
+ if (isBinaryExt(node.name)) return;
27985
+ const existing = openFiles.find((f3) => f3.path === node.path);
27986
+ if (existing) {
27987
+ setActiveFilePath(node.path);
27988
+ return;
27989
+ }
27990
+ if (loadingFiles[node.path]) return;
27991
+ setLoadingFiles((prev) => ({ ...prev, [node.path]: true }));
27992
+ const lang = getLanguage(node.name);
27993
+ if (MODE === "standalone") {
27994
+ const mockContent = generateMockContent(node.name, lang);
27995
+ setOpenFiles((prev) => [...prev, { path: node.path, name: node.name, content: mockContent, language: lang }]);
27996
+ setActiveFilePath(node.path);
27997
+ setLoadingFiles((prev) => {
27998
+ const next = { ...prev };
27999
+ delete next[node.path];
28000
+ return next;
28001
+ });
28002
+ return;
28003
+ }
28004
+ try {
28005
+ const encodedPath = node.path.split("/").map(encodeURIComponent).join("/");
28006
+ const data = await api(`/file/${encodedPath}`);
28007
+ setOpenFiles((prev) => [...prev, { path: node.path, name: node.name, content: data.content, language: lang }]);
28008
+ setActiveFilePath(node.path);
28009
+ } catch (err) {
28010
+ console.error(`[file-tree] failed to load ${node.path}:`, err);
28011
+ setOpenFiles((prev) => [...prev, { path: node.path, name: node.name, content: `// Failed to load file: ${err.message}
28012
+ `, language: lang }]);
28013
+ setActiveFilePath(node.path);
28014
+ } finally {
28015
+ setLoadingFiles((prev) => {
28016
+ const next = { ...prev };
28017
+ delete next[node.path];
28018
+ return next;
28019
+ });
28020
+ }
28021
+ }, [openFiles, toggleExpand, loadingFiles]);
28022
+ const closeFile = q2((path) => {
28023
+ setOpenFiles((prev) => {
28024
+ const next = prev.filter((f3) => f3.path !== path);
28025
+ if (activeFilePath === path) {
28026
+ const lastFile = next[next.length - 1];
28027
+ setActiveFilePath(lastFile ? lastFile.path : null);
28028
+ }
28029
+ return next;
28030
+ });
28031
+ }, [activeFilePath]);
28032
+ const activeFile = openFiles.find((f3) => f3.path === activeFilePath) ?? null;
28033
+ return { expanded, openFiles, activeFilePath, activeFile, toggleExpand, openFile, closeFile, setActiveFilePath, loadingFiles };
28034
+ }
28035
+ function generateMockContent(name, lang) {
28036
+ const ext = name.split(".").pop()?.toLowerCase() ?? "";
28037
+ if (ext === "json") return JSON.stringify({ name: "example", version: "1.0.0", dependencies: { react: "^18.0.0" } }, null, 2);
28038
+ if (ext === "md") return '# Example Document\n\nThis is a sample markdown file.\n\n## Section\n\n- Item 1\n- Item 2\n\n```js\nconsole.log("hello");\n```';
28039
+ if (ext === "css") return "/* styles */\n.container {\n display: flex;\n padding: 16px;\n color: var(--fg-1);\n}";
28040
+ if (ext === "html") return "<!doctype html>\n<html>\n<head><title>Example</title></head>\n<body>\n <h1>Hello</h1>\n</body>\n</html>";
28041
+ if (ext === "py") return "def hello():\n print('Hello, World!')\n\nif __name__ == '__main__':\n hello()";
28042
+ if (ext === "yaml" || ext === "yml") return "name: example\nversion: '1.0'\nservices:\n app:\n image: node:18\n ports:\n - '3000:3000'";
28043
+ return `// ${name}
28044
+ // Language: ${lang}
28045
+
28046
+ export function example() {
28047
+ return "Hello from ${name}";
28048
+ }
28049
+ `;
28050
+ }
28051
+ function createDemoTree() {
27773
28052
  return [
27774
28053
  {
27775
- label: t4("app.sectionWorkspace"),
28054
+ name: "src",
28055
+ path: "src",
28056
+ isDir: true,
28057
+ children: [
28058
+ { name: "index.ts", path: "src/index.ts", isDir: false },
28059
+ { name: "app.tsx", path: "src/app.tsx", isDir: false },
28060
+ { name: "config.ts", path: "src/config.ts", isDir: false },
28061
+ {
28062
+ name: "components",
28063
+ path: "src/components",
28064
+ isDir: true,
28065
+ children: [
28066
+ { name: "Header.tsx", path: "src/components/Header.tsx", isDir: false },
28067
+ { name: "Sidebar.tsx", path: "src/components/Sidebar.tsx", isDir: false },
28068
+ { name: "Button.tsx", path: "src/components/Button.tsx", isDir: false }
28069
+ ]
28070
+ },
28071
+ {
28072
+ name: "lib",
28073
+ path: "src/lib",
28074
+ isDir: true,
28075
+ children: [
28076
+ { name: "api.ts", path: "src/lib/api.ts", isDir: false },
28077
+ { name: "format.ts", path: "src/lib/format.ts", isDir: false }
28078
+ ]
28079
+ }
28080
+ ]
28081
+ },
28082
+ {
28083
+ name: "tests",
28084
+ path: "tests",
28085
+ isDir: true,
28086
+ children: [
28087
+ { name: "app.test.ts", path: "tests/app.test.ts", isDir: false },
28088
+ { name: "utils.test.ts", path: "tests/utils.test.ts", isDir: false }
28089
+ ]
28090
+ },
28091
+ { name: "package.json", path: "package.json", isDir: false },
28092
+ { name: "tsconfig.json", path: "tsconfig.json", isDir: false },
28093
+ { name: "README.md", path: "README.md", isDir: false },
28094
+ { name: "styles.css", path: "styles.css", isDir: false },
28095
+ { name: "index.html", path: "index.html", isDir: false }
28096
+ ];
28097
+ }
28098
+
28099
+ // dashboard/src/lib/line-comments.ts
28100
+ function useLineComments() {
28101
+ const [comments, setComments] = d2([]);
28102
+ const [draft, setDraft] = d2(null);
28103
+ const addComment = q2((file, lineNumber, content) => {
28104
+ const id = `comment-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
28105
+ setComments((prev) => [...prev, { id, file, lineNumber, content, timestamp: Date.now() }]);
28106
+ setDraft(null);
28107
+ }, []);
28108
+ const updateComment = q2((id, content) => {
28109
+ setComments((prev) => prev.map((c3) => c3.id === id ? { ...c3, content } : c3));
28110
+ }, []);
28111
+ const deleteComment = q2((id) => {
28112
+ setComments((prev) => prev.filter((c3) => c3.id !== id));
28113
+ }, []);
28114
+ const startDraft = q2((file, lineNumber) => {
28115
+ setDraft({ file, lineNumber, content: "" });
28116
+ }, []);
28117
+ const editComment = q2((id, content) => {
28118
+ const comment = comments.find((c3) => c3.id === id);
28119
+ if (comment) {
28120
+ setDraft({ file: comment.file, lineNumber: comment.lineNumber, content, editingId: id });
28121
+ }
28122
+ }, [comments]);
28123
+ const cancelDraft = q2(() => {
28124
+ setDraft(null);
28125
+ }, []);
28126
+ const setDraftContent = q2((content) => {
28127
+ setDraft((prev) => prev ? { ...prev, content } : null);
28128
+ }, []);
28129
+ const submitDraft = q2(() => {
28130
+ if (draft && draft.content.trim()) {
28131
+ if (draft.editingId) {
28132
+ updateComment(draft.editingId, draft.content.trim());
28133
+ } else {
28134
+ addComment(draft.file, draft.lineNumber, draft.content.trim());
28135
+ }
28136
+ setDraft(null);
28137
+ }
28138
+ }, [draft, addComment, updateComment]);
28139
+ const commentsForFile = q2(
28140
+ (file) => comments.filter((c3) => c3.file === file),
28141
+ [comments]
28142
+ );
28143
+ const commentsForLine = q2(
28144
+ (file, lineNumber) => comments.filter((c3) => c3.file === file && c3.lineNumber === lineNumber),
28145
+ [comments]
28146
+ );
28147
+ return {
28148
+ comments,
28149
+ draft,
28150
+ addComment,
28151
+ updateComment,
28152
+ deleteComment,
28153
+ startDraft,
28154
+ editComment,
28155
+ cancelDraft,
28156
+ setDraftContent,
28157
+ submitDraft,
28158
+ commentsForFile,
28159
+ commentsForLine
28160
+ };
28161
+ }
28162
+
28163
+ // dashboard/src/lib/review-diffs.ts
28164
+ function useReviewDiffs() {
28165
+ const [diffs, setDiffs] = d2([]);
28166
+ const [loading, setLoading] = d2(false);
28167
+ const loadDiffs = q2(async (ep = "/review-diffs") => {
28168
+ setLoading(true);
28169
+ try {
28170
+ const data = await api(ep);
28171
+ setDiffs(Array.isArray(data) ? data : []);
28172
+ } catch {
28173
+ setDiffs([]);
28174
+ } finally {
28175
+ setLoading(false);
28176
+ }
28177
+ }, []);
28178
+ const modifiedFiles = q2(() => new Set(diffs.map((d3) => d3.file)), [diffs]);
28179
+ const modifiedCount = q2(() => diffs.length, [diffs]);
28180
+ return { diffs, loading, modifiedFiles, modifiedCount, reload: loadDiffs };
28181
+ }
28182
+
28183
+ // dashboard/src/lib/diff-parser.ts
28184
+ function parseHunks(patch) {
28185
+ if (!patch) return [];
28186
+ const hunks = [];
28187
+ const rawLines = patch.split("\n");
28188
+ let cursor = 0;
28189
+ while (cursor < rawLines.length) {
28190
+ const line = rawLines[cursor];
28191
+ const m3 = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/.exec(line);
28192
+ if (m3) {
28193
+ const oldStart = parseInt(m3[1], 10);
28194
+ const oldLen = m3[2] !== void 0 ? parseInt(m3[2], 10) : 1;
28195
+ const newStart = parseInt(m3[3], 10);
28196
+ const newLen = m3[4] !== void 0 ? parseInt(m3[4], 10) : 1;
28197
+ const lines = [];
28198
+ let oldNum = oldStart;
28199
+ let newNum = newStart;
28200
+ cursor++;
28201
+ while (cursor < rawLines.length && !rawLines[cursor].startsWith("@@ ") && !rawLines[cursor].startsWith("diff ") && !rawLines[cursor].startsWith("--- ") && !rawLines[cursor].startsWith("index ")) {
28202
+ const l3 = rawLines[cursor];
28203
+ if (l3.startsWith("\\")) {
28204
+ cursor++;
28205
+ continue;
28206
+ }
28207
+ const ch = l3[0];
28208
+ const content = l3.slice(1);
28209
+ if (ch === "-") {
28210
+ lines.push({ type: "del", content, oldLineNum: oldNum });
28211
+ oldNum++;
28212
+ } else if (ch === "+") {
28213
+ lines.push({ type: "add", content, newLineNum: newNum });
28214
+ newNum++;
28215
+ } else {
28216
+ lines.push({ type: "ctx", content, oldLineNum: oldNum, newLineNum: newNum });
28217
+ oldNum++;
28218
+ newNum++;
28219
+ }
28220
+ cursor++;
28221
+ }
28222
+ hunks.push({ oldStart, oldLines: oldLen, newStart, newLines: newLen, lines });
28223
+ } else {
28224
+ cursor++;
28225
+ }
28226
+ }
28227
+ return hunks;
28228
+ }
28229
+
28230
+ // dashboard/src/panels/changes.ts
28231
+ var html6 = htm_module_default.bind(k);
28232
+ function escapeAttr(s3) {
28233
+ return s3.replace(/["&<>]/g, (c3) => ({ '"': "&quot;", "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c3]);
28234
+ }
28235
+ function lineDiff2(a3, b2) {
28236
+ const m3 = a3.length, n3 = b2.length;
28237
+ const dp = Array.from({ length: m3 + 1 }, () => new Array(n3 + 1).fill(0));
28238
+ for (let i4 = 1; i4 <= m3; i4++) for (let j5 = 1; j5 <= n3; j5++)
28239
+ dp[i4][j5] = a3[i4 - 1] === b2[j5 - 1] ? dp[i4 - 1][j5 - 1] + 1 : Math.max(dp[i4 - 1][j5], dp[i4][j5 - 1]);
28240
+ const out = [];
28241
+ let i3 = m3, j4 = n3;
28242
+ while (i3 > 0 || j4 > 0) {
28243
+ if (i3 > 0 && j4 > 0 && a3[i3 - 1] === b2[j4 - 1]) {
28244
+ out.push({ kind: "context", text: a3[i3 - 1] });
28245
+ i3--;
28246
+ j4--;
28247
+ } else if (j4 > 0 && (i3 === 0 || dp[i3][j4 - 1] >= dp[i3 - 1][j4])) {
28248
+ out.push({ kind: "ins", text: b2[j4 - 1] });
28249
+ j4--;
28250
+ } else {
28251
+ out.push({ kind: "del", text: a3[i3 - 1] });
28252
+ i3--;
28253
+ }
28254
+ }
28255
+ return out.reverse();
28256
+ }
28257
+ function pairDiffRows2(diff) {
28258
+ const rows = [];
28259
+ let k3 = 0;
28260
+ while (k3 < diff.length) {
28261
+ const e3 = diff[k3];
28262
+ if (e3.kind === "context") {
28263
+ rows.push({ left: e3.text, right: e3.text, kind: "context" });
28264
+ k3++;
28265
+ continue;
28266
+ }
28267
+ const d3 = [], ins = [];
28268
+ while (k3 < diff.length && diff[k3].kind === "del") d3.push(diff[k3].text), k3++;
28269
+ while (k3 < diff.length && diff[k3].kind === "ins") ins.push(diff[k3].text), k3++;
28270
+ const p3 = Math.max(d3.length, ins.length);
28271
+ for (let i3 = 0; i3 < p3; i3++) rows.push({ left: d3[i3] ?? null, right: ins[i3] ?? null, kind: d3[i3] != null && ins[i3] != null ? "change" : d3[i3] != null ? "del" : "ins" });
28272
+ }
28273
+ return rows;
28274
+ }
28275
+ function hE(s3) {
28276
+ return s3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
28277
+ }
28278
+ function renderDiffHtml(patch, style) {
28279
+ const hunks = parseHunks(patch);
28280
+ if (hunks.length === 0) return "";
28281
+ if (style === "unified") {
28282
+ let html9 = "";
28283
+ for (const hunk of hunks) {
28284
+ html9 += `<div class="diff-hunk-header">@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@</div>`;
28285
+ for (const line of hunk.lines) {
28286
+ const cls = line.type === "add" ? "diff-add" : line.type === "del" ? "diff-del" : "";
28287
+ const prefix = line.type === "add" ? "+" : line.type === "del" ? "-" : " ";
28288
+ html9 += `<div class="diff-line ${cls}"><span class="diff-ln-old">${line.oldLineNum ?? ""}</span><span class="diff-ln-new">${line.newLineNum ?? ""}</span><span class="diff-prefix">${prefix}</span><span class="diff-content">${hE(line.content)}</span></div>`;
28289
+ }
28290
+ }
28291
+ return html9;
28292
+ }
28293
+ const oldLines = [], newLines = [];
28294
+ for (const hunk of hunks) {
28295
+ for (const line of hunk.lines) {
28296
+ if (line.type === "ctx") {
28297
+ oldLines.push(line.content);
28298
+ newLines.push(line.content);
28299
+ } else if (line.type === "del") oldLines.push(line.content);
28300
+ else newLines.push(line.content);
28301
+ }
28302
+ }
28303
+ const diff = lineDiff2(oldLines, newLines);
28304
+ const rows = pairDiffRows2(diff);
28305
+ let oldNum = 1, newNum = 1;
28306
+ let html8 = `<div class="edit-diff-head"><div class="edit-diff-side edit-diff-side-old"><span class="edit-diff-marker">\u2212</span> Before</div><div class="edit-diff-side edit-diff-side-new"><span class="edit-diff-marker">+</span> After</div></div><div class="edit-diff-body">`;
28307
+ for (const row of rows) {
28308
+ html8 += `<div class="edit-diff-row edit-diff-row-${row.kind}">`;
28309
+ html8 += `<div class="edit-diff-cell edit-diff-cell-old">`;
28310
+ if (row.left != null) {
28311
+ html8 += `<span class="edit-diff-ln">${oldNum}</span><span class="edit-diff-marker">${row.kind === "del" || row.kind === "change" ? "\u2212" : " "}</span>${hE(row.left)}`;
28312
+ oldNum++;
28313
+ }
28314
+ html8 += `</div>`;
28315
+ html8 += `<div class="edit-diff-cell edit-diff-cell-new">`;
28316
+ if (row.right != null) {
28317
+ html8 += `<span class="edit-diff-ln">${newNum}</span><span class="edit-diff-marker">${row.kind === "ins" || row.kind === "change" ? "+" : " "}</span>${hE(row.right)}`;
28318
+ newNum++;
28319
+ }
28320
+ html8 += `</div></div>`;
28321
+ }
28322
+ html8 += `</div>`;
28323
+ return html8;
28324
+ }
28325
+ function ChangesPanel() {
28326
+ useLang();
28327
+ const { tree, loading } = useProjectTree();
28328
+ const { expanded, openFiles, activeFilePath, activeFile, toggleExpand, openFile, closeFile, setActiveFilePath } = useFileTreeState(tree);
28329
+ const { comments, draft, startDraft, cancelDraft, setDraftContent, submitDraft, commentsForFile, deleteComment, editComment } = useLineComments();
28330
+ const { diffs, modifiedFiles, modifiedCount, reload } = useReviewDiffs();
28331
+ const [diffSource, setDiffSource] = d2("git");
28332
+ const [checkpointList, setCheckpointList] = d2([]);
28333
+ const [selectedCheckpointId, setSelectedCheckpointId] = d2(null);
28334
+ const [createName, setCreateName] = d2("");
28335
+ const [leftPct, setLeftPct] = d2(30);
28336
+ const [rightPct, setRightPct] = d2(30);
28337
+ const [showOnlyModified, setShowOnlyModified] = d2(false);
28338
+ const [reviewMode, setReviewMode] = d2(true);
28339
+ const [diffStyle, setDiffStyle] = d2("unified");
28340
+ const [reviewHtml, setReviewHtml] = d2("");
28341
+ const openingFile = A2(false);
28342
+ y2(() => {
28343
+ if (openFiles.length === 0 && !openingFile.current) setReviewMode(true);
28344
+ }, [openFiles]);
28345
+ const diffEndpoint = diffSource === "checkpoint" ? selectedCheckpointId ? `/checkpoint-diffs?id=${selectedCheckpointId}` : null : diffSource === "git" ? "/git-diffs" : "/review-diffs";
28346
+ y2(() => {
28347
+ if (diffSource === "checkpoint") {
28348
+ api("/checkpoints").then((list2) => setCheckpointList(list2)).catch(() => setCheckpointList([]));
28349
+ }
28350
+ }, [diffSource]);
28351
+ y2(() => {
28352
+ if (diffEndpoint) {
28353
+ reload(diffEndpoint);
28354
+ } else {
28355
+ setReviewHtml(`<div class="review-empty">${t4("changes.reviewEmpty") || "Select a checkpoint to compare"}</div>`);
28356
+ }
28357
+ void diffEndpoint;
28358
+ }, [diffEndpoint, reload]);
28359
+ y2(() => {
28360
+ if (diffs.length === 0) {
28361
+ const emptyMsg = t4("changes.reviewEmpty") || "No changes to review";
28362
+ setReviewHtml(`<div class="review-empty">${emptyMsg}</div>`);
28363
+ return;
28364
+ }
28365
+ setReviewHtml(
28366
+ diffs.map((diff) => {
28367
+ const file = hE(diff.file);
28368
+ const chev = '<span class="chev">\u25B8</span>';
28369
+ const stat = `<span class="stat"><span class="add">+${diff.additions}</span><span class="rem"> -${diff.deletions}</span></span>`;
28370
+ const body = diff.patch ? `<div class="review-file-body" style="display:none">${renderDiffHtml(diff.patch, diffStyle)}</div>` : "";
28371
+ return `<div class="review-file-item" data-file="${escapeAttr(file)}"><div class="review-file-header">${chev}<span class="filename">${escapeAttr(file)}</span>${stat}</div>${body}</div>`;
28372
+ }).join("")
28373
+ );
28374
+ }, [diffs, diffStyle, t4]);
28375
+ const expandAll = q2(() => {
28376
+ document.querySelectorAll(".review-file-body").forEach((el) => {
28377
+ el.style.display = "";
28378
+ });
28379
+ document.querySelectorAll(".review-file-header .chev").forEach((el) => {
28380
+ el.textContent = "\u25BE";
28381
+ });
28382
+ }, []);
28383
+ const collapseAll = q2(() => {
28384
+ document.querySelectorAll(".review-file-body").forEach((el) => {
28385
+ el.style.display = "none";
28386
+ });
28387
+ document.querySelectorAll(".review-file-header .chev").forEach((el) => {
28388
+ el.textContent = "\u25B8";
28389
+ });
28390
+ }, []);
28391
+ const handleLeftResize = q2((delta) => {
28392
+ setLeftPct((prev) => {
28393
+ const containerWidth = window.innerWidth;
28394
+ const deltaPct = delta / containerWidth * 100;
28395
+ return Math.max(15, Math.min(50, prev + deltaPct));
28396
+ });
28397
+ }, []);
28398
+ const handleRightResize = q2((delta) => {
28399
+ setRightPct((prev) => {
28400
+ const containerWidth = window.innerWidth;
28401
+ const deltaPct = delta / containerWidth * 100;
28402
+ return Math.max(15, Math.min(50, prev - deltaPct));
28403
+ });
28404
+ }, []);
28405
+ const toggleModifiedFilter = q2(() => {
28406
+ setShowOnlyModified((prev) => !prev);
28407
+ }, []);
28408
+ const toggleReviewMode = q2(() => {
28409
+ setReviewMode((prev) => !prev);
28410
+ }, []);
28411
+ const openReviewWithFilePicker = q2(() => {
28412
+ setReviewMode(true);
28413
+ }, []);
28414
+ const handleOpenFile = q2(
28415
+ async (filePath) => {
28416
+ const findInTree = (nodes, path) => {
28417
+ for (const n3 of nodes) {
28418
+ if (n3.path === path) return n3;
28419
+ if (n3.children) {
28420
+ const found = findInTree(n3.children, path);
28421
+ if (found) return found;
28422
+ }
28423
+ }
28424
+ return null;
28425
+ };
28426
+ let node = findInTree(tree, filePath);
28427
+ if (!node) {
28428
+ const parts = filePath.split("/");
28429
+ const name = parts[parts.length - 1] || filePath;
28430
+ node = { path: filePath, name, isDir: false };
28431
+ }
28432
+ await openFile(node);
28433
+ },
28434
+ [tree, openFile]
28435
+ );
28436
+ y2(() => {
28437
+ const handler = (e3) => {
28438
+ const header = e3.target.closest(".review-file-header");
28439
+ if (!header) return;
28440
+ const item = header.closest(".review-file-item");
28441
+ if (!item) return;
28442
+ const filePath = item.getAttribute("data-file");
28443
+ if (!filePath) return;
28444
+ const body = item.querySelector(".review-file-body");
28445
+ if (body) {
28446
+ const isOpen = body.style.display !== "none";
28447
+ body.style.display = isOpen ? "none" : "";
28448
+ const chev = header.querySelector(".chev");
28449
+ if (chev) chev.textContent = isOpen ? "\u25B8" : "\u25BE";
28450
+ }
28451
+ };
28452
+ document.addEventListener("click", handler);
28453
+ return () => document.removeEventListener("click", handler);
28454
+ }, []);
28455
+ const activeFileComments = activeFile ? commentsForFile(activeFile.path) : [];
28456
+ return html6`
28457
+ <div class="changes-layout">
28458
+ <div class="changes-panel changes-panel-left" style=${{ width: `${leftPct}%` }}>
28459
+ <div class="changes-panel-header">
28460
+ <span class="glyph">◆</span>
28461
+ <span>${t4("changes.chatPanelTitle")}</span>
28462
+ </div>
28463
+ <div class="changes-panel-body">
28464
+ <${ChatPane}
28465
+ comments=${comments}
28466
+ deleteComment=${deleteComment}
28467
+ />
28468
+ </div>
28469
+ </div>
28470
+
28471
+ <${ResizeHandle} onResize=${handleLeftResize} direction="horizontal" />
28472
+
28473
+ <div class="changes-panel changes-panel-center">
28474
+ ${reviewMode ? html6`
28475
+ <${TabBar}
28476
+ reviewTab=${html6`<${ReviewTab} count=${modifiedCount()} active=${true} onClick=${toggleReviewMode} />`}
28477
+ fileList=${diffs.map((d3) => d3.file)}
28478
+ onOpenFile=${(f3) => {
28479
+ handleOpenFile(f3);
28480
+ setReviewMode(false);
28481
+ }}
28482
+ onToggleReview=${toggleReviewMode}
28483
+ files=${openFiles}
28484
+ activePath=${activeFilePath}
28485
+ onSelect=${setActiveFilePath}
28486
+ onClose=${closeFile}
28487
+ />
28488
+ <div class="review-controls" style=${{ display: "flex", alignItems: "center", gap: "8px", padding: "6px 12px", borderBottom: "1px solid var(--bd)", fontSize: "12px" }}>
28489
+ <select value=${diffSource} onChange=${(e3) => {
28490
+ const v3 = e3.target.value;
28491
+ setDiffSource(v3);
28492
+ if (v3 !== "checkpoint") setSelectedCheckpointId(null);
28493
+ }} style=${{ fontSize: "12px", fontWeight: 500, padding: "1px 4px", borderRadius: "3px", background: "var(--bg-elev)", color: "var(--fg-0)", border: "1px solid var(--bd)", cursor: "pointer", outline: "none" }}>
28494
+ <option value="git">${t4("changes.diffSourceGit")}</option>
28495
+ <option value="session">${t4("changes.diffSourceSession")}</option>
28496
+ <option value="checkpoint">${t4("changes.diffSourceCheckpoint")}</option>
28497
+ </select>
28498
+ ${diffSource !== "checkpoint" || selectedCheckpointId ? html6`
28499
+ <span style=${{ color: "var(--fg-3)" }}>${modifiedCount()}</span>
28500
+ <div style=${{ marginLeft: "auto", display: "flex", alignItems: "center", gap: "4px" }}>
28501
+ <button class=${`toggle-btn ${diffStyle === "unified" ? "active" : ""}`} onClick=${() => setDiffStyle("unified")} style=${{ fontSize: "11px", padding: "2px 6px" }}>${t4("changes.diffStyleUnified")}</button>
28502
+ <button class=${`toggle-btn ${diffStyle === "split" ? "active" : ""}`} onClick=${() => setDiffStyle("split")} style=${{ fontSize: "11px", padding: "2px 6px" }}>${t4("changes.diffStyleSplit")}</button>
28503
+ <button class="toggle-btn" onClick=${expandAll} style=${{ fontSize: "11px", padding: "2px 6px" }}>${t4("changes.expandAll")}</button>
28504
+ <button class="toggle-btn" onClick=${collapseAll} style=${{ fontSize: "11px", padding: "2px 6px" }}>${t4("changes.collapseAll")}</button>
28505
+ </div>
28506
+ ` : null}
28507
+ </div>
28508
+ ${diffSource === "checkpoint" && selectedCheckpointId ? html6`
28509
+ <div style=${{ padding: "4px 12px", fontSize: "11px", color: "var(--fg-3)", borderBottom: "1px solid var(--bd)", cursor: "pointer" }}>
28510
+ <span onClick=${() => setSelectedCheckpointId(null)} style=${{ color: "var(--c-brand)", cursor: "pointer" }}>← ${t4("changes.backToList")}</span>
28511
+ </div>
28512
+ ` : null}
28513
+ ${diffSource === "checkpoint" && !selectedCheckpointId ? html6`
28514
+ <div class="checkpoint-picker" style=${{ flex: "1", overflowY: "auto", padding: "8px 12px" }}>
28515
+ <div style=${{ display: "flex", gap: "6px", marginBottom: "8px" }}>
28516
+ <input
28517
+ value=${createName}
28518
+ onInput=${(e3) => setCreateName(e3.target.value)}
28519
+ placeholder=${t4("changes.createPlaceholder")}
28520
+ style=${{ flex: 1, fontSize: "12px", padding: "4px 8px", background: "var(--bg-input)", border: "1px solid var(--bd)", borderRadius: "3px", color: "var(--fg-0)" }}
28521
+ />
28522
+ <button
28523
+ class="primary"
28524
+ onClick=${async () => {
28525
+ const name = createName.trim();
28526
+ if (!name) return;
28527
+ try {
28528
+ await api("/checkpoint-create", { method: "POST", body: { name } });
28529
+ setCreateName("");
28530
+ const list2 = await api("/checkpoints");
28531
+ setCheckpointList(list2);
28532
+ } catch {
28533
+ alert("create failed");
28534
+ }
28535
+ }}
28536
+ disabled=${!createName.trim()}
28537
+ style=${{ padding: "5px 12px" }}
28538
+ >${t4("changes.createBtn")}</button>
28539
+ </div>
28540
+ ${checkpointList.length === 0 ? html6`
28541
+ <div class="empty" style=${{ textAlign: "center", margin: "12px" }}>${t4("changes.checkpointEmpty")}</div>
28542
+ ` : checkpointList.map((c3) => html6`
28543
+ <div
28544
+ key=${c3.id}
28545
+ class="checkpoint-item"
28546
+ style=${{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "6px 8px", cursor: "pointer", borderRadius: "4px", borderBottom: "1px solid var(--bd)" }}
28547
+ onMouseEnter=${(e3) => {
28548
+ e3.currentTarget.style.background = "var(--bg-hover)";
28549
+ }}
28550
+ onMouseLeave=${(e3) => {
28551
+ e3.currentTarget.style.background = "transparent";
28552
+ }}
28553
+ >
28554
+ <div
28555
+ onClick=${() => {
28556
+ setSelectedCheckpointId(c3.id);
28557
+ }}
28558
+ style=${{ display: "flex", flexDirection: "column", gap: "2px", flex: 1 }}
28559
+ >
28560
+ <span style=${{ fontSize: "13px", fontWeight: 500 }}>${c3.name}</span>
28561
+ <span style=${{ fontSize: "11px", color: "var(--fg-3)" }}>${c3.id.slice(0, 7)} · ${c3.fileCount} file${c3.fileCount === 1 ? "" : "s"}</span>
28562
+ </div>
28563
+ <div style=${{ display: "flex", alignItems: "center", gap: "8px" }}>
28564
+ <span style=${{ fontSize: "11px", color: "var(--fg-4)" }}>${c3.ago}</span>
28565
+ <button
28566
+ onClick=${async (e3) => {
28567
+ e3.stopPropagation();
28568
+ if (confirm(t4("changes.restoreConfirm").replace("{name}", c3.name))) {
28569
+ try {
28570
+ await api("/checkpoint-restore", { method: "POST", body: { id: c3.id } });
28571
+ setSelectedCheckpointId(null);
28572
+ setDiffSource("git");
28573
+ } catch {
28574
+ alert("restore failed");
28575
+ }
28576
+ }
28577
+ }}
28578
+ style=${{ fontSize: "11px", padding: "2px 6px", background: "var(--c-brand)", color: "#fff", border: "none", borderRadius: "3px", cursor: "pointer" }}
28579
+ >${t4("changes.restoreBtn")}</button>
28580
+ <button
28581
+ onClick=${async (e3) => {
28582
+ e3.stopPropagation();
28583
+ if (confirm(t4("changes.deleteConfirm").replace("{name}", c3.name))) {
28584
+ try {
28585
+ await api("/checkpoint-delete", { method: "POST", body: { id: c3.id } });
28586
+ setCheckpointList((prev) => prev.filter((x3) => x3.id !== c3.id));
28587
+ } catch {
28588
+ alert("delete failed");
28589
+ }
28590
+ }
28591
+ }}
28592
+ style=${{ fontSize: "11px", padding: "2px 6px", color: "var(--fg-3)", border: "1px solid var(--bd)", borderRadius: "3px", cursor: "pointer", background: "transparent" }}
28593
+ >${t4("changes.deleteBtn")}</button>
28594
+ </div>
28595
+ </div>
28596
+ `)}
28597
+ </div>
28598
+ ` : null}
28599
+ <div class="review-diff-view" style=${{ flex: "1", overflowY: "auto" }}>
28600
+ <div class="review-diff-list" style=${{ padding: "0 12px" }} key=${diffStyle} dangerouslySetInnerHTML=${{ __html: reviewHtml }}></div>
28601
+ </div>
28602
+ ` : html6`
28603
+ <${TabBar}
28604
+ reviewTab=${html6`<${ReviewTab} count=${modifiedCount()} active=${false} onClick=${toggleReviewMode} />`}
28605
+ fileList=${diffs.map((d3) => d3.file)}
28606
+ onOpenFile=${handleOpenFile}
28607
+ files=${openFiles}
28608
+ activePath=${activeFilePath}
28609
+ onSelect=${setActiveFilePath}
28610
+ onClose=${closeFile}
28611
+ />
28612
+ <${CodeViewer}
28613
+ key=${activeFile?.path ?? "empty"}
28614
+ file=${activeFile}
28615
+ comments=${activeFileComments}
28616
+ draft=${draft && draft.file === activeFilePath ? draft : null}
28617
+ onStartComment=${startDraft}
28618
+ onEditComment=${editComment}
28619
+ onCancelComment=${cancelDraft}
28620
+ onCommentChange=${setDraftContent}
28621
+ onSubmitComment=${submitDraft}
28622
+ onDeleteComment=${deleteComment}
28623
+ />
28624
+ `}
28625
+ </div>
28626
+
28627
+ <${ResizeHandle} onResize=${handleRightResize} direction="horizontal" />
28628
+
28629
+ <div class="changes-panel changes-panel-right" style=${{ width: `${rightPct}%` }}>
28630
+ <div class="changes-panel-header">
28631
+ <span class="glyph">▼</span>
28632
+ <span>${t4("changes.fileTreeTitle")}</span>
28633
+ </div>
28634
+ <${FileTreeToggle}
28635
+ showOnlyModified=${showOnlyModified}
28636
+ modifiedCount=${modifiedCount()}
28637
+ onToggle=${toggleModifiedFilter}
28638
+ />
28639
+ <div class="changes-panel-body">
28640
+ ${loading ? html6`<div class="empty" style=${{ margin: "12px", textAlign: "center" }}>${t4("changes.loadingFiles")}</div>` : html6`<${FileTree}
28641
+ nodes=${tree}
28642
+ expanded=${expanded}
28643
+ onToggle=${toggleExpand}
28644
+ onSelect=${(node) => {
28645
+ setReviewMode(false);
28646
+ openFile(node);
28647
+ }}
28648
+ modifiedFiles=${modifiedFiles()}
28649
+ showOnlyModified=${showOnlyModified}
28650
+ />`}
28651
+ </div>
28652
+ </div>
28653
+ </div>
28654
+ `;
28655
+ }
28656
+ function fmtCost2(usd, currency) {
28657
+ if (currency === "CNY" || currency === "\xA5") {
28658
+ return `\xA5${(usd * 7.2).toFixed(4)}`;
28659
+ }
28660
+ return `$${usd.toFixed(4)}`;
28661
+ }
28662
+ function ChatStatusBar2({ stats, model }) {
28663
+ useLang();
28664
+ if (!stats) {
28665
+ return html6`
28666
+ <div class="chat-statusbar">
28667
+ <span class="muted">—</span>
28668
+ </div>
28669
+ `;
28670
+ }
28671
+ const ctxPct = stats.contextCapTokens > 0 ? stats.lastPromptTokens / stats.contextCapTokens * 100 : 0;
28672
+ const balance = stats.balance && stats.balance.length > 0 ? stats.balance[0] : null;
28673
+ return html6`
28674
+ <div class="chat-statusbar">
28675
+ <span class="status-item">
28676
+ <span class="status-label">${t4("chat.statusModel")}</span>
28677
+ <code>${model ?? "\u2014"}</code>
28678
+ </span>
28679
+ <span class="status-item">
28680
+ <span class="status-label">${t4("chat.statusCtx")}</span>
28681
+ <span class="status-bar-mini">
28682
+ <span class="status-bar-mini-fill" style=${`width: ${Math.min(100, ctxPct).toFixed(1)}%;`}></span>
28683
+ </span>
28684
+ <span class="muted">${stats.lastPromptTokens.toLocaleString()} / ${(stats.contextCapTokens / 1e3).toFixed(0)}K</span>
28685
+ </span>
28686
+ <span class="status-item">
28687
+ <span class="status-label">${t4("chat.statusCache")}</span>
28688
+ <span class=${stats.cacheHitRatio >= 0.9 ? "status-ok" : stats.cacheHitRatio >= 0.6 ? "status-warn" : "status-err"}>
28689
+ ${(stats.cacheHitRatio * 100).toFixed(1)}%
28690
+ </span>
28691
+ </span>
28692
+ <span class="status-item">
28693
+ <span class="status-label">${t4("chat.statusTurn")}</span>
28694
+ <code>${fmtCost2(stats.lastTurnCostUsd, balance?.currency)}</code>
28695
+ </span>
28696
+ <span class="status-item">
28697
+ <span class="status-label">${t4("chat.statusSession")}</span>
28698
+ <code>${fmtCost2(stats.totalCostUsd, balance?.currency)}</code>
28699
+ <span class="muted" style="font-size: 10px;">
28700
+ ${t4("chat.statusTurns", { count: stats.turns, s: stats.turns === 1 ? "" : "s" })}
28701
+ </span>
28702
+ </span>
28703
+ ${balance ? html6`
28704
+ <span class="status-item">
28705
+ <span class="status-label">${t4("chat.statusBalance")}</span>
28706
+ <code>${balance.total_balance} ${balance.currency}</code>
28707
+ </span>
28708
+ ` : null}
28709
+ </div>
28710
+ `;
28711
+ }
28712
+ function CommentCard(props) {
28713
+ return html6`
28714
+ <div class="comment-card">
28715
+ <span class="comment-card-icon">⬥</span>
28716
+ <span class="comment-card-file">${props.fileName}:${props.lineNumber}</span>
28717
+ <span class="comment-card-content">${props.content}</span>
28718
+ <span class="comment-card-remove" onClick=${props.onRemove}>×</span>
28719
+ </div>
28720
+ `;
28721
+ }
28722
+ function filterModifiedNodes(nodes, modifiedFiles) {
28723
+ return nodes.map((node) => {
28724
+ if (node.isDir && node.children) {
28725
+ const filteredChildren = filterModifiedNodes(node.children, modifiedFiles);
28726
+ if (filteredChildren.length === 0) return null;
28727
+ return { ...node, children: filteredChildren };
28728
+ }
28729
+ if (modifiedFiles.has(node.path)) return node;
28730
+ return null;
28731
+ }).filter((n3) => n3 !== null);
28732
+ }
28733
+ function renderTree(props) {
28734
+ const { nodes, expanded, onToggle, onSelect, indent = 0, modifiedFiles = /* @__PURE__ */ new Set(), showOnlyModified = false } = props;
28735
+ const displayNodes = showOnlyModified ? filterModifiedNodes(nodes, modifiedFiles) : nodes;
28736
+ return displayNodes.map((node) => {
28737
+ const isExpanded = expanded.has(node.path);
28738
+ const indentEls = [];
28739
+ for (let i3 = 0; i3 < indent; i3++) {
28740
+ indentEls.push(html6`<span class="indent" key=${`indent-${i3}`} />`);
28741
+ }
28742
+ if (node.isDir) {
28743
+ const cls2 = isExpanded ? "tree-node open" : "tree-node";
28744
+ return html6`
28745
+ <div key=${node.path}>
28746
+ <div class=${cls2} onClick=${() => onToggle(node.path)}>
28747
+ ${indentEls}
28748
+ <span class="arrow">${isExpanded ? "\u25BE" : "\u25B8"}</span>
28749
+ <span class="icon dir">▼</span>
28750
+ <span class="name">${node.name}</span>
28751
+ </div>
28752
+ ${isExpanded && node.children && node.children.length > 0 ? renderTree({ nodes: node.children, expanded, onToggle, onSelect, indent: indent + 1, modifiedFiles, showOnlyModified }) : null}
28753
+ ${isExpanded && (!node.children || node.children.length === 0) ? html6`<div class="tree-node" style=${{ paddingLeft: `${(indent + 1) * 14 + 8}px` }}>
28754
+ <span class="name muted">${t4("changes.treeEmpty")}</span>
28755
+ </div>` : null}
28756
+ </div>
28757
+ `;
28758
+ }
28759
+ const { icon, cls } = getFileIcon(node.name);
28760
+ const isModified = modifiedFiles.has(node.path);
28761
+ return html6`
28762
+ <div
28763
+ key=${node.path}
28764
+ class="tree-node"
28765
+ onClick=${() => onSelect(node)}
28766
+ style=${{ paddingLeft: `${indent * 14 + 8}px` }}
28767
+ >
28768
+ <span class=${`icon ${cls}`}>${icon}</span>
28769
+ <span class="name">${node.name}</span>
28770
+ ${isModified ? html6`<span class="mod-indicator" />` : null}
28771
+ </div>
28772
+ `;
28773
+ });
28774
+ }
28775
+ function FileTree(props) {
28776
+ return html6`
28777
+ <div class="tree">
28778
+ ${renderTree(props)}
28779
+ </div>
28780
+ `;
28781
+ }
28782
+ function FileTreeToggle(props) {
28783
+ return html6`
28784
+ <div class="file-tree-toggle">
28785
+ <button
28786
+ class=${`toggle-btn ${props.showOnlyModified ? "active" : ""}`}
28787
+ onClick=${props.onToggle}
28788
+ >
28789
+ ${props.modifiedCount} ${t4("changes.changes")}
28790
+ </button>
28791
+ <button
28792
+ class=${`toggle-btn ${!props.showOnlyModified ? "active" : ""}`}
28793
+ onClick=${props.onToggle}
28794
+ >
28795
+ ${t4("changes.allFiles")}
28796
+ </button>
28797
+ </div>
28798
+ `;
28799
+ }
28800
+ function ReviewTab(props) {
28801
+ return html6`
28802
+ <div
28803
+ class=${`editor-tab review-tab${props.active ? " active" : ""}`}
28804
+ onClick=${props.onClick}
28805
+ style=${{ display: "flex", alignItems: "center", gap: "3px", padding: "4px 6px", cursor: props.onClick ? "pointer" : "default" }}
28806
+ >
28807
+ <span class="review-icon">◑</span>
28808
+ <span>${t4("changes.review")}</span>
28809
+ <span style=${{ color: "var(--fg-3)", fontSize: "10.5px" }}>${props.count}</span>
28810
+ </div>
28811
+ `;
28812
+ }
28813
+ function ResizeHandle(props) {
28814
+ const { onResize, direction } = props;
28815
+ const dragging = A2(false);
28816
+ const startX = A2(0);
28817
+ const onMouseDown = q2((e3) => {
28818
+ e3.preventDefault();
28819
+ dragging.current = true;
28820
+ startX.current = direction === "horizontal" ? e3.clientX : e3.clientY;
28821
+ document.body.style.cursor = direction === "horizontal" ? "col-resize" : "row-resize";
28822
+ document.body.style.userSelect = "none";
28823
+ }, [direction]);
28824
+ y2(() => {
28825
+ const onMouseMove = (e3) => {
28826
+ if (!dragging.current) return;
28827
+ const current = direction === "horizontal" ? e3.clientX : e3.clientY;
28828
+ const delta = current - startX.current;
28829
+ startX.current = current;
28830
+ onResize(delta);
28831
+ };
28832
+ const onMouseUp = () => {
28833
+ if (!dragging.current) return;
28834
+ dragging.current = false;
28835
+ document.body.style.cursor = "";
28836
+ document.body.style.userSelect = "";
28837
+ };
28838
+ document.addEventListener("mousemove", onMouseMove);
28839
+ document.addEventListener("mouseup", onMouseUp);
28840
+ return () => {
28841
+ document.removeEventListener("mousemove", onMouseMove);
28842
+ document.removeEventListener("mouseup", onMouseUp);
28843
+ };
28844
+ }, [onResize, direction]);
28845
+ const isH = direction === "horizontal";
28846
+ return html6`
28847
+ <div
28848
+ onMouseDown=${onMouseDown}
28849
+ style=${{
28850
+ width: isH ? "4px" : "100%",
28851
+ height: isH ? "100%" : "4px",
28852
+ cursor: isH ? "col-resize" : "row-resize",
28853
+ background: "var(--bd)",
28854
+ flexShrink: 0,
28855
+ transition: "background 0.15s"
28856
+ }}
28857
+ onMouseEnter=${(e3) => {
28858
+ e3.target.style.background = "var(--c-brand)";
28859
+ }}
28860
+ onMouseLeave=${(e3) => {
28861
+ e3.target.style.background = "var(--bd)";
28862
+ }}
28863
+ />
28864
+ `;
28865
+ }
28866
+ function TabBar(props) {
28867
+ const { files, activePath, onSelect, onClose, reviewTab, fileList, onOpenFile } = props;
28868
+ const popupRef = A2(null);
28869
+ const btnRef = A2(null);
28870
+ y2(() => {
28871
+ const btn = btnRef.current;
28872
+ if (!btn || !fileList || fileList.length === 0) return;
28873
+ const toggle = (e3) => {
28874
+ e3.stopPropagation();
28875
+ if (popupRef.current) {
28876
+ popupRef.current.remove();
28877
+ popupRef.current = null;
28878
+ return;
28879
+ }
28880
+ const allFiles = fileList;
28881
+ const popup = document.createElement("div");
28882
+ popupRef.current = popup;
28883
+ popup.style.cssText = "position:fixed;top:20%;left:50%;transform:translateX(-50%);background:var(--bg-elev-2);border:1px solid var(--bd);border-radius:6px;max-height:400px;display:flex;flex-direction:column;z-index:1000;min-width:380px;max-width:600px;box-shadow:0 8px 24px rgba(0,0,0,.4)";
28884
+ const input = document.createElement("input");
28885
+ input.placeholder = "\u641C\u7D22\u6587\u4EF6...";
28886
+ input.style.cssText = "margin:6px 8px;padding:5px 8px;font-size:12px;background:var(--bg);color:var(--fg-0);border:1px solid var(--bd);border-radius:4px;outline:none;flex-shrink:0";
28887
+ input.onclick = (ev) => ev.stopPropagation();
28888
+ popup.appendChild(input);
28889
+ const listWrap = document.createElement("div");
28890
+ listWrap.style.cssText = "overflow-y:auto;flex:1;padding:0 4px 4px";
28891
+ popup.appendChild(listWrap);
28892
+ function renderList(filter) {
28893
+ listWrap.innerHTML = "";
28894
+ const q4 = filter.toLowerCase();
28895
+ for (const f3 of allFiles) {
28896
+ if (q4 && !f3.toLowerCase().includes(q4)) continue;
28897
+ const row = document.createElement("div");
28898
+ row.textContent = f3;
28899
+ row.style.cssText = "padding:3px 8px;font-size:11px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:var(--font-mono);border-radius:3px";
28900
+ row.onmouseenter = () => row.style.background = "var(--bg-hover)";
28901
+ row.onmouseleave = () => row.style.background = "transparent";
28902
+ row.onclick = (ev) => {
28903
+ ev.stopPropagation();
28904
+ onOpenFile?.(f3);
28905
+ popup.remove();
28906
+ popupRef.current = null;
28907
+ };
28908
+ listWrap.appendChild(row);
28909
+ }
28910
+ }
28911
+ renderList("");
28912
+ input.oninput = () => renderList(input.value);
28913
+ setTimeout(() => input.focus(), 50);
28914
+ document.body.appendChild(popup);
28915
+ const close = (ev) => {
28916
+ if (popupRef.current && !popup.contains(ev.target) && ev.target !== btn) {
28917
+ popup.remove();
28918
+ popupRef.current = null;
28919
+ document.removeEventListener("mousedown", close, true);
28920
+ }
28921
+ };
28922
+ document.addEventListener("mousedown", close, true);
28923
+ };
28924
+ btn.addEventListener("click", toggle);
28925
+ return () => {
28926
+ btn.removeEventListener("click", toggle);
28927
+ if (popupRef.current) {
28928
+ popupRef.current.remove();
28929
+ popupRef.current = null;
28930
+ }
28931
+ };
28932
+ }, [fileList, onOpenFile]);
28933
+ return html6`
28934
+ <div class="editor-tabs">
28935
+ ${reviewTab || null}
28936
+ ${fileList ? html6`
28937
+ <span
28938
+ ref=${btnRef}
28939
+ style=${{
28940
+ fontSize: "14px",
28941
+ padding: "4px 3px",
28942
+ cursor: "pointer",
28943
+ color: "var(--fg-3)",
28944
+ userSelect: "none",
28945
+ lineHeight: "1",
28946
+ fontFamily: "var(--font-mono)"
28947
+ }}
28948
+ title="Open file"
28949
+ >+</span>
28950
+ ` : null}
28951
+ ${files.map((f3) => html6`
28952
+ <div
28953
+ key=${f3.path}
28954
+ class=${`editor-tab ${f3.path === activePath ? "active" : ""}`}
28955
+ onClick=${() => onSelect(f3.path)}
28956
+ title=${f3.path}
28957
+ >
28958
+ <span>${f3.name}</span>
28959
+ <span
28960
+ class="x"
28961
+ onClick=${(e3) => {
28962
+ e3.stopPropagation();
28963
+ onClose(f3.path);
28964
+ }}
28965
+ title=${t4("changes.tabClose")}
28966
+ >×</span>
28967
+ </div>
28968
+ `)}
28969
+ </div>
28970
+ `;
28971
+ }
28972
+ function CodeViewer(props) {
28973
+ const { file, comments = [], draft, onStartComment, onEditComment, onCancelComment, onCommentChange, onSubmitComment, onDeleteComment } = props;
28974
+ const codeRef = A2(null);
28975
+ const [hoveredLine, setHoveredLine] = d2(null);
28976
+ y2(() => {
28977
+ if (!file) return;
28978
+ const el = codeRef.current;
28979
+ if (!el) return;
28980
+ el.innerHTML = "";
28981
+ const lines = file.content.split("\n");
28982
+ const commentsByLine = /* @__PURE__ */ new Map();
28983
+ comments.forEach((c3) => {
28984
+ const existing = commentsByLine.get(c3.lineNumber) || [];
28985
+ existing.push(c3);
28986
+ commentsByLine.set(c3.lineNumber, existing);
28987
+ });
28988
+ lines.forEach((line, i3) => {
28989
+ const lineNumber = i3 + 1;
28990
+ const lineComments = commentsByLine.get(lineNumber) || [];
28991
+ const hasComments = lineComments.length > 0;
28992
+ const lineDiv = document.createElement("div");
28993
+ lineDiv.className = "editor-line";
28994
+ lineDiv.dataset.lineNumber = String(lineNumber);
28995
+ lineDiv.style.position = "relative";
28996
+ lineDiv.addEventListener("mouseenter", () => setHoveredLine(lineNumber));
28997
+ lineDiv.addEventListener("mouseleave", () => setHoveredLine(null));
28998
+ const gutter = document.createElement("div");
28999
+ gutter.className = "lineno";
29000
+ gutter.textContent = String(lineNumber);
29001
+ gutter.style.position = "relative";
29002
+ gutter.style.display = "flex";
29003
+ gutter.style.alignItems = "center";
29004
+ gutter.style.justifyContent = "center";
29005
+ gutter.style.gap = "4px";
29006
+ if (onStartComment) {
29007
+ const isVisible = hoveredLine === lineNumber && (!draft || draft.file !== file.path || draft.lineNumber !== lineNumber);
29008
+ const anchorBtn = document.createElement("span");
29009
+ anchorBtn.className = `line-comment-anchor ${isVisible ? "visible" : ""}`;
29010
+ anchorBtn.style.cssText = "width:16px;height:16px;display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;cursor:pointer;transition:opacity 0.15s ease;flex-shrink:0;";
29011
+ if (isVisible) {
29012
+ anchorBtn.style.opacity = "1";
29013
+ anchorBtn.style.pointerEvents = "auto";
29014
+ }
29015
+ if (hasComments) {
29016
+ anchorBtn.innerHTML = `<span class="comment-count" style="background:rgba(121,192,255,0.12);border-radius:2px;padding:0 3px;font-size:10px;color:#79c0ff;font-family:monospace;">${lineComments.length}</span>`;
29017
+ } else {
29018
+ anchorBtn.innerHTML = `<span class="plus-icon" style="font-family:monospace;font-size:14px;color:#6e7681;line-height:1;">+</span>`;
29019
+ }
29020
+ anchorBtn.addEventListener("mouseenter", () => {
29021
+ anchorBtn.style.opacity = "1";
29022
+ });
29023
+ anchorBtn.addEventListener("click", (e3) => {
29024
+ e3.stopPropagation();
29025
+ onStartComment(file.path, lineNumber);
29026
+ });
29027
+ gutter.appendChild(anchorBtn);
29028
+ }
29029
+ const content = document.createElement("span");
29030
+ content.className = "ln-content";
29031
+ content.textContent = line || " ";
29032
+ lineDiv.appendChild(gutter);
29033
+ lineDiv.appendChild(content);
29034
+ el.appendChild(lineDiv);
29035
+ if (draft && draft.file === file.path && draft.lineNumber === lineNumber) {
29036
+ const editorContainer = document.createElement("div");
29037
+ editorContainer.className = "line-comment-editor";
29038
+ const labelDiv = document.createElement("div");
29039
+ labelDiv.className = "line-comment-label";
29040
+ labelDiv.textContent = `${t4("changes.commentLabel")} ${lineNumber}`;
29041
+ const textarea = document.createElement("textarea");
29042
+ textarea.className = "line-comment-textarea";
29043
+ textarea.placeholder = t4("changes.commentPlaceholder");
29044
+ textarea.rows = 2;
29045
+ textarea.value = draft.content;
29046
+ let isComposing = false;
29047
+ textarea.addEventListener("compositionstart", () => {
29048
+ isComposing = true;
29049
+ });
29050
+ textarea.addEventListener("compositionend", (e3) => {
29051
+ isComposing = false;
29052
+ if (onCommentChange) onCommentChange(e3.target.value);
29053
+ });
29054
+ textarea.addEventListener("input", (e3) => {
29055
+ if (!isComposing && onCommentChange) onCommentChange(e3.target.value);
29056
+ });
29057
+ textarea.addEventListener("keydown", (e3) => {
29058
+ if (e3.key === "Escape" && onCancelComment) {
29059
+ e3.preventDefault();
29060
+ onCancelComment();
29061
+ } else if (e3.key === "Enter" && e3.ctrlKey && onSubmitComment) {
29062
+ e3.preventDefault();
29063
+ onSubmitComment();
29064
+ }
29065
+ });
29066
+ const actionsDiv = document.createElement("div");
29067
+ actionsDiv.className = "line-comment-actions";
29068
+ actionsDiv.style.cssText = "display:flex;gap:4px;justify-content:flex-end;";
29069
+ const cancelBtn = document.createElement("button");
29070
+ cancelBtn.className = "btn ghost";
29071
+ cancelBtn.textContent = t4("changes.commentCancel");
29072
+ cancelBtn.style.cssText = "background:transparent;border:none;color:#6e7681;padding:3px 8px;font-size:11px;cursor:pointer;";
29073
+ cancelBtn.addEventListener("click", () => {
29074
+ if (onCancelComment) onCancelComment();
29075
+ });
29076
+ const submitBtn = document.createElement("button");
29077
+ submitBtn.className = "btn primary";
29078
+ submitBtn.textContent = t4("changes.commentSubmit");
29079
+ submitBtn.style.cssText = "background:#79c0ff;color:#0a0c10;border:none;padding:3px 8px;font-size:11px;cursor:pointer;border-radius:2px;font-weight:600;";
29080
+ submitBtn.disabled = !draft.content.trim();
29081
+ submitBtn.addEventListener("click", () => {
29082
+ if (onSubmitComment) onSubmitComment();
29083
+ });
29084
+ actionsDiv.appendChild(cancelBtn);
29085
+ actionsDiv.appendChild(submitBtn);
29086
+ editorContainer.appendChild(labelDiv);
29087
+ editorContainer.appendChild(textarea);
29088
+ editorContainer.appendChild(actionsDiv);
29089
+ el.appendChild(editorContainer);
29090
+ setTimeout(() => textarea.focus(), 0);
29091
+ }
29092
+ if (hasComments) {
29093
+ lineComments.forEach((comment) => {
29094
+ if (el.querySelector(`.line-comment-bubble[data-id="${comment.id}"]`)) return;
29095
+ const isEditing = draft && draft.editingId === comment.id;
29096
+ if (isEditing) return;
29097
+ const bubbleDiv = document.createElement("div");
29098
+ bubbleDiv.className = "line-comment-bubble";
29099
+ bubbleDiv.dataset.id = comment.id;
29100
+ const contentDiv = document.createElement("div");
29101
+ contentDiv.className = "bubble-content";
29102
+ contentDiv.textContent = comment.content;
29103
+ const footerDiv = document.createElement("div");
29104
+ footerDiv.className = "bubble-footer";
29105
+ const lineSpan = document.createElement("span");
29106
+ lineSpan.className = "bubble-line";
29107
+ lineSpan.textContent = `\u8BC4\u8BBA\u7B2C ${comment.lineNumber} \u884C`;
29108
+ const actionsDiv = document.createElement("div");
29109
+ actionsDiv.className = "bubble-actions";
29110
+ actionsDiv.style.display = "flex";
29111
+ actionsDiv.style.gap = "4px";
29112
+ if (onEditComment) {
29113
+ const editBtn = document.createElement("button");
29114
+ editBtn.className = "bubble-btn";
29115
+ editBtn.textContent = "\u7F16\u8F91";
29116
+ editBtn.style.cssText = "background:transparent;border:none;color:#6e7681;padding:3px 8px;font-size:11px;cursor:pointer;border-radius:2px;";
29117
+ editBtn.addEventListener("click", (e3) => {
29118
+ e3.stopPropagation();
29119
+ onEditComment(comment.id, comment.content);
29120
+ });
29121
+ actionsDiv.appendChild(editBtn);
29122
+ }
29123
+ if (onDeleteComment) {
29124
+ const deleteBtn = document.createElement("button");
29125
+ deleteBtn.className = "bubble-btn danger";
29126
+ deleteBtn.textContent = "\u5220\u9664";
29127
+ deleteBtn.style.cssText = "background:transparent;border:none;color:#6e7681;padding:3px 8px;font-size:11px;cursor:pointer;border-radius:2px;";
29128
+ deleteBtn.addEventListener("click", (e3) => {
29129
+ e3.stopPropagation();
29130
+ onDeleteComment(comment.id);
29131
+ });
29132
+ actionsDiv.appendChild(deleteBtn);
29133
+ }
29134
+ footerDiv.appendChild(lineSpan);
29135
+ footerDiv.appendChild(actionsDiv);
29136
+ bubbleDiv.appendChild(contentDiv);
29137
+ bubbleDiv.appendChild(footerDiv);
29138
+ el.appendChild(bubbleDiv);
29139
+ });
29140
+ }
29141
+ });
29142
+ if (common_default) {
29143
+ const codeEl = codeRef.current;
29144
+ if (codeEl) {
29145
+ codeEl.querySelectorAll(".ln-content").forEach((span) => {
29146
+ const text = span.textContent ?? "";
29147
+ try {
29148
+ const result = common_default.highlight(text, { language: file.language, ignoreIllegals: true });
29149
+ span.innerHTML = result.value;
29150
+ } catch {
29151
+ span.textContent = text;
29152
+ }
29153
+ });
29154
+ }
29155
+ }
29156
+ }, [file, comments, draft]);
29157
+ y2(() => {
29158
+ if (!codeRef.current || !file) return;
29159
+ const anchors = codeRef.current.querySelectorAll(".line-comment-anchor");
29160
+ anchors.forEach((anchor) => {
29161
+ const lineDiv = anchor.closest(".editor-line");
29162
+ if (!lineDiv) return;
29163
+ const lineNumber = parseInt(lineDiv.dataset.lineNumber || "0", 10);
29164
+ const isVisible = hoveredLine === lineNumber && (!draft || draft.file !== file.path || draft.lineNumber !== lineNumber);
29165
+ anchor.style.opacity = isVisible ? "1" : "0";
29166
+ anchor.style.pointerEvents = isVisible ? "auto" : "none";
29167
+ });
29168
+ }, [hoveredLine, draft, file]);
29169
+ if (!file) {
29170
+ return html6`
29171
+ <div class="editor-area" style=${{ display: "flex", alignItems: "center", justifyContent: "center" }}>
29172
+ <div class="empty">${t4("changes.viewerPlaceholder")}</div>
29173
+ </div>
29174
+ `;
29175
+ }
29176
+ return html6`
29177
+ <div class="editor-area" ref=${codeRef} />
29178
+ <div class="editor-status">
29179
+ <span class="glyph">◆</span>
29180
+ <span class="v">${file.name}</span>
29181
+ <span class="grow"></span>
29182
+ <span>${file.language}</span>
29183
+ <span class="v">${String(file.content.split("\n").length)} lines</span>
29184
+ </div>
29185
+ `;
29186
+ }
29187
+ function ChatPane(props) {
29188
+ useLang();
29189
+ const [messages, setMessages] = d2([]);
29190
+ const [streaming, setStreaming] = d2(null);
29191
+ const [activeTool, setActiveTool] = d2(null);
29192
+ const [busy, setBusy] = d2(false);
29193
+ const [input, setInput] = d2("");
29194
+ const [error, setError] = d2(null);
29195
+ const [statusLine, setStatusLine] = d2(null);
29196
+ const [stats, setStats] = d2(null);
29197
+ const [model, setModel] = d2(null);
29198
+ const shouldAutoScroll = A2(true);
29199
+ const feedRef = A2(null);
29200
+ const [slashCommands, setSlashCommands] = d2([]);
29201
+ const [popoverKind, setPopoverKind] = d2(null);
29202
+ const [popoverItems, setPopoverItems] = d2([]);
29203
+ const [popoverSel, setPopoverSel] = d2(0);
29204
+ y2(() => {
29205
+ let cancelled = false;
29206
+ (async () => {
29207
+ try {
29208
+ const r3 = await api("/slash");
29209
+ if (!cancelled) setSlashCommands(r3.commands);
29210
+ } catch {
29211
+ }
29212
+ })();
29213
+ return () => {
29214
+ cancelled = true;
29215
+ };
29216
+ }, []);
29217
+ y2(() => {
29218
+ let cancelled = false;
29219
+ (async () => {
29220
+ try {
29221
+ const data = await api("/messages");
29222
+ if (!cancelled) {
29223
+ setMessages(data.messages ?? []);
29224
+ setBusy(Boolean(data.busy));
29225
+ }
29226
+ } catch {
29227
+ if (!cancelled) setMessages([]);
29228
+ }
29229
+ })();
29230
+ return () => {
29231
+ cancelled = true;
29232
+ };
29233
+ }, []);
29234
+ y2(() => {
29235
+ let cancelled = false;
29236
+ (async () => {
29237
+ try {
29238
+ const data = await api("/overview");
29239
+ if (!cancelled) {
29240
+ setStats(data.stats ?? null);
29241
+ setModel(data.model ?? null);
29242
+ }
29243
+ } catch {
29244
+ }
29245
+ })();
29246
+ return () => {
29247
+ cancelled = true;
29248
+ };
29249
+ }, []);
29250
+ y2(() => {
29251
+ const es = new EventSource(`/api/events?token=${TOKEN}`);
29252
+ es.onmessage = (ev) => {
29253
+ let dash;
29254
+ try {
29255
+ dash = JSON.parse(ev.data);
29256
+ } catch {
29257
+ return;
29258
+ }
29259
+ if (dash.kind === "ping") return;
29260
+ if (dash.kind === "busy-change") {
29261
+ setBusy(dash.busy);
29262
+ return;
29263
+ }
29264
+ if (dash.kind === "user") {
29265
+ setMessages((prev) => [...prev, { id: dash.id, role: "user", text: dash.text }]);
29266
+ return;
29267
+ }
29268
+ if (dash.kind === "assistant_delta") {
29269
+ setStreaming((cur) => {
29270
+ const text = (cur?.text ?? "") + (dash.contentDelta ?? "");
29271
+ const reasoning = (cur?.reasoning ?? "") + (dash.reasoningDelta ?? "");
29272
+ return { id: dash.id, text, reasoning };
29273
+ });
29274
+ return;
29275
+ }
29276
+ if (dash.kind === "assistant_final") {
29277
+ setStreaming(null);
29278
+ setMessages((prev) => [
29279
+ ...prev,
29280
+ { id: dash.id, role: "assistant", text: dash.text, reasoning: dash.reasoning }
29281
+ ]);
29282
+ return;
29283
+ }
29284
+ if (dash.kind === "tool_start") {
29285
+ setActiveTool({ id: dash.id, toolName: dash.toolName, args: dash.args });
29286
+ return;
29287
+ }
29288
+ if (dash.kind === "tool") {
29289
+ setActiveTool((cur) => cur && cur.id === dash.id ? null : cur);
29290
+ setMessages((prev) => [
29291
+ ...prev,
29292
+ { id: dash.id, role: "tool", text: dash.content, toolName: dash.toolName, toolArgs: dash.args }
29293
+ ]);
29294
+ return;
29295
+ }
29296
+ if (dash.kind === "warning" || dash.kind === "error" || dash.kind === "info") {
29297
+ if (dash.kind === "error") setActiveTool(null);
29298
+ setMessages((prev) => [...prev, { id: dash.id, role: dash.kind, text: dash.text }]);
29299
+ return;
29300
+ }
29301
+ if (dash.kind === "status") {
29302
+ setStatusLine(dash.text);
29303
+ setTimeout(() => setStatusLine((cur) => cur === dash.text ? null : cur), 5e3);
29304
+ return;
29305
+ }
29306
+ };
29307
+ es.onerror = () => {
29308
+ setError(t4("chat.eventStreamError"));
29309
+ setTimeout(() => setError(null), 3e3);
29310
+ };
29311
+ return () => es.close();
29312
+ }, []);
29313
+ y2(() => {
29314
+ if (!shouldAutoScroll.current) return;
29315
+ const el = feedRef.current;
29316
+ if (!el) return;
29317
+ el.scrollTop = el.scrollHeight;
29318
+ }, [messages, streaming]);
29319
+ y2(() => {
29320
+ const el = feedRef.current;
29321
+ if (!el) return;
29322
+ const onScroll = () => {
29323
+ const distFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
29324
+ shouldAutoScroll.current = distFromBottom < 80;
29325
+ };
29326
+ el.addEventListener("scroll", onScroll, { passive: true });
29327
+ return () => el.removeEventListener("scroll", onScroll);
29328
+ }, []);
29329
+ const updatePopover = q2(
29330
+ async (text) => {
29331
+ const slashMatch = /^\/([A-Za-z0-9_-]*)$/.exec(text);
29332
+ if (slashMatch) {
29333
+ const prefix = slashMatch[1].toLowerCase();
29334
+ const items = slashCommands.filter((c3) => c3.cmd.startsWith(prefix)).slice(0, 12).map((c3) => ({
29335
+ label: `/${c3.cmd}`,
29336
+ meta: c3.summary,
29337
+ insert: `/${c3.cmd}${c3.argsHint ? " " : ""}`
29338
+ }));
29339
+ setPopoverKind("slash");
29340
+ setPopoverItems(items);
29341
+ setPopoverSel(0);
29342
+ return;
29343
+ }
29344
+ setPopoverKind(null);
29345
+ },
29346
+ [slashCommands]
29347
+ );
29348
+ const applyPopover = q2(() => {
29349
+ const item = popoverItems[popoverSel];
29350
+ if (!item) return false;
29351
+ setInput(item.insert);
29352
+ setPopoverKind(null);
29353
+ return true;
29354
+ }, [popoverItems, popoverSel, popoverKind, input]);
29355
+ const onInput = q2(
29356
+ (e3) => {
29357
+ const v3 = e3.target.value;
29358
+ setInput(v3);
29359
+ updatePopover(v3);
29360
+ },
29361
+ [updatePopover]
29362
+ );
29363
+ const send = q2(async () => {
29364
+ const text = input.trim();
29365
+ if (busy) return;
29366
+ if (!text && props.comments.length === 0) return;
29367
+ setError(null);
29368
+ let prompt = text;
29369
+ if (props.comments.length > 0) {
29370
+ const commentRefs = props.comments.map((c3) => `\u{1F4DD} ${c3.file}:${c3.lineNumber} ${c3.content}`).join("\n");
29371
+ prompt = text ? `${text}
29372
+
29373
+ ${commentRefs}` : commentRefs;
29374
+ }
29375
+ try {
29376
+ const res = await api("/submit", {
29377
+ method: "POST",
29378
+ body: { prompt }
29379
+ });
29380
+ if (!res.accepted) {
29381
+ setError(res.reason ?? "rejected");
29382
+ return;
29383
+ }
29384
+ setInput("");
29385
+ props.comments.forEach((c3) => props.deleteComment(c3.id));
29386
+ } catch (err) {
29387
+ setError(err.message);
29388
+ }
29389
+ }, [input, busy, props.comments]);
29390
+ const abort = q2(async () => {
29391
+ try {
29392
+ await api("/abort", { method: "POST" });
29393
+ } catch {
29394
+ }
29395
+ }, []);
29396
+ const newConversation = q2(async () => {
29397
+ if (busy) {
29398
+ if (!confirm(t4("changes.newConfirmBusy"))) return;
29399
+ } else if (messages.length > 0 && !confirm(t4("changes.newConfirm"))) {
29400
+ return;
29401
+ }
29402
+ try {
29403
+ await api("/submit", { method: "POST", body: { prompt: "/new" } });
29404
+ setMessages([]);
29405
+ setStreaming(null);
29406
+ setActiveTool(null);
29407
+ showToast(t4("changes.newToast"), "info");
29408
+ setTimeout(async () => {
29409
+ try {
29410
+ const r3 = await api("/messages");
29411
+ setMessages(r3.messages ?? []);
29412
+ } catch {
29413
+ }
29414
+ }, 200);
29415
+ } catch (err) {
29416
+ setError(t4("changes.newFailed", { error: err.message }));
29417
+ }
29418
+ }, [busy, messages.length]);
29419
+ const clearScrollback = q2(async () => {
29420
+ try {
29421
+ await api("/submit", { method: "POST", body: { prompt: "/clear" } });
29422
+ setMessages([]);
29423
+ setStreaming(null);
29424
+ setActiveTool(null);
29425
+ showToast(t4("changes.clearToast"), "info");
29426
+ setTimeout(async () => {
29427
+ try {
29428
+ const r3 = await api("/messages");
29429
+ setMessages(r3.messages ?? []);
29430
+ } catch {
29431
+ }
29432
+ }, 200);
29433
+ } catch (err) {
29434
+ setError(t4("changes.clearFailed", { error: err.message }));
29435
+ }
29436
+ }, []);
29437
+ const onKeyDown = q2((e3) => {
29438
+ if (popoverKind && popoverItems.length > 0) {
29439
+ if (e3.key === "ArrowDown") {
29440
+ e3.preventDefault();
29441
+ setPopoverSel((i3) => (i3 + 1) % popoverItems.length);
29442
+ return;
29443
+ }
29444
+ if (e3.key === "ArrowUp") {
29445
+ e3.preventDefault();
29446
+ setPopoverSel((i3) => (i3 - 1 + popoverItems.length) % popoverItems.length);
29447
+ return;
29448
+ }
29449
+ if (e3.key === "Tab" || e3.key === "Enter" && !e3.shiftKey) {
29450
+ e3.preventDefault();
29451
+ if (applyPopover() && e3.key === "Enter" && popoverKind === "slash") send();
29452
+ return;
29453
+ }
29454
+ if (e3.key === "Escape") {
29455
+ e3.preventDefault();
29456
+ setPopoverKind(null);
29457
+ return;
29458
+ }
29459
+ }
29460
+ if (e3.key === "Escape" && busy) {
29461
+ e3.preventDefault();
29462
+ abort();
29463
+ return;
29464
+ }
29465
+ if (e3.key === "Enter" && !e3.shiftKey) {
29466
+ e3.preventDefault();
29467
+ send();
29468
+ }
29469
+ }, [send, abort, busy, popoverKind, popoverItems, applyPopover]);
29470
+ const allMessages = streaming ? [...messages, { id: streaming.id, role: "assistant", text: streaming.text, reasoning: streaming.reasoning }] : messages;
29471
+ return html6`
29472
+ <div style=${{ display: "flex", flexDirection: "column", height: "100%" }}>
29473
+ ${statusLine ? html6`<div class="changes-panel-header"><span>${statusLine}</span></div>` : null}
29474
+ <div class="chat-feed" style=${{ flex: 1, overflowY: "auto", padding: "8px" }} ref=${feedRef}>
29475
+ ${allMessages.length === 0 && !streaming ? html6`<div class="empty" style=${{ margin: "12px", textAlign: "center" }}>${t4("changes.chatWelcome")}</div>` : null}
29476
+ ${allMessages.map((msg) => {
29477
+ const isStreaming = streaming && msg.id === streaming.id;
29478
+ if (msg.role === "tool") {
29479
+ return html6`
29480
+ <div class="chat-msg tool" key=${msg.id}>
29481
+ <div class="glyph">▣</div>
29482
+ <${ToolCard} msg=${msg} />
29483
+ </div>
29484
+ `;
29485
+ }
29486
+ return html6`
29487
+ <${ChatMessage}
29488
+ key=${msg.id}
29489
+ msg=${{ id: msg.id, role: msg.role, text: msg.text, reasoning: msg.reasoning, toolName: msg.toolName, toolArgs: msg.toolArgs }}
29490
+ streaming=${Boolean(isStreaming)}
29491
+ />
29492
+ `;
29493
+ })}
29494
+ </div>
29495
+ ${error ? html6`<div class="notice err" style=${{ margin: "0 8px 4px" }}>${error}</div>` : null}
29496
+ <div style=${{ padding: "8px", borderTop: "1px solid var(--bd)", flexShrink: 0 }}>
29497
+ ${props.comments.length > 0 ? html6`
29498
+ <div class="comment-cards-container" style=${{ display: "flex", flexWrap: "wrap", gap: "6px", marginBottom: "8px" }}>
29499
+ ${props.comments.map((comment) => html6`
29500
+ <${CommentCard}
29501
+ key=${comment.id}
29502
+ fileName=${comment.file}
29503
+ lineNumber=${comment.lineNumber}
29504
+ content=${comment.content}
29505
+ onRemove=${() => props.deleteComment(comment.id)}
29506
+ />
29507
+ `)}
29508
+ </div>
29509
+ ` : null}
29510
+ <div style=${{ display: "flex", gap: "8px", alignItems: "flex-end", position: "relative" }}>
29511
+ <button style=${{ background: "transparent", border: "none", color: "var(--fg-3)", padding: "8px", cursor: "pointer", fontSize: "18px", flexShrink: 0 }}>+</button>
29512
+ <div style=${{ flex: 1, position: "relative" }}>
29513
+ ${popoverKind && popoverItems.length > 0 ? html6`
29514
+ <div class="popover" style="position:absolute;bottom:calc(100% + 6px);left:0;width:380px;max-height:280px;overflow-y:auto;z-index:10">
29515
+ <div class="popover-h">${t4("chat.slashCommands")}</div>
29516
+ ${popoverItems.map(
29517
+ (it, i3) => html6`
29518
+ <div
29519
+ class=${`popover-row ${i3 === popoverSel ? "sel" : ""}`}
29520
+ onMouseDown=${(e3) => {
29521
+ e3.preventDefault();
29522
+ setPopoverSel(i3);
29523
+ applyPopover();
29524
+ }}
29525
+ >
29526
+ <span class="g">/</span>
29527
+ <span class="name">${it.label}</span>
29528
+ ${it.meta ? html6`<span class="meta">${it.meta}</span>` : null}
29529
+ </div>
29530
+ `
29531
+ )}
29532
+ </div>
29533
+ ` : null}
29534
+ <textarea
29535
+ class="input"
29536
+ style=${{ width: "100%", resize: "none", minHeight: "36px", fontFamily: "inherit", fontSize: "13px", padding: "8px 10px", lineHeight: "1.4", background: "var(--bg-input)", border: "1px solid var(--bd)", borderRadius: "4px", color: "var(--fg-0)" }}
29537
+ placeholder=${props.comments.length > 0 ? "\u603B\u7ED3\u8BC4\u8BBA..." : t4("changes.chatPlaceholder")}
29538
+ value=${input}
29539
+ onInput=${onInput}
29540
+ onKeyDown=${onKeyDown}
29541
+ onBlur=${() => setTimeout(() => setPopoverKind(null), 150)}
29542
+ rows="2"
29543
+ />
29544
+ </div>
29545
+ <div style=${{ display: "flex", flexDirection: "column", gap: "6px", flexShrink: 0 }}>
29546
+ <button class="primary" onClick=${send} disabled=${busy || !input.trim() && props.comments.length === 0} style=${{ padding: "8px 12px", borderRadius: "4px" }}>${t4("changes.chatSend")}</button>
29547
+ <div style=${{ display: "flex", gap: "6px" }}>
29548
+ <button onClick=${newConversation} title=${t4("changes.newTitle")}>${t4("changes.newConversation")}</button>
29549
+ <button onClick=${clearScrollback} title=${t4("changes.clearTitle")}>${t4("changes.clearConversation")}</button>
29550
+ </div>
29551
+ </div>
29552
+ </div>
29553
+ </div>
29554
+ <${ChatStatusBar2} stats=${stats} model=${model} />
29555
+ </div>
29556
+ `;
29557
+ }
29558
+
29559
+ // dashboard/app.js
29560
+ var html7 = htm_module_default.bind(k);
29561
+ function tabSections() {
29562
+ return [
29563
+ {
29564
+ label: t4("app.sectionWorkspace"),
29565
+ tabs: [
29566
+ { id: "chat", name: t4("app.tabChat"), glyph: "\u25C6", panel: () => html7`<${ChatPanel} />` },
29567
+ { id: "plans", name: t4("app.tabPlans"), glyph: "\u229E", panel: () => html7`<${PlansPanel} />` },
29568
+ { id: "sessions", name: t4("app.tabSessions"), glyph: "\u203A", panel: () => html7`<${SessionsPanel} />` }
29569
+ ]
29570
+ },
29571
+ {
29572
+ label: t4("app.sectionChanges"),
27776
29573
  tabs: [
27777
- { id: "chat", name: t4("app.tabChat"), glyph: "\u25C6", panel: () => html5`<${ChatPanel} />` },
27778
- { id: "plans", name: t4("app.tabPlans"), glyph: "\u229E", panel: () => html5`<${PlansPanel} />` },
27779
- { id: "sessions", name: t4("app.tabSessions"), glyph: "\u203A", panel: () => html5`<${SessionsPanel} />` }
29574
+ { id: "changes", name: t4("app.tabChanges"), glyph: "\u25A8", panel: () => html7`<${ChangesPanel} />` }
27780
29575
  ]
27781
29576
  },
27782
29577
  {
27783
29578
  label: t4("app.sectionObserve"),
27784
29579
  tabs: [
27785
- { id: "overview", name: t4("app.tabOverview"), glyph: "\u25C8", panel: () => html5`<${OverviewPanel} />` },
27786
- { id: "usage", name: t4("app.tabUsage"), glyph: "$", panel: () => html5`<${UsagePanel} />` },
27787
- { id: "health", name: t4("app.tabSystem"), glyph: "+", panel: () => html5`<${SystemPanel} />` },
27788
- { id: "semantic", name: t4("app.tabSemantic"), glyph: "\u2248", panel: () => html5`<${SemanticPanel} />` }
29580
+ { id: "overview", name: t4("app.tabOverview"), glyph: "\u25C8", panel: () => html7`<${OverviewPanel} />` },
29581
+ { id: "usage", name: t4("app.tabUsage"), glyph: "$", panel: () => html7`<${UsagePanel} />` },
29582
+ { id: "health", name: t4("app.tabSystem"), glyph: "+", panel: () => html7`<${SystemPanel} />` },
29583
+ { id: "semantic", name: t4("app.tabSemantic"), glyph: "\u2248", panel: () => html7`<${SemanticPanel} />` }
27789
29584
  ]
27790
29585
  },
27791
29586
  {
27792
29587
  label: t4("app.sectionConfigure"),
27793
29588
  tabs: [
27794
- { id: "tools", name: t4("app.tabTools"), glyph: "\u25A3", panel: () => html5`<${ToolsPanel} />` },
27795
- { id: "permissions", name: t4("app.tabPermissions"), glyph: "\u258E", panel: () => html5`<${PermissionsPanel} />` },
27796
- { id: "mcp", name: t4("app.tabMcp"), glyph: "M", panel: () => html5`<${McpPanel} />` },
27797
- { id: "skills", name: t4("app.tabSkills"), glyph: "S", panel: () => html5`<${SkillsPanel} />` },
27798
- { id: "memory", name: t4("app.tabMemory"), glyph: "\xB7", panel: () => html5`<${MemoryPanel} />` },
27799
- { id: "hooks", name: t4("app.tabHooks"), glyph: "H", panel: () => html5`<${HooksPanel} />` },
27800
- { id: "settings", name: t4("app.tabSettings"), glyph: "\u2318", panel: () => html5`<${SettingsPanel} />` }
29589
+ { id: "tools", name: t4("app.tabTools"), glyph: "\u25A3", panel: () => html7`<${ToolsPanel} />` },
29590
+ { id: "permissions", name: t4("app.tabPermissions"), glyph: "\u258E", panel: () => html7`<${PermissionsPanel} />` },
29591
+ { id: "mcp", name: t4("app.tabMcp"), glyph: "M", panel: () => html7`<${McpPanel} />` },
29592
+ { id: "skills", name: t4("app.tabSkills"), glyph: "S", panel: () => html7`<${SkillsPanel} />` },
29593
+ { id: "memory", name: t4("app.tabMemory"), glyph: "\xB7", panel: () => html7`<${MemoryPanel} />` },
29594
+ { id: "hooks", name: t4("app.tabHooks"), glyph: "H", panel: () => html7`<${HooksPanel} />` },
29595
+ { id: "settings", name: t4("app.tabSettings"), glyph: "\u2318", panel: () => html7`<${SettingsPanel} />` }
27801
29596
  ]
27802
29597
  }
27803
29598
  ];
@@ -27848,7 +29643,7 @@ function App() {
27848
29643
  return () => appBus.removeEventListener("navigate-tab", onNav);
27849
29644
  }, []);
27850
29645
  const pickTab = q2((id) => setActiveId(id), []);
27851
- return html5`
29646
+ return html7`
27852
29647
  <div class=${`app ${sidebarCollapsed ? "collapsed" : ""}`}>
27853
29648
  <aside class="app-side">
27854
29649
  <div class="brand">
@@ -27858,10 +29653,10 @@ function App() {
27858
29653
  </div>
27859
29654
  <div class="side-tabs">
27860
29655
  ${TAB_SECTIONS.map(
27861
- (section) => html5`
29656
+ (section) => html7`
27862
29657
  <div class="side-section">${section.label}</div>
27863
29658
  ${section.tabs.map(
27864
- (tab) => html5`
29659
+ (tab) => html7`
27865
29660
  <div
27866
29661
  class=${`side-tab ${tab.id === active.id ? "active" : ""}`}
27867
29662
  onClick=${() => pickTab(tab.id)}
@@ -27904,5 +29699,5 @@ function App() {
27904
29699
  <${ErrorOverlay} />
27905
29700
  `;
27906
29701
  }
27907
- R(html5`<${App} />`, document.getElementById("root"));
29702
+ R(html7`<${App} />`, document.getElementById("root"));
27908
29703
  //# sourceMappingURL=app.js.map