reasonix 0.30.5 → 0.32.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/README.md +17 -7
- package/README.zh-CN.md +16 -6
- package/dashboard/app.css +149 -0
- package/dashboard/dist/app.js +1039 -88
- package/dashboard/dist/app.js.map +1 -1
- package/dist/cli/index.js +3743 -3115
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +43 -97
- package/dist/index.js +1927 -737
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dashboard/dist/app.js
CHANGED
|
@@ -19264,8 +19264,36 @@ var en = {
|
|
|
19264
19264
|
effortHigh: "high (cheaper / faster)",
|
|
19265
19265
|
webSearch: "web search",
|
|
19266
19266
|
webSearchNote: "web_fetch + web_search tools",
|
|
19267
|
+
sectionCompute: "Compute",
|
|
19268
|
+
proNext: "/pro one-shot",
|
|
19269
|
+
proArm: "Arm for next turn",
|
|
19270
|
+
proArmed: "Armed \u2014 disarms after next turn",
|
|
19271
|
+
proNextNote: "next turn runs on deepseek-v4-pro, then auto-disarms",
|
|
19272
|
+
sectionBudget: "Budget",
|
|
19273
|
+
budgetOf: "of",
|
|
19274
|
+
budgetSetCap: "set a cap",
|
|
19275
|
+
budgetCustom: "custom",
|
|
19276
|
+
budgetBumpHint: "bump the cap to keep going",
|
|
19277
|
+
budgetClear: "Clear cap",
|
|
19278
|
+
budgetIdleLine: "warns at 80% \xB7 refuses past 100%",
|
|
19279
|
+
budgetWarnLine: "approaching cap \u2014 loop will refuse past 100%",
|
|
19280
|
+
budgetRefusing: "cap exhausted \u2014 next turn refused until bumped or cleared",
|
|
19281
|
+
sectionLoop: "Loop",
|
|
19282
|
+
loopIdleHint: "Auto-resubmit a prompt on a fixed interval.",
|
|
19283
|
+
loopCostHint: "Each iteration costs ~{cost} (last turn).",
|
|
19284
|
+
loopInterval: "interval",
|
|
19285
|
+
loopCustom: "custom",
|
|
19286
|
+
loopRangeError: "interval must fall in 5s..6h",
|
|
19287
|
+
loopPrompt: "prompt",
|
|
19288
|
+
loopPromptPlaceholder: "e.g. check the deploy status and report any errors",
|
|
19289
|
+
loopStart: "Start loop",
|
|
19290
|
+
loopStop: "Stop",
|
|
19291
|
+
loopRunning: "running",
|
|
19292
|
+
loopIter: "iter {iter}",
|
|
19293
|
+
loopFiresIn: "fires in {remaining}",
|
|
19267
19294
|
sectionRuntime: "Runtime",
|
|
19268
19295
|
activeModel: "active model",
|
|
19296
|
+
modelPricingLine: "${hit} hit \xB7 ${miss} miss \xB7 ${out} out per 1M tok",
|
|
19269
19297
|
editMode: "edit mode",
|
|
19270
19298
|
editModeNote: "switch from the Chat tab header",
|
|
19271
19299
|
sectionLanguage: "Language",
|
|
@@ -19348,6 +19376,7 @@ var en = {
|
|
|
19348
19376
|
tokens7d: "tokens \xB7 7d",
|
|
19349
19377
|
cacheHit: "cache hit",
|
|
19350
19378
|
toolCalls24h: "tool calls \xB7 24h",
|
|
19379
|
+
budget: "budget",
|
|
19351
19380
|
currentSession: "current session",
|
|
19352
19381
|
noSession: "No live session \u2014 /dashboard from inside reasonix code to attach.",
|
|
19353
19382
|
promptTok: "prompt tok",
|
|
@@ -19578,6 +19607,7 @@ var en = {
|
|
|
19578
19607
|
filterPlaceholder: "filter plans",
|
|
19579
19608
|
active: "active",
|
|
19580
19609
|
done: "done",
|
|
19610
|
+
idle: "idle",
|
|
19581
19611
|
steps: "steps",
|
|
19582
19612
|
pickHint: "Pick a plan on the left.",
|
|
19583
19613
|
noTitle: "(no title)",
|
|
@@ -19607,16 +19637,34 @@ var en = {
|
|
|
19607
19637
|
pullModel: "Pull {model}",
|
|
19608
19638
|
indexStatus: "index status",
|
|
19609
19639
|
builtStatus: "\u25CF built",
|
|
19640
|
+
incompatibleStatus: "\u25CF incompatible",
|
|
19610
19641
|
chunks: "chunks",
|
|
19611
19642
|
files: "files",
|
|
19612
19643
|
dim: "dim",
|
|
19613
19644
|
size: "size",
|
|
19614
19645
|
lastBuild: "last build",
|
|
19646
|
+
builtWith: "built with",
|
|
19647
|
+
currentTarget: "current target",
|
|
19648
|
+
incompatibleHint: "This on-disk index was built for a different provider or model. Run Rebuild to replace it.",
|
|
19615
19649
|
runIndexHint: "Run an index to enable semantic_search.",
|
|
19616
19650
|
reIndex: "Re-index",
|
|
19617
19651
|
build: "Build",
|
|
19618
19652
|
rebuild: "Rebuild",
|
|
19619
19653
|
stop: "Stop",
|
|
19654
|
+
provider: "provider",
|
|
19655
|
+
providerType: "service type",
|
|
19656
|
+
openaiCompat: "openai-compatible",
|
|
19657
|
+
apiUrl: "API URL",
|
|
19658
|
+
apiKey: "API key",
|
|
19659
|
+
apiKeyStoredNote: "API key is stored in ~/.reasonix/config.json \u2014 do not share that file.",
|
|
19660
|
+
customRequestBody: "custom request body",
|
|
19661
|
+
invalidCustomRequestBody: "Custom request body must be valid JSON: {error}",
|
|
19662
|
+
customRequestBodyMustBeObject: "Custom request body must be a JSON object.",
|
|
19663
|
+
saveBeforeIndex: "Save semantic settings before starting an index.",
|
|
19664
|
+
extraBody: "extra body",
|
|
19665
|
+
keepExistingKey: "leave blank to keep existing key",
|
|
19666
|
+
remoteProvider: "Remote embedding provider",
|
|
19667
|
+
remoteProviderDesc: "Configure the full OpenAI-compatible embeddings URL here. Reasonix will send requests exactly to the URL you provide.",
|
|
19620
19668
|
ollama: "ollama",
|
|
19621
19669
|
binary: "binary",
|
|
19622
19670
|
found: "found",
|
|
@@ -19644,11 +19692,14 @@ var en = {
|
|
|
19644
19692
|
nothingSkipped: "nothing skipped \u2014 all walked files would be indexed.",
|
|
19645
19693
|
firstIncluded: "first {count} included file(s)",
|
|
19646
19694
|
job: "Job",
|
|
19695
|
+
phaseSetup: "preparing",
|
|
19647
19696
|
phaseScan: "scanning files",
|
|
19648
19697
|
phaseEmbed: "embedding chunks",
|
|
19649
19698
|
phaseWrite: "writing index",
|
|
19650
19699
|
phaseDone: "done",
|
|
19651
19700
|
phaseError: "error",
|
|
19701
|
+
phaseCancelled: "cancelled",
|
|
19702
|
+
setupFailed: "setup failed",
|
|
19652
19703
|
stopping: "stopping",
|
|
19653
19704
|
scanned: "scanned {count}",
|
|
19654
19705
|
changed: "changed {count}",
|
|
@@ -19709,7 +19760,17 @@ var en = {
|
|
|
19709
19760
|
accept: "Accept",
|
|
19710
19761
|
reject: "Reject",
|
|
19711
19762
|
arguments: "arguments",
|
|
19712
|
-
revisePlaceholder: "What needs to change before the next step? Leave blank to just continue."
|
|
19763
|
+
revisePlaceholder: "What needs to change before the next step? Leave blank to just continue.",
|
|
19764
|
+
pickerFilter: "Filter\u2026",
|
|
19765
|
+
pickerEmpty: "Nothing to show.",
|
|
19766
|
+
pickerLoadMore: "Load more",
|
|
19767
|
+
pickerPick: "Open",
|
|
19768
|
+
pickerInstall: "Install",
|
|
19769
|
+
pickerUninstall: "Uninstall",
|
|
19770
|
+
pickerRename: "Rename\u2026",
|
|
19771
|
+
pickerNew: "New\u2026",
|
|
19772
|
+
pickerNewPlaceholder: "Name (leave blank for default)",
|
|
19773
|
+
viewerClose: "Close"
|
|
19713
19774
|
}
|
|
19714
19775
|
};
|
|
19715
19776
|
|
|
@@ -19777,8 +19838,36 @@ var zhCN = {
|
|
|
19777
19838
|
effortHigh: "high\uFF08\u66F4\u4FBF\u5B9C / \u66F4\u5FEB\uFF09",
|
|
19778
19839
|
webSearch: "\u7F51\u9875\u641C\u7D22",
|
|
19779
19840
|
webSearchNote: "web_fetch + web_search \u5DE5\u5177",
|
|
19841
|
+
sectionCompute: "\u8BA1\u7B97",
|
|
19842
|
+
proNext: "/pro \u5355\u8F6E",
|
|
19843
|
+
proArm: "\u4E3A\u4E0B\u4E00\u8F6E\u88C5\u5907",
|
|
19844
|
+
proArmed: "\u5DF2\u88C5\u5907 \u2014 \u4E0B\u4E00\u8F6E\u540E\u81EA\u52A8\u89E3\u9664",
|
|
19845
|
+
proNextNote: "\u4E0B\u4E00\u8F6E\u4F7F\u7528 deepseek-v4-pro\uFF0C\u4E4B\u540E\u81EA\u52A8\u89E3\u9664",
|
|
19846
|
+
sectionBudget: "\u9884\u7B97",
|
|
19847
|
+
budgetOf: "/",
|
|
19848
|
+
budgetSetCap: "\u8BBE\u7F6E\u4E0A\u9650",
|
|
19849
|
+
budgetCustom: "\u81EA\u5B9A\u4E49",
|
|
19850
|
+
budgetBumpHint: "\u63D0\u9AD8\u4E0A\u9650\u4EE5\u7EE7\u7EED",
|
|
19851
|
+
budgetClear: "\u6E05\u9664\u4E0A\u9650",
|
|
19852
|
+
budgetIdleLine: "80% \u65F6\u63D0\u9192 \xB7 100% \u540E\u62D2\u7EDD\u6267\u884C",
|
|
19853
|
+
budgetWarnLine: "\u63A5\u8FD1\u4E0A\u9650 \u2014 \u8D85\u8FC7 100% \u5C06\u62D2\u7EDD\u6267\u884C",
|
|
19854
|
+
budgetRefusing: "\u5DF2\u8D85\u51FA\u4E0A\u9650 \u2014 \u63D0\u9AD8\u6216\u6E05\u9664\u540E\u624D\u4F1A\u7EE7\u7EED",
|
|
19855
|
+
sectionLoop: "\u5FAA\u73AF",
|
|
19856
|
+
loopIdleHint: "\u6309\u56FA\u5B9A\u95F4\u9694\u81EA\u52A8\u91CD\u65B0\u63D0\u4EA4\u4E00\u6BB5\u63D0\u793A\u8BCD\u3002",
|
|
19857
|
+
loopCostHint: "\u6BCF\u6B21\u8FED\u4EE3\u7EA6 {cost}\uFF08\u4E0A\u4E00\u8F6E\u6210\u672C\uFF09\u3002",
|
|
19858
|
+
loopInterval: "\u95F4\u9694",
|
|
19859
|
+
loopCustom: "\u81EA\u5B9A\u4E49",
|
|
19860
|
+
loopRangeError: "\u95F4\u9694\u9700\u5728 5s..6h \u4E4B\u95F4",
|
|
19861
|
+
loopPrompt: "\u63D0\u793A\u8BCD",
|
|
19862
|
+
loopPromptPlaceholder: "\u4F8B\u5982\uFF1A\u68C0\u67E5\u90E8\u7F72\u72B6\u6001\u5E76\u6C47\u62A5\u4EFB\u4F55\u9519\u8BEF",
|
|
19863
|
+
loopStart: "\u542F\u52A8\u5FAA\u73AF",
|
|
19864
|
+
loopStop: "\u505C\u6B62",
|
|
19865
|
+
loopRunning: "\u8FD0\u884C\u4E2D",
|
|
19866
|
+
loopIter: "\u7B2C {iter} \u6B21",
|
|
19867
|
+
loopFiresIn: "{remaining} \u540E\u89E6\u53D1",
|
|
19780
19868
|
sectionRuntime: "\u8FD0\u884C\u65F6",
|
|
19781
19869
|
activeModel: "\u5F53\u524D\u6A21\u578B",
|
|
19870
|
+
modelPricingLine: "${hit} \u547D\u4E2D \xB7 ${miss} \u672A\u547D\u4E2D \xB7 ${out} \u8F93\u51FA / 100 \u4E07 tok",
|
|
19782
19871
|
editMode: "\u7F16\u8F91\u6A21\u5F0F",
|
|
19783
19872
|
editModeNote: "\u5728\u5BF9\u8BDD\u6807\u7B7E\u9875\u5934\u90E8\u5207\u6362",
|
|
19784
19873
|
sectionLanguage: "\u8BED\u8A00",
|
|
@@ -19861,6 +19950,7 @@ var zhCN = {
|
|
|
19861
19950
|
tokens7d: "tokens \xB7 7 \u5929",
|
|
19862
19951
|
cacheHit: "\u7F13\u5B58\u547D\u4E2D",
|
|
19863
19952
|
toolCalls24h: "\u5DE5\u5177\u8C03\u7528 \xB7 24 \u5C0F\u65F6",
|
|
19953
|
+
budget: "\u9884\u7B97",
|
|
19864
19954
|
currentSession: "\u5F53\u524D\u4F1A\u8BDD",
|
|
19865
19955
|
noSession: "\u65E0\u6D3B\u8DC3\u4F1A\u8BDD \u2014 \u5728 reasonix code \u5185\u6267\u884C /dashboard \u8FDB\u884C\u8FDE\u63A5\u3002",
|
|
19866
19956
|
promptTok: "\u63D0\u793A tokens",
|
|
@@ -20091,6 +20181,7 @@ var zhCN = {
|
|
|
20091
20181
|
filterPlaceholder: "\u7B5B\u9009\u8BA1\u5212",
|
|
20092
20182
|
active: "\u8FDB\u884C\u4E2D",
|
|
20093
20183
|
done: "\u5DF2\u5B8C\u6210",
|
|
20184
|
+
idle: "\u672A\u5F00\u59CB",
|
|
20094
20185
|
steps: "\u6B65\u9AA4",
|
|
20095
20186
|
pickHint: "\u9009\u62E9\u5DE6\u4FA7\u7684\u8BA1\u5212\u3002",
|
|
20096
20187
|
noTitle: "\uFF08\u65E0\u6807\u9898\uFF09",
|
|
@@ -20120,16 +20211,33 @@ var zhCN = {
|
|
|
20120
20211
|
pullModel: "\u62C9\u53D6 {model}",
|
|
20121
20212
|
indexStatus: "\u7D22\u5F15\u72B6\u6001",
|
|
20122
20213
|
builtStatus: "\u25CF \u5DF2\u6784\u5EFA",
|
|
20214
|
+
incompatibleStatus: "\u25CF \u4E0D\u517C\u5BB9",
|
|
20123
20215
|
chunks: "\u5206\u5757",
|
|
20124
20216
|
files: "\u6587\u4EF6",
|
|
20125
20217
|
dim: "\u7EF4\u5EA6",
|
|
20126
20218
|
size: "\u5927\u5C0F",
|
|
20127
20219
|
lastBuild: "\u4E0A\u6B21\u6784\u5EFA",
|
|
20220
|
+
builtWith: "\u6784\u5EFA\u6765\u6E90",
|
|
20221
|
+
currentTarget: "\u5F53\u524D\u76EE\u6807",
|
|
20222
|
+
incompatibleHint: "\u78C1\u76D8\u4E0A\u7684\u8FD9\u4E2A\u7D22\u5F15\u662F\u4E3A\u4E0D\u540C\u7684 provider \u6216 model \u6784\u5EFA\u7684\u3002\u8FD0\u884C\u201C\u5B8C\u5168\u91CD\u5EFA\u201D\u5373\u53EF\u66FF\u6362\u3002",
|
|
20128
20223
|
runIndexHint: "\u8FD0\u884C\u7D22\u5F15\u4EE5\u542F\u7528 semantic_search\u3002",
|
|
20129
20224
|
reIndex: "\u91CD\u5EFA\u7D22\u5F15",
|
|
20130
20225
|
build: "\u6784\u5EFA",
|
|
20131
20226
|
rebuild: "\u5B8C\u5168\u91CD\u5EFA",
|
|
20132
20227
|
stop: "\u505C\u6B62",
|
|
20228
|
+
provider: "\u63D0\u4F9B\u65B9",
|
|
20229
|
+
providerType: "\u670D\u52A1\u7C7B\u578B",
|
|
20230
|
+
openaiCompat: "OpenAI-Compatible",
|
|
20231
|
+
apiUrl: "API URL",
|
|
20232
|
+
apiKey: "API Key",
|
|
20233
|
+
customRequestBody: "\u81EA\u5B9A\u4E49\u8BF7\u6C42\u4F53",
|
|
20234
|
+
invalidCustomRequestBody: "\u81EA\u5B9A\u4E49\u8BF7\u6C42\u4F53\u5FC5\u987B\u662F\u5408\u6CD5 JSON\uFF1A{error}",
|
|
20235
|
+
customRequestBodyMustBeObject: "\u81EA\u5B9A\u4E49\u8BF7\u6C42\u4F53\u5FC5\u987B\u662F JSON \u5BF9\u8C61\u3002",
|
|
20236
|
+
saveBeforeIndex: "\u8BF7\u5148\u4FDD\u5B58\u8BED\u4E49\u8BBE\u7F6E\uFF0C\u518D\u542F\u52A8\u7D22\u5F15\u3002",
|
|
20237
|
+
extraBody: "\u6269\u5C55\u8BF7\u6C42\u4F53",
|
|
20238
|
+
keepExistingKey: "\u7559\u7A7A\u5219\u4FDD\u7559\u73B0\u6709 Key",
|
|
20239
|
+
remoteProvider: "\u8FDC\u7A0B\u5411\u91CF\u670D\u52A1",
|
|
20240
|
+
remoteProviderDesc: "\u5728\u8FD9\u91CC\u914D\u7F6E OpenAI-Compatible embeddings \u7684\u5B8C\u6574 URL\u3002Reasonix \u4F1A\u4E25\u683C\u4F7F\u7528\u4F60\u63D0\u4F9B\u7684 URL \u53D1\u8D77\u8BF7\u6C42\u3002",
|
|
20133
20241
|
ollama: "Ollama",
|
|
20134
20242
|
binary: "\u4E8C\u8FDB\u5236",
|
|
20135
20243
|
found: "\u5DF2\u627E\u5230",
|
|
@@ -20157,11 +20265,14 @@ var zhCN = {
|
|
|
20157
20265
|
nothingSkipped: "\u65E0\u8DF3\u8FC7 \u2014 \u6240\u6709\u904D\u5386\u7684\u6587\u4EF6\u90FD\u5C06\u88AB\u7D22\u5F15\u3002",
|
|
20158
20266
|
firstIncluded: "\u524D {count} \u4E2A\u5305\u542B\u7684\u6587\u4EF6",
|
|
20159
20267
|
job: "\u4EFB\u52A1",
|
|
20268
|
+
phaseSetup: "\u521D\u59CB\u5316\u4E2D",
|
|
20160
20269
|
phaseScan: "\u626B\u63CF\u6587\u4EF6",
|
|
20161
20270
|
phaseEmbed: "\u5D4C\u5165\u5206\u5757",
|
|
20162
20271
|
phaseWrite: "\u5199\u5165\u7D22\u5F15",
|
|
20163
20272
|
phaseDone: "\u5B8C\u6210",
|
|
20164
20273
|
phaseError: "\u9519\u8BEF",
|
|
20274
|
+
phaseCancelled: "\u5DF2\u505C\u6B62",
|
|
20275
|
+
setupFailed: "\u521D\u59CB\u5316\u5931\u8D25",
|
|
20165
20276
|
stopping: "\u505C\u6B62\u4E2D",
|
|
20166
20277
|
scanned: "\u5DF2\u626B\u63CF {count}",
|
|
20167
20278
|
changed: "\u5DF2\u53D8\u66F4 {count}",
|
|
@@ -20222,7 +20333,17 @@ var zhCN = {
|
|
|
20222
20333
|
accept: "\u63A5\u53D7",
|
|
20223
20334
|
reject: "\u62D2\u7EDD",
|
|
20224
20335
|
arguments: "\u53C2\u6570",
|
|
20225
|
-
revisePlaceholder: "\u4E0B\u4E00\u6B65\u4E4B\u524D\u9700\u8981\u66F4\u6539\u4EC0\u4E48\uFF1F\u7559\u7A7A\u5219\u76F4\u63A5\u7EE7\u7EED\u3002"
|
|
20336
|
+
revisePlaceholder: "\u4E0B\u4E00\u6B65\u4E4B\u524D\u9700\u8981\u66F4\u6539\u4EC0\u4E48\uFF1F\u7559\u7A7A\u5219\u76F4\u63A5\u7EE7\u7EED\u3002",
|
|
20337
|
+
pickerFilter: "\u8FC7\u6EE4\u2026",
|
|
20338
|
+
pickerEmpty: "\u6682\u65E0\u5185\u5BB9\u3002",
|
|
20339
|
+
pickerLoadMore: "\u52A0\u8F7D\u66F4\u591A",
|
|
20340
|
+
pickerPick: "\u6253\u5F00",
|
|
20341
|
+
pickerInstall: "\u5B89\u88C5",
|
|
20342
|
+
pickerUninstall: "\u5378\u8F7D",
|
|
20343
|
+
pickerRename: "\u91CD\u547D\u540D\u2026",
|
|
20344
|
+
pickerNew: "\u65B0\u5EFA\u2026",
|
|
20345
|
+
pickerNewPlaceholder: "\u540D\u79F0\uFF08\u7559\u7A7A\u4F7F\u7528\u9ED8\u8BA4\uFF09",
|
|
20346
|
+
viewerClose: "\u5173\u95ED"
|
|
20226
20347
|
}
|
|
20227
20348
|
};
|
|
20228
20349
|
|
|
@@ -22930,6 +23051,156 @@ function CheckpointModal({ modal, onResolve }) {
|
|
|
22930
23051
|
<//>
|
|
22931
23052
|
`;
|
|
22932
23053
|
}
|
|
23054
|
+
function PickerModal({
|
|
23055
|
+
modal,
|
|
23056
|
+
onResolve
|
|
23057
|
+
}) {
|
|
23058
|
+
useLang();
|
|
23059
|
+
const [selectedId, setSelectedId] = d2(modal.items[0]?.id ?? null);
|
|
23060
|
+
const [query2, setQuery] = d2(modal.query ?? "");
|
|
23061
|
+
const [renameTarget, setRenameTarget] = d2(null);
|
|
23062
|
+
const [renameText, setRenameText] = d2("");
|
|
23063
|
+
const [showNew, setShowNew] = d2(false);
|
|
23064
|
+
const [newText, setNewText] = d2("");
|
|
23065
|
+
const has = (a3) => modal.actions.includes(a3);
|
|
23066
|
+
const selected = modal.items.find((i3) => i3.id === selectedId) ?? null;
|
|
23067
|
+
const submitRefine = (next) => {
|
|
23068
|
+
setQuery(next);
|
|
23069
|
+
if (has("refine")) onResolve("picker", { action: "refine", query: next });
|
|
23070
|
+
};
|
|
23071
|
+
const startRename = (id) => {
|
|
23072
|
+
const item = modal.items.find((i3) => i3.id === id);
|
|
23073
|
+
if (!item) return;
|
|
23074
|
+
setRenameTarget(id);
|
|
23075
|
+
setRenameText(item.title);
|
|
23076
|
+
};
|
|
23077
|
+
const sendRename = () => {
|
|
23078
|
+
if (!renameTarget || !renameText.trim()) return;
|
|
23079
|
+
onResolve("picker", { action: "rename", id: renameTarget, text: renameText });
|
|
23080
|
+
setRenameTarget(null);
|
|
23081
|
+
setRenameText("");
|
|
23082
|
+
};
|
|
23083
|
+
const sendNew = () => {
|
|
23084
|
+
onResolve("picker", newText.trim() ? { action: "new", text: newText } : { action: "new" });
|
|
23085
|
+
setShowNew(false);
|
|
23086
|
+
setNewText("");
|
|
23087
|
+
};
|
|
23088
|
+
return html4`
|
|
23089
|
+
<${ModalCard}
|
|
23090
|
+
accent="#fcd34d"
|
|
23091
|
+
icon="≡"
|
|
23092
|
+
title=${modal.title}
|
|
23093
|
+
subtitle=${modal.hint}
|
|
23094
|
+
>
|
|
23095
|
+
${has("refine") ? html4`<input
|
|
23096
|
+
class="modal-picker-search"
|
|
23097
|
+
type="search"
|
|
23098
|
+
placeholder=${t4("modal.pickerFilter")}
|
|
23099
|
+
value=${query2}
|
|
23100
|
+
onInput=${(e3) => submitRefine(e3.target.value)}
|
|
23101
|
+
/>` : null}
|
|
23102
|
+
<div class="modal-picker-list">
|
|
23103
|
+
${modal.items.length === 0 ? html4`<div class="modal-picker-empty">${t4("modal.pickerEmpty")}</div>` : modal.items.map(
|
|
23104
|
+
(it) => html4`
|
|
23105
|
+
<button
|
|
23106
|
+
key=${it.id}
|
|
23107
|
+
class=${`modal-picker-row${it.id === selectedId ? " selected" : ""}`}
|
|
23108
|
+
onClick=${() => setSelectedId(it.id)}
|
|
23109
|
+
onDblClick=${() => has("pick") && onResolve("picker", { action: "pick", id: it.id })}
|
|
23110
|
+
>
|
|
23111
|
+
<span class="modal-picker-title">${it.title}</span>
|
|
23112
|
+
${it.badge ? html4`<span class="modal-picker-badge">${it.badge}</span>` : null}
|
|
23113
|
+
${it.subtitle ? html4`<span class="modal-picker-subtitle">${it.subtitle}</span>` : null}
|
|
23114
|
+
${it.meta ? html4`<span class="modal-picker-meta">${it.meta}</span>` : null}
|
|
23115
|
+
</button>
|
|
23116
|
+
`
|
|
23117
|
+
)}
|
|
23118
|
+
</div>
|
|
23119
|
+
${modal.hasMore && has("load-more") ? html4`<button
|
|
23120
|
+
class="modal-picker-more"
|
|
23121
|
+
onClick=${() => onResolve("picker", { action: "load-more" })}
|
|
23122
|
+
>${t4("modal.pickerLoadMore")}</button>` : null}
|
|
23123
|
+
${renameTarget ? html4`
|
|
23124
|
+
<div class="modal-picker-form">
|
|
23125
|
+
<input
|
|
23126
|
+
type="text"
|
|
23127
|
+
value=${renameText}
|
|
23128
|
+
onInput=${(e3) => setRenameText(e3.target.value)}
|
|
23129
|
+
/>
|
|
23130
|
+
<div class="modal-actions">
|
|
23131
|
+
<button class="primary" onClick=${sendRename} disabled=${!renameText.trim()}>${t4("common.save")}</button>
|
|
23132
|
+
<button onClick=${() => setRenameTarget(null)}>${t4("common.back")}</button>
|
|
23133
|
+
</div>
|
|
23134
|
+
</div>
|
|
23135
|
+
` : showNew ? html4`
|
|
23136
|
+
<div class="modal-picker-form">
|
|
23137
|
+
<input
|
|
23138
|
+
type="text"
|
|
23139
|
+
placeholder=${t4("modal.pickerNewPlaceholder")}
|
|
23140
|
+
value=${newText}
|
|
23141
|
+
onInput=${(e3) => setNewText(e3.target.value)}
|
|
23142
|
+
/>
|
|
23143
|
+
<div class="modal-actions">
|
|
23144
|
+
<button class="primary" onClick=${sendNew}>${t4("common.add")}</button>
|
|
23145
|
+
<button onClick=${() => setShowNew(false)}>${t4("common.back")}</button>
|
|
23146
|
+
</div>
|
|
23147
|
+
</div>
|
|
23148
|
+
` : html4`
|
|
23149
|
+
<div class="modal-actions">
|
|
23150
|
+
${has("pick") && selected ? html4`<button
|
|
23151
|
+
class="primary"
|
|
23152
|
+
onClick=${() => onResolve("picker", { action: "pick", id: selected.id })}
|
|
23153
|
+
>${t4("modal.pickerPick")}</button>` : null}
|
|
23154
|
+
${has("install") && selected ? html4`<button
|
|
23155
|
+
class="primary"
|
|
23156
|
+
onClick=${() => onResolve("picker", { action: "install", id: selected.id })}
|
|
23157
|
+
>${t4("modal.pickerInstall")}</button>` : null}
|
|
23158
|
+
${has("uninstall") && selected ? html4`<button
|
|
23159
|
+
onClick=${() => onResolve("picker", { action: "uninstall", id: selected.id })}
|
|
23160
|
+
>${t4("modal.pickerUninstall")}</button>` : null}
|
|
23161
|
+
${has("rename") && selected ? html4`<button onClick=${() => startRename(selected.id)}>${t4("modal.pickerRename")}</button>` : null}
|
|
23162
|
+
${has("delete") && selected ? html4`<button
|
|
23163
|
+
class="danger"
|
|
23164
|
+
onClick=${() => onResolve("picker", { action: "delete", id: selected.id })}
|
|
23165
|
+
>${t4("common.delete")}</button>` : null}
|
|
23166
|
+
${has("new") ? html4`<button onClick=${() => setShowNew(true)}>${t4("modal.pickerNew")}</button>` : null}
|
|
23167
|
+
<button onClick=${() => onResolve("picker", { action: "cancel" })}>${t4("modal.cancel")}</button>
|
|
23168
|
+
</div>
|
|
23169
|
+
`}
|
|
23170
|
+
<//>
|
|
23171
|
+
`;
|
|
23172
|
+
}
|
|
23173
|
+
function ViewerModal({
|
|
23174
|
+
modal,
|
|
23175
|
+
onResolve
|
|
23176
|
+
}) {
|
|
23177
|
+
useLang();
|
|
23178
|
+
return html4`
|
|
23179
|
+
<${ModalCard}
|
|
23180
|
+
accent="#67e8f9"
|
|
23181
|
+
icon="◇"
|
|
23182
|
+
title=${modal.title}
|
|
23183
|
+
subtitle=${modal.meta}
|
|
23184
|
+
>
|
|
23185
|
+
${modal.steps && modal.steps.length > 0 ? html4`
|
|
23186
|
+
<ol class="modal-viewer-steps">
|
|
23187
|
+
${modal.steps.map(
|
|
23188
|
+
(s3) => html4`
|
|
23189
|
+
<li key=${s3.id} class=${`modal-viewer-step modal-viewer-step-${s3.status}`}>
|
|
23190
|
+
<span class="modal-viewer-step-mark">${s3.status === "done" ? "\u2713" : "\xB7"}</span>
|
|
23191
|
+
<span class="modal-viewer-step-title">${s3.title}</span>
|
|
23192
|
+
</li>
|
|
23193
|
+
`
|
|
23194
|
+
)}
|
|
23195
|
+
</ol>
|
|
23196
|
+
` : null}
|
|
23197
|
+
${modal.body ? html4`<div class="md modal-viewer-body" dangerouslySetInnerHTML=${{ __html: marked.parse(modal.body) }}></div>` : null}
|
|
23198
|
+
<div class="modal-actions">
|
|
23199
|
+
<button onClick=${() => onResolve("viewer", { action: "close" })}>${t4("modal.viewerClose")}</button>
|
|
23200
|
+
</div>
|
|
23201
|
+
<//>
|
|
23202
|
+
`;
|
|
23203
|
+
}
|
|
22933
23204
|
function RevisionModal({ modal, onResolve }) {
|
|
22934
23205
|
useLang();
|
|
22935
23206
|
const riskColor = (r3) => r3 === "high" ? "#f87171" : r3 === "med" ? "#fbbf24" : r3 === "low" ? "#86efac" : "#9ca3af";
|
|
@@ -23514,7 +23785,7 @@ function ChatPanel() {
|
|
|
23514
23785
|
</div>` : null}
|
|
23515
23786
|
${error ? html4`<div class="notice err">${error}</div>` : null}
|
|
23516
23787
|
|
|
23517
|
-
${modal ? modal.kind === "shell" ? html4`<${ShellModal} modal=${modal} onResolve=${resolveModal} />` : modal.kind === "choice" ? html4`<${ChoiceModal} modal=${modal} onResolve=${resolveModal} />` : modal.kind === "plan" ? html4`<${PlanModal} modal=${modal} onResolve=${resolveModal} />` : modal.kind === "edit-review" ? html4`<${EditReviewModal} modal=${modal} onResolve=${resolveModal} />` : modal.kind === "workspace" ? html4`<${WorkspaceModal} modal=${modal} onResolve=${resolveModal} />` : modal.kind === "checkpoint" ? html4`<${CheckpointModal} modal=${modal} onResolve=${resolveModal} />` : modal.kind === "revision" ? html4`<${RevisionModal} modal=${modal} onResolve=${resolveModal} />` : null : null}
|
|
23788
|
+
${modal ? modal.kind === "shell" ? html4`<${ShellModal} modal=${modal} onResolve=${resolveModal} />` : modal.kind === "choice" ? html4`<${ChoiceModal} modal=${modal} onResolve=${resolveModal} />` : modal.kind === "plan" ? html4`<${PlanModal} modal=${modal} onResolve=${resolveModal} />` : modal.kind === "edit-review" ? html4`<${EditReviewModal} modal=${modal} onResolve=${resolveModal} />` : modal.kind === "workspace" ? html4`<${WorkspaceModal} modal=${modal} onResolve=${resolveModal} />` : modal.kind === "checkpoint" ? html4`<${CheckpointModal} modal=${modal} onResolve=${resolveModal} />` : modal.kind === "revision" ? html4`<${RevisionModal} modal=${modal} onResolve=${resolveModal} />` : modal.kind === "picker" ? html4`<${PickerModal} modal=${modal} onResolve=${resolveModal} />` : modal.kind === "viewer" ? html4`<${ViewerModal} modal=${modal} onResolve=${resolveModal} />` : null : null}
|
|
23518
23789
|
|
|
23519
23790
|
<div class="chat-body">
|
|
23520
23791
|
<div class="chat-main">
|
|
@@ -23599,7 +23870,7 @@ function SideRail({ stats, budgetUsd, activePlan }) {
|
|
|
23599
23870
|
const cacheTone = cachePct >= 80 ? "ok" : cachePct >= 50 ? "" : "warn";
|
|
23600
23871
|
const showBudget = stats != null && typeof budgetUsd === "number" && budgetUsd > 0;
|
|
23601
23872
|
const budgetPct = showBudget ? Math.min(120, stats.totalCostUsd / budgetUsd * 100) : 0;
|
|
23602
|
-
const
|
|
23873
|
+
const budgetTone2 = budgetPct >= 100 ? "err" : budgetPct >= 80 ? "warn" : "";
|
|
23603
23874
|
const walletCurrency = stats?.balance?.[0]?.currency;
|
|
23604
23875
|
return html4`
|
|
23605
23876
|
<aside class="chat-rail">
|
|
@@ -23622,8 +23893,8 @@ function SideRail({ stats, budgetUsd, activePlan }) {
|
|
|
23622
23893
|
<div class="rh">${t4("chat.railToolBudget")}</div>
|
|
23623
23894
|
<div class="progress-row">
|
|
23624
23895
|
<span class="lbl">${t4("chat.railSpend")}</span>
|
|
23625
|
-
<div class=${`progress ${
|
|
23626
|
-
<span class="v" style=${
|
|
23896
|
+
<div class=${`progress ${budgetTone2}`}><div class="progress-fill" style=${`width:${Math.min(100, budgetPct)}%`}></div></div>
|
|
23897
|
+
<span class="v" style=${budgetTone2 === "err" ? "color:var(--c-err)" : budgetTone2 === "warn" ? "color:var(--c-warn)" : ""}>${fmtCost(stats.totalCostUsd, walletCurrency)} / ${fmtCost(budgetUsd, walletCurrency)}</span>
|
|
23627
23898
|
</div>
|
|
23628
23899
|
</div>
|
|
23629
23900
|
` : null}
|
|
@@ -24564,6 +24835,35 @@ function MemoryPanel() {
|
|
|
24564
24835
|
`;
|
|
24565
24836
|
}
|
|
24566
24837
|
|
|
24838
|
+
// dashboard/src/lib/budget.ts
|
|
24839
|
+
function deriveBudgetState(cap, spent) {
|
|
24840
|
+
const safeSpent = typeof spent === "number" && spent >= 0 ? spent : 0;
|
|
24841
|
+
if (typeof cap !== "number" || cap <= 0) {
|
|
24842
|
+
return { kind: "off", spent: safeSpent };
|
|
24843
|
+
}
|
|
24844
|
+
const pct = safeSpent / cap * 100;
|
|
24845
|
+
if (pct >= 100) return { kind: "exhausted", cap, spent: safeSpent, pct };
|
|
24846
|
+
if (pct >= 80) return { kind: "warn", cap, spent: safeSpent, pct };
|
|
24847
|
+
return { kind: "running", cap, spent: safeSpent, pct };
|
|
24848
|
+
}
|
|
24849
|
+
var QUICK_CAPS_USD = [1, 5, 10, 25, 50];
|
|
24850
|
+
function bumpSuggestions(currentCap) {
|
|
24851
|
+
if (currentCap <= 0) return [];
|
|
24852
|
+
return [niceUp(currentCap * 1.5), niceUp(currentCap * 2), niceUp(currentCap * 4)];
|
|
24853
|
+
}
|
|
24854
|
+
function niceUp(n3) {
|
|
24855
|
+
const eps = 1e-9;
|
|
24856
|
+
if (n3 < 1) return Math.ceil((n3 - eps) * 10) / 10;
|
|
24857
|
+
if (n3 < 10) return Math.ceil((n3 - eps) * 2) / 2;
|
|
24858
|
+
if (n3 < 100) return Math.ceil(n3 - eps);
|
|
24859
|
+
return Math.ceil((n3 - eps) / 5) * 5;
|
|
24860
|
+
}
|
|
24861
|
+
function budgetTone(state) {
|
|
24862
|
+
if (state.kind === "exhausted") return "err";
|
|
24863
|
+
if (state.kind === "warn") return "warn";
|
|
24864
|
+
return "";
|
|
24865
|
+
}
|
|
24866
|
+
|
|
24567
24867
|
// dashboard/src/lib/use-poll.ts
|
|
24568
24868
|
function usePoll(path, intervalMs = 2e3) {
|
|
24569
24869
|
const [data, setData] = d2(null);
|
|
@@ -24634,6 +24934,19 @@ function balanceKpi(c3) {
|
|
|
24634
24934
|
const symbol = c3.balance.currency === "CNY" ? "\xA5" : c3.balance.currency === "USD" ? "$" : "";
|
|
24635
24935
|
return kpi(t4("overview.balance"), `${symbol}${c3.balance.total}`, c3.balance.currency, "flat");
|
|
24636
24936
|
}
|
|
24937
|
+
function budgetKpi(o3) {
|
|
24938
|
+
const state = deriveBudgetState(o3.budgetUsd, o3.cockpit?.currentSession?.totalCostUsd ?? null);
|
|
24939
|
+
if (state.kind === "off") return null;
|
|
24940
|
+
const tone = budgetTone(state);
|
|
24941
|
+
const valueColor = tone === "err" ? "color:var(--c-err)" : tone === "warn" ? "color:var(--c-warn)" : "";
|
|
24942
|
+
return html4`
|
|
24943
|
+
<div class="kpi cock-w-1">
|
|
24944
|
+
<div class="label">${t4("overview.budget")}</div>
|
|
24945
|
+
<div class="value" style=${valueColor}>${fmtUsd(state.spent)} / ${fmtUsd(state.cap)}</div>
|
|
24946
|
+
<div class=${`progress ${tone}`} style="margin-top:4px"><div class="progress-fill" style=${`width:${Math.min(100, state.pct)}%`}></div></div>
|
|
24947
|
+
</div>
|
|
24948
|
+
`;
|
|
24949
|
+
}
|
|
24637
24950
|
function tokens7dKpi(c3) {
|
|
24638
24951
|
if (!c3.tokens7d) return kpi(t4("overview.tokens7d"), "\u2014", t4("overview.noUsageYet"), "flat");
|
|
24639
24952
|
const d3 = deltaPctText(c3.tokens7d.deltaPct);
|
|
@@ -24789,6 +25102,7 @@ function OverviewPanel() {
|
|
|
24789
25102
|
${tokens7dKpi(c3)}
|
|
24790
25103
|
${cacheHitKpi(c3)}
|
|
24791
25104
|
${toolCallsKpi(c3)}
|
|
25105
|
+
${budgetKpi(o3)}
|
|
24792
25106
|
|
|
24793
25107
|
${currentSessionBlock(c3)}
|
|
24794
25108
|
${costTrendSpark(c3)}
|
|
@@ -25001,7 +25315,7 @@ function PermissionsPanel() {
|
|
|
25001
25315
|
function statusPill(p3) {
|
|
25002
25316
|
if (p3.completionRatio >= 1) return html4`<span class="pill ok">${t4("plans.done")}</span>`;
|
|
25003
25317
|
if (p3.completionRatio > 0) return html4`<span class="pill info">${t4("plans.active")}</span>`;
|
|
25004
|
-
return html4`<span class="pill"
|
|
25318
|
+
return html4`<span class="pill">${t4("plans.idle")}</span>`;
|
|
25005
25319
|
}
|
|
25006
25320
|
function PlansPanel() {
|
|
25007
25321
|
useLang();
|
|
@@ -25114,13 +25428,20 @@ function PlansPanel() {
|
|
|
25114
25428
|
function SemanticPanel() {
|
|
25115
25429
|
useLang();
|
|
25116
25430
|
const [data, setData] = d2(null);
|
|
25431
|
+
const [draft, setDraft] = d2(null);
|
|
25432
|
+
const [draftDirty, setDraftDirty] = d2(false);
|
|
25433
|
+
const draftDirtyRef = A2(false);
|
|
25117
25434
|
const [error, setError] = d2(null);
|
|
25118
25435
|
const [busy, setBusy] = d2(false);
|
|
25119
25436
|
const [info, setInfo] = d2(null);
|
|
25120
25437
|
const load = q2(async () => {
|
|
25121
25438
|
try {
|
|
25122
|
-
const
|
|
25123
|
-
|
|
25439
|
+
const [semantic, config] = await Promise.all([
|
|
25440
|
+
api("/semantic"),
|
|
25441
|
+
api("/semantic/config")
|
|
25442
|
+
]);
|
|
25443
|
+
setData(semantic);
|
|
25444
|
+
setDraft((current) => current && draftDirtyRef.current ? current : toConfigDraft(config));
|
|
25124
25445
|
} catch (err) {
|
|
25125
25446
|
setError(err.message);
|
|
25126
25447
|
}
|
|
@@ -25128,7 +25449,7 @@ function SemanticPanel() {
|
|
|
25128
25449
|
y2(() => {
|
|
25129
25450
|
load();
|
|
25130
25451
|
const phase2 = data?.job?.phase;
|
|
25131
|
-
const running2 = phase2
|
|
25452
|
+
const running2 = isActiveSemanticPhase(phase2);
|
|
25132
25453
|
const pulling2 = data?.pull?.status === "pulling";
|
|
25133
25454
|
const ms = running2 || pulling2 ? 1200 : 5e3;
|
|
25134
25455
|
const id = setInterval(load, ms);
|
|
@@ -25136,10 +25457,18 @@ function SemanticPanel() {
|
|
|
25136
25457
|
}, [load, data?.job?.phase, data?.pull?.status]);
|
|
25137
25458
|
const start = q2(
|
|
25138
25459
|
async (rebuild) => {
|
|
25460
|
+
if (!draft) return;
|
|
25139
25461
|
setBusy(true);
|
|
25140
25462
|
setError(null);
|
|
25141
25463
|
setInfo(null);
|
|
25142
25464
|
try {
|
|
25465
|
+
const validation = validateSemanticDraft(draft);
|
|
25466
|
+
if (draftDirty) {
|
|
25467
|
+
throw new Error(t4("semantic.saveBeforeIndex"));
|
|
25468
|
+
}
|
|
25469
|
+
if (validation.error) {
|
|
25470
|
+
throw new Error(validation.error);
|
|
25471
|
+
}
|
|
25143
25472
|
await api("/semantic/start", { method: "POST", body: { rebuild: !!rebuild } });
|
|
25144
25473
|
setInfo(rebuild ? t4("semantic.rebuildStarted") : t4("semantic.incrementalStarted"));
|
|
25145
25474
|
await load();
|
|
@@ -25149,7 +25478,7 @@ function SemanticPanel() {
|
|
|
25149
25478
|
setBusy(false);
|
|
25150
25479
|
}
|
|
25151
25480
|
},
|
|
25152
|
-
[load]
|
|
25481
|
+
[draft, draftDirty, load]
|
|
25153
25482
|
);
|
|
25154
25483
|
const stop = q2(async () => {
|
|
25155
25484
|
setBusy(true);
|
|
@@ -25169,10 +25498,11 @@ function SemanticPanel() {
|
|
|
25169
25498
|
setError(null);
|
|
25170
25499
|
setInfo(t4("semantic.startingDaemon"));
|
|
25171
25500
|
try {
|
|
25172
|
-
const r3 = await api("/semantic/ollama/start", {
|
|
25173
|
-
|
|
25174
|
-
|
|
25175
|
-
);
|
|
25501
|
+
const r3 = await api("/semantic/ollama/start", {
|
|
25502
|
+
method: "POST",
|
|
25503
|
+
body: {}
|
|
25504
|
+
});
|
|
25505
|
+
setInfo(r3.ready ? t4("semantic.daemonUp") : t4("semantic.daemonTimeout"));
|
|
25176
25506
|
await load();
|
|
25177
25507
|
} catch (err) {
|
|
25178
25508
|
setError(err.message);
|
|
@@ -25196,10 +25526,44 @@ function SemanticPanel() {
|
|
|
25196
25526
|
},
|
|
25197
25527
|
[load]
|
|
25198
25528
|
);
|
|
25199
|
-
|
|
25529
|
+
const saveProviderConfig = q2(async () => {
|
|
25530
|
+
if (!draft) return;
|
|
25531
|
+
setBusy(true);
|
|
25532
|
+
setError(null);
|
|
25533
|
+
setInfo(null);
|
|
25534
|
+
try {
|
|
25535
|
+
const extraBody = semanticValidation.extraBody;
|
|
25536
|
+
await api("/semantic/config", {
|
|
25537
|
+
method: "POST",
|
|
25538
|
+
body: {
|
|
25539
|
+
provider: draft.provider,
|
|
25540
|
+
ollama: {
|
|
25541
|
+
baseUrl: draft.ollama.baseUrl,
|
|
25542
|
+
model: draft.ollama.model
|
|
25543
|
+
},
|
|
25544
|
+
openaiCompat: {
|
|
25545
|
+
baseUrl: draft.openaiCompat.baseUrl,
|
|
25546
|
+
apiKey: draft.openaiCompat.apiKey,
|
|
25547
|
+
model: draft.openaiCompat.model,
|
|
25548
|
+
extraBody
|
|
25549
|
+
}
|
|
25550
|
+
}
|
|
25551
|
+
});
|
|
25552
|
+
setDraftDirty(false);
|
|
25553
|
+
draftDirtyRef.current = false;
|
|
25554
|
+
setInfo(t4("semantic.savedConfig", { count: 1 }));
|
|
25555
|
+
await load();
|
|
25556
|
+
} catch (err) {
|
|
25557
|
+
setError(err.message);
|
|
25558
|
+
} finally {
|
|
25559
|
+
setBusy(false);
|
|
25560
|
+
}
|
|
25561
|
+
}, [draft, load]);
|
|
25562
|
+
if (!data && !error) {
|
|
25200
25563
|
return html4`<div class="card" style="color:var(--fg-3)">${t4("common.loading")}</div>`;
|
|
25564
|
+
}
|
|
25201
25565
|
if (error && !data) return html4`<div class="card accent-err">${error}</div>`;
|
|
25202
|
-
if (!data) return null;
|
|
25566
|
+
if (!data || !draft) return null;
|
|
25203
25567
|
if (!data.attached) {
|
|
25204
25568
|
return html4`
|
|
25205
25569
|
<div class="card" style="color:var(--fg-3)">
|
|
@@ -25210,35 +25574,168 @@ function SemanticPanel() {
|
|
|
25210
25574
|
}
|
|
25211
25575
|
const job = data.job;
|
|
25212
25576
|
const phase = job?.phase;
|
|
25213
|
-
const running = phase
|
|
25577
|
+
const running = isActiveSemanticPhase(phase);
|
|
25214
25578
|
const pull = data.pull;
|
|
25215
25579
|
const pulling = pull?.status === "pulling";
|
|
25216
|
-
const
|
|
25217
|
-
const
|
|
25218
|
-
const
|
|
25219
|
-
const
|
|
25220
|
-
const
|
|
25221
|
-
const
|
|
25222
|
-
const
|
|
25580
|
+
const provider = data.providerStatus?.kind ?? draft.provider;
|
|
25581
|
+
const ready = data.providerStatus?.ready === true;
|
|
25582
|
+
const isOllama = provider === "ollama";
|
|
25583
|
+
const ollama = data.providerStatus?.kind === "ollama" ? data.providerStatus : null;
|
|
25584
|
+
const remote = data.providerStatus?.kind === "openai-compat" ? data.providerStatus : null;
|
|
25585
|
+
const binaryFound = ollama?.binaryFound === true;
|
|
25586
|
+
const daemonRunning = ollama?.daemonRunning === true;
|
|
25587
|
+
const modelPulled = ollama?.modelPulled === true;
|
|
25588
|
+
const modelName = isOllama ? ollama?.modelName ?? draft.ollama.model ?? "nomic-embed-text" : draft.openaiCompat.model;
|
|
25223
25589
|
const sectionH3 = (text) => html4`
|
|
25224
25590
|
<h3 style="margin:18px 0 8px;font-family:var(--font-mono);font-size:11px;color:var(--fg-3);text-transform:uppercase;letter-spacing:.1em">${text}</h3>
|
|
25225
25591
|
`;
|
|
25226
25592
|
const idx = data.index;
|
|
25593
|
+
const indexReady = idx?.exists === true && idx.compatible !== false;
|
|
25594
|
+
const indexMismatch = idx?.exists === true && idx.compatible === false;
|
|
25595
|
+
const semanticValidation = validateSemanticDraft(draft);
|
|
25596
|
+
const semanticDraftBlocked = draftDirty || semanticValidation.error !== null;
|
|
25227
25597
|
return html4`
|
|
25228
25598
|
<div style="display:grid;grid-template-columns:minmax(0,1fr) 280px;gap:14px;align-items:start">
|
|
25229
25599
|
<div style="display:flex;flex-direction:column;gap:10px;min-width:0">
|
|
25230
25600
|
<div class="chips">
|
|
25231
|
-
<span class=${`chip-f static ${
|
|
25232
|
-
${
|
|
25601
|
+
<span class=${`chip-f static ${indexReady ? "active" : ""}`}>
|
|
25602
|
+
${indexReady ? t4("semantic.indexBuilt") : t4("semantic.noIndex")}
|
|
25233
25603
|
</span>
|
|
25234
25604
|
${ready ? html4`<span class="chip-f static" style="border-color:var(--c-ok);color:var(--c-ok)">${t4("semantic.ready")}</span>` : html4`<span class="chip-f static" style="border-color:var(--c-warn);color:var(--c-warn)">${t4("semantic.setupNeeded")}</span>`}
|
|
25235
25605
|
</div>
|
|
25236
|
-
${info ? html4`<div><span class="pill info">${info}</span></div>` : null}
|
|
25237
25606
|
${error ? html4`<div class="card accent-err">${error}</div>` : null}
|
|
25238
25607
|
|
|
25239
|
-
|
|
25608
|
+
<div class="card">
|
|
25609
|
+
<div class="card-h"><span class="title">${t4("semantic.provider")}</span></div>
|
|
25610
|
+
<div class="form-row">
|
|
25611
|
+
<span class="lbl">${t4("semantic.providerType")}</span>
|
|
25612
|
+
<select
|
|
25613
|
+
class="input mono"
|
|
25614
|
+
value=${draft.provider}
|
|
25615
|
+
onInput=${(e3) => {
|
|
25616
|
+
draftDirtyRef.current = true;
|
|
25617
|
+
setDraftDirty(true);
|
|
25618
|
+
setDraft({
|
|
25619
|
+
...draft,
|
|
25620
|
+
provider: e3.target.value
|
|
25621
|
+
});
|
|
25622
|
+
}}
|
|
25623
|
+
>
|
|
25624
|
+
<option value="ollama">Ollama</option>
|
|
25625
|
+
<option value="openai-compat">OpenAI-Compatible</option>
|
|
25626
|
+
</select>
|
|
25627
|
+
</div>
|
|
25628
|
+
${draft.provider === "ollama" ? html4`
|
|
25629
|
+
<div class="form-row">
|
|
25630
|
+
<span class="lbl">${t4("semantic.model")}</span>
|
|
25631
|
+
<input
|
|
25632
|
+
class="input mono"
|
|
25633
|
+
type="text"
|
|
25634
|
+
value=${draft.ollama.model}
|
|
25635
|
+
onInput=${(e3) => {
|
|
25636
|
+
draftDirtyRef.current = true;
|
|
25637
|
+
setDraftDirty(true);
|
|
25638
|
+
setDraft({
|
|
25639
|
+
...draft,
|
|
25640
|
+
ollama: { ...draft.ollama, model: e3.target.value }
|
|
25641
|
+
});
|
|
25642
|
+
}}
|
|
25643
|
+
/>
|
|
25644
|
+
</div>
|
|
25645
|
+
` : html4`
|
|
25646
|
+
<div class="form-row">
|
|
25647
|
+
<span class="lbl">${t4("semantic.apiUrl")}</span>
|
|
25648
|
+
<input
|
|
25649
|
+
class="input mono"
|
|
25650
|
+
type="text"
|
|
25651
|
+
placeholder="https://api.openai.com/v1/embeddings"
|
|
25652
|
+
value=${draft.openaiCompat.baseUrl}
|
|
25653
|
+
onInput=${(e3) => {
|
|
25654
|
+
draftDirtyRef.current = true;
|
|
25655
|
+
setDraftDirty(true);
|
|
25656
|
+
setDraft({
|
|
25657
|
+
...draft,
|
|
25658
|
+
openaiCompat: {
|
|
25659
|
+
...draft.openaiCompat,
|
|
25660
|
+
baseUrl: e3.target.value
|
|
25661
|
+
}
|
|
25662
|
+
});
|
|
25663
|
+
}}
|
|
25664
|
+
/>
|
|
25665
|
+
</div>
|
|
25666
|
+
<div class="form-row">
|
|
25667
|
+
<span class="lbl">${t4("semantic.apiKey")}</span>
|
|
25668
|
+
<input
|
|
25669
|
+
class="input mono"
|
|
25670
|
+
type="password"
|
|
25671
|
+
placeholder=${draft.openaiCompat.apiKeySet ? t4("semantic.keepExistingKey") : "sk-..."}
|
|
25672
|
+
value=${draft.openaiCompat.apiKey}
|
|
25673
|
+
onInput=${(e3) => {
|
|
25674
|
+
draftDirtyRef.current = true;
|
|
25675
|
+
setDraftDirty(true);
|
|
25676
|
+
setDraft({
|
|
25677
|
+
...draft,
|
|
25678
|
+
openaiCompat: {
|
|
25679
|
+
...draft.openaiCompat,
|
|
25680
|
+
apiKey: e3.target.value
|
|
25681
|
+
}
|
|
25682
|
+
});
|
|
25683
|
+
}}
|
|
25684
|
+
/>
|
|
25685
|
+
<div style="color:var(--fg-3);font-size:12px">${t4("semantic.apiKeyStoredNote")}</div>
|
|
25686
|
+
</div>
|
|
25687
|
+
<div class="form-row">
|
|
25688
|
+
<span class="lbl">${t4("semantic.model")}</span>
|
|
25689
|
+
<input
|
|
25690
|
+
class="input mono"
|
|
25691
|
+
type="text"
|
|
25692
|
+
value=${draft.openaiCompat.model}
|
|
25693
|
+
onInput=${(e3) => {
|
|
25694
|
+
draftDirtyRef.current = true;
|
|
25695
|
+
setDraftDirty(true);
|
|
25696
|
+
setDraft({
|
|
25697
|
+
...draft,
|
|
25698
|
+
openaiCompat: {
|
|
25699
|
+
...draft.openaiCompat,
|
|
25700
|
+
model: e3.target.value
|
|
25701
|
+
}
|
|
25702
|
+
});
|
|
25703
|
+
}}
|
|
25704
|
+
/>
|
|
25705
|
+
</div>
|
|
25706
|
+
<details style="margin-top:10px">
|
|
25707
|
+
<summary style="cursor:pointer;color:var(--fg-2);font-size:12px">${t4("semantic.customRequestBody")}</summary>
|
|
25708
|
+
<div class="form-row" style="margin-top:10px">
|
|
25709
|
+
<span class="lbl">${t4("semantic.customRequestBody")}</span>
|
|
25710
|
+
<textarea
|
|
25711
|
+
class="input mono"
|
|
25712
|
+
rows="6"
|
|
25713
|
+
value=${draft.openaiCompat.extraBodyText}
|
|
25714
|
+
onInput=${(e3) => {
|
|
25715
|
+
draftDirtyRef.current = true;
|
|
25716
|
+
setDraftDirty(true);
|
|
25717
|
+
setDraft({
|
|
25718
|
+
...draft,
|
|
25719
|
+
openaiCompat: {
|
|
25720
|
+
...draft.openaiCompat,
|
|
25721
|
+
extraBodyText: e3.target.value
|
|
25722
|
+
}
|
|
25723
|
+
});
|
|
25724
|
+
}}
|
|
25725
|
+
></textarea>
|
|
25726
|
+
</div>
|
|
25727
|
+
</details>
|
|
25728
|
+
${semanticValidation.error ? html4`<div style="color:var(--c-err);font-size:12px;margin-top:-2px">${semanticValidation.error}</div>` : null}
|
|
25729
|
+
`}
|
|
25730
|
+
<div style="display:flex;gap:6px;margin-top:10px">
|
|
25731
|
+
<button class="btn primary" disabled=${busy || semanticValidation.error !== null} onClick=${saveProviderConfig}>${t4("common.save")}</button>
|
|
25732
|
+
</div>
|
|
25733
|
+
</div>
|
|
25734
|
+
${info ? html4`<div><span class="pill info">${info}</span></div>` : null}
|
|
25735
|
+
|
|
25736
|
+
${indexReady ? html4`<${SemanticSearchSection} />` : null}
|
|
25240
25737
|
|
|
25241
|
-
${!binaryFound ? html4`
|
|
25738
|
+
${isOllama && !binaryFound ? html4`
|
|
25242
25739
|
<div class="card">
|
|
25243
25740
|
<div class="card-h"><span class="title">${t4("semantic.installOllama")}</span></div>
|
|
25244
25741
|
<div class="card-b" style="font-size:13px">
|
|
@@ -25251,7 +25748,7 @@ function SemanticPanel() {
|
|
|
25251
25748
|
</div>
|
|
25252
25749
|
</div>
|
|
25253
25750
|
` : null}
|
|
25254
|
-
${binaryFound && !daemonRunning ? html4`
|
|
25751
|
+
${isOllama && binaryFound && !daemonRunning ? html4`
|
|
25255
25752
|
<div class="card">
|
|
25256
25753
|
<div class="card-h"><span class="title">${t4("semantic.daemon")}</span></div>
|
|
25257
25754
|
<div class="card-b" style="font-size:13px">
|
|
@@ -25263,7 +25760,7 @@ function SemanticPanel() {
|
|
|
25263
25760
|
</div>
|
|
25264
25761
|
</div>
|
|
25265
25762
|
` : null}
|
|
25266
|
-
${daemonRunning && !modelPulled ? html4`
|
|
25763
|
+
${isOllama && daemonRunning && !modelPulled ? html4`
|
|
25267
25764
|
<div class="card">
|
|
25268
25765
|
<div class="card-h"><span class="title">${t4("semantic.model")}</span></div>
|
|
25269
25766
|
<div class="card-b" style="font-size:13px">
|
|
@@ -25283,6 +25780,14 @@ function SemanticPanel() {
|
|
|
25283
25780
|
</div>
|
|
25284
25781
|
</div>
|
|
25285
25782
|
` : null}
|
|
25783
|
+
${!isOllama ? html4`
|
|
25784
|
+
<div class="card">
|
|
25785
|
+
<div class="card-h"><span class="title">${t4("semantic.remoteProvider")}</span></div>
|
|
25786
|
+
<div class="card-b" style="font-size:13px;color:var(--fg-2)">
|
|
25787
|
+
${t4("semantic.remoteProviderDesc")}
|
|
25788
|
+
</div>
|
|
25789
|
+
</div>
|
|
25790
|
+
` : null}
|
|
25286
25791
|
|
|
25287
25792
|
${job ? html4`
|
|
25288
25793
|
${sectionH3(t4("semantic.job"))}
|
|
@@ -25295,42 +25800,42 @@ function SemanticPanel() {
|
|
|
25295
25800
|
<div class="card-h">
|
|
25296
25801
|
<span class="title">${t4("semantic.indexStatus")}</span>
|
|
25297
25802
|
<span class="meta">
|
|
25298
|
-
${idx?.exists ? html4`<span class="pill ok">${t4("semantic.builtStatus")}</span>` : html4`<span class="pill">${t4("system.none")}</span>`}
|
|
25803
|
+
${idx?.exists ? idx.compatible === false ? html4`<span class="pill warn">${t4("semantic.incompatibleStatus")}</span>` : html4`<span class="pill ok">${t4("semantic.builtStatus")}</span>` : html4`<span class="pill">${t4("system.none")}</span>`}
|
|
25299
25804
|
</span>
|
|
25300
25805
|
</div>
|
|
25301
25806
|
${idx?.exists ? html4`
|
|
25807
|
+
<div class="rail-kv"><span class="k">${t4("semantic.provider")}</span><span class="v">${idx.builtWith?.provider ?? idx.provider ?? provider}</span></div>
|
|
25302
25808
|
<div class="rail-kv"><span class="k">${t4("semantic.chunks")}</span><span class="v">${fmtNum(idx.chunks)}</span></div>
|
|
25303
25809
|
<div class="rail-kv"><span class="k">${t4("semantic.files")}</span><span class="v">${fmtNum(idx.files)}</span></div>
|
|
25304
|
-
<div class="rail-kv"><span class="k">${t4("semantic.model")}</span><span class="v" style="font-size:11px">${idx.model ?? modelName}</span></div>
|
|
25810
|
+
<div class="rail-kv"><span class="k">${t4("semantic.model")}</span><span class="v" style="font-size:11px">${idx.builtWith?.model ?? idx.model ?? modelName}</span></div>
|
|
25305
25811
|
<div class="rail-kv"><span class="k">${t4("semantic.dim")}</span><span class="v">${fmtNum(idx.dim)}</span></div>
|
|
25306
25812
|
<div class="rail-kv"><span class="k">${t4("semantic.size")}</span><span class="v">${fmtBytes(idx.sizeBytes)}</span></div>
|
|
25307
25813
|
<div class="rail-kv"><span class="k">${t4("semantic.lastBuild")}</span><span class="v">${fmtRelativeTime(idx.lastBuiltMs ?? null)}</span></div>
|
|
25308
|
-
|
|
25309
|
-
|
|
25310
|
-
|
|
25311
|
-
|
|
25312
|
-
|
|
25814
|
+
${idx.compatible === false ? html4`
|
|
25815
|
+
<div class="rail-kv"><span class="k">${t4("semantic.builtWith")}</span><span class="v" style="font-size:11px">${idx.builtWith?.provider} · ${idx.builtWith?.model}</span></div>
|
|
25816
|
+
<div class="rail-kv"><span class="k">${t4("semantic.currentTarget")}</span><span class="v" style="font-size:11px">${idx.current?.provider} · ${idx.current?.model}</span></div>
|
|
25817
|
+
<div style="color:var(--c-warn);font-size:12px;padding-top:8px">${t4("semantic.incompatibleHint")}</div>
|
|
25818
|
+
` : null}
|
|
25819
|
+
` : html4`<div style="color:var(--fg-3);font-size:12.5px;padding:6px 0">${t4("semantic.runIndexHint")}</div>`}
|
|
25313
25820
|
<div style="display:flex;gap:6px;margin-top:10px;flex-wrap:wrap">
|
|
25314
|
-
<button class="primary" disabled=${busy || running || !ready} onClick=${() => start(false)}>${
|
|
25315
|
-
${idx?.exists ? html4`<button disabled=${busy || running || !ready} onClick=${() => start(true)}>${t4("semantic.rebuild")}</button>` : null}
|
|
25821
|
+
<button class="primary" disabled=${busy || running || !ready || semanticDraftBlocked} onClick=${() => start(false)}>${indexReady ? t4("semantic.reIndex") : t4("semantic.build")}</button>
|
|
25822
|
+
${idx?.exists ? html4`<button disabled=${busy || running || !ready || semanticDraftBlocked} onClick=${() => start(true)}>${t4("semantic.rebuild")}</button>` : null}
|
|
25316
25823
|
${running ? html4`<button onClick=${stop} style="border-color:var(--c-err);color:var(--c-err)">${t4("semantic.stop")}</button>` : null}
|
|
25317
25824
|
</div>
|
|
25318
25825
|
</div>
|
|
25319
25826
|
|
|
25320
25827
|
<div class="card">
|
|
25321
|
-
<div class="card-h"><span class="title">${t4("semantic.ollama")}</span></div>
|
|
25322
|
-
|
|
25323
|
-
|
|
25324
|
-
|
|
25325
|
-
|
|
25326
|
-
|
|
25327
|
-
|
|
25328
|
-
|
|
25329
|
-
|
|
25330
|
-
|
|
25331
|
-
|
|
25332
|
-
<span class="v">${modelPulled ? html4`<span class="pill ok">${t4("semantic.pulled")}</span>` : html4`<span class="pill warn">${t4("semantic.missing")}</span>`}</span>
|
|
25333
|
-
</div>
|
|
25828
|
+
<div class="card-h"><span class="title">${isOllama ? t4("semantic.ollama") : t4("semantic.openaiCompat")}</span></div>
|
|
25829
|
+
${isOllama ? html4`
|
|
25830
|
+
<div class="rail-kv"><span class="k">${t4("semantic.binary")}</span><span class="v">${binaryFound ? html4`<span class="pill ok">${t4("semantic.found")}</span>` : html4`<span class="pill err">${t4("semantic.missing")}</span>`}</span></div>
|
|
25831
|
+
<div class="rail-kv"><span class="k">${t4("semantic.daemonStatus")}</span><span class="v">${daemonRunning ? html4`<span class="pill ok">${t4("semantic.up")}</span>` : html4`<span class="pill warn">${t4("semantic.down")}</span>`}</span></div>
|
|
25832
|
+
<div class="rail-kv"><span class="k">${t4("semantic.model")}</span><span class="v">${modelPulled ? html4`<span class="pill ok">${t4("semantic.pulled")}</span>` : html4`<span class="pill warn">${t4("semantic.missing")}</span>`}</span></div>
|
|
25833
|
+
` : html4`
|
|
25834
|
+
<div class="rail-kv"><span class="k">${t4("semantic.apiUrl")}</span><span class="v" style="font-size:11px;max-width:160px;overflow-wrap:anywhere;word-break:break-word;text-align:right">${remote?.baseUrl ?? draft.openaiCompat.baseUrl}</span></div>
|
|
25835
|
+
<div class="rail-kv"><span class="k">${t4("semantic.apiKey")}</span><span class="v">${remote?.apiKeySet ? html4`<span class="pill ok">${t4("semantic.found")}</span>` : html4`<span class="pill warn">${t4("semantic.missing")}</span>`}</span></div>
|
|
25836
|
+
<div class="rail-kv"><span class="k">${t4("semantic.model")}</span><span class="v" style="font-size:11px">${remote?.model ?? draft.openaiCompat.model}</span></div>
|
|
25837
|
+
<div class="rail-kv"><span class="k">${t4("semantic.extraBody")}</span><span class="v">${fmtNum(remote?.extraBodyKeys.length ?? 0)}</span></div>
|
|
25838
|
+
`}
|
|
25334
25839
|
</div>
|
|
25335
25840
|
|
|
25336
25841
|
<${SemanticExcludesCard} />
|
|
@@ -25338,6 +25843,44 @@ function SemanticPanel() {
|
|
|
25338
25843
|
</div>
|
|
25339
25844
|
`;
|
|
25340
25845
|
}
|
|
25846
|
+
function toConfigDraft(config) {
|
|
25847
|
+
return {
|
|
25848
|
+
provider: config.provider,
|
|
25849
|
+
ollama: {
|
|
25850
|
+
baseUrl: config.ollama.baseUrl,
|
|
25851
|
+
model: config.ollama.model
|
|
25852
|
+
},
|
|
25853
|
+
openaiCompat: {
|
|
25854
|
+
baseUrl: config.openaiCompat.baseUrl,
|
|
25855
|
+
apiKey: "",
|
|
25856
|
+
model: config.openaiCompat.model,
|
|
25857
|
+
extraBodyText: JSON.stringify(config.openaiCompat.extraBody ?? {}, null, 2),
|
|
25858
|
+
apiKeySet: config.openaiCompat.apiKeySet
|
|
25859
|
+
}
|
|
25860
|
+
};
|
|
25861
|
+
}
|
|
25862
|
+
function validateSemanticDraft(draft) {
|
|
25863
|
+
if (draft.provider !== "openai-compat") {
|
|
25864
|
+
return { extraBody: {}, error: null };
|
|
25865
|
+
}
|
|
25866
|
+
const raw = draft.openaiCompat.extraBodyText.trim();
|
|
25867
|
+
if (!raw) {
|
|
25868
|
+
return { extraBody: {}, error: null };
|
|
25869
|
+
}
|
|
25870
|
+
let parsed;
|
|
25871
|
+
try {
|
|
25872
|
+
parsed = JSON.parse(raw);
|
|
25873
|
+
} catch (err) {
|
|
25874
|
+
return {
|
|
25875
|
+
extraBody: {},
|
|
25876
|
+
error: t4("semantic.invalidCustomRequestBody", { error: err.message })
|
|
25877
|
+
};
|
|
25878
|
+
}
|
|
25879
|
+
if (!isPlainObject(parsed)) {
|
|
25880
|
+
return { extraBody: {}, error: t4("semantic.customRequestBodyMustBeObject") };
|
|
25881
|
+
}
|
|
25882
|
+
return { extraBody: parsed, error: null };
|
|
25883
|
+
}
|
|
25341
25884
|
function SemanticSearchSection() {
|
|
25342
25885
|
useLang();
|
|
25343
25886
|
const [query2, setQuery] = d2("");
|
|
@@ -25510,11 +26053,7 @@ function SemanticExcludesCard() {
|
|
|
25510
26053
|
<div class="card-h">
|
|
25511
26054
|
<span class="title">${t4("semantic.indexConfig")}</span>
|
|
25512
26055
|
<span class="meta">
|
|
25513
|
-
<a
|
|
25514
|
-
class="mono"
|
|
25515
|
-
style="color:var(--c-brand);text-decoration:none;font-size:11px;cursor:pointer"
|
|
25516
|
-
onClick=${reset}
|
|
25517
|
-
>${t4("semantic.reset")}</a>
|
|
26056
|
+
<a class="mono" style="color:var(--c-brand);text-decoration:none;font-size:11px;cursor:pointer" onClick=${reset}>${t4("semantic.reset")}</a>
|
|
25518
26057
|
</span>
|
|
25519
26058
|
</div>
|
|
25520
26059
|
${info ? html4`<div style="margin-bottom:8px"><span class="pill ok">${info}</span></div>` : null}
|
|
@@ -25546,11 +26085,7 @@ function SemanticExcludesCard() {
|
|
|
25546
26085
|
placeholder="**/*.test.ts"
|
|
25547
26086
|
/>
|
|
25548
26087
|
|
|
25549
|
-
<div
|
|
25550
|
-
class="checkbox-row"
|
|
25551
|
-
style="margin-top:8px;cursor:pointer"
|
|
25552
|
-
onClick=${() => setDraft({ ...draft, respectGitignore: !draft.respectGitignore })}
|
|
25553
|
-
>
|
|
26088
|
+
<div class="checkbox-row" style="margin-top:8px;cursor:pointer" onClick=${() => setDraft({ ...draft, respectGitignore: !draft.respectGitignore })}>
|
|
25554
26089
|
<span class=${`box ${draft.respectGitignore ? "on" : ""}`}>${draft.respectGitignore ? "\u2713" : ""}</span>
|
|
25555
26090
|
<span>${t4("semantic.respectGitignore")}</span>
|
|
25556
26091
|
</div>
|
|
@@ -25570,9 +26105,7 @@ function SemanticExcludesCard() {
|
|
|
25570
26105
|
</div>
|
|
25571
26106
|
|
|
25572
26107
|
<div style="display:flex;gap:6px;margin-top:10px">
|
|
25573
|
-
<button class="btn ghost" style="flex:1" disabled=${busy} onClick=${runPreview}>
|
|
25574
|
-
<span class="g">⊕</span><span>${t4("semantic.preview")}</span>
|
|
25575
|
-
</button>
|
|
26108
|
+
<button class="btn ghost" style="flex:1" disabled=${busy} onClick=${runPreview}><span class="g">⊕</span><span>${t4("semantic.preview")}</span></button>
|
|
25576
26109
|
<button class="btn primary" style="flex:1" disabled=${busy} onClick=${save}>${t4("common.save")}</button>
|
|
25577
26110
|
</div>
|
|
25578
26111
|
|
|
@@ -25597,19 +26130,17 @@ function ExcludesPreview({ preview }) {
|
|
|
25597
26130
|
].filter((k3) => (buckets[k3] || 0) > 0);
|
|
25598
26131
|
return html4`
|
|
25599
26132
|
<div class="excludes-preview">
|
|
25600
|
-
<div class="summary">
|
|
25601
|
-
${t4("semantic.previewSummary", { included: preview.filesIncluded, skipped: totalSkipped })}
|
|
25602
|
-
</div>
|
|
26133
|
+
<div class="summary">${t4("semantic.previewSummary", { included: preview.filesIncluded, skipped: totalSkipped })}</div>
|
|
25603
26134
|
${reasons.length === 0 ? html4`<div style="color:var(--fg-3)">${t4("semantic.nothingSkipped")}</div>` : reasons.map(
|
|
25604
26135
|
(r3) => html4`
|
|
25605
|
-
|
|
25606
|
-
|
|
25607
|
-
|
|
25608
|
-
|
|
25609
|
-
|
|
25610
|
-
|
|
25611
|
-
|
|
25612
|
-
|
|
26136
|
+
<details>
|
|
26137
|
+
<summary><strong>${r3}: ${buckets[r3]}</strong></summary>
|
|
26138
|
+
<ul>
|
|
26139
|
+
${(samples[r3] || []).map((p3) => html4`<li><code>${p3}</code></li>`)}
|
|
26140
|
+
${(buckets[r3] || 0) > (samples[r3] || []).length ? html4`<li style="color:var(--fg-3)">…${(buckets[r3] || 0) - (samples[r3] || []).length} more</li>` : null}
|
|
26141
|
+
</ul>
|
|
26142
|
+
</details>
|
|
26143
|
+
`
|
|
25613
26144
|
)}
|
|
25614
26145
|
${preview.sampleIncluded?.length ? html4`
|
|
25615
26146
|
<details>
|
|
@@ -25642,10 +26173,7 @@ function ChipFormRow({
|
|
|
25642
26173
|
};
|
|
25643
26174
|
return html4`
|
|
25644
26175
|
<div class="form-row">
|
|
25645
|
-
<span class="lbl">
|
|
25646
|
-
${label}
|
|
25647
|
-
${sub ? html4`<span style="color:var(--fg-3);font-weight:400;text-transform:none;letter-spacing:0"> · ${sub}</span>` : null}
|
|
25648
|
-
</span>
|
|
26176
|
+
<span class="lbl">${label}${sub ? html4`<span style="color:var(--fg-3);font-weight:400;text-transform:none;letter-spacing:0"> · ${sub}</span>` : null}</span>
|
|
25649
26177
|
<div style="display:flex;flex-wrap:wrap;gap:4px">
|
|
25650
26178
|
${value.map(
|
|
25651
26179
|
(e3) => html4`
|
|
@@ -25676,22 +26204,27 @@ function ChipFormRow({
|
|
|
25676
26204
|
function SemanticJobView({ job, running }) {
|
|
25677
26205
|
useLang();
|
|
25678
26206
|
const phaseLabel = {
|
|
26207
|
+
setup: t4("semantic.phaseSetup"),
|
|
25679
26208
|
scan: t4("semantic.phaseScan"),
|
|
25680
26209
|
embed: t4("semantic.phaseEmbed"),
|
|
25681
26210
|
write: t4("semantic.phaseWrite"),
|
|
25682
26211
|
done: t4("semantic.phaseDone"),
|
|
25683
|
-
error: t4("semantic.phaseError")
|
|
26212
|
+
error: t4("semantic.phaseError"),
|
|
26213
|
+
cancelled: t4("semantic.phaseCancelled")
|
|
25684
26214
|
}[job.phase] ?? job.phase;
|
|
25685
26215
|
const total = job.chunksTotal ?? 0;
|
|
25686
26216
|
const doneN = job.chunksDone ?? 0;
|
|
25687
26217
|
const ratio = total > 0 ? Math.min(1, doneN / total) : 0;
|
|
25688
|
-
const
|
|
26218
|
+
const elapsedBase = job.finishedAt ?? Date.now();
|
|
26219
|
+
const elapsedSeconds = (elapsedBase - job.startedAt) / 1e3;
|
|
26220
|
+
const elapsed = elapsedSeconds < 0.1 ? "<0.1s" : `${elapsedSeconds.toFixed(1)}s`;
|
|
26221
|
+
const phaseSummary = job.phase === "error" && job.lastPhase === "setup" ? t4("semantic.setupFailed") : phaseLabel;
|
|
25689
26222
|
return html4`
|
|
25690
26223
|
<div class="kv">
|
|
25691
26224
|
<div><span class="kv-key">phase</span>
|
|
25692
|
-
<span class=${`pill ${job.phase === "error" ? "pill-err" : running ? "pill-active" : "pill-dim"}`}>${
|
|
25693
|
-
${job.aborted ? html4`<span class="pill warn" style="margin-left: 6px;">${t4("semantic.stopping")}</span>` : null}
|
|
25694
|
-
<span style="color:var(--fg-3);margin-left:8px">${elapsed}
|
|
26225
|
+
<span class=${`pill ${job.phase === "error" ? "pill-err" : job.phase === "cancelled" ? "warn" : running ? "pill-active" : "pill-dim"}`}>${phaseSummary}</span>
|
|
26226
|
+
${job.aborted && running ? html4`<span class="pill warn" style="margin-left: 6px;">${t4("semantic.stopping")}</span>` : null}
|
|
26227
|
+
<span style="color:var(--fg-3);margin-left:8px">${elapsed}</span>
|
|
25695
26228
|
</div>
|
|
25696
26229
|
${job.filesScanned !== null && job.filesScanned !== void 0 ? html4`<div><span class="kv-key">${t4("semantic.files")}</span>${t4("semantic.scanned", { count: job.filesScanned })}${job.filesChanged != null ? ` \xB7 ${t4("semantic.changed", { count: job.filesChanged })}` : ""}${job.filesSkipped ? ` \xB7 ${t4("semantic.skipped", { count: job.filesSkipped })}` : ""}</div>` : null}
|
|
25697
26230
|
${total > 0 ? html4`
|
|
@@ -25725,6 +26258,14 @@ function SkipBucketsView({ buckets }) {
|
|
|
25725
26258
|
const parts = order.filter(([k3]) => (buckets[k3] || 0) > 0).map(([k3, label]) => `${label}: ${buckets[k3]}`);
|
|
25726
26259
|
return html4`<div><span class="kv-key">${t4("semantic.skipped")}</span>${t4("semantic.skippedFiles", { total, details: parts.join(", ") })}</div>`;
|
|
25727
26260
|
}
|
|
26261
|
+
function isActiveSemanticPhase(phase) {
|
|
26262
|
+
return phase === "setup" || phase === "scan" || phase === "embed" || phase === "write";
|
|
26263
|
+
}
|
|
26264
|
+
function isPlainObject(value) {
|
|
26265
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
26266
|
+
const proto = Object.getPrototypeOf(value);
|
|
26267
|
+
return proto === Object.prototype || proto === null;
|
|
26268
|
+
}
|
|
25728
26269
|
|
|
25729
26270
|
// dashboard/src/panels/sessions.ts
|
|
25730
26271
|
function SessionsPanel() {
|
|
@@ -25828,7 +26369,314 @@ function SessionsPanel() {
|
|
|
25828
26369
|
`;
|
|
25829
26370
|
}
|
|
25830
26371
|
|
|
26372
|
+
// dashboard/src/lib/loop-control.ts
|
|
26373
|
+
var INTERVAL_PRESETS_MS = [
|
|
26374
|
+
{ ms: 3e4, label: "30s" },
|
|
26375
|
+
{ ms: 6e4, label: "1m" },
|
|
26376
|
+
{ ms: 5 * 6e4, label: "5m" },
|
|
26377
|
+
{ ms: 15 * 6e4, label: "15m" },
|
|
26378
|
+
{ ms: 60 * 6e4, label: "1h" },
|
|
26379
|
+
{ ms: 6 * 60 * 6e4, label: "6h" }
|
|
26380
|
+
];
|
|
26381
|
+
var UNIT_TO_MS = {
|
|
26382
|
+
s: 1e3,
|
|
26383
|
+
m: 6e4,
|
|
26384
|
+
h: 60 * 6e4
|
|
26385
|
+
};
|
|
26386
|
+
var MIN_INTERVAL_MS = 5e3;
|
|
26387
|
+
var MAX_INTERVAL_MS = 6 * 60 * 6e4;
|
|
26388
|
+
function parseCustomInterval(value, unit) {
|
|
26389
|
+
const n3 = Number.parseFloat(value);
|
|
26390
|
+
if (!Number.isFinite(n3) || n3 <= 0) return null;
|
|
26391
|
+
const ms = Math.round(n3 * UNIT_TO_MS[unit]);
|
|
26392
|
+
if (ms < MIN_INTERVAL_MS || ms > MAX_INTERVAL_MS) return null;
|
|
26393
|
+
return ms;
|
|
26394
|
+
}
|
|
26395
|
+
function formatRemaining(ms) {
|
|
26396
|
+
const safe = Math.max(0, Math.floor(ms / 1e3));
|
|
26397
|
+
const h3 = Math.floor(safe / 3600);
|
|
26398
|
+
const m3 = Math.floor(safe % 3600 / 60);
|
|
26399
|
+
const s3 = safe % 60;
|
|
26400
|
+
if (h3 > 0) return m3 > 0 ? `${h3}h ${m3}m` : `${h3}h`;
|
|
26401
|
+
if (m3 > 0) return s3 > 0 ? `${m3}m ${s3}s` : `${m3}m`;
|
|
26402
|
+
return `${s3}s`;
|
|
26403
|
+
}
|
|
26404
|
+
|
|
25831
26405
|
// dashboard/src/panels/settings.ts
|
|
26406
|
+
function fmtUsd22(n3) {
|
|
26407
|
+
return `$${n3.toFixed(n3 < 1 ? 4 : 2)}`;
|
|
26408
|
+
}
|
|
26409
|
+
function formatPricing(p3) {
|
|
26410
|
+
if (!p3) return null;
|
|
26411
|
+
return t4("settings.modelPricingLine", {
|
|
26412
|
+
hit: p3.inputCacheHit.toFixed(3),
|
|
26413
|
+
miss: p3.inputCacheMiss.toFixed(3),
|
|
26414
|
+
out: p3.output.toFixed(3)
|
|
26415
|
+
});
|
|
26416
|
+
}
|
|
26417
|
+
function ModelRow({
|
|
26418
|
+
current,
|
|
26419
|
+
catalog,
|
|
26420
|
+
saving,
|
|
26421
|
+
onPick
|
|
26422
|
+
}) {
|
|
26423
|
+
const list2 = catalog?.models ?? null;
|
|
26424
|
+
const ready = list2 && list2.length > 0;
|
|
26425
|
+
if (!ready) {
|
|
26426
|
+
return html4`<code class="mono">${current ?? "\u2014"}</code>`;
|
|
26427
|
+
}
|
|
26428
|
+
const options2 = list2.includes(current) ? list2 : [current, ...list2];
|
|
26429
|
+
const price = catalog?.pricing[current];
|
|
26430
|
+
return html4`
|
|
26431
|
+
<span style="display:inline-flex;flex-direction:column;gap:4px">
|
|
26432
|
+
<select
|
|
26433
|
+
value=${current}
|
|
26434
|
+
onChange=${(e3) => {
|
|
26435
|
+
const next = e3.target.value;
|
|
26436
|
+
if (next && next !== current) onPick(next);
|
|
26437
|
+
}}
|
|
26438
|
+
disabled=${saving}
|
|
26439
|
+
style="font-family:var(--font-mono);min-width:200px"
|
|
26440
|
+
>
|
|
26441
|
+
${options2.map((m3) => html4`<option key=${m3} value=${m3}>${m3}</option>`)}
|
|
26442
|
+
</select>
|
|
26443
|
+
${price ? html4`<span style="color:var(--fg-3);font-size:11px;font-family:var(--font-mono)">${formatPricing(price)}</span>` : null}
|
|
26444
|
+
</span>
|
|
26445
|
+
`;
|
|
26446
|
+
}
|
|
26447
|
+
function BudgetGauge({ state }) {
|
|
26448
|
+
if (state.kind === "off") return null;
|
|
26449
|
+
const tone = budgetTone(state);
|
|
26450
|
+
const fill = Math.min(100, state.pct);
|
|
26451
|
+
const valueColor = tone === "err" ? "color:var(--c-err)" : tone === "warn" ? "color:var(--c-warn)" : "color:var(--fg-1)";
|
|
26452
|
+
return html4`
|
|
26453
|
+
<div style="display:flex;flex-direction:column;gap:6px">
|
|
26454
|
+
<div style="display:flex;justify-content:space-between;align-items:baseline;font-size:13px">
|
|
26455
|
+
<span style=${valueColor}>
|
|
26456
|
+
<strong style="font-family:var(--font-mono)">${fmtUsd22(state.spent)}</strong>
|
|
26457
|
+
<span style="color:var(--fg-3)"> ${t4("settings.budgetOf")} </span>
|
|
26458
|
+
<strong style="font-family:var(--font-mono)">${fmtUsd22(state.cap)}</strong>
|
|
26459
|
+
</span>
|
|
26460
|
+
<span style=${`font-family:var(--font-mono);font-size:11px;${valueColor}`}>${state.pct.toFixed(1)}%</span>
|
|
26461
|
+
</div>
|
|
26462
|
+
<div class=${`progress ${tone}`}><div class="progress-fill" style=${`width:${fill}%`}></div></div>
|
|
26463
|
+
<span style="color:var(--fg-3);font-size:11px">
|
|
26464
|
+
${state.kind === "exhausted" ? t4("settings.budgetRefusing") : state.kind === "warn" ? t4("settings.budgetWarnLine") : t4("settings.budgetIdleLine")}
|
|
26465
|
+
</span>
|
|
26466
|
+
</div>
|
|
26467
|
+
`;
|
|
26468
|
+
}
|
|
26469
|
+
function BudgetSection({ state, saving, onSetCap, onClear }) {
|
|
26470
|
+
const [custom, setCustom] = d2("");
|
|
26471
|
+
const submitCustom = () => {
|
|
26472
|
+
const n3 = Number.parseFloat(custom);
|
|
26473
|
+
if (Number.isFinite(n3) && n3 > 0) {
|
|
26474
|
+
onSetCap(n3);
|
|
26475
|
+
setCustom("");
|
|
26476
|
+
}
|
|
26477
|
+
};
|
|
26478
|
+
const quickButtons = (caps) => caps.map(
|
|
26479
|
+
(c3) => html4`
|
|
26480
|
+
<button
|
|
26481
|
+
key=${c3}
|
|
26482
|
+
class="btn"
|
|
26483
|
+
style="font-family:var(--font-mono)"
|
|
26484
|
+
disabled=${saving}
|
|
26485
|
+
onClick=${() => onSetCap(c3)}
|
|
26486
|
+
>$${c3}</button>
|
|
26487
|
+
`
|
|
26488
|
+
);
|
|
26489
|
+
const customField = html4`
|
|
26490
|
+
<span style="display:inline-flex;align-items:center;gap:4px;margin-left:auto">
|
|
26491
|
+
<span style="color:var(--fg-3);font-size:11px">${t4("settings.budgetCustom")}</span>
|
|
26492
|
+
<input
|
|
26493
|
+
type="number"
|
|
26494
|
+
min="0.01"
|
|
26495
|
+
step="0.01"
|
|
26496
|
+
value=${custom}
|
|
26497
|
+
placeholder="0.00"
|
|
26498
|
+
onInput=${(e3) => setCustom(e3.target.value)}
|
|
26499
|
+
onKeyDown=${(e3) => {
|
|
26500
|
+
if (e3.key === "Enter") submitCustom();
|
|
26501
|
+
}}
|
|
26502
|
+
style="width:72px;font-family:var(--font-mono)"
|
|
26503
|
+
disabled=${saving}
|
|
26504
|
+
/>
|
|
26505
|
+
<button
|
|
26506
|
+
class="btn primary"
|
|
26507
|
+
disabled=${saving || !(Number.parseFloat(custom) > 0)}
|
|
26508
|
+
onClick=${submitCustom}
|
|
26509
|
+
>→</button>
|
|
26510
|
+
</span>
|
|
26511
|
+
`;
|
|
26512
|
+
return html4`
|
|
26513
|
+
<div class="card" style="display:flex;flex-direction:column;gap:12px">
|
|
26514
|
+
<${BudgetGauge} state=${state} />
|
|
26515
|
+
|
|
26516
|
+
${state.kind === "off" ? html4`
|
|
26517
|
+
<div>
|
|
26518
|
+
<div style="color:var(--fg-3);font-size:11px;margin-bottom:6px">${t4("settings.budgetSetCap")}</div>
|
|
26519
|
+
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
|
|
26520
|
+
${quickButtons(QUICK_CAPS_USD)}
|
|
26521
|
+
${customField}
|
|
26522
|
+
</div>
|
|
26523
|
+
</div>
|
|
26524
|
+
` : state.kind === "warn" || state.kind === "exhausted" ? html4`
|
|
26525
|
+
<div>
|
|
26526
|
+
<div style="color:var(--fg-3);font-size:11px;margin-bottom:6px">${t4("settings.budgetBumpHint")}</div>
|
|
26527
|
+
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
|
|
26528
|
+
${bumpSuggestions(state.cap).map(
|
|
26529
|
+
(next) => html4`
|
|
26530
|
+
<button
|
|
26531
|
+
key=${next}
|
|
26532
|
+
class="btn primary"
|
|
26533
|
+
style="font-family:var(--font-mono)"
|
|
26534
|
+
disabled=${saving}
|
|
26535
|
+
onClick=${() => onSetCap(next)}
|
|
26536
|
+
>→ $${next % 1 === 0 ? next : next.toFixed(2)}</button>
|
|
26537
|
+
`
|
|
26538
|
+
)}
|
|
26539
|
+
${customField}
|
|
26540
|
+
</div>
|
|
26541
|
+
<div style="margin-top:8px">
|
|
26542
|
+
<button class="btn" disabled=${saving} onClick=${onClear}>${t4("settings.budgetClear")}</button>
|
|
26543
|
+
</div>
|
|
26544
|
+
</div>
|
|
26545
|
+
` : html4`
|
|
26546
|
+
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
|
|
26547
|
+
${bumpSuggestions(state.cap).map(
|
|
26548
|
+
(next) => html4`
|
|
26549
|
+
<button
|
|
26550
|
+
key=${next}
|
|
26551
|
+
class="btn"
|
|
26552
|
+
style="font-family:var(--font-mono)"
|
|
26553
|
+
disabled=${saving}
|
|
26554
|
+
onClick=${() => onSetCap(next)}
|
|
26555
|
+
>→ $${next % 1 === 0 ? next : next.toFixed(2)}</button>
|
|
26556
|
+
`
|
|
26557
|
+
)}
|
|
26558
|
+
${customField}
|
|
26559
|
+
<button
|
|
26560
|
+
class="btn"
|
|
26561
|
+
style="margin-left:8px"
|
|
26562
|
+
disabled=${saving}
|
|
26563
|
+
onClick=${onClear}
|
|
26564
|
+
>${t4("settings.budgetClear")}</button>
|
|
26565
|
+
</div>
|
|
26566
|
+
`}
|
|
26567
|
+
</div>
|
|
26568
|
+
`;
|
|
26569
|
+
}
|
|
26570
|
+
function LoopSection({
|
|
26571
|
+
status,
|
|
26572
|
+
remainingMs,
|
|
26573
|
+
avgIterCostUsd,
|
|
26574
|
+
busy,
|
|
26575
|
+
onStart,
|
|
26576
|
+
onStop
|
|
26577
|
+
}) {
|
|
26578
|
+
const [intervalMs, setIntervalMs] = d2(INTERVAL_PRESETS_MS[1].ms);
|
|
26579
|
+
const [prompt, setPrompt] = d2("");
|
|
26580
|
+
const [customValue, setCustomValue] = d2("");
|
|
26581
|
+
const [customUnit, setCustomUnit] = d2("m");
|
|
26582
|
+
if (status) {
|
|
26583
|
+
return html4`
|
|
26584
|
+
<div class="card" style="display:flex;flex-direction:column;gap:10px">
|
|
26585
|
+
<div style="display:flex;justify-content:space-between;align-items:baseline">
|
|
26586
|
+
<span style="color:var(--c-warn);font-family:var(--font-mono);font-size:11px">⟳ ${t4("settings.loopRunning")}</span>
|
|
26587
|
+
<span style="color:var(--fg-3);font-size:11px">
|
|
26588
|
+
${t4("settings.loopIter", { iter: status.iter })} · ${t4("settings.loopFiresIn", { remaining: formatRemaining(remainingMs) })}
|
|
26589
|
+
</span>
|
|
26590
|
+
</div>
|
|
26591
|
+
<div style="background:var(--bg-elev-2);border:1px solid var(--bd);border-radius:var(--r);padding:8px 10px;font-family:var(--font-mono);font-size:12px;color:var(--fg-1);white-space:pre-wrap;max-height:120px;overflow-y:auto">${status.prompt}</div>
|
|
26592
|
+
<div>
|
|
26593
|
+
<button class="btn danger" disabled=${busy} onClick=${onStop}>${t4("settings.loopStop")}</button>
|
|
26594
|
+
</div>
|
|
26595
|
+
</div>
|
|
26596
|
+
`;
|
|
26597
|
+
}
|
|
26598
|
+
const customMs = parseCustomInterval(customValue, customUnit);
|
|
26599
|
+
const canStart = !busy && intervalMs > 0 && prompt.trim().length > 0;
|
|
26600
|
+
return html4`
|
|
26601
|
+
<div class="card" style="display:flex;flex-direction:column;gap:10px">
|
|
26602
|
+
<div style="color:var(--fg-3);font-size:11px">
|
|
26603
|
+
${t4("settings.loopIdleHint")}
|
|
26604
|
+
${typeof avgIterCostUsd === "number" && avgIterCostUsd > 0 ? html4` ${t4("settings.loopCostHint", { cost: `$${avgIterCostUsd.toFixed(4)}` })}` : null}
|
|
26605
|
+
</div>
|
|
26606
|
+
<div style="display:flex;flex-direction:column;gap:6px">
|
|
26607
|
+
<span style="color:var(--fg-3);font-size:11px">${t4("settings.loopInterval")}</span>
|
|
26608
|
+
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
|
|
26609
|
+
${INTERVAL_PRESETS_MS.map(
|
|
26610
|
+
(p3) => html4`
|
|
26611
|
+
<button
|
|
26612
|
+
key=${p3.ms}
|
|
26613
|
+
class=${`btn ${intervalMs === p3.ms && customValue === "" ? "primary" : ""}`}
|
|
26614
|
+
style="font-family:var(--font-mono)"
|
|
26615
|
+
disabled=${busy}
|
|
26616
|
+
onClick=${() => {
|
|
26617
|
+
setIntervalMs(p3.ms);
|
|
26618
|
+
setCustomValue("");
|
|
26619
|
+
}}
|
|
26620
|
+
>${p3.label}</button>
|
|
26621
|
+
`
|
|
26622
|
+
)}
|
|
26623
|
+
<span style="display:inline-flex;align-items:center;gap:4px;margin-left:auto">
|
|
26624
|
+
<span style="color:var(--fg-3);font-size:11px">${t4("settings.loopCustom")}</span>
|
|
26625
|
+
<input
|
|
26626
|
+
type="number"
|
|
26627
|
+
min="1"
|
|
26628
|
+
step="1"
|
|
26629
|
+
value=${customValue}
|
|
26630
|
+
onInput=${(e3) => {
|
|
26631
|
+
const raw = e3.target.value;
|
|
26632
|
+
setCustomValue(raw);
|
|
26633
|
+
const ms = parseCustomInterval(raw, customUnit);
|
|
26634
|
+
if (ms !== null) setIntervalMs(ms);
|
|
26635
|
+
}}
|
|
26636
|
+
style="width:64px;font-family:var(--font-mono)"
|
|
26637
|
+
disabled=${busy}
|
|
26638
|
+
/>
|
|
26639
|
+
<select
|
|
26640
|
+
value=${customUnit}
|
|
26641
|
+
onChange=${(e3) => {
|
|
26642
|
+
const next = e3.target.value;
|
|
26643
|
+
setCustomUnit(next);
|
|
26644
|
+
if (customValue) {
|
|
26645
|
+
const ms = parseCustomInterval(customValue, next);
|
|
26646
|
+
if (ms !== null) setIntervalMs(ms);
|
|
26647
|
+
}
|
|
26648
|
+
}}
|
|
26649
|
+
disabled=${busy}
|
|
26650
|
+
>
|
|
26651
|
+
<option value="s">s</option>
|
|
26652
|
+
<option value="m">m</option>
|
|
26653
|
+
<option value="h">h</option>
|
|
26654
|
+
</select>
|
|
26655
|
+
</span>
|
|
26656
|
+
</div>
|
|
26657
|
+
${customValue && customMs === null ? html4`<span style="color:var(--c-err);font-size:11px">${t4("settings.loopRangeError")}</span>` : null}
|
|
26658
|
+
</div>
|
|
26659
|
+
<div style="display:flex;flex-direction:column;gap:6px">
|
|
26660
|
+
<span style="color:var(--fg-3);font-size:11px">${t4("settings.loopPrompt")}</span>
|
|
26661
|
+
<textarea
|
|
26662
|
+
rows="3"
|
|
26663
|
+
placeholder=${t4("settings.loopPromptPlaceholder")}
|
|
26664
|
+
value=${prompt}
|
|
26665
|
+
onInput=${(e3) => setPrompt(e3.target.value)}
|
|
26666
|
+
style="width:100%;font-family:var(--font-mono);resize:vertical"
|
|
26667
|
+
disabled=${busy}
|
|
26668
|
+
></textarea>
|
|
26669
|
+
</div>
|
|
26670
|
+
<div>
|
|
26671
|
+
<button
|
|
26672
|
+
class="btn primary"
|
|
26673
|
+
disabled=${!canStart}
|
|
26674
|
+
onClick=${() => onStart(intervalMs, prompt.trim())}
|
|
26675
|
+
>${t4("settings.loopStart")}</button>
|
|
26676
|
+
</div>
|
|
26677
|
+
</div>
|
|
26678
|
+
`;
|
|
26679
|
+
}
|
|
25832
26680
|
function SettingsPanel() {
|
|
25833
26681
|
useLang();
|
|
25834
26682
|
const [data, setData] = d2(null);
|
|
@@ -25836,6 +26684,12 @@ function SettingsPanel() {
|
|
|
25836
26684
|
const [saving, setSaving] = d2(false);
|
|
25837
26685
|
const [saved, setSaved] = d2(null);
|
|
25838
26686
|
const [draft, setDraft] = d2({});
|
|
26687
|
+
const [catalog, setCatalog] = d2(null);
|
|
26688
|
+
const [loopStatus, setLoopStatus] = d2(null);
|
|
26689
|
+
const [loopAvgCost, setLoopAvgCost] = d2(null);
|
|
26690
|
+
const [loopBusy, setLoopBusy] = d2(false);
|
|
26691
|
+
const lastStatusSyncRef = A2(0);
|
|
26692
|
+
const [now, setNow] = d2(() => Date.now());
|
|
25839
26693
|
const load = q2(async () => {
|
|
25840
26694
|
try {
|
|
25841
26695
|
const r3 = await api("/settings");
|
|
@@ -25848,6 +26702,64 @@ function SettingsPanel() {
|
|
|
25848
26702
|
y2(() => {
|
|
25849
26703
|
load();
|
|
25850
26704
|
}, [load]);
|
|
26705
|
+
y2(() => {
|
|
26706
|
+
api("/models").then(setCatalog).catch(() => void 0);
|
|
26707
|
+
}, []);
|
|
26708
|
+
const refreshLoop = q2(async () => {
|
|
26709
|
+
try {
|
|
26710
|
+
const r3 = await api("/loop/status");
|
|
26711
|
+
setLoopStatus(r3.status);
|
|
26712
|
+
lastStatusSyncRef.current = Date.now();
|
|
26713
|
+
} catch {
|
|
26714
|
+
}
|
|
26715
|
+
try {
|
|
26716
|
+
const r3 = await api("/overview");
|
|
26717
|
+
setLoopAvgCost(r3.stats?.lastTurnCostUsd ?? null);
|
|
26718
|
+
} catch {
|
|
26719
|
+
}
|
|
26720
|
+
}, []);
|
|
26721
|
+
y2(() => {
|
|
26722
|
+
let cancelled = false;
|
|
26723
|
+
refreshLoop();
|
|
26724
|
+
const id = setInterval(() => {
|
|
26725
|
+
if (!cancelled) refreshLoop();
|
|
26726
|
+
}, 5e3);
|
|
26727
|
+
return () => {
|
|
26728
|
+
cancelled = true;
|
|
26729
|
+
clearInterval(id);
|
|
26730
|
+
};
|
|
26731
|
+
}, [refreshLoop]);
|
|
26732
|
+
y2(() => {
|
|
26733
|
+
if (!loopStatus) return;
|
|
26734
|
+
const id = setInterval(() => setNow(Date.now()), 1e3);
|
|
26735
|
+
return () => clearInterval(id);
|
|
26736
|
+
}, [loopStatus]);
|
|
26737
|
+
const remainingMs = loopStatus ? Math.max(0, loopStatus.nextFireMs - (now - lastStatusSyncRef.current)) : 0;
|
|
26738
|
+
const startLoop = q2(
|
|
26739
|
+
async (intervalMs, prompt) => {
|
|
26740
|
+
setLoopBusy(true);
|
|
26741
|
+
try {
|
|
26742
|
+
await api("/loop/start", { method: "POST", body: { intervalMs, prompt } });
|
|
26743
|
+
await refreshLoop();
|
|
26744
|
+
} catch (err) {
|
|
26745
|
+
setError(err.message);
|
|
26746
|
+
} finally {
|
|
26747
|
+
setLoopBusy(false);
|
|
26748
|
+
}
|
|
26749
|
+
},
|
|
26750
|
+
[refreshLoop]
|
|
26751
|
+
);
|
|
26752
|
+
const stopLoop = q2(async () => {
|
|
26753
|
+
setLoopBusy(true);
|
|
26754
|
+
try {
|
|
26755
|
+
await api("/loop/stop", { method: "POST" });
|
|
26756
|
+
await refreshLoop();
|
|
26757
|
+
} catch (err) {
|
|
26758
|
+
setError(err.message);
|
|
26759
|
+
} finally {
|
|
26760
|
+
setLoopBusy(false);
|
|
26761
|
+
}
|
|
26762
|
+
}, [refreshLoop]);
|
|
25851
26763
|
const save = q2(
|
|
25852
26764
|
async (fields) => {
|
|
25853
26765
|
setSaving(true);
|
|
@@ -25991,11 +26903,50 @@ function SettingsPanel() {
|
|
|
25991
26903
|
)}
|
|
25992
26904
|
</div>
|
|
25993
26905
|
|
|
26906
|
+
${sectionH3(t4("settings.sectionCompute"))}
|
|
26907
|
+
<div class="card">
|
|
26908
|
+
${fieldRow(
|
|
26909
|
+
t4("settings.proNext"),
|
|
26910
|
+
html4`
|
|
26911
|
+
<button
|
|
26912
|
+
class=${`btn ${v3.proNext ? "primary" : ""}`}
|
|
26913
|
+
onClick=${() => save({ proNext: !v3.proNext })}
|
|
26914
|
+
disabled=${saving}
|
|
26915
|
+
>${v3.proNext ? t4("settings.proArmed") : t4("settings.proArm")}</button>
|
|
26916
|
+
`,
|
|
26917
|
+
t4("settings.proNextNote")
|
|
26918
|
+
)}
|
|
26919
|
+
</div>
|
|
26920
|
+
|
|
26921
|
+
${sectionH3(t4("settings.sectionBudget"))}
|
|
26922
|
+
<${BudgetSection}
|
|
26923
|
+
state=${deriveBudgetState(v3.budgetUsd, v3.sessionSpendUsd)}
|
|
26924
|
+
saving=${saving}
|
|
26925
|
+
onSetCap=${(usd) => save({ budgetUsd: usd })}
|
|
26926
|
+
onClear=${() => save({ budgetUsd: null })}
|
|
26927
|
+
/>
|
|
26928
|
+
|
|
26929
|
+
${sectionH3(t4("settings.sectionLoop"))}
|
|
26930
|
+
<${LoopSection}
|
|
26931
|
+
status=${loopStatus}
|
|
26932
|
+
remainingMs=${remainingMs}
|
|
26933
|
+
avgIterCostUsd=${loopAvgCost}
|
|
26934
|
+
busy=${loopBusy}
|
|
26935
|
+
onStart=${startLoop}
|
|
26936
|
+
onStop=${stopLoop}
|
|
26937
|
+
/>
|
|
26938
|
+
|
|
25994
26939
|
${sectionH3(t4("settings.sectionRuntime"))}
|
|
25995
26940
|
<div class="card">
|
|
25996
26941
|
${fieldRow(
|
|
25997
26942
|
t4("settings.activeModel"),
|
|
25998
|
-
html4
|
|
26943
|
+
html4`<${ModelRow}
|
|
26944
|
+
current=${v3.model ?? "\u2014"}
|
|
26945
|
+
catalog=${catalog}
|
|
26946
|
+
saving=${saving}
|
|
26947
|
+
onPick=${(m3) => save({ model: m3 })}
|
|
26948
|
+
/>`,
|
|
26949
|
+
t4("settings.appliesNextTurn")
|
|
25999
26950
|
)}
|
|
26000
26951
|
${fieldRow(
|
|
26001
26952
|
t4("settings.editMode"),
|