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.
- package/dashboard/app.css +580 -0
- package/dashboard/dist/app.js +1827 -32
- package/dashboard/dist/app.js.map +1 -1
- package/dist/cli/chat-G7CUW4ZI.js +45 -0
- package/dist/cli/{chunk-TPK2CHWR.js → chunk-26UDIXLD.js} +1222 -1209
- package/dist/cli/chunk-26UDIXLD.js.map +1 -0
- package/dist/cli/{chunk-SUZRC4NC.js → chunk-4X3NY5ZM.js} +2 -2
- package/dist/cli/{chunk-5ZCRXN7S.js → chunk-4YV2GBYG.js} +2237 -2211
- package/dist/cli/chunk-4YV2GBYG.js.map +1 -0
- package/dist/cli/{chunk-6NMWJSES.js → chunk-5GKJLNP2.js} +2 -2
- package/dist/cli/{chunk-6DR4F3MC.js → chunk-7DLHHBGN.js} +49 -18
- package/dist/cli/chunk-7DLHHBGN.js.map +1 -0
- package/dist/cli/{chunk-NLV2YORE.js → chunk-A5LSGEEK.js} +43 -3
- package/dist/cli/chunk-A5LSGEEK.js.map +1 -0
- package/dist/cli/{code-3BBVXXY6.js → chunk-AVB3WZWU.js} +51 -132
- package/dist/cli/chunk-AVB3WZWU.js.map +1 -0
- package/dist/cli/{chunk-4D662BWT.js → chunk-CLAN6PVH.js} +4 -95
- package/dist/cli/chunk-CLAN6PVH.js.map +1 -0
- package/dist/cli/chunk-CPTZ5OHX.js +18 -0
- package/dist/cli/chunk-CPTZ5OHX.js.map +1 -0
- package/dist/cli/{chunk-SWLIVNTP.js → chunk-CZSJILQP.js} +84 -1
- package/dist/cli/chunk-CZSJILQP.js.map +1 -0
- package/dist/cli/{chunk-MHDNZXJJ.js → chunk-E46ECXJD.js} +7 -2
- package/dist/cli/{chunk-MHDNZXJJ.js.map → chunk-E46ECXJD.js.map} +1 -1
- package/dist/cli/{chunk-AKDDHHE6.js → chunk-FFNOMR32.js} +2 -2
- package/dist/cli/{chunk-AJGLCSZS.js → chunk-H7PHYVPM.js} +138 -40
- package/dist/cli/chunk-H7PHYVPM.js.map +1 -0
- package/dist/cli/{chunk-SJNIIH5W.js → chunk-HCC42PEI.js} +5 -1
- package/dist/cli/{chunk-SJNIIH5W.js.map → chunk-HCC42PEI.js.map} +1 -1
- package/dist/cli/chunk-IYF36OCJ.js +45 -0
- package/dist/cli/chunk-IYF36OCJ.js.map +1 -0
- package/dist/cli/{chunk-BQR5TTNY.js → chunk-JWCTX5S4.js} +2 -2
- package/dist/cli/{chunk-DDA76P44.js → chunk-R4YTW7PR.js} +5 -21
- package/dist/cli/chunk-R4YTW7PR.js.map +1 -0
- package/dist/cli/{chunk-7G3SESEU.js → chunk-RFX7TYVV.js} +2 -15
- package/dist/cli/chunk-RFX7TYVV.js.map +1 -0
- package/dist/cli/{chunk-NTVW2TWO.js → chunk-SZH34P45.js} +186 -12
- package/dist/cli/chunk-SZH34P45.js.map +1 -0
- package/dist/cli/chunk-UCMTWZKU.js +100 -0
- package/dist/cli/chunk-UCMTWZKU.js.map +1 -0
- package/dist/cli/{chunk-V5D77TFD.js → chunk-ULBW7DYL.js} +2 -2
- package/dist/cli/{chunk-TPDWAMG6.js → chunk-UVRXTSK3.js} +195 -20
- package/dist/cli/chunk-UVRXTSK3.js.map +1 -0
- package/dist/cli/chunk-VLNRQMCI.js +413 -0
- package/dist/cli/chunk-VLNRQMCI.js.map +1 -0
- package/dist/cli/{chunk-TGO7X47P.js → chunk-WKOMCPXP.js} +9 -7
- package/dist/cli/{chunk-TGO7X47P.js.map → chunk-WKOMCPXP.js.map} +1 -1
- package/dist/cli/{chunk-6CXT5JRM.js → chunk-XST7BSZJ.js} +2 -1
- package/dist/cli/{chunk-6CXT5JRM.js.map → chunk-XST7BSZJ.js.map} +1 -1
- package/dist/cli/code-YQGVLIT2.js +147 -0
- package/dist/cli/code-YQGVLIT2.js.map +1 -0
- package/dist/cli/{commands-PJMHSP3Z.js → commands-FQZOBLLZ.js} +6 -4
- package/dist/cli/{commands-PJMHSP3Z.js.map → commands-FQZOBLLZ.js.map} +1 -1
- package/dist/cli/{commit-R6SC44W5.js → commit-ZS24SHPG.js} +2 -2
- package/dist/cli/desktop-6OLENOOO.js +807 -0
- package/dist/cli/desktop-6OLENOOO.js.map +1 -0
- package/dist/cli/{diff-LXBBKOZA.js → diff-2VUKNGEI.js} +4 -4
- package/dist/cli/{doctor-ZBUEBRXP.js → doctor-JO2WNN6C.js} +8 -7
- package/dist/cli/{events-SQXPVV7B.js → events-APSVNROZ.js} +3 -3
- package/dist/cli/index.js +93 -33
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{mcp-RABKZDX4.js → mcp-DCKOE5RF.js} +2 -2
- package/dist/cli/{mcp-browse-H6O73SHN.js → mcp-browse-D6GBP5RQ.js} +2 -2
- package/dist/cli/{mcp-inspect-XWBO52H6.js → mcp-inspect-KFGFPJ3E.js} +42 -5
- package/dist/cli/mcp-inspect-KFGFPJ3E.js.map +1 -0
- package/dist/cli/{prompt-CZSOFYK6.js → prompt-PKCCLLAD.js} +3 -3
- package/dist/cli/{prune-sessions-FCFOYCBP.js → prune-sessions-LV33R47N.js} +2 -2
- package/dist/cli/{replay-TWTUIUUB.js → replay-WFCYX7XF.js} +5 -5
- package/dist/cli/{run-RWBLIICY.js → run-IUJYEPMT.js} +18 -15
- package/dist/cli/run-IUJYEPMT.js.map +1 -0
- package/dist/cli/{server-EPU4QONU.js → server-CN4QPPVJ.js} +531 -88
- package/dist/cli/server-CN4QPPVJ.js.map +1 -0
- package/dist/cli/{sessions-TWUFHOUX.js → sessions-F5GPGTJN.js} +10 -10
- package/dist/cli/{setup-WHXXHIZV.js → setup-WWMDBPSB.js} +6 -6
- package/dist/cli/{version-RAMBOIYL.js → version-KQUPV6T5.js} +10 -10
- package/dist/index.d.ts +43 -0
- package/dist/index.js +440 -127
- package/dist/index.js.map +1 -1
- package/package.json +1 -3
- package/dist/cli/chat-QCY6CH7O.js +0 -42
- package/dist/cli/chunk-4D662BWT.js.map +0 -1
- package/dist/cli/chunk-5ZCRXN7S.js.map +0 -1
- package/dist/cli/chunk-6DR4F3MC.js.map +0 -1
- package/dist/cli/chunk-7G3SESEU.js.map +0 -1
- package/dist/cli/chunk-AJGLCSZS.js.map +0 -1
- package/dist/cli/chunk-BQNUJJN7.js +0 -42
- package/dist/cli/chunk-BQNUJJN7.js.map +0 -1
- package/dist/cli/chunk-DDA76P44.js.map +0 -1
- package/dist/cli/chunk-NLV2YORE.js.map +0 -1
- package/dist/cli/chunk-NTVW2TWO.js.map +0 -1
- package/dist/cli/chunk-SWLIVNTP.js.map +0 -1
- package/dist/cli/chunk-TPDWAMG6.js.map +0 -1
- package/dist/cli/chunk-TPK2CHWR.js.map +0 -1
- package/dist/cli/code-3BBVXXY6.js.map +0 -1
- package/dist/cli/mcp-inspect-XWBO52H6.js.map +0 -1
- package/dist/cli/run-RWBLIICY.js.map +0 -1
- package/dist/cli/server-EPU4QONU.js.map +0 -1
- /package/dist/cli/{chat-QCY6CH7O.js.map → chat-G7CUW4ZI.js.map} +0 -0
- /package/dist/cli/{chunk-SUZRC4NC.js.map → chunk-4X3NY5ZM.js.map} +0 -0
- /package/dist/cli/{chunk-6NMWJSES.js.map → chunk-5GKJLNP2.js.map} +0 -0
- /package/dist/cli/{chunk-AKDDHHE6.js.map → chunk-FFNOMR32.js.map} +0 -0
- /package/dist/cli/{chunk-BQR5TTNY.js.map → chunk-JWCTX5S4.js.map} +0 -0
- /package/dist/cli/{chunk-V5D77TFD.js.map → chunk-ULBW7DYL.js.map} +0 -0
- /package/dist/cli/{commit-R6SC44W5.js.map → commit-ZS24SHPG.js.map} +0 -0
- /package/dist/cli/{diff-LXBBKOZA.js.map → diff-2VUKNGEI.js.map} +0 -0
- /package/dist/cli/{doctor-ZBUEBRXP.js.map → doctor-JO2WNN6C.js.map} +0 -0
- /package/dist/cli/{events-SQXPVV7B.js.map → events-APSVNROZ.js.map} +0 -0
- /package/dist/cli/{mcp-RABKZDX4.js.map → mcp-DCKOE5RF.js.map} +0 -0
- /package/dist/cli/{mcp-browse-H6O73SHN.js.map → mcp-browse-D6GBP5RQ.js.map} +0 -0
- /package/dist/cli/{prompt-CZSOFYK6.js.map → prompt-PKCCLLAD.js.map} +0 -0
- /package/dist/cli/{prune-sessions-FCFOYCBP.js.map → prune-sessions-LV33R47N.js.map} +0 -0
- /package/dist/cli/{replay-TWTUIUUB.js.map → replay-WFCYX7XF.js.map} +0 -0
- /package/dist/cli/{sessions-TWUFHOUX.js.map → sessions-F5GPGTJN.js.map} +0 -0
- /package/dist/cli/{setup-WHXXHIZV.js.map → setup-WWMDBPSB.js.map} +0 -0
- /package/dist/cli/{version-RAMBOIYL.js.map → version-KQUPV6T5.js.map} +0 -0
package/dashboard/dist/app.js
CHANGED
|
@@ -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,
|
|
925
|
+
constructor(reason, html8) {
|
|
926
926
|
super(reason);
|
|
927
927
|
this.name = "HTMLInjectionError";
|
|
928
|
-
this.html =
|
|
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 =
|
|
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) ||
|
|
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 =
|
|
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(
|
|
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 (
|
|
1346
|
+
if (getLanguage2(a3.language).supersetOf === b2.language) {
|
|
1347
1347
|
return 1;
|
|
1348
|
-
} else if (
|
|
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
|
|
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 =
|
|
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/
|
|
27862
|
+
// dashboard/src/lib/file-tree.ts
|
|
27771
27863
|
var html5 = htm_module_default.bind(k);
|
|
27772
|
-
|
|
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
|
-
|
|
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) => ({ '"': """, "&": "&", "<": "<", ">": ">" })[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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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: "
|
|
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: () =>
|
|
27786
|
-
{ id: "usage", name: t4("app.tabUsage"), glyph: "$", panel: () =>
|
|
27787
|
-
{ id: "health", name: t4("app.tabSystem"), glyph: "+", panel: () =>
|
|
27788
|
-
{ id: "semantic", name: t4("app.tabSemantic"), glyph: "\u2248", panel: () =>
|
|
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: () =>
|
|
27795
|
-
{ id: "permissions", name: t4("app.tabPermissions"), glyph: "\u258E", panel: () =>
|
|
27796
|
-
{ id: "mcp", name: t4("app.tabMcp"), glyph: "M", panel: () =>
|
|
27797
|
-
{ id: "skills", name: t4("app.tabSkills"), glyph: "S", panel: () =>
|
|
27798
|
-
{ id: "memory", name: t4("app.tabMemory"), glyph: "\xB7", panel: () =>
|
|
27799
|
-
{ id: "hooks", name: t4("app.tabHooks"), glyph: "H", panel: () =>
|
|
27800
|
-
{ id: "settings", name: t4("app.tabSettings"), glyph: "\u2318", panel: () =>
|
|
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
|
|
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) =>
|
|
29656
|
+
(section) => html7`
|
|
27862
29657
|
<div class="side-section">${section.label}</div>
|
|
27863
29658
|
${section.tabs.map(
|
|
27864
|
-
(tab) =>
|
|
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(
|
|
29702
|
+
R(html7`<${App} />`, document.getElementById("root"));
|
|
27908
29703
|
//# sourceMappingURL=app.js.map
|