reasonix 0.30.4 → 0.31.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 +23 -0
- package/README.zh-CN.md +23 -0
- package/dashboard/app.css +149 -0
- package/dashboard/dist/app.js +691 -8
- package/dashboard/dist/app.js.map +1 -1
- package/dist/cli/{chunk-COFBA5FV.js → chunk-VWFJNLIK.js} +50 -8
- package/dist/cli/chunk-VWFJNLIK.js.map +1 -0
- package/dist/cli/index.js +2148 -1075
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-VF7B6BWR.js → prompt-XHICFAYN.js} +2 -2
- package/dist/index.d.ts +22 -4
- package/dist/index.js +2279 -362
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-COFBA5FV.js.map +0 -1
- /package/dist/cli/{prompt-VF7B6BWR.js.map → prompt-XHICFAYN.js.map} +0 -0
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)",
|
|
@@ -19709,7 +19739,17 @@ var en = {
|
|
|
19709
19739
|
accept: "Accept",
|
|
19710
19740
|
reject: "Reject",
|
|
19711
19741
|
arguments: "arguments",
|
|
19712
|
-
revisePlaceholder: "What needs to change before the next step? Leave blank to just continue."
|
|
19742
|
+
revisePlaceholder: "What needs to change before the next step? Leave blank to just continue.",
|
|
19743
|
+
pickerFilter: "Filter\u2026",
|
|
19744
|
+
pickerEmpty: "Nothing to show.",
|
|
19745
|
+
pickerLoadMore: "Load more",
|
|
19746
|
+
pickerPick: "Open",
|
|
19747
|
+
pickerInstall: "Install",
|
|
19748
|
+
pickerUninstall: "Uninstall",
|
|
19749
|
+
pickerRename: "Rename\u2026",
|
|
19750
|
+
pickerNew: "New\u2026",
|
|
19751
|
+
pickerNewPlaceholder: "Name (leave blank for default)",
|
|
19752
|
+
viewerClose: "Close"
|
|
19713
19753
|
}
|
|
19714
19754
|
};
|
|
19715
19755
|
|
|
@@ -19777,8 +19817,36 @@ var zhCN = {
|
|
|
19777
19817
|
effortHigh: "high\uFF08\u66F4\u4FBF\u5B9C / \u66F4\u5FEB\uFF09",
|
|
19778
19818
|
webSearch: "\u7F51\u9875\u641C\u7D22",
|
|
19779
19819
|
webSearchNote: "web_fetch + web_search \u5DE5\u5177",
|
|
19820
|
+
sectionCompute: "\u8BA1\u7B97",
|
|
19821
|
+
proNext: "/pro \u5355\u8F6E",
|
|
19822
|
+
proArm: "\u4E3A\u4E0B\u4E00\u8F6E\u88C5\u5907",
|
|
19823
|
+
proArmed: "\u5DF2\u88C5\u5907 \u2014 \u4E0B\u4E00\u8F6E\u540E\u81EA\u52A8\u89E3\u9664",
|
|
19824
|
+
proNextNote: "\u4E0B\u4E00\u8F6E\u4F7F\u7528 deepseek-v4-pro\uFF0C\u4E4B\u540E\u81EA\u52A8\u89E3\u9664",
|
|
19825
|
+
sectionBudget: "\u9884\u7B97",
|
|
19826
|
+
budgetOf: "/",
|
|
19827
|
+
budgetSetCap: "\u8BBE\u7F6E\u4E0A\u9650",
|
|
19828
|
+
budgetCustom: "\u81EA\u5B9A\u4E49",
|
|
19829
|
+
budgetBumpHint: "\u63D0\u9AD8\u4E0A\u9650\u4EE5\u7EE7\u7EED",
|
|
19830
|
+
budgetClear: "\u6E05\u9664\u4E0A\u9650",
|
|
19831
|
+
budgetIdleLine: "80% \u65F6\u63D0\u9192 \xB7 100% \u540E\u62D2\u7EDD\u6267\u884C",
|
|
19832
|
+
budgetWarnLine: "\u63A5\u8FD1\u4E0A\u9650 \u2014 \u8D85\u8FC7 100% \u5C06\u62D2\u7EDD\u6267\u884C",
|
|
19833
|
+
budgetRefusing: "\u5DF2\u8D85\u51FA\u4E0A\u9650 \u2014 \u63D0\u9AD8\u6216\u6E05\u9664\u540E\u624D\u4F1A\u7EE7\u7EED",
|
|
19834
|
+
sectionLoop: "\u5FAA\u73AF",
|
|
19835
|
+
loopIdleHint: "\u6309\u56FA\u5B9A\u95F4\u9694\u81EA\u52A8\u91CD\u65B0\u63D0\u4EA4\u4E00\u6BB5\u63D0\u793A\u8BCD\u3002",
|
|
19836
|
+
loopCostHint: "\u6BCF\u6B21\u8FED\u4EE3\u7EA6 {cost}\uFF08\u4E0A\u4E00\u8F6E\u6210\u672C\uFF09\u3002",
|
|
19837
|
+
loopInterval: "\u95F4\u9694",
|
|
19838
|
+
loopCustom: "\u81EA\u5B9A\u4E49",
|
|
19839
|
+
loopRangeError: "\u95F4\u9694\u9700\u5728 5s..6h \u4E4B\u95F4",
|
|
19840
|
+
loopPrompt: "\u63D0\u793A\u8BCD",
|
|
19841
|
+
loopPromptPlaceholder: "\u4F8B\u5982\uFF1A\u68C0\u67E5\u90E8\u7F72\u72B6\u6001\u5E76\u6C47\u62A5\u4EFB\u4F55\u9519\u8BEF",
|
|
19842
|
+
loopStart: "\u542F\u52A8\u5FAA\u73AF",
|
|
19843
|
+
loopStop: "\u505C\u6B62",
|
|
19844
|
+
loopRunning: "\u8FD0\u884C\u4E2D",
|
|
19845
|
+
loopIter: "\u7B2C {iter} \u6B21",
|
|
19846
|
+
loopFiresIn: "{remaining} \u540E\u89E6\u53D1",
|
|
19780
19847
|
sectionRuntime: "\u8FD0\u884C\u65F6",
|
|
19781
19848
|
activeModel: "\u5F53\u524D\u6A21\u578B",
|
|
19849
|
+
modelPricingLine: "${hit} \u547D\u4E2D \xB7 ${miss} \u672A\u547D\u4E2D \xB7 ${out} \u8F93\u51FA / 100 \u4E07 tok",
|
|
19782
19850
|
editMode: "\u7F16\u8F91\u6A21\u5F0F",
|
|
19783
19851
|
editModeNote: "\u5728\u5BF9\u8BDD\u6807\u7B7E\u9875\u5934\u90E8\u5207\u6362",
|
|
19784
19852
|
sectionLanguage: "\u8BED\u8A00",
|
|
@@ -19861,6 +19929,7 @@ var zhCN = {
|
|
|
19861
19929
|
tokens7d: "tokens \xB7 7 \u5929",
|
|
19862
19930
|
cacheHit: "\u7F13\u5B58\u547D\u4E2D",
|
|
19863
19931
|
toolCalls24h: "\u5DE5\u5177\u8C03\u7528 \xB7 24 \u5C0F\u65F6",
|
|
19932
|
+
budget: "\u9884\u7B97",
|
|
19864
19933
|
currentSession: "\u5F53\u524D\u4F1A\u8BDD",
|
|
19865
19934
|
noSession: "\u65E0\u6D3B\u8DC3\u4F1A\u8BDD \u2014 \u5728 reasonix code \u5185\u6267\u884C /dashboard \u8FDB\u884C\u8FDE\u63A5\u3002",
|
|
19866
19935
|
promptTok: "\u63D0\u793A tokens",
|
|
@@ -20091,6 +20160,7 @@ var zhCN = {
|
|
|
20091
20160
|
filterPlaceholder: "\u7B5B\u9009\u8BA1\u5212",
|
|
20092
20161
|
active: "\u8FDB\u884C\u4E2D",
|
|
20093
20162
|
done: "\u5DF2\u5B8C\u6210",
|
|
20163
|
+
idle: "\u672A\u5F00\u59CB",
|
|
20094
20164
|
steps: "\u6B65\u9AA4",
|
|
20095
20165
|
pickHint: "\u9009\u62E9\u5DE6\u4FA7\u7684\u8BA1\u5212\u3002",
|
|
20096
20166
|
noTitle: "\uFF08\u65E0\u6807\u9898\uFF09",
|
|
@@ -20222,7 +20292,17 @@ var zhCN = {
|
|
|
20222
20292
|
accept: "\u63A5\u53D7",
|
|
20223
20293
|
reject: "\u62D2\u7EDD",
|
|
20224
20294
|
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"
|
|
20295
|
+
revisePlaceholder: "\u4E0B\u4E00\u6B65\u4E4B\u524D\u9700\u8981\u66F4\u6539\u4EC0\u4E48\uFF1F\u7559\u7A7A\u5219\u76F4\u63A5\u7EE7\u7EED\u3002",
|
|
20296
|
+
pickerFilter: "\u8FC7\u6EE4\u2026",
|
|
20297
|
+
pickerEmpty: "\u6682\u65E0\u5185\u5BB9\u3002",
|
|
20298
|
+
pickerLoadMore: "\u52A0\u8F7D\u66F4\u591A",
|
|
20299
|
+
pickerPick: "\u6253\u5F00",
|
|
20300
|
+
pickerInstall: "\u5B89\u88C5",
|
|
20301
|
+
pickerUninstall: "\u5378\u8F7D",
|
|
20302
|
+
pickerRename: "\u91CD\u547D\u540D\u2026",
|
|
20303
|
+
pickerNew: "\u65B0\u5EFA\u2026",
|
|
20304
|
+
pickerNewPlaceholder: "\u540D\u79F0\uFF08\u7559\u7A7A\u4F7F\u7528\u9ED8\u8BA4\uFF09",
|
|
20305
|
+
viewerClose: "\u5173\u95ED"
|
|
20226
20306
|
}
|
|
20227
20307
|
};
|
|
20228
20308
|
|
|
@@ -22930,6 +23010,156 @@ function CheckpointModal({ modal, onResolve }) {
|
|
|
22930
23010
|
<//>
|
|
22931
23011
|
`;
|
|
22932
23012
|
}
|
|
23013
|
+
function PickerModal({
|
|
23014
|
+
modal,
|
|
23015
|
+
onResolve
|
|
23016
|
+
}) {
|
|
23017
|
+
useLang();
|
|
23018
|
+
const [selectedId, setSelectedId] = d2(modal.items[0]?.id ?? null);
|
|
23019
|
+
const [query2, setQuery] = d2(modal.query ?? "");
|
|
23020
|
+
const [renameTarget, setRenameTarget] = d2(null);
|
|
23021
|
+
const [renameText, setRenameText] = d2("");
|
|
23022
|
+
const [showNew, setShowNew] = d2(false);
|
|
23023
|
+
const [newText, setNewText] = d2("");
|
|
23024
|
+
const has = (a3) => modal.actions.includes(a3);
|
|
23025
|
+
const selected = modal.items.find((i3) => i3.id === selectedId) ?? null;
|
|
23026
|
+
const submitRefine = (next) => {
|
|
23027
|
+
setQuery(next);
|
|
23028
|
+
if (has("refine")) onResolve("picker", { action: "refine", query: next });
|
|
23029
|
+
};
|
|
23030
|
+
const startRename = (id) => {
|
|
23031
|
+
const item = modal.items.find((i3) => i3.id === id);
|
|
23032
|
+
if (!item) return;
|
|
23033
|
+
setRenameTarget(id);
|
|
23034
|
+
setRenameText(item.title);
|
|
23035
|
+
};
|
|
23036
|
+
const sendRename = () => {
|
|
23037
|
+
if (!renameTarget || !renameText.trim()) return;
|
|
23038
|
+
onResolve("picker", { action: "rename", id: renameTarget, text: renameText });
|
|
23039
|
+
setRenameTarget(null);
|
|
23040
|
+
setRenameText("");
|
|
23041
|
+
};
|
|
23042
|
+
const sendNew = () => {
|
|
23043
|
+
onResolve("picker", newText.trim() ? { action: "new", text: newText } : { action: "new" });
|
|
23044
|
+
setShowNew(false);
|
|
23045
|
+
setNewText("");
|
|
23046
|
+
};
|
|
23047
|
+
return html4`
|
|
23048
|
+
<${ModalCard}
|
|
23049
|
+
accent="#fcd34d"
|
|
23050
|
+
icon="≡"
|
|
23051
|
+
title=${modal.title}
|
|
23052
|
+
subtitle=${modal.hint}
|
|
23053
|
+
>
|
|
23054
|
+
${has("refine") ? html4`<input
|
|
23055
|
+
class="modal-picker-search"
|
|
23056
|
+
type="search"
|
|
23057
|
+
placeholder=${t4("modal.pickerFilter")}
|
|
23058
|
+
value=${query2}
|
|
23059
|
+
onInput=${(e3) => submitRefine(e3.target.value)}
|
|
23060
|
+
/>` : null}
|
|
23061
|
+
<div class="modal-picker-list">
|
|
23062
|
+
${modal.items.length === 0 ? html4`<div class="modal-picker-empty">${t4("modal.pickerEmpty")}</div>` : modal.items.map(
|
|
23063
|
+
(it) => html4`
|
|
23064
|
+
<button
|
|
23065
|
+
key=${it.id}
|
|
23066
|
+
class=${`modal-picker-row${it.id === selectedId ? " selected" : ""}`}
|
|
23067
|
+
onClick=${() => setSelectedId(it.id)}
|
|
23068
|
+
onDblClick=${() => has("pick") && onResolve("picker", { action: "pick", id: it.id })}
|
|
23069
|
+
>
|
|
23070
|
+
<span class="modal-picker-title">${it.title}</span>
|
|
23071
|
+
${it.badge ? html4`<span class="modal-picker-badge">${it.badge}</span>` : null}
|
|
23072
|
+
${it.subtitle ? html4`<span class="modal-picker-subtitle">${it.subtitle}</span>` : null}
|
|
23073
|
+
${it.meta ? html4`<span class="modal-picker-meta">${it.meta}</span>` : null}
|
|
23074
|
+
</button>
|
|
23075
|
+
`
|
|
23076
|
+
)}
|
|
23077
|
+
</div>
|
|
23078
|
+
${modal.hasMore && has("load-more") ? html4`<button
|
|
23079
|
+
class="modal-picker-more"
|
|
23080
|
+
onClick=${() => onResolve("picker", { action: "load-more" })}
|
|
23081
|
+
>${t4("modal.pickerLoadMore")}</button>` : null}
|
|
23082
|
+
${renameTarget ? html4`
|
|
23083
|
+
<div class="modal-picker-form">
|
|
23084
|
+
<input
|
|
23085
|
+
type="text"
|
|
23086
|
+
value=${renameText}
|
|
23087
|
+
onInput=${(e3) => setRenameText(e3.target.value)}
|
|
23088
|
+
/>
|
|
23089
|
+
<div class="modal-actions">
|
|
23090
|
+
<button class="primary" onClick=${sendRename} disabled=${!renameText.trim()}>${t4("common.save")}</button>
|
|
23091
|
+
<button onClick=${() => setRenameTarget(null)}>${t4("common.back")}</button>
|
|
23092
|
+
</div>
|
|
23093
|
+
</div>
|
|
23094
|
+
` : showNew ? html4`
|
|
23095
|
+
<div class="modal-picker-form">
|
|
23096
|
+
<input
|
|
23097
|
+
type="text"
|
|
23098
|
+
placeholder=${t4("modal.pickerNewPlaceholder")}
|
|
23099
|
+
value=${newText}
|
|
23100
|
+
onInput=${(e3) => setNewText(e3.target.value)}
|
|
23101
|
+
/>
|
|
23102
|
+
<div class="modal-actions">
|
|
23103
|
+
<button class="primary" onClick=${sendNew}>${t4("common.add")}</button>
|
|
23104
|
+
<button onClick=${() => setShowNew(false)}>${t4("common.back")}</button>
|
|
23105
|
+
</div>
|
|
23106
|
+
</div>
|
|
23107
|
+
` : html4`
|
|
23108
|
+
<div class="modal-actions">
|
|
23109
|
+
${has("pick") && selected ? html4`<button
|
|
23110
|
+
class="primary"
|
|
23111
|
+
onClick=${() => onResolve("picker", { action: "pick", id: selected.id })}
|
|
23112
|
+
>${t4("modal.pickerPick")}</button>` : null}
|
|
23113
|
+
${has("install") && selected ? html4`<button
|
|
23114
|
+
class="primary"
|
|
23115
|
+
onClick=${() => onResolve("picker", { action: "install", id: selected.id })}
|
|
23116
|
+
>${t4("modal.pickerInstall")}</button>` : null}
|
|
23117
|
+
${has("uninstall") && selected ? html4`<button
|
|
23118
|
+
onClick=${() => onResolve("picker", { action: "uninstall", id: selected.id })}
|
|
23119
|
+
>${t4("modal.pickerUninstall")}</button>` : null}
|
|
23120
|
+
${has("rename") && selected ? html4`<button onClick=${() => startRename(selected.id)}>${t4("modal.pickerRename")}</button>` : null}
|
|
23121
|
+
${has("delete") && selected ? html4`<button
|
|
23122
|
+
class="danger"
|
|
23123
|
+
onClick=${() => onResolve("picker", { action: "delete", id: selected.id })}
|
|
23124
|
+
>${t4("common.delete")}</button>` : null}
|
|
23125
|
+
${has("new") ? html4`<button onClick=${() => setShowNew(true)}>${t4("modal.pickerNew")}</button>` : null}
|
|
23126
|
+
<button onClick=${() => onResolve("picker", { action: "cancel" })}>${t4("modal.cancel")}</button>
|
|
23127
|
+
</div>
|
|
23128
|
+
`}
|
|
23129
|
+
<//>
|
|
23130
|
+
`;
|
|
23131
|
+
}
|
|
23132
|
+
function ViewerModal({
|
|
23133
|
+
modal,
|
|
23134
|
+
onResolve
|
|
23135
|
+
}) {
|
|
23136
|
+
useLang();
|
|
23137
|
+
return html4`
|
|
23138
|
+
<${ModalCard}
|
|
23139
|
+
accent="#67e8f9"
|
|
23140
|
+
icon="◇"
|
|
23141
|
+
title=${modal.title}
|
|
23142
|
+
subtitle=${modal.meta}
|
|
23143
|
+
>
|
|
23144
|
+
${modal.steps && modal.steps.length > 0 ? html4`
|
|
23145
|
+
<ol class="modal-viewer-steps">
|
|
23146
|
+
${modal.steps.map(
|
|
23147
|
+
(s3) => html4`
|
|
23148
|
+
<li key=${s3.id} class=${`modal-viewer-step modal-viewer-step-${s3.status}`}>
|
|
23149
|
+
<span class="modal-viewer-step-mark">${s3.status === "done" ? "\u2713" : "\xB7"}</span>
|
|
23150
|
+
<span class="modal-viewer-step-title">${s3.title}</span>
|
|
23151
|
+
</li>
|
|
23152
|
+
`
|
|
23153
|
+
)}
|
|
23154
|
+
</ol>
|
|
23155
|
+
` : null}
|
|
23156
|
+
${modal.body ? html4`<div class="md modal-viewer-body" dangerouslySetInnerHTML=${{ __html: marked.parse(modal.body) }}></div>` : null}
|
|
23157
|
+
<div class="modal-actions">
|
|
23158
|
+
<button onClick=${() => onResolve("viewer", { action: "close" })}>${t4("modal.viewerClose")}</button>
|
|
23159
|
+
</div>
|
|
23160
|
+
<//>
|
|
23161
|
+
`;
|
|
23162
|
+
}
|
|
22933
23163
|
function RevisionModal({ modal, onResolve }) {
|
|
22934
23164
|
useLang();
|
|
22935
23165
|
const riskColor = (r3) => r3 === "high" ? "#f87171" : r3 === "med" ? "#fbbf24" : r3 === "low" ? "#86efac" : "#9ca3af";
|
|
@@ -23514,7 +23744,7 @@ function ChatPanel() {
|
|
|
23514
23744
|
</div>` : null}
|
|
23515
23745
|
${error ? html4`<div class="notice err">${error}</div>` : null}
|
|
23516
23746
|
|
|
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}
|
|
23747
|
+
${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
23748
|
|
|
23519
23749
|
<div class="chat-body">
|
|
23520
23750
|
<div class="chat-main">
|
|
@@ -23599,7 +23829,7 @@ function SideRail({ stats, budgetUsd, activePlan }) {
|
|
|
23599
23829
|
const cacheTone = cachePct >= 80 ? "ok" : cachePct >= 50 ? "" : "warn";
|
|
23600
23830
|
const showBudget = stats != null && typeof budgetUsd === "number" && budgetUsd > 0;
|
|
23601
23831
|
const budgetPct = showBudget ? Math.min(120, stats.totalCostUsd / budgetUsd * 100) : 0;
|
|
23602
|
-
const
|
|
23832
|
+
const budgetTone2 = budgetPct >= 100 ? "err" : budgetPct >= 80 ? "warn" : "";
|
|
23603
23833
|
const walletCurrency = stats?.balance?.[0]?.currency;
|
|
23604
23834
|
return html4`
|
|
23605
23835
|
<aside class="chat-rail">
|
|
@@ -23622,8 +23852,8 @@ function SideRail({ stats, budgetUsd, activePlan }) {
|
|
|
23622
23852
|
<div class="rh">${t4("chat.railToolBudget")}</div>
|
|
23623
23853
|
<div class="progress-row">
|
|
23624
23854
|
<span class="lbl">${t4("chat.railSpend")}</span>
|
|
23625
|
-
<div class=${`progress ${
|
|
23626
|
-
<span class="v" style=${
|
|
23855
|
+
<div class=${`progress ${budgetTone2}`}><div class="progress-fill" style=${`width:${Math.min(100, budgetPct)}%`}></div></div>
|
|
23856
|
+
<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
23857
|
</div>
|
|
23628
23858
|
</div>
|
|
23629
23859
|
` : null}
|
|
@@ -24564,6 +24794,35 @@ function MemoryPanel() {
|
|
|
24564
24794
|
`;
|
|
24565
24795
|
}
|
|
24566
24796
|
|
|
24797
|
+
// dashboard/src/lib/budget.ts
|
|
24798
|
+
function deriveBudgetState(cap, spent) {
|
|
24799
|
+
const safeSpent = typeof spent === "number" && spent >= 0 ? spent : 0;
|
|
24800
|
+
if (typeof cap !== "number" || cap <= 0) {
|
|
24801
|
+
return { kind: "off", spent: safeSpent };
|
|
24802
|
+
}
|
|
24803
|
+
const pct = safeSpent / cap * 100;
|
|
24804
|
+
if (pct >= 100) return { kind: "exhausted", cap, spent: safeSpent, pct };
|
|
24805
|
+
if (pct >= 80) return { kind: "warn", cap, spent: safeSpent, pct };
|
|
24806
|
+
return { kind: "running", cap, spent: safeSpent, pct };
|
|
24807
|
+
}
|
|
24808
|
+
var QUICK_CAPS_USD = [1, 5, 10, 25, 50];
|
|
24809
|
+
function bumpSuggestions(currentCap) {
|
|
24810
|
+
if (currentCap <= 0) return [];
|
|
24811
|
+
return [niceUp(currentCap * 1.5), niceUp(currentCap * 2), niceUp(currentCap * 4)];
|
|
24812
|
+
}
|
|
24813
|
+
function niceUp(n3) {
|
|
24814
|
+
const eps = 1e-9;
|
|
24815
|
+
if (n3 < 1) return Math.ceil((n3 - eps) * 10) / 10;
|
|
24816
|
+
if (n3 < 10) return Math.ceil((n3 - eps) * 2) / 2;
|
|
24817
|
+
if (n3 < 100) return Math.ceil(n3 - eps);
|
|
24818
|
+
return Math.ceil((n3 - eps) / 5) * 5;
|
|
24819
|
+
}
|
|
24820
|
+
function budgetTone(state) {
|
|
24821
|
+
if (state.kind === "exhausted") return "err";
|
|
24822
|
+
if (state.kind === "warn") return "warn";
|
|
24823
|
+
return "";
|
|
24824
|
+
}
|
|
24825
|
+
|
|
24567
24826
|
// dashboard/src/lib/use-poll.ts
|
|
24568
24827
|
function usePoll(path, intervalMs = 2e3) {
|
|
24569
24828
|
const [data, setData] = d2(null);
|
|
@@ -24634,6 +24893,19 @@ function balanceKpi(c3) {
|
|
|
24634
24893
|
const symbol = c3.balance.currency === "CNY" ? "\xA5" : c3.balance.currency === "USD" ? "$" : "";
|
|
24635
24894
|
return kpi(t4("overview.balance"), `${symbol}${c3.balance.total}`, c3.balance.currency, "flat");
|
|
24636
24895
|
}
|
|
24896
|
+
function budgetKpi(o3) {
|
|
24897
|
+
const state = deriveBudgetState(o3.budgetUsd, o3.cockpit?.currentSession?.totalCostUsd ?? null);
|
|
24898
|
+
if (state.kind === "off") return null;
|
|
24899
|
+
const tone = budgetTone(state);
|
|
24900
|
+
const valueColor = tone === "err" ? "color:var(--c-err)" : tone === "warn" ? "color:var(--c-warn)" : "";
|
|
24901
|
+
return html4`
|
|
24902
|
+
<div class="kpi cock-w-1">
|
|
24903
|
+
<div class="label">${t4("overview.budget")}</div>
|
|
24904
|
+
<div class="value" style=${valueColor}>${fmtUsd(state.spent)} / ${fmtUsd(state.cap)}</div>
|
|
24905
|
+
<div class=${`progress ${tone}`} style="margin-top:4px"><div class="progress-fill" style=${`width:${Math.min(100, state.pct)}%`}></div></div>
|
|
24906
|
+
</div>
|
|
24907
|
+
`;
|
|
24908
|
+
}
|
|
24637
24909
|
function tokens7dKpi(c3) {
|
|
24638
24910
|
if (!c3.tokens7d) return kpi(t4("overview.tokens7d"), "\u2014", t4("overview.noUsageYet"), "flat");
|
|
24639
24911
|
const d3 = deltaPctText(c3.tokens7d.deltaPct);
|
|
@@ -24789,6 +25061,7 @@ function OverviewPanel() {
|
|
|
24789
25061
|
${tokens7dKpi(c3)}
|
|
24790
25062
|
${cacheHitKpi(c3)}
|
|
24791
25063
|
${toolCallsKpi(c3)}
|
|
25064
|
+
${budgetKpi(o3)}
|
|
24792
25065
|
|
|
24793
25066
|
${currentSessionBlock(c3)}
|
|
24794
25067
|
${costTrendSpark(c3)}
|
|
@@ -25001,7 +25274,7 @@ function PermissionsPanel() {
|
|
|
25001
25274
|
function statusPill(p3) {
|
|
25002
25275
|
if (p3.completionRatio >= 1) return html4`<span class="pill ok">${t4("plans.done")}</span>`;
|
|
25003
25276
|
if (p3.completionRatio > 0) return html4`<span class="pill info">${t4("plans.active")}</span>`;
|
|
25004
|
-
return html4`<span class="pill"
|
|
25277
|
+
return html4`<span class="pill">${t4("plans.idle")}</span>`;
|
|
25005
25278
|
}
|
|
25006
25279
|
function PlansPanel() {
|
|
25007
25280
|
useLang();
|
|
@@ -25828,7 +26101,314 @@ function SessionsPanel() {
|
|
|
25828
26101
|
`;
|
|
25829
26102
|
}
|
|
25830
26103
|
|
|
26104
|
+
// dashboard/src/lib/loop-control.ts
|
|
26105
|
+
var INTERVAL_PRESETS_MS = [
|
|
26106
|
+
{ ms: 3e4, label: "30s" },
|
|
26107
|
+
{ ms: 6e4, label: "1m" },
|
|
26108
|
+
{ ms: 5 * 6e4, label: "5m" },
|
|
26109
|
+
{ ms: 15 * 6e4, label: "15m" },
|
|
26110
|
+
{ ms: 60 * 6e4, label: "1h" },
|
|
26111
|
+
{ ms: 6 * 60 * 6e4, label: "6h" }
|
|
26112
|
+
];
|
|
26113
|
+
var UNIT_TO_MS = {
|
|
26114
|
+
s: 1e3,
|
|
26115
|
+
m: 6e4,
|
|
26116
|
+
h: 60 * 6e4
|
|
26117
|
+
};
|
|
26118
|
+
var MIN_INTERVAL_MS = 5e3;
|
|
26119
|
+
var MAX_INTERVAL_MS = 6 * 60 * 6e4;
|
|
26120
|
+
function parseCustomInterval(value, unit) {
|
|
26121
|
+
const n3 = Number.parseFloat(value);
|
|
26122
|
+
if (!Number.isFinite(n3) || n3 <= 0) return null;
|
|
26123
|
+
const ms = Math.round(n3 * UNIT_TO_MS[unit]);
|
|
26124
|
+
if (ms < MIN_INTERVAL_MS || ms > MAX_INTERVAL_MS) return null;
|
|
26125
|
+
return ms;
|
|
26126
|
+
}
|
|
26127
|
+
function formatRemaining(ms) {
|
|
26128
|
+
const safe = Math.max(0, Math.floor(ms / 1e3));
|
|
26129
|
+
const h3 = Math.floor(safe / 3600);
|
|
26130
|
+
const m3 = Math.floor(safe % 3600 / 60);
|
|
26131
|
+
const s3 = safe % 60;
|
|
26132
|
+
if (h3 > 0) return m3 > 0 ? `${h3}h ${m3}m` : `${h3}h`;
|
|
26133
|
+
if (m3 > 0) return s3 > 0 ? `${m3}m ${s3}s` : `${m3}m`;
|
|
26134
|
+
return `${s3}s`;
|
|
26135
|
+
}
|
|
26136
|
+
|
|
25831
26137
|
// dashboard/src/panels/settings.ts
|
|
26138
|
+
function fmtUsd22(n3) {
|
|
26139
|
+
return `$${n3.toFixed(n3 < 1 ? 4 : 2)}`;
|
|
26140
|
+
}
|
|
26141
|
+
function formatPricing(p3) {
|
|
26142
|
+
if (!p3) return null;
|
|
26143
|
+
return t4("settings.modelPricingLine", {
|
|
26144
|
+
hit: p3.inputCacheHit.toFixed(3),
|
|
26145
|
+
miss: p3.inputCacheMiss.toFixed(3),
|
|
26146
|
+
out: p3.output.toFixed(3)
|
|
26147
|
+
});
|
|
26148
|
+
}
|
|
26149
|
+
function ModelRow({
|
|
26150
|
+
current,
|
|
26151
|
+
catalog,
|
|
26152
|
+
saving,
|
|
26153
|
+
onPick
|
|
26154
|
+
}) {
|
|
26155
|
+
const list2 = catalog?.models ?? null;
|
|
26156
|
+
const ready = list2 && list2.length > 0;
|
|
26157
|
+
if (!ready) {
|
|
26158
|
+
return html4`<code class="mono">${current ?? "\u2014"}</code>`;
|
|
26159
|
+
}
|
|
26160
|
+
const options2 = list2.includes(current) ? list2 : [current, ...list2];
|
|
26161
|
+
const price = catalog?.pricing[current];
|
|
26162
|
+
return html4`
|
|
26163
|
+
<span style="display:inline-flex;flex-direction:column;gap:4px">
|
|
26164
|
+
<select
|
|
26165
|
+
value=${current}
|
|
26166
|
+
onChange=${(e3) => {
|
|
26167
|
+
const next = e3.target.value;
|
|
26168
|
+
if (next && next !== current) onPick(next);
|
|
26169
|
+
}}
|
|
26170
|
+
disabled=${saving}
|
|
26171
|
+
style="font-family:var(--font-mono);min-width:200px"
|
|
26172
|
+
>
|
|
26173
|
+
${options2.map((m3) => html4`<option key=${m3} value=${m3}>${m3}</option>`)}
|
|
26174
|
+
</select>
|
|
26175
|
+
${price ? html4`<span style="color:var(--fg-3);font-size:11px;font-family:var(--font-mono)">${formatPricing(price)}</span>` : null}
|
|
26176
|
+
</span>
|
|
26177
|
+
`;
|
|
26178
|
+
}
|
|
26179
|
+
function BudgetGauge({ state }) {
|
|
26180
|
+
if (state.kind === "off") return null;
|
|
26181
|
+
const tone = budgetTone(state);
|
|
26182
|
+
const fill = Math.min(100, state.pct);
|
|
26183
|
+
const valueColor = tone === "err" ? "color:var(--c-err)" : tone === "warn" ? "color:var(--c-warn)" : "color:var(--fg-1)";
|
|
26184
|
+
return html4`
|
|
26185
|
+
<div style="display:flex;flex-direction:column;gap:6px">
|
|
26186
|
+
<div style="display:flex;justify-content:space-between;align-items:baseline;font-size:13px">
|
|
26187
|
+
<span style=${valueColor}>
|
|
26188
|
+
<strong style="font-family:var(--font-mono)">${fmtUsd22(state.spent)}</strong>
|
|
26189
|
+
<span style="color:var(--fg-3)"> ${t4("settings.budgetOf")} </span>
|
|
26190
|
+
<strong style="font-family:var(--font-mono)">${fmtUsd22(state.cap)}</strong>
|
|
26191
|
+
</span>
|
|
26192
|
+
<span style=${`font-family:var(--font-mono);font-size:11px;${valueColor}`}>${state.pct.toFixed(1)}%</span>
|
|
26193
|
+
</div>
|
|
26194
|
+
<div class=${`progress ${tone}`}><div class="progress-fill" style=${`width:${fill}%`}></div></div>
|
|
26195
|
+
<span style="color:var(--fg-3);font-size:11px">
|
|
26196
|
+
${state.kind === "exhausted" ? t4("settings.budgetRefusing") : state.kind === "warn" ? t4("settings.budgetWarnLine") : t4("settings.budgetIdleLine")}
|
|
26197
|
+
</span>
|
|
26198
|
+
</div>
|
|
26199
|
+
`;
|
|
26200
|
+
}
|
|
26201
|
+
function BudgetSection({ state, saving, onSetCap, onClear }) {
|
|
26202
|
+
const [custom, setCustom] = d2("");
|
|
26203
|
+
const submitCustom = () => {
|
|
26204
|
+
const n3 = Number.parseFloat(custom);
|
|
26205
|
+
if (Number.isFinite(n3) && n3 > 0) {
|
|
26206
|
+
onSetCap(n3);
|
|
26207
|
+
setCustom("");
|
|
26208
|
+
}
|
|
26209
|
+
};
|
|
26210
|
+
const quickButtons = (caps) => caps.map(
|
|
26211
|
+
(c3) => html4`
|
|
26212
|
+
<button
|
|
26213
|
+
key=${c3}
|
|
26214
|
+
class="btn"
|
|
26215
|
+
style="font-family:var(--font-mono)"
|
|
26216
|
+
disabled=${saving}
|
|
26217
|
+
onClick=${() => onSetCap(c3)}
|
|
26218
|
+
>$${c3}</button>
|
|
26219
|
+
`
|
|
26220
|
+
);
|
|
26221
|
+
const customField = html4`
|
|
26222
|
+
<span style="display:inline-flex;align-items:center;gap:4px;margin-left:auto">
|
|
26223
|
+
<span style="color:var(--fg-3);font-size:11px">${t4("settings.budgetCustom")}</span>
|
|
26224
|
+
<input
|
|
26225
|
+
type="number"
|
|
26226
|
+
min="0.01"
|
|
26227
|
+
step="0.01"
|
|
26228
|
+
value=${custom}
|
|
26229
|
+
placeholder="0.00"
|
|
26230
|
+
onInput=${(e3) => setCustom(e3.target.value)}
|
|
26231
|
+
onKeyDown=${(e3) => {
|
|
26232
|
+
if (e3.key === "Enter") submitCustom();
|
|
26233
|
+
}}
|
|
26234
|
+
style="width:72px;font-family:var(--font-mono)"
|
|
26235
|
+
disabled=${saving}
|
|
26236
|
+
/>
|
|
26237
|
+
<button
|
|
26238
|
+
class="btn primary"
|
|
26239
|
+
disabled=${saving || !(Number.parseFloat(custom) > 0)}
|
|
26240
|
+
onClick=${submitCustom}
|
|
26241
|
+
>→</button>
|
|
26242
|
+
</span>
|
|
26243
|
+
`;
|
|
26244
|
+
return html4`
|
|
26245
|
+
<div class="card" style="display:flex;flex-direction:column;gap:12px">
|
|
26246
|
+
<${BudgetGauge} state=${state} />
|
|
26247
|
+
|
|
26248
|
+
${state.kind === "off" ? html4`
|
|
26249
|
+
<div>
|
|
26250
|
+
<div style="color:var(--fg-3);font-size:11px;margin-bottom:6px">${t4("settings.budgetSetCap")}</div>
|
|
26251
|
+
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
|
|
26252
|
+
${quickButtons(QUICK_CAPS_USD)}
|
|
26253
|
+
${customField}
|
|
26254
|
+
</div>
|
|
26255
|
+
</div>
|
|
26256
|
+
` : state.kind === "warn" || state.kind === "exhausted" ? html4`
|
|
26257
|
+
<div>
|
|
26258
|
+
<div style="color:var(--fg-3);font-size:11px;margin-bottom:6px">${t4("settings.budgetBumpHint")}</div>
|
|
26259
|
+
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
|
|
26260
|
+
${bumpSuggestions(state.cap).map(
|
|
26261
|
+
(next) => html4`
|
|
26262
|
+
<button
|
|
26263
|
+
key=${next}
|
|
26264
|
+
class="btn primary"
|
|
26265
|
+
style="font-family:var(--font-mono)"
|
|
26266
|
+
disabled=${saving}
|
|
26267
|
+
onClick=${() => onSetCap(next)}
|
|
26268
|
+
>→ $${next % 1 === 0 ? next : next.toFixed(2)}</button>
|
|
26269
|
+
`
|
|
26270
|
+
)}
|
|
26271
|
+
${customField}
|
|
26272
|
+
</div>
|
|
26273
|
+
<div style="margin-top:8px">
|
|
26274
|
+
<button class="btn" disabled=${saving} onClick=${onClear}>${t4("settings.budgetClear")}</button>
|
|
26275
|
+
</div>
|
|
26276
|
+
</div>
|
|
26277
|
+
` : html4`
|
|
26278
|
+
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
|
|
26279
|
+
${bumpSuggestions(state.cap).map(
|
|
26280
|
+
(next) => html4`
|
|
26281
|
+
<button
|
|
26282
|
+
key=${next}
|
|
26283
|
+
class="btn"
|
|
26284
|
+
style="font-family:var(--font-mono)"
|
|
26285
|
+
disabled=${saving}
|
|
26286
|
+
onClick=${() => onSetCap(next)}
|
|
26287
|
+
>→ $${next % 1 === 0 ? next : next.toFixed(2)}</button>
|
|
26288
|
+
`
|
|
26289
|
+
)}
|
|
26290
|
+
${customField}
|
|
26291
|
+
<button
|
|
26292
|
+
class="btn"
|
|
26293
|
+
style="margin-left:8px"
|
|
26294
|
+
disabled=${saving}
|
|
26295
|
+
onClick=${onClear}
|
|
26296
|
+
>${t4("settings.budgetClear")}</button>
|
|
26297
|
+
</div>
|
|
26298
|
+
`}
|
|
26299
|
+
</div>
|
|
26300
|
+
`;
|
|
26301
|
+
}
|
|
26302
|
+
function LoopSection({
|
|
26303
|
+
status,
|
|
26304
|
+
remainingMs,
|
|
26305
|
+
avgIterCostUsd,
|
|
26306
|
+
busy,
|
|
26307
|
+
onStart,
|
|
26308
|
+
onStop
|
|
26309
|
+
}) {
|
|
26310
|
+
const [intervalMs, setIntervalMs] = d2(INTERVAL_PRESETS_MS[1].ms);
|
|
26311
|
+
const [prompt, setPrompt] = d2("");
|
|
26312
|
+
const [customValue, setCustomValue] = d2("");
|
|
26313
|
+
const [customUnit, setCustomUnit] = d2("m");
|
|
26314
|
+
if (status) {
|
|
26315
|
+
return html4`
|
|
26316
|
+
<div class="card" style="display:flex;flex-direction:column;gap:10px">
|
|
26317
|
+
<div style="display:flex;justify-content:space-between;align-items:baseline">
|
|
26318
|
+
<span style="color:var(--c-warn);font-family:var(--font-mono);font-size:11px">⟳ ${t4("settings.loopRunning")}</span>
|
|
26319
|
+
<span style="color:var(--fg-3);font-size:11px">
|
|
26320
|
+
${t4("settings.loopIter", { iter: status.iter })} · ${t4("settings.loopFiresIn", { remaining: formatRemaining(remainingMs) })}
|
|
26321
|
+
</span>
|
|
26322
|
+
</div>
|
|
26323
|
+
<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>
|
|
26324
|
+
<div>
|
|
26325
|
+
<button class="btn danger" disabled=${busy} onClick=${onStop}>${t4("settings.loopStop")}</button>
|
|
26326
|
+
</div>
|
|
26327
|
+
</div>
|
|
26328
|
+
`;
|
|
26329
|
+
}
|
|
26330
|
+
const customMs = parseCustomInterval(customValue, customUnit);
|
|
26331
|
+
const canStart = !busy && intervalMs > 0 && prompt.trim().length > 0;
|
|
26332
|
+
return html4`
|
|
26333
|
+
<div class="card" style="display:flex;flex-direction:column;gap:10px">
|
|
26334
|
+
<div style="color:var(--fg-3);font-size:11px">
|
|
26335
|
+
${t4("settings.loopIdleHint")}
|
|
26336
|
+
${typeof avgIterCostUsd === "number" && avgIterCostUsd > 0 ? html4` ${t4("settings.loopCostHint", { cost: `$${avgIterCostUsd.toFixed(4)}` })}` : null}
|
|
26337
|
+
</div>
|
|
26338
|
+
<div style="display:flex;flex-direction:column;gap:6px">
|
|
26339
|
+
<span style="color:var(--fg-3);font-size:11px">${t4("settings.loopInterval")}</span>
|
|
26340
|
+
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
|
|
26341
|
+
${INTERVAL_PRESETS_MS.map(
|
|
26342
|
+
(p3) => html4`
|
|
26343
|
+
<button
|
|
26344
|
+
key=${p3.ms}
|
|
26345
|
+
class=${`btn ${intervalMs === p3.ms && customValue === "" ? "primary" : ""}`}
|
|
26346
|
+
style="font-family:var(--font-mono)"
|
|
26347
|
+
disabled=${busy}
|
|
26348
|
+
onClick=${() => {
|
|
26349
|
+
setIntervalMs(p3.ms);
|
|
26350
|
+
setCustomValue("");
|
|
26351
|
+
}}
|
|
26352
|
+
>${p3.label}</button>
|
|
26353
|
+
`
|
|
26354
|
+
)}
|
|
26355
|
+
<span style="display:inline-flex;align-items:center;gap:4px;margin-left:auto">
|
|
26356
|
+
<span style="color:var(--fg-3);font-size:11px">${t4("settings.loopCustom")}</span>
|
|
26357
|
+
<input
|
|
26358
|
+
type="number"
|
|
26359
|
+
min="1"
|
|
26360
|
+
step="1"
|
|
26361
|
+
value=${customValue}
|
|
26362
|
+
onInput=${(e3) => {
|
|
26363
|
+
const raw = e3.target.value;
|
|
26364
|
+
setCustomValue(raw);
|
|
26365
|
+
const ms = parseCustomInterval(raw, customUnit);
|
|
26366
|
+
if (ms !== null) setIntervalMs(ms);
|
|
26367
|
+
}}
|
|
26368
|
+
style="width:64px;font-family:var(--font-mono)"
|
|
26369
|
+
disabled=${busy}
|
|
26370
|
+
/>
|
|
26371
|
+
<select
|
|
26372
|
+
value=${customUnit}
|
|
26373
|
+
onChange=${(e3) => {
|
|
26374
|
+
const next = e3.target.value;
|
|
26375
|
+
setCustomUnit(next);
|
|
26376
|
+
if (customValue) {
|
|
26377
|
+
const ms = parseCustomInterval(customValue, next);
|
|
26378
|
+
if (ms !== null) setIntervalMs(ms);
|
|
26379
|
+
}
|
|
26380
|
+
}}
|
|
26381
|
+
disabled=${busy}
|
|
26382
|
+
>
|
|
26383
|
+
<option value="s">s</option>
|
|
26384
|
+
<option value="m">m</option>
|
|
26385
|
+
<option value="h">h</option>
|
|
26386
|
+
</select>
|
|
26387
|
+
</span>
|
|
26388
|
+
</div>
|
|
26389
|
+
${customValue && customMs === null ? html4`<span style="color:var(--c-err);font-size:11px">${t4("settings.loopRangeError")}</span>` : null}
|
|
26390
|
+
</div>
|
|
26391
|
+
<div style="display:flex;flex-direction:column;gap:6px">
|
|
26392
|
+
<span style="color:var(--fg-3);font-size:11px">${t4("settings.loopPrompt")}</span>
|
|
26393
|
+
<textarea
|
|
26394
|
+
rows="3"
|
|
26395
|
+
placeholder=${t4("settings.loopPromptPlaceholder")}
|
|
26396
|
+
value=${prompt}
|
|
26397
|
+
onInput=${(e3) => setPrompt(e3.target.value)}
|
|
26398
|
+
style="width:100%;font-family:var(--font-mono);resize:vertical"
|
|
26399
|
+
disabled=${busy}
|
|
26400
|
+
></textarea>
|
|
26401
|
+
</div>
|
|
26402
|
+
<div>
|
|
26403
|
+
<button
|
|
26404
|
+
class="btn primary"
|
|
26405
|
+
disabled=${!canStart}
|
|
26406
|
+
onClick=${() => onStart(intervalMs, prompt.trim())}
|
|
26407
|
+
>${t4("settings.loopStart")}</button>
|
|
26408
|
+
</div>
|
|
26409
|
+
</div>
|
|
26410
|
+
`;
|
|
26411
|
+
}
|
|
25832
26412
|
function SettingsPanel() {
|
|
25833
26413
|
useLang();
|
|
25834
26414
|
const [data, setData] = d2(null);
|
|
@@ -25836,6 +26416,12 @@ function SettingsPanel() {
|
|
|
25836
26416
|
const [saving, setSaving] = d2(false);
|
|
25837
26417
|
const [saved, setSaved] = d2(null);
|
|
25838
26418
|
const [draft, setDraft] = d2({});
|
|
26419
|
+
const [catalog, setCatalog] = d2(null);
|
|
26420
|
+
const [loopStatus, setLoopStatus] = d2(null);
|
|
26421
|
+
const [loopAvgCost, setLoopAvgCost] = d2(null);
|
|
26422
|
+
const [loopBusy, setLoopBusy] = d2(false);
|
|
26423
|
+
const lastStatusSyncRef = A2(0);
|
|
26424
|
+
const [now, setNow] = d2(() => Date.now());
|
|
25839
26425
|
const load = q2(async () => {
|
|
25840
26426
|
try {
|
|
25841
26427
|
const r3 = await api("/settings");
|
|
@@ -25848,6 +26434,64 @@ function SettingsPanel() {
|
|
|
25848
26434
|
y2(() => {
|
|
25849
26435
|
load();
|
|
25850
26436
|
}, [load]);
|
|
26437
|
+
y2(() => {
|
|
26438
|
+
api("/models").then(setCatalog).catch(() => void 0);
|
|
26439
|
+
}, []);
|
|
26440
|
+
const refreshLoop = q2(async () => {
|
|
26441
|
+
try {
|
|
26442
|
+
const r3 = await api("/loop/status");
|
|
26443
|
+
setLoopStatus(r3.status);
|
|
26444
|
+
lastStatusSyncRef.current = Date.now();
|
|
26445
|
+
} catch {
|
|
26446
|
+
}
|
|
26447
|
+
try {
|
|
26448
|
+
const r3 = await api("/overview");
|
|
26449
|
+
setLoopAvgCost(r3.stats?.lastTurnCostUsd ?? null);
|
|
26450
|
+
} catch {
|
|
26451
|
+
}
|
|
26452
|
+
}, []);
|
|
26453
|
+
y2(() => {
|
|
26454
|
+
let cancelled = false;
|
|
26455
|
+
refreshLoop();
|
|
26456
|
+
const id = setInterval(() => {
|
|
26457
|
+
if (!cancelled) refreshLoop();
|
|
26458
|
+
}, 5e3);
|
|
26459
|
+
return () => {
|
|
26460
|
+
cancelled = true;
|
|
26461
|
+
clearInterval(id);
|
|
26462
|
+
};
|
|
26463
|
+
}, [refreshLoop]);
|
|
26464
|
+
y2(() => {
|
|
26465
|
+
if (!loopStatus) return;
|
|
26466
|
+
const id = setInterval(() => setNow(Date.now()), 1e3);
|
|
26467
|
+
return () => clearInterval(id);
|
|
26468
|
+
}, [loopStatus]);
|
|
26469
|
+
const remainingMs = loopStatus ? Math.max(0, loopStatus.nextFireMs - (now - lastStatusSyncRef.current)) : 0;
|
|
26470
|
+
const startLoop = q2(
|
|
26471
|
+
async (intervalMs, prompt) => {
|
|
26472
|
+
setLoopBusy(true);
|
|
26473
|
+
try {
|
|
26474
|
+
await api("/loop/start", { method: "POST", body: { intervalMs, prompt } });
|
|
26475
|
+
await refreshLoop();
|
|
26476
|
+
} catch (err) {
|
|
26477
|
+
setError(err.message);
|
|
26478
|
+
} finally {
|
|
26479
|
+
setLoopBusy(false);
|
|
26480
|
+
}
|
|
26481
|
+
},
|
|
26482
|
+
[refreshLoop]
|
|
26483
|
+
);
|
|
26484
|
+
const stopLoop = q2(async () => {
|
|
26485
|
+
setLoopBusy(true);
|
|
26486
|
+
try {
|
|
26487
|
+
await api("/loop/stop", { method: "POST" });
|
|
26488
|
+
await refreshLoop();
|
|
26489
|
+
} catch (err) {
|
|
26490
|
+
setError(err.message);
|
|
26491
|
+
} finally {
|
|
26492
|
+
setLoopBusy(false);
|
|
26493
|
+
}
|
|
26494
|
+
}, [refreshLoop]);
|
|
25851
26495
|
const save = q2(
|
|
25852
26496
|
async (fields) => {
|
|
25853
26497
|
setSaving(true);
|
|
@@ -25991,11 +26635,50 @@ function SettingsPanel() {
|
|
|
25991
26635
|
)}
|
|
25992
26636
|
</div>
|
|
25993
26637
|
|
|
26638
|
+
${sectionH3(t4("settings.sectionCompute"))}
|
|
26639
|
+
<div class="card">
|
|
26640
|
+
${fieldRow(
|
|
26641
|
+
t4("settings.proNext"),
|
|
26642
|
+
html4`
|
|
26643
|
+
<button
|
|
26644
|
+
class=${`btn ${v3.proNext ? "primary" : ""}`}
|
|
26645
|
+
onClick=${() => save({ proNext: !v3.proNext })}
|
|
26646
|
+
disabled=${saving}
|
|
26647
|
+
>${v3.proNext ? t4("settings.proArmed") : t4("settings.proArm")}</button>
|
|
26648
|
+
`,
|
|
26649
|
+
t4("settings.proNextNote")
|
|
26650
|
+
)}
|
|
26651
|
+
</div>
|
|
26652
|
+
|
|
26653
|
+
${sectionH3(t4("settings.sectionBudget"))}
|
|
26654
|
+
<${BudgetSection}
|
|
26655
|
+
state=${deriveBudgetState(v3.budgetUsd, v3.sessionSpendUsd)}
|
|
26656
|
+
saving=${saving}
|
|
26657
|
+
onSetCap=${(usd) => save({ budgetUsd: usd })}
|
|
26658
|
+
onClear=${() => save({ budgetUsd: null })}
|
|
26659
|
+
/>
|
|
26660
|
+
|
|
26661
|
+
${sectionH3(t4("settings.sectionLoop"))}
|
|
26662
|
+
<${LoopSection}
|
|
26663
|
+
status=${loopStatus}
|
|
26664
|
+
remainingMs=${remainingMs}
|
|
26665
|
+
avgIterCostUsd=${loopAvgCost}
|
|
26666
|
+
busy=${loopBusy}
|
|
26667
|
+
onStart=${startLoop}
|
|
26668
|
+
onStop=${stopLoop}
|
|
26669
|
+
/>
|
|
26670
|
+
|
|
25994
26671
|
${sectionH3(t4("settings.sectionRuntime"))}
|
|
25995
26672
|
<div class="card">
|
|
25996
26673
|
${fieldRow(
|
|
25997
26674
|
t4("settings.activeModel"),
|
|
25998
|
-
html4
|
|
26675
|
+
html4`<${ModelRow}
|
|
26676
|
+
current=${v3.model ?? "\u2014"}
|
|
26677
|
+
catalog=${catalog}
|
|
26678
|
+
saving=${saving}
|
|
26679
|
+
onPick=${(m3) => save({ model: m3 })}
|
|
26680
|
+
/>`,
|
|
26681
|
+
t4("settings.appliesNextTurn")
|
|
25999
26682
|
)}
|
|
26000
26683
|
${fieldRow(
|
|
26001
26684
|
t4("settings.editMode"),
|