ds-bot 0.1.0 → 0.1.1
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 +3 -1
- package/client.js +513 -71
- package/index.js +3 -1
- package/package.json +4 -1
- package/src/host/channel.js +12 -5
- package/src/host/dsh-update.js +168 -0
- package/src/host/groups.js +2 -0
- package/src/host/models.js +65 -8
- package/src/host/parts.js +18 -2
- package/src/host/permissions.js +42 -0
- package/src/host/roster.js +14 -1
- package/src/host/update.js +45 -40
- package/src/host/usage.js +26 -4
package/client.js
CHANGED
|
@@ -217,6 +217,10 @@ var ZH = {
|
|
|
217
217
|
"Click again to delete. Its chats are archived.": "再点一次删除,它的对话会归档。",
|
|
218
218
|
"Removes the Bot from the team.": "把这个 Bot 从团队里移除。",
|
|
219
219
|
"Pick a model and {name} can start.": "选好模型,{name} 就能开始工作。",
|
|
220
|
+
"Permissions": "权限",
|
|
221
|
+
"Read only": "仅可查看",
|
|
222
|
+
"Workspace": "工作区内修改",
|
|
223
|
+
"Full access": "完全权限",
|
|
220
224
|
"The only Main Bot. To hand the role to another Bot, use Main Bot on the Bots page.": "这是唯一的主 Bot。要把主 Bot 交给别的 Bot,请到「Bot」页的主 Bot 里换。",
|
|
221
225
|
Change: "更换",
|
|
222
226
|
"Can create and change Bots, and runs every group chat.": "可以创建和修改 Bot,并管理所有群聊。",
|
|
@@ -245,6 +249,7 @@ var ZH = {
|
|
|
245
249
|
Create: "创建",
|
|
246
250
|
// Model menu
|
|
247
251
|
Default: "默认",
|
|
252
|
+
"Thinking level": "思考强度",
|
|
248
253
|
"Model: {model}": "模型:{model}",
|
|
249
254
|
none: "无",
|
|
250
255
|
"Choose a model": "选择模型",
|
|
@@ -368,6 +373,13 @@ var ZH = {
|
|
|
368
373
|
"The update did not finish: {reason}": "更新没有完成:{reason}",
|
|
369
374
|
"What's new": "更新说明",
|
|
370
375
|
"Updating…": "正在更新…",
|
|
376
|
+
"DSH {version} is available": "DSH {version} 可以更新",
|
|
377
|
+
"Updating DSH…": "正在更新 DSH…",
|
|
378
|
+
"DSH update": "DSH 更新",
|
|
379
|
+
"DSH {version}": "DSH {version}",
|
|
380
|
+
"Restart DSH to start using DSH {version}.": "重启 DSH 后开始使用 DSH {version}。",
|
|
381
|
+
"Update DS Bot {version} first.": "先更新 DS Bot {version}。",
|
|
382
|
+
"Open updates": "打开更新",
|
|
371
383
|
// Command palette
|
|
372
384
|
Messages: "消息",
|
|
373
385
|
Commands: "命令",
|
|
@@ -601,6 +613,10 @@ var ZH = {
|
|
|
601
613
|
"Resume listening": "继续听",
|
|
602
614
|
Pause: "暂停",
|
|
603
615
|
"End voice chat": "结束语音对话",
|
|
616
|
+
"Recognizing…": "识别中…",
|
|
617
|
+
"Voice input needs its speech model": "语音输入还没有语音模型",
|
|
618
|
+
"Set up voice input": "设置语音输入",
|
|
619
|
+
"That was too long to recognize": "这段太长了,识别不了",
|
|
604
620
|
// Usage page
|
|
605
621
|
"24 hours": "24 小时",
|
|
606
622
|
"7 days": "7 天",
|
|
@@ -802,6 +818,7 @@ var HOST_ERRORS = [
|
|
|
802
818
|
];
|
|
803
819
|
var HOST_ZH = {
|
|
804
820
|
"Request failed": "请求失败",
|
|
821
|
+
"Permissions are not available in this DeepSeek Harness": "这个 DeepSeek Harness 不支持权限设置",
|
|
805
822
|
"Avatar images must be PNG, JPEG, WebP, or GIF": "头像图片必须是 PNG、JPEG、WebP 或 GIF",
|
|
806
823
|
"Avatar image is too large": "头像图片太大了",
|
|
807
824
|
"A Bot needs a name": "Bot 需要一个名字",
|
|
@@ -2014,6 +2031,8 @@ button.bt-q-opt:active{background:var(--bt-active)}
|
|
|
2014
2031
|
.bt-set-body{gap:20px}
|
|
2015
2032
|
.bt-drawer-model{display:flex;flex-direction:column}
|
|
2016
2033
|
.bt-drawer-model .bt-note{margin-top:6px}
|
|
2034
|
+
.bt-drawer-permission{display:flex;flex-direction:column}
|
|
2035
|
+
.bt-seg button.bt-perm-danger,.bt-seg button.bt-perm-danger[aria-pressed=true]{color:#e02135}
|
|
2017
2036
|
|
|
2018
2037
|
/* Settings dialog: a 198px page list beside the page. */
|
|
2019
2038
|
.bt-settings-layer{position:fixed;inset:var(--dsh-frame-chrome-top,0px) 0 0;z-index:60;display:flex;align-items:center;justify-content:center;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif}
|
|
@@ -2113,6 +2132,8 @@ button.bt-set-row:hover{background:var(--bt-hover)}
|
|
|
2113
2132
|
.bt-voice-status{font-size:13px;line-height:18px;color:var(--bt-ink-2);min-height:18px}
|
|
2114
2133
|
.bt-voice-caption{min-height:44px;font-size:15px;line-height:22px;color:var(--bt-ink);animation:bt-blur-in .25s ease-out;display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden}
|
|
2115
2134
|
.bt-voice-note{font-size:12px;color:#e02135}
|
|
2135
|
+
.bt-voice-setup{height:32px;padding:0 14px;border-radius:16px;border:0;background:var(--bt-hover);color:var(--bt-ink);font:inherit;font-size:13px;cursor:pointer;transition:background-color .15s ease}
|
|
2136
|
+
.bt-voice-setup:hover{background:var(--bt-active)}
|
|
2116
2137
|
.bt-voice-bar{display:flex;gap:16px;padding:0 0 32px}
|
|
2117
2138
|
.bt-voice-btn{position:relative;width:52px;height:52px;border-radius:50%;border:0;background:var(--bt-hover);color:var(--bt-ink);display:grid;place-items:center;cursor:pointer;transition:background-color .15s ease,transform .12s ease}
|
|
2118
2139
|
.bt-voice-btn:hover{background:var(--bt-active)}
|
|
@@ -3513,12 +3534,22 @@ var SECRET_CSS = `
|
|
|
3513
3534
|
@media (prefers-reduced-motion:reduce){.bt-secret-glide{transition:none}.bt-secret-check,.bt-secret-done,.bt-secret-ok,.bt-secret-edit{animation:none}}
|
|
3514
3535
|
`;
|
|
3515
3536
|
|
|
3537
|
+
// src/client/turn-reasoning.js
|
|
3538
|
+
function turnReasoning(steps, current) {
|
|
3539
|
+
const members = steps.length ? steps : [current];
|
|
3540
|
+
if (members.find((step) => stepReasoning(step) !== "")?.step !== current.step) return "";
|
|
3541
|
+
return members.filter((step) => !thinkingLive(step)).map(stepReasoning).filter(Boolean).join("\n\n");
|
|
3542
|
+
}
|
|
3543
|
+
var texts = (step) => step.blocks.filter((block) => block.kind === "text" && block.text.trim() !== "");
|
|
3544
|
+
var thinkingLive = (step) => step.status === "running" && texts(step).length === 0 && step.blocks.some((block) => block.kind === "reasoning");
|
|
3545
|
+
var stepReasoning = (step) => step.blocks.filter((block) => block.kind === "reasoning").map((block) => block.text).join("\n").trim();
|
|
3546
|
+
|
|
3516
3547
|
// src/client/cells.js
|
|
3517
3548
|
var import_react6 = require("react");
|
|
3518
3549
|
var import_react_dom = require("react-dom");
|
|
3519
3550
|
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
3520
3551
|
var mdLabels = () => ({ code: { copyLabel: t("Copy"), copiedLabel: t("Copied") }, footnotes: t("Footnotes") });
|
|
3521
|
-
function Bubbles({ texts, roster, selfId, actions, streaming, sessionId, msgKey, useUi, lead }) {
|
|
3552
|
+
function Bubbles({ texts: texts2, roster, selfId, actions, streaming, sessionId, msgKey, useUi, lead }) {
|
|
3522
3553
|
const onClick = (event) => {
|
|
3523
3554
|
const target = mentionTarget(event);
|
|
3524
3555
|
if (target === null) return;
|
|
@@ -3530,10 +3561,10 @@ function Bubbles({ texts, roster, selfId, actions, streaming, sessionId, msgKey,
|
|
|
3530
3561
|
return (0, import_react6.createElement)(
|
|
3531
3562
|
"div",
|
|
3532
3563
|
{ className: "bt-astack", "data-lead": lead ? "" : void 0, onClickCapture: onClick },
|
|
3533
|
-
|
|
3564
|
+
texts2.map((text, index) => {
|
|
3534
3565
|
const key = msgKey ? `${msgKey}:${index}` : void 0;
|
|
3535
3566
|
const picked = key ? reactions?.[index] ?? [] : [];
|
|
3536
|
-
const last = index ===
|
|
3567
|
+
const last = index === texts2.length - 1;
|
|
3537
3568
|
const live = streaming && last;
|
|
3538
3569
|
return (0, import_react6.createElement)(
|
|
3539
3570
|
"div",
|
|
@@ -3638,8 +3669,7 @@ function MessageTools({ text, name, sessionId, react, actions }) {
|
|
|
3638
3669
|
), document.body) : null
|
|
3639
3670
|
);
|
|
3640
3671
|
}
|
|
3641
|
-
function ThinkingRow({ text
|
|
3642
|
-
if (running || !text) return null;
|
|
3672
|
+
function ThinkingRow({ text }) {
|
|
3643
3673
|
return (0, import_react6.createElement)(
|
|
3644
3674
|
"details",
|
|
3645
3675
|
{ className: "bt-think" },
|
|
@@ -3647,22 +3677,22 @@ function ThinkingRow({ text, running }) {
|
|
|
3647
3677
|
(0, import_react6.createElement)("div", { className: "bt-think-text" }, text)
|
|
3648
3678
|
);
|
|
3649
3679
|
}
|
|
3650
|
-
function AssistantCell({ node, groupPart, sessionId, useRoster, useUi, actions }) {
|
|
3680
|
+
function AssistantCell({ node, groupPart, sessionId, useRoster, useUi, useTurnSteps, actions }) {
|
|
3651
3681
|
const roster = useRoster((value) => value);
|
|
3652
3682
|
const data = node.data;
|
|
3683
|
+
const steps = useTurnSteps(String(data.turn));
|
|
3653
3684
|
const hidden = Boolean(roster.roomsById[sessionId]) || isHiddenTurn(roster, sessionId, data.turn);
|
|
3654
|
-
const
|
|
3685
|
+
const texts2 = groupPart === "reasoning" ? [] : data.blocks.filter((block) => block.kind === "text" && block.text.trim() !== "").map((block) => block.text);
|
|
3655
3686
|
const running = data.status === "running";
|
|
3656
|
-
useLiveNote(actions, sessionId, `text:${node.key ?? data.seq}`, !hidden && running &&
|
|
3687
|
+
useLiveNote(actions, sessionId, `text:${node.key ?? data.seq}`, !hidden && running && texts2.length > 0 ? { text: true } : null);
|
|
3657
3688
|
if (hidden) return null;
|
|
3658
|
-
const reasoning = groupPart === "response" ? "" :
|
|
3659
|
-
|
|
3660
|
-
if (texts.length === 0 && !reasoning) return null;
|
|
3689
|
+
const reasoning = groupPart === "response" ? "" : turnReasoning(steps, data);
|
|
3690
|
+
if (texts2.length === 0 && !reasoning) return null;
|
|
3661
3691
|
return (0, import_react6.createElement)(
|
|
3662
3692
|
import_react6.Fragment,
|
|
3663
3693
|
null,
|
|
3664
|
-
reasoning ? (0, import_react6.createElement)(ThinkingRow, { text: reasoning
|
|
3665
|
-
|
|
3694
|
+
reasoning ? (0, import_react6.createElement)(ThinkingRow, { text: reasoning }) : null,
|
|
3695
|
+
texts2.length ? (0, import_react6.createElement)(Bubbles, { texts: texts2, roster, selfId: roster.byId[sessionId]?.id ?? sessionId, actions, streaming: running, sessionId, msgKey: `${sessionId}:${data.turn}:${node.key ?? data.seq ?? ""}`, useUi }) : null
|
|
3666
3696
|
);
|
|
3667
3697
|
}
|
|
3668
3698
|
function EventLine({ prefix, bots, joiner = t("and"), onOpen }) {
|
|
@@ -4667,6 +4697,7 @@ function UpdateButton({ useUpdate, actions }) {
|
|
|
4667
4697
|
const [open, setOpen] = (0, import_react8.useState)(false);
|
|
4668
4698
|
const [box, setBox] = (0, import_react8.useState)(null);
|
|
4669
4699
|
const [error, setError] = (0, import_react8.useState)("");
|
|
4700
|
+
const [dshError, setDshError] = (0, import_react8.useState)("");
|
|
4670
4701
|
const button = (0, import_react8.useRef)(null);
|
|
4671
4702
|
(0, import_react8.useEffect)(() => {
|
|
4672
4703
|
if (!open) return void 0;
|
|
@@ -4689,7 +4720,11 @@ function UpdateButton({ useUpdate, actions }) {
|
|
|
4689
4720
|
};
|
|
4690
4721
|
}, [open]);
|
|
4691
4722
|
const phase = update?.phase ?? "idle";
|
|
4692
|
-
|
|
4723
|
+
const dsh = update?.dsh;
|
|
4724
|
+
const dshPhase = dsh?.phase ?? "idle";
|
|
4725
|
+
const dshRow = dsh && dsh.verified && dsh.current && dsh.verified !== dsh.current;
|
|
4726
|
+
if (!update?.available && !dshRow && phase === "idle" && dshPhase === "idle") return null;
|
|
4727
|
+
const cloudPhase = phase === "installing" || dshPhase === "installing" ? "installing" : phase !== "idle" ? phase : dshPhase;
|
|
4693
4728
|
const toggle = () => {
|
|
4694
4729
|
const rect = button.current?.getBoundingClientRect();
|
|
4695
4730
|
if (rect) setBox({ left: Math.max(8, Math.min(rect.left, window.innerWidth - 288)), top: rect.bottom + 6 });
|
|
@@ -4699,8 +4734,14 @@ function UpdateButton({ useUpdate, actions }) {
|
|
|
4699
4734
|
setError("");
|
|
4700
4735
|
void actions.installUpdate().catch((failure) => setError(hostText(failure?.message ?? String(failure))));
|
|
4701
4736
|
};
|
|
4702
|
-
const
|
|
4737
|
+
const startDsh = () => {
|
|
4738
|
+
setDshError("");
|
|
4739
|
+
void actions.installDshUpdate().catch((failure) => setDshError(hostText(failure?.message ?? String(failure))));
|
|
4740
|
+
};
|
|
4741
|
+
const label = phase === "installing" ? t("Updating DS Bot…") : dshPhase === "installing" ? t("Updating DSH…") : phase === "installed" ? t("DS Bot {version} is installed. Restart DSH to use it.", { version: update.installed ?? update.latest }) : dshPhase === "installed" ? t("Restart DSH to start using DSH {version}.", { version: dsh.installed ?? dsh.verified }) : update?.available ? t("DS Bot {version} is available", { version: update.latest }) : t("DSH {version} is available", { version: dsh?.verified });
|
|
4703
4742
|
const message = error || (phase === "failed" ? update.error : "");
|
|
4743
|
+
const dshMessage = dshError || (dshPhase === "failed" ? dsh?.error : "");
|
|
4744
|
+
const dshText = dshPhase === "installed" ? t("Restart DSH to start using DSH {version}.", { version: dsh.installed ?? dsh.verified }) : dsh?.available === false && dsh?.blockedBy && dsh.kind === "npm" ? t("Update DS Bot {version} first.", { version: dsh.blockedBy }) : t("You have {version}.", { version: dsh?.current });
|
|
4704
4745
|
return (0, import_react8.createElement)(
|
|
4705
4746
|
import_react8.Fragment,
|
|
4706
4747
|
null,
|
|
@@ -4708,30 +4749,51 @@ function UpdateButton({ useUpdate, actions }) {
|
|
|
4708
4749
|
ref: button,
|
|
4709
4750
|
type: "button",
|
|
4710
4751
|
className: "bt-round bt-update",
|
|
4711
|
-
"data-phase":
|
|
4752
|
+
"data-phase": cloudPhase,
|
|
4712
4753
|
"aria-label": label,
|
|
4713
4754
|
title: label,
|
|
4714
4755
|
"aria-haspopup": "dialog",
|
|
4715
4756
|
"aria-expanded": open,
|
|
4716
4757
|
onClick: toggle
|
|
4717
|
-
}, (0, import_react8.createElement)(CloudDownloadIcon, { done: phase === "installed" }), phase === "failed" ? (0, import_react8.createElement)("span", { className: "bt-update-dot", "aria-hidden": true }) : null),
|
|
4758
|
+
}, (0, import_react8.createElement)(CloudDownloadIcon, { done: phase === "installed" }), phase === "failed" || dshPhase === "failed" ? (0, import_react8.createElement)("span", { className: "bt-update-dot", "aria-hidden": true }) : null),
|
|
4718
4759
|
open && box ? (0, import_react_dom3.createPortal)((0, import_react8.createElement)(
|
|
4719
4760
|
"div",
|
|
4720
|
-
{ className: "bt-update-pop", role: "dialog", "aria-label": t("DS Bot update"), style: box },
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4761
|
+
{ className: "bt-update-pop", role: "dialog", "aria-label": update?.available || phase !== "idle" ? t("DS Bot update") : t("DSH update"), style: box },
|
|
4762
|
+
update?.available || phase !== "idle" ? (0, import_react8.createElement)(
|
|
4763
|
+
import_react8.Fragment,
|
|
4764
|
+
null,
|
|
4765
|
+
(0, import_react8.createElement)("div", { className: "bt-update-title" }, phase === "installed" ? t("Update installed") : t("DS Bot {version}", { version: update.latest })),
|
|
4766
|
+
(0, import_react8.createElement)("div", { className: "bt-update-text" }, phase === "installed" ? t("Restart DSH to start using DS Bot {version}.", { version: update.installed ?? update.latest }) : t("You have {version}.", { version: update.current })),
|
|
4767
|
+
message ? (0, import_react8.createElement)("div", { className: "bt-update-error", role: "alert" }, t("The update did not finish: {reason}", { reason: message })) : null,
|
|
4768
|
+
(0, import_react8.createElement)(
|
|
4769
|
+
"div",
|
|
4770
|
+
{ className: "bt-update-actions" },
|
|
4771
|
+
update.notesUrl ? (0, import_react8.createElement)("a", { className: "bt-update-notes", href: update.notesUrl, target: "_blank", rel: "noopener noreferrer" }, t("What's new")) : null,
|
|
4772
|
+
phase === "installed" ? null : (0, import_react8.createElement)("button", {
|
|
4773
|
+
type: "button",
|
|
4774
|
+
className: "bt-update-go",
|
|
4775
|
+
disabled: phase === "installing",
|
|
4776
|
+
onClick: start
|
|
4777
|
+
}, phase === "installing" ? t("Updating…") : phase === "failed" ? t("Try again") : t("Update"))
|
|
4778
|
+
)
|
|
4779
|
+
) : null,
|
|
4780
|
+
dshRow ? (0, import_react8.createElement)(
|
|
4725
4781
|
"div",
|
|
4726
|
-
{ className: "bt-update-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4782
|
+
{ className: "bt-update-dsh" },
|
|
4783
|
+
(0, import_react8.createElement)("div", { className: "bt-update-title" }, t("DSH {version}", { version: dsh.verified })),
|
|
4784
|
+
(0, import_react8.createElement)("div", { className: "bt-update-text" }, dshText),
|
|
4785
|
+
dshMessage ? (0, import_react8.createElement)("div", { className: "bt-update-error", role: "alert" }, t("The update did not finish: {reason}", { reason: dshMessage })) : null,
|
|
4786
|
+
(0, import_react8.createElement)(
|
|
4787
|
+
"div",
|
|
4788
|
+
{ className: "bt-update-actions" },
|
|
4789
|
+
dsh.kind === "app" ? (0, import_react8.createElement)("button", { type: "button", className: "bt-update-go", onClick: () => actions.openAppUpdates() }, t("Open updates")) : !dsh.available && dshPhase === "idle" ? null : dshPhase === "installed" ? null : (0, import_react8.createElement)("button", {
|
|
4790
|
+
type: "button",
|
|
4791
|
+
className: "bt-update-go",
|
|
4792
|
+
disabled: dshPhase === "installing",
|
|
4793
|
+
onClick: startDsh
|
|
4794
|
+
}, dshPhase === "installing" ? t("Updating…") : dshPhase === "failed" ? t("Try again") : t("Update"))
|
|
4795
|
+
)
|
|
4796
|
+
) : null
|
|
4735
4797
|
), document.body) : null
|
|
4736
4798
|
);
|
|
4737
4799
|
}
|
|
@@ -5027,6 +5089,7 @@ var SIDEBAR_CSS = `
|
|
|
5027
5089
|
.bt-update-go:hover:not(:disabled){opacity:.9}
|
|
5028
5090
|
.bt-update-go:active:not(:disabled){transform:scale(.96)}
|
|
5029
5091
|
.bt-update-go:disabled{opacity:.6;cursor:default}
|
|
5092
|
+
.bt-update-dsh{margin-top:12px;padding-top:12px;border-top:.5px solid var(--bt-line-2)}
|
|
5030
5093
|
@media (prefers-reduced-motion:reduce){.bt-round.bt-update,.bt-update-pop,.bt-update .bt-cloud-arrow{animation:none}}
|
|
5031
5094
|
|
|
5032
5095
|
/* The DSH Agent row and its expanding Session list. The black whale is the
|
|
@@ -5354,10 +5417,11 @@ function ComposerVoice({ sessionId, inputActions, actions, useUi }) {
|
|
|
5354
5417
|
}
|
|
5355
5418
|
}, [quote?.token]);
|
|
5356
5419
|
if (sessionId === void 0) return null;
|
|
5420
|
+
const pluginMic = actions.speech() !== void 0;
|
|
5357
5421
|
return (0, import_react10.createElement)(
|
|
5358
5422
|
import_react10.Fragment,
|
|
5359
5423
|
null,
|
|
5360
|
-
(0, import_react10.createElement)("button", {
|
|
5424
|
+
pluginMic ? null : (0, import_react10.createElement)("button", {
|
|
5361
5425
|
type: "button",
|
|
5362
5426
|
className: "bt-mic",
|
|
5363
5427
|
"aria-label": dictation.listening ? t("Stop dictation") : t("Dictate"),
|
|
@@ -5449,6 +5513,132 @@ function ComposerPlus({ sessionId, useRoster, actions }) {
|
|
|
5449
5513
|
);
|
|
5450
5514
|
}
|
|
5451
5515
|
|
|
5516
|
+
// src/client/speech.js
|
|
5517
|
+
var VOICE_BUNDLE = "@deepseek-ai/dsh-experimental-voice-input-bundle";
|
|
5518
|
+
var USABLE = /* @__PURE__ */ new Set(["ready", "standby", "waking"]);
|
|
5519
|
+
async function speechCatalog(speech) {
|
|
5520
|
+
const result = await speech.catalog();
|
|
5521
|
+
if (!result?.ok) throw new Error(result?.error?.message ?? "Speech catalog failed");
|
|
5522
|
+
const catalog = result.value;
|
|
5523
|
+
const provider = catalog.providers.find((item) => item.id === catalog.selection.providerId);
|
|
5524
|
+
return { catalog, usable: USABLE.has(provider?.preparation?.phase) };
|
|
5525
|
+
}
|
|
5526
|
+
function waveBytes(samples, rate = 16e3) {
|
|
5527
|
+
const bytes = new Uint8Array(44 + samples.length * 2);
|
|
5528
|
+
const view = new DataView(bytes.buffer);
|
|
5529
|
+
const ascii = (at, text) => {
|
|
5530
|
+
for (let index = 0; index < text.length; index += 1) bytes[at + index] = text.charCodeAt(index);
|
|
5531
|
+
};
|
|
5532
|
+
ascii(0, "RIFF");
|
|
5533
|
+
view.setUint32(4, 36 + samples.length * 2, true);
|
|
5534
|
+
ascii(8, "WAVEfmt ");
|
|
5535
|
+
view.setUint32(16, 16, true);
|
|
5536
|
+
view.setUint16(20, 1, true);
|
|
5537
|
+
view.setUint16(22, 1, true);
|
|
5538
|
+
view.setUint32(24, rate, true);
|
|
5539
|
+
view.setUint32(28, rate * 2, true);
|
|
5540
|
+
view.setUint16(32, 2, true);
|
|
5541
|
+
view.setUint16(34, 16, true);
|
|
5542
|
+
ascii(36, "data");
|
|
5543
|
+
view.setUint32(40, samples.length * 2, true);
|
|
5544
|
+
samples.forEach((sample, index) => {
|
|
5545
|
+
const value = Math.max(-1, Math.min(1, sample));
|
|
5546
|
+
view.setInt16(44 + index * 2, value < 0 ? Math.round(value * 32768) : Math.round(value * 32767), true);
|
|
5547
|
+
});
|
|
5548
|
+
return bytes;
|
|
5549
|
+
}
|
|
5550
|
+
function base64Of(bytes) {
|
|
5551
|
+
let text = "";
|
|
5552
|
+
for (let index = 0; index < bytes.length; index += 32768) text += String.fromCharCode(...bytes.subarray(index, index + 32768));
|
|
5553
|
+
return btoa(text);
|
|
5554
|
+
}
|
|
5555
|
+
var captureError = (kind, cause) => Object.assign(new Error(kind), { kind, cause });
|
|
5556
|
+
async function openCapture() {
|
|
5557
|
+
const devices = navigator.mediaDevices;
|
|
5558
|
+
if (!devices?.getUserMedia || typeof MediaRecorder === "undefined") throw captureError("unavailable");
|
|
5559
|
+
let stream;
|
|
5560
|
+
try {
|
|
5561
|
+
stream = await devices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true }, video: false });
|
|
5562
|
+
} catch (error) {
|
|
5563
|
+
throw captureError(error?.name === "NotAllowedError" ? "blocked" : "failed", error);
|
|
5564
|
+
}
|
|
5565
|
+
const audio = new AudioContext();
|
|
5566
|
+
const analyser = audio.createAnalyser();
|
|
5567
|
+
analyser.fftSize = 512;
|
|
5568
|
+
audio.createMediaStreamSource(stream).connect(analyser);
|
|
5569
|
+
const frame = new Float32Array(analyser.fftSize);
|
|
5570
|
+
const recorder = new MediaRecorder(stream);
|
|
5571
|
+
const chunks = [];
|
|
5572
|
+
recorder.ondataavailable = (event) => {
|
|
5573
|
+
if (event.data.size > 0) chunks.push(event.data);
|
|
5574
|
+
};
|
|
5575
|
+
recorder.start();
|
|
5576
|
+
let closed = false;
|
|
5577
|
+
const close = () => {
|
|
5578
|
+
if (closed) return;
|
|
5579
|
+
closed = true;
|
|
5580
|
+
if (recorder.state !== "inactive") recorder.stop();
|
|
5581
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
5582
|
+
void audio.close().catch(() => {
|
|
5583
|
+
});
|
|
5584
|
+
};
|
|
5585
|
+
return {
|
|
5586
|
+
level() {
|
|
5587
|
+
if (closed) return 0;
|
|
5588
|
+
analyser.getFloatTimeDomainData(frame);
|
|
5589
|
+
let sum = 0;
|
|
5590
|
+
for (const sample of frame) sum += sample * sample;
|
|
5591
|
+
return Math.min(1, Math.sqrt(sum / frame.length) * 4);
|
|
5592
|
+
},
|
|
5593
|
+
cancel: close,
|
|
5594
|
+
async finish(maxSeconds) {
|
|
5595
|
+
if (closed || recorder.state === "inactive") {
|
|
5596
|
+
close();
|
|
5597
|
+
return null;
|
|
5598
|
+
}
|
|
5599
|
+
try {
|
|
5600
|
+
await new Promise((resolve) => {
|
|
5601
|
+
recorder.onstop = resolve;
|
|
5602
|
+
recorder.stop();
|
|
5603
|
+
});
|
|
5604
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
5605
|
+
const blob = new Blob(chunks, { type: recorder.mimeType });
|
|
5606
|
+
if (blob.size === 0) return null;
|
|
5607
|
+
const decoded = await audio.decodeAudioData(await blob.arrayBuffer());
|
|
5608
|
+
const length = Math.max(1, Math.floor(Math.min(decoded.duration, maxSeconds) * 16e3));
|
|
5609
|
+
const offline = new OfflineAudioContext(1, length, 16e3);
|
|
5610
|
+
const source = offline.createBufferSource();
|
|
5611
|
+
source.buffer = decoded;
|
|
5612
|
+
source.connect(offline.destination);
|
|
5613
|
+
source.start();
|
|
5614
|
+
return waveBytes((await offline.startRendering()).getChannelData(0));
|
|
5615
|
+
} finally {
|
|
5616
|
+
close();
|
|
5617
|
+
}
|
|
5618
|
+
}
|
|
5619
|
+
};
|
|
5620
|
+
}
|
|
5621
|
+
async function transcribeWave(speech, catalog, wave, signal) {
|
|
5622
|
+
if (wave.byteLength > catalog.maxAudioBytes) throw captureError("too-long");
|
|
5623
|
+
const result = await speech.transcribe({ audioBase64: base64Of(wave), ...catalog.selection }, signal);
|
|
5624
|
+
if (!result?.ok) throw Object.assign(new Error(result?.error?.message ?? "Transcription failed"), { kind: "service" });
|
|
5625
|
+
return String(result.value?.text ?? "").trim();
|
|
5626
|
+
}
|
|
5627
|
+
function turnDetector({ loud = 0.12, quiet = 0.06, quietMs = 1200 } = {}) {
|
|
5628
|
+
let spokeAt = 0;
|
|
5629
|
+
let quietSince = 0;
|
|
5630
|
+
return (level, now) => {
|
|
5631
|
+
if (level >= loud) {
|
|
5632
|
+
spokeAt ||= now;
|
|
5633
|
+
quietSince = 0;
|
|
5634
|
+
return "speech";
|
|
5635
|
+
}
|
|
5636
|
+
if (!spokeAt) return "waiting";
|
|
5637
|
+
if (level < quiet) quietSince ||= now;
|
|
5638
|
+
return quietSince && now - quietSince >= quietMs ? "done" : "speech";
|
|
5639
|
+
};
|
|
5640
|
+
}
|
|
5641
|
+
|
|
5452
5642
|
// src/client/overlay-hooks.js
|
|
5453
5643
|
var import_react11 = require("react");
|
|
5454
5644
|
var escapeLayers = [];
|
|
@@ -5680,10 +5870,19 @@ var MENU_EXIT_MS = 120;
|
|
|
5680
5870
|
function groupModels(models) {
|
|
5681
5871
|
const groups = /* @__PURE__ */ new Map();
|
|
5682
5872
|
for (const model of models ?? []) {
|
|
5683
|
-
|
|
5684
|
-
groups.
|
|
5873
|
+
const provider = model.provider ?? model.providerName ?? model.ref.split("/")[0];
|
|
5874
|
+
if (!groups.has(provider)) groups.set(provider, []);
|
|
5875
|
+
groups.get(provider).push(model);
|
|
5685
5876
|
}
|
|
5686
|
-
|
|
5877
|
+
const names = /* @__PURE__ */ new Map();
|
|
5878
|
+
for (const [provider, list] of groups) {
|
|
5879
|
+
const name = list[0].providerName ?? provider;
|
|
5880
|
+
names.set(name, (names.get(name) ?? 0) + 1);
|
|
5881
|
+
}
|
|
5882
|
+
return [...groups].map(([provider, list]) => {
|
|
5883
|
+
const name = list[0].providerName ?? provider;
|
|
5884
|
+
return [names.get(name) > 1 ? `${name} (${provider})` : name, list];
|
|
5885
|
+
});
|
|
5687
5886
|
}
|
|
5688
5887
|
var servedOf = (roster, ref) => (roster.models ?? []).find((model) => model.ref === ref);
|
|
5689
5888
|
function modelOf(bot, roster) {
|
|
@@ -5693,10 +5892,57 @@ function modelOf(bot, roster) {
|
|
|
5693
5892
|
const [provider, ...rest] = ref.split("/");
|
|
5694
5893
|
return { ref, name: served?.name ?? rest.join("/"), provider: served?.providerName ?? provider, served: served !== void 0 };
|
|
5695
5894
|
}
|
|
5895
|
+
var effortCache = /* @__PURE__ */ new Map();
|
|
5896
|
+
function useEfforts(served, ref, actions) {
|
|
5897
|
+
const [fetched, setFetched] = (0, import_react13.useState)(void 0);
|
|
5898
|
+
(0, import_react13.useEffect)(() => {
|
|
5899
|
+
setFetched(void 0);
|
|
5900
|
+
if (ref === void 0 || served?.reasoning !== void 0 || actions?.efforts === void 0) return void 0;
|
|
5901
|
+
let live = true;
|
|
5902
|
+
if (effortCache.has(ref)) setFetched(effortCache.get(ref));
|
|
5903
|
+
else void Promise.resolve(actions.efforts(ref)).then((value) => {
|
|
5904
|
+
const reasoning = value?.efforts ? value : null;
|
|
5905
|
+
effortCache.set(ref, reasoning);
|
|
5906
|
+
if (live) setFetched(reasoning);
|
|
5907
|
+
}).catch(() => {
|
|
5908
|
+
});
|
|
5909
|
+
return () => {
|
|
5910
|
+
live = false;
|
|
5911
|
+
};
|
|
5912
|
+
}, [ref, served?.reasoning !== void 0, actions]);
|
|
5913
|
+
return served?.reasoning ?? fetched;
|
|
5914
|
+
}
|
|
5915
|
+
function effortRows(reasoning) {
|
|
5916
|
+
if (reasoning?.efforts === void 0 || reasoning.efforts.length === 0) return [];
|
|
5917
|
+
return [
|
|
5918
|
+
{ effort: void 0, label: t("Default") },
|
|
5919
|
+
...reasoning.efforts.map((effort) => ({ effort: effort.id, label: effort.name }))
|
|
5920
|
+
];
|
|
5921
|
+
}
|
|
5922
|
+
function EffortRows({ reasoning, current, onPick }) {
|
|
5923
|
+
return (0, import_react13.createElement)(
|
|
5924
|
+
import_react13.Fragment,
|
|
5925
|
+
null,
|
|
5926
|
+
(0, import_react13.createElement)("div", { className: "bt-menu-sep", role: "separator" }),
|
|
5927
|
+
(0, import_react13.createElement)("div", { className: "bt-model-group", role: "presentation" }, t("Thinking level")),
|
|
5928
|
+
effortRows(reasoning).map((row) => (0, import_react13.createElement)(
|
|
5929
|
+
"button",
|
|
5930
|
+
{
|
|
5931
|
+
key: row.effort ?? "default",
|
|
5932
|
+
type: "button",
|
|
5933
|
+
role: "menuitemradio",
|
|
5934
|
+
"aria-checked": row.effort === current,
|
|
5935
|
+
onClick: () => onPick(row.effort)
|
|
5936
|
+
},
|
|
5937
|
+
(0, import_react13.createElement)("span", { className: "bt-menu-label" }, row.label),
|
|
5938
|
+
(0, import_react13.createElement)("span", { className: "bt-model-tick", "aria-hidden": true }, row.effort === current ? (0, import_react13.createElement)(CheckIcon) : null)
|
|
5939
|
+
))
|
|
5940
|
+
);
|
|
5941
|
+
}
|
|
5696
5942
|
function ModelRows({ roster, current, onPick }) {
|
|
5697
5943
|
return groupModels(roster.models).map(([provider, list]) => (0, import_react13.createElement)(
|
|
5698
5944
|
import_react13.Fragment,
|
|
5699
|
-
{ key: provider },
|
|
5945
|
+
{ key: list[0].provider ?? provider },
|
|
5700
5946
|
(0, import_react13.createElement)("div", { className: "bt-model-group", role: "presentation" }, provider),
|
|
5701
5947
|
list.map((model) => (0, import_react13.createElement)(
|
|
5702
5948
|
"button",
|
|
@@ -5714,13 +5960,18 @@ function ModelRows({ roster, current, onPick }) {
|
|
|
5714
5960
|
))
|
|
5715
5961
|
));
|
|
5716
5962
|
}
|
|
5717
|
-
function ModelMenu({ bot, roster, actions, run, wide = false, onPick }) {
|
|
5963
|
+
function ModelMenu({ bot, roster, actions, run, wide = false, onPick, onPickEffort }) {
|
|
5718
5964
|
const [place, setPlace] = (0, import_react13.useState)(null);
|
|
5719
5965
|
const [shownPlace, leaving] = useLinger(place, MENU_EXIT_MS);
|
|
5720
5966
|
const button = (0, import_react13.useRef)(null);
|
|
5721
5967
|
const menu = (0, import_react13.useRef)(null);
|
|
5722
5968
|
const shown = modelOf(bot, roster);
|
|
5723
5969
|
const current = bot.model ?? bot.appliedModel;
|
|
5970
|
+
const served = servedOf(roster, current);
|
|
5971
|
+
const reasoning = useEfforts(served, current, actions);
|
|
5972
|
+
const picked = reasoning?.efforts?.some((effort) => effort.id === bot.effort) ? bot.effort : void 0;
|
|
5973
|
+
const currentEffort = picked ?? reasoning?.defaultEffort;
|
|
5974
|
+
const effortLabel = currentEffort === void 0 ? reasoning?.efforts?.length > 0 ? t("Default") : void 0 : reasoning.efforts.find((effort) => effort.id === currentEffort)?.name ?? currentEffort;
|
|
5724
5975
|
const close = () => setPlace(null);
|
|
5725
5976
|
(0, import_react13.useEffect)(() => {
|
|
5726
5977
|
if (place === null) return void 0;
|
|
@@ -5764,6 +6015,11 @@ function ModelMenu({ bot, roster, actions, run, wide = false, onPick }) {
|
|
|
5764
6015
|
if (onPick) onPick(ref);
|
|
5765
6016
|
else if (ref !== current || !bot.model) void run(actions.updateBot(bot.id, { model: ref }));
|
|
5766
6017
|
};
|
|
6018
|
+
const pickEffort = (effort) => {
|
|
6019
|
+
close();
|
|
6020
|
+
if (onPickEffort) onPickEffort(effort);
|
|
6021
|
+
else if (effort !== bot.effort) void run(actions.updateBot(bot.id, { effort: effort ?? null }));
|
|
6022
|
+
};
|
|
5767
6023
|
const models = roster.models ?? [];
|
|
5768
6024
|
return (0, import_react13.createElement)(
|
|
5769
6025
|
import_react13.Fragment,
|
|
@@ -5785,7 +6041,7 @@ function ModelMenu({ bot, roster, actions, run, wide = false, onPick }) {
|
|
|
5785
6041
|
"span",
|
|
5786
6042
|
{ className: "bt-model-copy" },
|
|
5787
6043
|
(0, import_react13.createElement)("span", { className: "bt-model-name" }, shown?.name ?? t("Choose a model")),
|
|
5788
|
-
shown ? (0, import_react13.createElement)("span", { className: "bt-model-provider" }, shown.served || models.length === 0 ? shown.provider : t("{provider} · not available", { provider: shown.provider })) : null
|
|
6044
|
+
shown ? (0, import_react13.createElement)("span", { className: "bt-model-provider" }, shown.served || models.length === 0 ? effortLabel === void 0 ? shown.provider : `${shown.provider} · ${effortLabel}` : t("{provider} · not available", { provider: shown.provider })) : null
|
|
5789
6045
|
),
|
|
5790
6046
|
(0, import_react13.createElement)("svg", { className: "bt-model-caret", width: 12, height: 12, viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true }, (0, import_react13.createElement)("path", { d: "M5.5 8l4.5 4.5L14.5 8" }))
|
|
5791
6047
|
),
|
|
@@ -5793,6 +6049,7 @@ function ModelMenu({ bot, roster, actions, run, wide = false, onPick }) {
|
|
|
5793
6049
|
"div",
|
|
5794
6050
|
{ ref: menu, className: `bt-menu bt-model-menu${shownPlace.bottom !== void 0 ? " bt-menu-up" : ""}`, role: "menu", "aria-label": t("Model for {name}", { name: bot.name }), style: shownPlace, "data-leaving": leaving || void 0 },
|
|
5795
6051
|
models.length > 0 ? (0, import_react13.createElement)(ModelRows, { roster, current, onPick: pick2 }) : (0, import_react13.createElement)("div", { className: "bt-model-none" }, t("No models are set up in DeepSeek Harness yet.")),
|
|
6052
|
+
effortRows(reasoning).length > 0 ? (0, import_react13.createElement)(EffortRows, { reasoning, current: picked, onPick: pickEffort }) : null,
|
|
5796
6053
|
(0, import_react13.createElement)("div", { className: "bt-menu-sep", role: "separator" }),
|
|
5797
6054
|
(0, import_react13.createElement)("button", { type: "button", role: "menuitem", onClick: () => {
|
|
5798
6055
|
close();
|
|
@@ -5824,15 +6081,21 @@ function ModelChoiceDock({ sessionId, useRoster, actions }) {
|
|
|
5824
6081
|
};
|
|
5825
6082
|
const text = lost ? t("{model} is not available anymore. Pick another model for me.", { model: bot.model.split("/").slice(1).join("/") }) : models.length === 0 ? t("Hi, I'm {name}. I don't have a model yet. How do you want to set one up?", { name: bot.name }) : t("Hi, I'm {name}. Pick a model for me first. You can change it later in my details.", { name: bot.name });
|
|
5826
6083
|
const choices = models.length === 0 ? setupChoices(actions).map(([label, run]) => (0, import_react13.createElement)("button", { key: label, type: "button", className: "bt-model-chip", onClick: run }, label)) : [
|
|
5827
|
-
...models.map((
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
className: "bt-model-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
6084
|
+
...groupModels(models).map(([provider, list]) => (0, import_react13.createElement)(
|
|
6085
|
+
"div",
|
|
6086
|
+
{ key: list[0].provider ?? provider, className: "bt-model-choice-group", role: "group", "aria-label": provider },
|
|
6087
|
+
(0, import_react13.createElement)("div", { className: "bt-model-choice-provider", "aria-hidden": true }, provider),
|
|
6088
|
+
(0, import_react13.createElement)("div", { className: "bt-model-choice-chips" }, list.map((model) => (0, import_react13.createElement)("button", {
|
|
6089
|
+
key: model.ref,
|
|
6090
|
+
type: "button",
|
|
6091
|
+
className: "bt-model-chip",
|
|
6092
|
+
disabled: busy !== "",
|
|
6093
|
+
"aria-busy": busy === model.ref || void 0,
|
|
6094
|
+
"aria-label": `${model.name} · ${provider}`,
|
|
6095
|
+
title: model.ref === roster.defaultModel ? `${model.ref} · ${t("Harness default")}` : model.ref,
|
|
6096
|
+
onClick: () => pick2(model.ref)
|
|
6097
|
+
}, model.name, busy === model.ref ? (0, import_react13.createElement)("span", { className: "bt-model-spin", "aria-label": t("Saving…") }) : null)))
|
|
6098
|
+
)),
|
|
5836
6099
|
(0, import_react13.createElement)("button", { key: "manage", type: "button", className: "bt-model-chip bt-model-chip-quiet", onClick: openHarnessSettings }, (0, import_react13.createElement)(SlidersIcon), t("Manage models"))
|
|
5837
6100
|
];
|
|
5838
6101
|
return (0, import_react13.createElement)(
|
|
@@ -5905,6 +6168,9 @@ var MODEL_CSS = `
|
|
|
5905
6168
|
.bt-model-none{padding:8px;font-size:13px;line-height:18px;color:var(--bt-ink-2)}
|
|
5906
6169
|
.bt-model-dock{width:100%;max-width:var(--dsh-chat-content-width,748px);margin:0 auto;display:flex;flex-direction:column;align-items:flex-start;gap:8px;padding:0 0 10px;box-sizing:border-box;animation:bt-model-in ${SPRING_POP.duration}ms ${SPRING_POP.easing} backwards}
|
|
5907
6170
|
.bt-model-chips{display:flex;flex-wrap:wrap;gap:6px;margin-left:30px;max-width:min(560px,calc(100% - 30px));max-height:min(240px,36vh);overflow-y:auto;scrollbar-width:thin}
|
|
6171
|
+
.bt-model-choice-group{display:flex;flex-direction:column;gap:6px;width:100%;min-width:0}
|
|
6172
|
+
.bt-model-choice-provider{padding-top:4px;font-size:12px;line-height:16px;font-weight:500;color:var(--bt-ink-2);overflow-wrap:anywhere}
|
|
6173
|
+
.bt-model-choice-chips{display:flex;flex-wrap:wrap;gap:6px}
|
|
5908
6174
|
.bt-model-chip{display:inline-flex;align-items:center;gap:6px;min-height:30px;padding:5px 12px;border:.5px solid var(--bt-line-2);border-radius:999px;background:var(--bt-surface,transparent);color:var(--bt-ink);font:inherit;font-size:13px;line-height:18px;cursor:pointer;transition:background-color .12s ease,border-color .12s ease}
|
|
5909
6175
|
.bt-model-chip:hover:not(:disabled){background:var(--bt-hover)}
|
|
5910
6176
|
.bt-model-chip:focus-visible{outline:2px solid var(--bt-accent);outline-offset:1px}
|
|
@@ -7031,9 +7297,10 @@ function startModel(roster) {
|
|
|
7031
7297
|
const models = roster.models ?? [];
|
|
7032
7298
|
return (models.find((model) => model.ref === roster.defaultModel) ?? models[0])?.ref ?? "";
|
|
7033
7299
|
}
|
|
7034
|
-
function createPayload({ name, look, image, model, brief }) {
|
|
7300
|
+
function createPayload({ name, look, image, model, effort, brief }) {
|
|
7035
7301
|
const payload = { name, color: look.color, avatar: image ? { shape: look.shape, image } : { shape: look.shape } };
|
|
7036
7302
|
if (model) payload.model = model;
|
|
7303
|
+
if (effort) payload.effort = effort;
|
|
7037
7304
|
if (brief) payload.brief = brief;
|
|
7038
7305
|
return payload;
|
|
7039
7306
|
}
|
|
@@ -7051,6 +7318,7 @@ function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCr
|
|
|
7051
7318
|
const [image, setImage] = (0, import_react17.useState)(null);
|
|
7052
7319
|
const [name, setName] = (0, import_react17.useState)(start?.name ?? "");
|
|
7053
7320
|
const [model, setModel] = (0, import_react17.useState)(() => startModel(roster));
|
|
7321
|
+
const [effort, setEffort] = (0, import_react17.useState)(void 0);
|
|
7054
7322
|
const [busy, setBusy] = (0, import_react17.useState)(false);
|
|
7055
7323
|
const [error, setError] = (0, import_react17.useState)("");
|
|
7056
7324
|
const fileRef = (0, import_react17.useRef)(null);
|
|
@@ -7067,7 +7335,7 @@ function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCr
|
|
|
7067
7335
|
setError("");
|
|
7068
7336
|
setBusy(true);
|
|
7069
7337
|
try {
|
|
7070
|
-
const bot = await actions.createBot(createPayload({ name: finalName, look, image, model, brief }));
|
|
7338
|
+
const bot = await actions.createBot(createPayload({ name: finalName, look, image, model, effort, brief }));
|
|
7071
7339
|
onCreated(bot);
|
|
7072
7340
|
} catch (failure) {
|
|
7073
7341
|
setError(failure?.message ?? String(failure));
|
|
@@ -7151,7 +7419,10 @@ function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCr
|
|
|
7151
7419
|
"div",
|
|
7152
7420
|
{ className: "bt-field" },
|
|
7153
7421
|
(0, import_react17.createElement)("span", { className: "bt-field-label" }, t("Model")),
|
|
7154
|
-
(0, import_react17.createElement)(ModelMenu, { bot: { id: "new-bot", name: finalName, model: model || void 0 }, roster, actions, wide: true, onPick:
|
|
7422
|
+
(0, import_react17.createElement)(ModelMenu, { bot: { id: "new-bot", name: finalName, model: model || void 0, effort }, roster, actions, wide: true, onPick: (ref) => {
|
|
7423
|
+
setModel(ref);
|
|
7424
|
+
setEffort(void 0);
|
|
7425
|
+
}, onPickEffort: setEffort }),
|
|
7155
7426
|
(0, import_react17.createElement)("span", { className: "bt-note" }, (roster.models ?? []).length > 0 ? t("The Bot runs on this model. You can change it later in its details.") : t("Add a provider and a model in DeepSeek Harness first, or pick one in the Bot's chat later."))
|
|
7156
7427
|
),
|
|
7157
7428
|
error ? (0, import_react17.createElement)("div", { className: "bt-error", role: "alert", style: { padding: 0 } }, error) : null,
|
|
@@ -7513,7 +7784,15 @@ function ExchangeDialog({ pair, roster, actions }) {
|
|
|
7513
7784
|
// src/client/voice.js
|
|
7514
7785
|
var import_react20 = require("react");
|
|
7515
7786
|
var plainSpeech = (text) => stripHeader(text).replace(/```[\s\S]*?```/g, " ").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/[*_`#>|~-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
7516
|
-
var VOICE_LABELS = {
|
|
7787
|
+
var VOICE_LABELS = {
|
|
7788
|
+
listening: "Listening",
|
|
7789
|
+
thinking: "Thinking",
|
|
7790
|
+
speaking: "Speaking",
|
|
7791
|
+
paused: "Paused",
|
|
7792
|
+
setup: "Voice input needs its speech model",
|
|
7793
|
+
unsupported: "Voice chat is not available in this browser"
|
|
7794
|
+
};
|
|
7795
|
+
var CAPTURE_NOTES = { blocked: "Microphone access is blocked", unavailable: "Voice chat is not available in this browser", "too-long": "That was too long to recognize" };
|
|
7517
7796
|
function useMicLevel(enabled) {
|
|
7518
7797
|
const [level, setLevel] = (0, import_react20.useState)(0);
|
|
7519
7798
|
(0, import_react20.useEffect)(() => {
|
|
@@ -7566,27 +7845,59 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus }
|
|
|
7566
7845
|
const entry = Array.isArray(outline) ? outline[outline.length - 1] : void 0;
|
|
7567
7846
|
return entry ? `${outline.length}|${entry.response ?? ""}` : "0|";
|
|
7568
7847
|
});
|
|
7569
|
-
const [
|
|
7848
|
+
const [speech] = (0, import_react20.useState)(() => actions.speech());
|
|
7849
|
+
const [phase, setPhase] = (0, import_react20.useState)(speech ? "starting" : speechApi() ? "listening" : "unsupported");
|
|
7850
|
+
const [catalog, setCatalog] = (0, import_react20.useState)(null);
|
|
7570
7851
|
const [heard, setHeard] = (0, import_react20.useState)("");
|
|
7571
7852
|
const [said, setSaid] = (0, import_react20.useState)("");
|
|
7853
|
+
const [note, setNote] = (0, import_react20.useState)("");
|
|
7854
|
+
const [serviceLevel, setServiceLevel] = (0, import_react20.useState)(0);
|
|
7855
|
+
const [cycle, setCycle] = (0, import_react20.useState)(0);
|
|
7572
7856
|
const waiting = (0, import_react20.useRef)(null);
|
|
7857
|
+
const reply = (0, import_react20.useRef)(lastReply);
|
|
7858
|
+
reply.current = lastReply;
|
|
7859
|
+
const turn = (0, import_react20.useRef)(0);
|
|
7573
7860
|
const paneLeft = usePaneLeft();
|
|
7574
7861
|
const close = () => {
|
|
7862
|
+
turn.current += 1;
|
|
7575
7863
|
window.speechSynthesis?.cancel();
|
|
7576
7864
|
actions.closeVoice();
|
|
7577
7865
|
};
|
|
7578
7866
|
useEscape(close);
|
|
7867
|
+
const submit = (text) => {
|
|
7868
|
+
setHeard(text);
|
|
7869
|
+
setPhase("thinking");
|
|
7870
|
+
waiting.current = reply.current.split("|")[0];
|
|
7871
|
+
void actions.send(sessionId, text.trim()).catch(() => setPhase("listening"));
|
|
7872
|
+
};
|
|
7579
7873
|
const dictation = useDictation((text, final) => {
|
|
7580
7874
|
setHeard(text);
|
|
7581
7875
|
if (!final) return;
|
|
7582
7876
|
dictation.stop();
|
|
7583
|
-
|
|
7584
|
-
waiting.current = lastReply.split("|")[0];
|
|
7585
|
-
void actions.send(sessionId, text.trim()).catch(() => setPhase("listening"));
|
|
7877
|
+
submit(text);
|
|
7586
7878
|
});
|
|
7879
|
+
(0, import_react20.useEffect)(() => {
|
|
7880
|
+
if (!note) return void 0;
|
|
7881
|
+
const timer = setTimeout(() => setNote(""), 2600);
|
|
7882
|
+
return () => clearTimeout(timer);
|
|
7883
|
+
}, [note]);
|
|
7884
|
+
(0, import_react20.useEffect)(() => {
|
|
7885
|
+
if (!speech) return void 0;
|
|
7886
|
+
let live = true;
|
|
7887
|
+
speechCatalog(speech).then(({ catalog: next, usable }) => {
|
|
7888
|
+
if (!live) return;
|
|
7889
|
+
setCatalog(next);
|
|
7890
|
+
setPhase(usable ? "listening" : "setup");
|
|
7891
|
+
}, () => {
|
|
7892
|
+
if (live) setPhase("setup");
|
|
7893
|
+
});
|
|
7894
|
+
return () => {
|
|
7895
|
+
live = false;
|
|
7896
|
+
};
|
|
7897
|
+
}, [speech]);
|
|
7587
7898
|
const listen = phase === "listening";
|
|
7588
7899
|
(0, import_react20.useEffect)(() => {
|
|
7589
|
-
if (!listen || dictation.listening) return void 0;
|
|
7900
|
+
if (speech || !listen || dictation.listening) return void 0;
|
|
7590
7901
|
const timer = setTimeout(() => dictation.start({ continuous: false, onEnd: () => {
|
|
7591
7902
|
} }), 250);
|
|
7592
7903
|
return () => clearTimeout(timer);
|
|
@@ -7594,20 +7905,82 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus }
|
|
|
7594
7905
|
(0, import_react20.useEffect)(() => {
|
|
7595
7906
|
if (phase !== "listening") dictation.stop();
|
|
7596
7907
|
}, [phase]);
|
|
7908
|
+
(0, import_react20.useEffect)(() => {
|
|
7909
|
+
if (!speech || !catalog || !listen) return void 0;
|
|
7910
|
+
const own = turn.current;
|
|
7911
|
+
let capture = null;
|
|
7912
|
+
let frame = 0;
|
|
7913
|
+
let stopped = false;
|
|
7914
|
+
let smooth = 0;
|
|
7915
|
+
const detect = turnDetector();
|
|
7916
|
+
const limit = Math.max(1, catalog.maxDurationSeconds - 1) * 1e3;
|
|
7917
|
+
const finish = async (current) => {
|
|
7918
|
+
setPhase("transcribing");
|
|
7919
|
+
try {
|
|
7920
|
+
const wave = await current.finish(catalog.maxDurationSeconds);
|
|
7921
|
+
const text = wave ? await transcribeWave(speech, catalog, wave, new AbortController().signal) : "";
|
|
7922
|
+
if (turn.current !== own) return;
|
|
7923
|
+
if (text) submit(text);
|
|
7924
|
+
else setPhase("listening");
|
|
7925
|
+
} catch (error) {
|
|
7926
|
+
if (turn.current !== own) return;
|
|
7927
|
+
setNote(t(CAPTURE_NOTES[error?.kind] ?? "Couldn't hear that"));
|
|
7928
|
+
setPhase("listening");
|
|
7929
|
+
}
|
|
7930
|
+
};
|
|
7931
|
+
openCapture().then((opened) => {
|
|
7932
|
+
if (stopped) {
|
|
7933
|
+
opened.cancel();
|
|
7934
|
+
return;
|
|
7935
|
+
}
|
|
7936
|
+
capture = opened;
|
|
7937
|
+
const began = performance.now();
|
|
7938
|
+
const tick = () => {
|
|
7939
|
+
const now = performance.now();
|
|
7940
|
+
const level2 = capture.level();
|
|
7941
|
+
smooth = smooth * 0.8 + level2 * 0.2;
|
|
7942
|
+
setServiceLevel(Math.round(smooth * 100) / 100);
|
|
7943
|
+
const state = detect(level2, now);
|
|
7944
|
+
if (state === "done" || state === "speech" && now - began >= limit) {
|
|
7945
|
+
const current = capture;
|
|
7946
|
+
capture = null;
|
|
7947
|
+
setServiceLevel(0);
|
|
7948
|
+
void finish(current);
|
|
7949
|
+
return;
|
|
7950
|
+
}
|
|
7951
|
+
if (state === "waiting" && now - began >= limit) {
|
|
7952
|
+
setCycle((value) => value + 1);
|
|
7953
|
+
return;
|
|
7954
|
+
}
|
|
7955
|
+
frame = requestAnimationFrame(tick);
|
|
7956
|
+
};
|
|
7957
|
+
tick();
|
|
7958
|
+
}, (error) => {
|
|
7959
|
+
if (stopped) return;
|
|
7960
|
+
setNote(t(CAPTURE_NOTES[error?.kind] ?? "Couldn't hear that"));
|
|
7961
|
+
setPhase("paused");
|
|
7962
|
+
});
|
|
7963
|
+
return () => {
|
|
7964
|
+
stopped = true;
|
|
7965
|
+
cancelAnimationFrame(frame);
|
|
7966
|
+
capture?.cancel();
|
|
7967
|
+
setServiceLevel(0);
|
|
7968
|
+
};
|
|
7969
|
+
}, [speech, catalog, listen, cycle]);
|
|
7597
7970
|
(0, import_react20.useEffect)(() => {
|
|
7598
7971
|
if (phase !== "thinking" || running || waiting.current === null) return;
|
|
7599
7972
|
const [count, text] = [lastReply.slice(0, lastReply.indexOf("|")), lastReply.slice(lastReply.indexOf("|") + 1)];
|
|
7600
7973
|
if (count === waiting.current || !text.trim()) return;
|
|
7601
7974
|
waiting.current = null;
|
|
7602
|
-
const
|
|
7603
|
-
setSaid(
|
|
7975
|
+
const spoken = plainSpeech(text);
|
|
7976
|
+
setSaid(spoken);
|
|
7604
7977
|
setHeard("");
|
|
7605
|
-
if (!window.speechSynthesis || !
|
|
7978
|
+
if (!window.speechSynthesis || !spoken) {
|
|
7606
7979
|
setPhase("listening");
|
|
7607
7980
|
return;
|
|
7608
7981
|
}
|
|
7609
|
-
const utterance = new SpeechSynthesisUtterance(
|
|
7610
|
-
utterance.lang = /[\u3400-\u9fff]/.test(
|
|
7982
|
+
const utterance = new SpeechSynthesisUtterance(spoken);
|
|
7983
|
+
utterance.lang = /[\u3400-\u9fff]/.test(spoken) ? "zh-CN" : "en-US";
|
|
7611
7984
|
utterance.onend = () => setPhase((current) => current === "speaking" ? "listening" : current);
|
|
7612
7985
|
utterance.onerror = utterance.onend;
|
|
7613
7986
|
setPhase("speaking");
|
|
@@ -7615,23 +7988,28 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus }
|
|
|
7615
7988
|
window.speechSynthesis.speak(utterance);
|
|
7616
7989
|
}, [phase, running, lastReply]);
|
|
7617
7990
|
(0, import_react20.useEffect)(() => () => window.speechSynthesis?.cancel(), []);
|
|
7618
|
-
const
|
|
7619
|
-
const
|
|
7991
|
+
const micLevel = useMicLevel(!speech && phase === "listening" && dictation.listening);
|
|
7992
|
+
const level = speech ? serviceLevel : micLevel;
|
|
7993
|
+
const markState = phase === "thinking" || phase === "transcribing" ? "thinking" : phase === "speaking" ? "sending" : "idle";
|
|
7994
|
+
const blocked = phase === "unsupported" || phase === "setup" || phase === "starting";
|
|
7620
7995
|
const togglePause = () => {
|
|
7621
7996
|
if (phase === "paused") {
|
|
7622
7997
|
setPhase("listening");
|
|
7623
7998
|
return;
|
|
7624
7999
|
}
|
|
8000
|
+
turn.current += 1;
|
|
7625
8001
|
window.speechSynthesis?.cancel();
|
|
7626
8002
|
setPhase("paused");
|
|
7627
8003
|
};
|
|
8004
|
+
const openSetup = phase === "setup" ? actions.voiceSetup() : null;
|
|
7628
8005
|
const caption = phase === "speaking" ? said : heard;
|
|
8006
|
+
const shownNote = note || dictation.note;
|
|
7629
8007
|
return (0, import_react20.createElement)(
|
|
7630
8008
|
"div",
|
|
7631
8009
|
{ className: "bt-pane bt-voice-pane", style: { left: paneLeft } },
|
|
7632
8010
|
(0, import_react20.createElement)(
|
|
7633
8011
|
"div",
|
|
7634
|
-
{ className: "bt-voice-stage", role: "dialog", "aria-label": t("Voice chat with {name}", { name }), "data-phase": phase },
|
|
8012
|
+
{ className: "bt-voice-stage", role: "dialog", "aria-label": t("Voice chat with {name}", { name }), "data-phase": phase === "transcribing" ? "thinking" : phase },
|
|
7635
8013
|
(0, import_react20.createElement)(
|
|
7636
8014
|
"div",
|
|
7637
8015
|
{ className: "bt-voice-orb", style: { "--bt-level": level } },
|
|
@@ -7643,10 +8021,14 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus }
|
|
|
7643
8021
|
(0, import_react20.createElement)(
|
|
7644
8022
|
"div",
|
|
7645
8023
|
{ className: "bt-voice-status", role: "status" },
|
|
7646
|
-
phase === "thinking" ? (0, import_react20.createElement)(Shimmer, null, t("Thinking…")) : VOICE_LABELS[phase] ? t(VOICE_LABELS[phase]) : ""
|
|
8024
|
+
phase === "thinking" ? (0, import_react20.createElement)(Shimmer, null, t("Thinking…")) : phase === "transcribing" ? (0, import_react20.createElement)(Shimmer, null, t("Recognizing…")) : VOICE_LABELS[phase] ? t(VOICE_LABELS[phase]) : ""
|
|
7647
8025
|
),
|
|
7648
8026
|
(0, import_react20.createElement)("div", { className: "bt-voice-caption", key: `${phase}:${caption.slice(0, 12)}` }, caption),
|
|
7649
|
-
|
|
8027
|
+
openSetup ? (0, import_react20.createElement)("button", { type: "button", className: "bt-voice-setup", onClick: () => {
|
|
8028
|
+
close();
|
|
8029
|
+
openSetup();
|
|
8030
|
+
} }, t("Set up voice input")) : null,
|
|
8031
|
+
shownNote ? (0, import_react20.createElement)("div", { className: "bt-voice-note" }, shownNote) : null
|
|
7650
8032
|
),
|
|
7651
8033
|
(0, import_react20.createElement)(
|
|
7652
8034
|
"div",
|
|
@@ -7655,7 +8037,7 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus }
|
|
|
7655
8037
|
type: "button",
|
|
7656
8038
|
className: "bt-voice-btn",
|
|
7657
8039
|
"aria-pressed": phase === "paused",
|
|
7658
|
-
disabled:
|
|
8040
|
+
disabled: blocked,
|
|
7659
8041
|
"aria-label": phase === "paused" ? t("Resume listening") : t("Pause"),
|
|
7660
8042
|
onClick: togglePause
|
|
7661
8043
|
}, (0, import_react20.createElement)(MicIcon, { size: 20 }), phase === "paused" ? (0, import_react20.createElement)("span", { className: "bt-voice-slash", "aria-hidden": true }) : null),
|
|
@@ -9611,6 +9993,40 @@ var hostOf = (url) => {
|
|
|
9611
9993
|
};
|
|
9612
9994
|
var shortUrl = (url) => String(url ?? "").replace(/^https?:\/\//i, "").replace(/^www\./i, "").replace(/\/$/, "");
|
|
9613
9995
|
var EMPTY_TABS = { ids: [], active: null };
|
|
9996
|
+
var PERMISSION_LABELS = { "read-only": "Read only", "workspace-write": "Workspace", "danger-full-access": "Full access" };
|
|
9997
|
+
var DANGER = /* @__PURE__ */ new Set(["danger-full-access"]);
|
|
9998
|
+
function PermissionRow({ bot, roster, actions, run }) {
|
|
9999
|
+
const [pending, setPending] = (0, import_react27.useState)(null);
|
|
10000
|
+
const catalog = roster.permissions;
|
|
10001
|
+
const saved = bot.permission ?? catalog?.defaultPreset;
|
|
10002
|
+
(0, import_react27.useEffect)(() => {
|
|
10003
|
+
if (pending === saved) setPending(null);
|
|
10004
|
+
}, [pending, saved]);
|
|
10005
|
+
if (!catalog?.options?.length) return null;
|
|
10006
|
+
const current = pending ?? saved;
|
|
10007
|
+
const pick2 = (value) => {
|
|
10008
|
+
if (value === current && bot.permission !== void 0) return;
|
|
10009
|
+
setPending(value);
|
|
10010
|
+
void run(actions.updateBot(bot.id, { permission: value })).catch(() => setPending(null));
|
|
10011
|
+
};
|
|
10012
|
+
return (0, import_react27.createElement)(
|
|
10013
|
+
"div",
|
|
10014
|
+
{ className: "bt-drawer-permission" },
|
|
10015
|
+
(0, import_react27.createElement)("div", { className: "bt-section-title" }, t("Permissions")),
|
|
10016
|
+
(0, import_react27.createElement)(
|
|
10017
|
+
Segmented,
|
|
10018
|
+
{ label: t("Permissions"), value: current },
|
|
10019
|
+
catalog.options.map((option) => (0, import_react27.createElement)("button", {
|
|
10020
|
+
key: option.value,
|
|
10021
|
+
type: "button",
|
|
10022
|
+
"aria-pressed": option.value === current,
|
|
10023
|
+
title: option.description,
|
|
10024
|
+
onClick: () => pick2(option.value),
|
|
10025
|
+
className: DANGER.has(option.value) ? "bt-perm-danger" : void 0
|
|
10026
|
+
}, PERMISSION_LABELS[option.value] ? t(PERMISSION_LABELS[option.value]) : option.name))
|
|
10027
|
+
)
|
|
10028
|
+
);
|
|
10029
|
+
}
|
|
9614
10030
|
function BrowserCards({ bot, sessionId, roster, actions, useBrowserTabs, useBrowserPages }) {
|
|
9615
10031
|
const state = useBrowserTabs((map) => map[bot.id] ?? EMPTY_TABS);
|
|
9616
10032
|
const pages = useBrowserPages((map) => map);
|
|
@@ -9842,6 +10258,7 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a
|
|
|
9842
10258
|
(0, import_react27.createElement)(ModelMenu, { bot, roster, actions, run, wide: true }),
|
|
9843
10259
|
modelOf(bot, roster) ? null : (0, import_react27.createElement)("div", { className: "bt-note" }, t("Pick a model and {name} can start.", { name: bot.name }))
|
|
9844
10260
|
),
|
|
10261
|
+
(0, import_react27.createElement)(PermissionRow, { key: "permission", bot, roster, actions, run }),
|
|
9845
10262
|
bot.instructions ? (0, import_react27.createElement)(About, { key: "about", text: bot.instructions }) : (0, import_react27.createElement)("button", { key: "about", type: "button", className: "bt-more", onClick: () => setView("settings") }, t("Add instructions in Bot settings")),
|
|
9846
10263
|
roster.memory === false ? null : (0, import_react27.createElement)(MemoryRow, { key: "memory", bot, actions }),
|
|
9847
10264
|
usage ? (0, import_react27.createElement)(
|
|
@@ -11362,7 +11779,7 @@ function writeMode(mode, storage = globalThis.localStorage) {
|
|
|
11362
11779
|
}
|
|
11363
11780
|
|
|
11364
11781
|
// src/client/index.js
|
|
11365
|
-
var inject = ["sessions", "uiWorkspace", "slots", "theme", "layout", "configForms"];
|
|
11782
|
+
var inject = ["sessions", "uiWorkspace", "uiConversation", "slots", "theme", "layout", "configForms"];
|
|
11366
11783
|
function apply(ctx) {
|
|
11367
11784
|
ctx.effect(() => installLocale(ctx.get("locale")), "ds-bot: interface copy");
|
|
11368
11785
|
const roster = createSource(EMPTY_ROSTER);
|
|
@@ -11533,7 +11950,7 @@ function apply(ctx) {
|
|
|
11533
11950
|
const value = await call("update-check", { fresh: false }).catch(() => null);
|
|
11534
11951
|
if (stopped) return;
|
|
11535
11952
|
if (value) update.set(value);
|
|
11536
|
-
timer = setTimeout(loop, value?.phase === "installing" ? 2e3 : 60 * 6e4);
|
|
11953
|
+
timer = setTimeout(loop, value?.phase === "installing" || value?.dsh?.phase === "installing" ? 2e3 : 60 * 6e4);
|
|
11537
11954
|
};
|
|
11538
11955
|
wakeUpdate = loop;
|
|
11539
11956
|
void loop();
|
|
@@ -11906,6 +12323,7 @@ function apply(ctx) {
|
|
|
11906
12323
|
updateBot: (id, patch) => after(call("update-bot", { id, ...patch })),
|
|
11907
12324
|
refreshModels: () => after(call("models")).catch(() => {
|
|
11908
12325
|
}),
|
|
12326
|
+
efforts: (ref) => call("efforts", { ref }),
|
|
11909
12327
|
deleteBot: (id) => after(call("delete-bot", { id })),
|
|
11910
12328
|
duplicateBot: (id) => after(call("duplicate-bot", { id })),
|
|
11911
12329
|
setPrefs: (prefs) => after(call("set-prefs", prefs)),
|
|
@@ -11992,11 +12410,23 @@ function apply(ctx) {
|
|
|
11992
12410
|
actions.signIn = () => ui.set((state) => ({ ...state, signIn: (state.signIn ?? 0) + 1 }));
|
|
11993
12411
|
actions.closeSignIn = () => ui.set((state) => ({ ...state, signIn: null }));
|
|
11994
12412
|
actions.accountT = () => ctx.get("locale").bind("settings.account");
|
|
12413
|
+
actions.speech = () => ctx.get("remote.speech") ?? void 0;
|
|
12414
|
+
actions.voiceSetup = () => {
|
|
12415
|
+
const navigation = ctx.get("pluginNavigation");
|
|
12416
|
+
return navigation ? () => navigation.openBundle(VOICE_BUNDLE) : null;
|
|
12417
|
+
};
|
|
11995
12418
|
actions.seatAccount = (on) => ui.set((state) => state.accountSeated === on ? state : { ...state, accountSeated: on });
|
|
11996
12419
|
actions.installUpdate = async () => {
|
|
11997
|
-
|
|
12420
|
+
const value = await call("update-install");
|
|
12421
|
+
update.set((previous) => ({ ...previous, ...value }));
|
|
12422
|
+
wakeUpdate();
|
|
12423
|
+
};
|
|
12424
|
+
actions.installDshUpdate = async () => {
|
|
12425
|
+
const dsh = await call("dsh-update-install");
|
|
12426
|
+
update.set((previous) => ({ ...previous, dsh }));
|
|
11998
12427
|
wakeUpdate();
|
|
11999
12428
|
};
|
|
12429
|
+
actions.openAppUpdates = () => window.dshDesktop?.updates?.open?.();
|
|
12000
12430
|
actions.noteTurnTime = (sessionId, turn, key, time) => {
|
|
12001
12431
|
if (sessionId === void 0 || !Number.isFinite(turn) || !Number.isFinite(time)) return;
|
|
12002
12432
|
turnClock.set((state) => {
|
|
@@ -12037,6 +12467,18 @@ function apply(ctx) {
|
|
|
12037
12467
|
const browserPages = browsers?.pages ?? createSource({});
|
|
12038
12468
|
const browserTabs = browsers?.tabs ?? createSource({});
|
|
12039
12469
|
const face = () => ({ actions, hooks: { roster, ui, activity, memoryNews, registry, turnClock, live, browserPages, browserTabs, surface, update } });
|
|
12470
|
+
const assistantFace = (sessionId) => {
|
|
12471
|
+
const binding = ctx.sessions.binding(sessionId);
|
|
12472
|
+
if (binding === void 0) throw new Error(`ds-bot: unknown session "${sessionId}"`);
|
|
12473
|
+
const chat = ctx.uiConversation.binding(binding).target("chat");
|
|
12474
|
+
return { ...face(), keyedHooks: {
|
|
12475
|
+
turnSteps: (turn) => {
|
|
12476
|
+
const snapshot = chat.getSnapshot();
|
|
12477
|
+
if (snapshot === void 0) throw new Error("ds-bot: Chat target is unavailable");
|
|
12478
|
+
return snapshot.nodes.turnDataSource(Number(turn), "assistant-step");
|
|
12479
|
+
}
|
|
12480
|
+
} };
|
|
12481
|
+
};
|
|
12040
12482
|
const shadow = (name, options, component) => ctx.slots.inject(name, () => ctx.slots.register({ name, ...options }, component));
|
|
12041
12483
|
const installShell = () => [
|
|
12042
12484
|
installCompiledStyles(SHELL_CSS, "shell"),
|
|
@@ -12071,7 +12513,7 @@ function apply(ctx) {
|
|
|
12071
12513
|
// the mode is held at `verbose` while this group is mounted.
|
|
12072
12514
|
holdFlatTranscript(ctx.configForms.get("ui-chat")),
|
|
12073
12515
|
shadow("conversation.session.header", { priority: -10, inject: face }, HeaderPill),
|
|
12074
|
-
shadow("conversation.chat.node", { key: "assistant-step", priority: -10, locale: "chat", inject:
|
|
12516
|
+
shadow("conversation.chat.node", { key: "assistant-step", priority: -10, locale: "chat", inject: assistantFace }, AssistantCell),
|
|
12075
12517
|
shadow("conversation.chat.node", { key: "turn-trigger", priority: -10, locale: "chat", inject: face }, TriggerCell),
|
|
12076
12518
|
shadow("conversation.chat.node", { key: "tool-call", priority: -10, locale: "chat", inject: face }, ToolCell),
|
|
12077
12519
|
shadow("conversation.chat.node", { key: "turn-process", priority: -10, locale: "chat", inject: face }, TurnProcessCell),
|