ds-bot 0.1.0 → 0.1.2
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 +5 -3
- package/client.js +1013 -371
- package/index.js +3 -1
- package/package.json +1 -1
- package/src/host/channel.js +14 -5
- package/src/host/dsh-update.js +170 -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 +68 -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 天",
|
|
@@ -667,6 +683,25 @@ var ZH = {
|
|
|
667
683
|
"Compared with the day before": "与前一天相比",
|
|
668
684
|
"Compared with the {count} days before": "与再往前 {count} 天相比",
|
|
669
685
|
"{share}% of all usage from {span}": "占 {span} 全部用量的 {share}%",
|
|
686
|
+
// About
|
|
687
|
+
"About DS Bot": "关于 DS Bot",
|
|
688
|
+
"Version {version}": "版本 {version}",
|
|
689
|
+
"Star DS Bot on GitHub": "在 GitHub 上给 DS Bot 点 Star",
|
|
690
|
+
"{count} stars": "{count} 个 Star",
|
|
691
|
+
"If DS Bot helps you, a star on GitHub helps more people find it.": "如果 DS Bot 对你有用,欢迎在 GitHub 上点个 Star,让更多人看到它。",
|
|
692
|
+
Updates: "更新",
|
|
693
|
+
"Check for updates": "检查更新",
|
|
694
|
+
"Checking…": "正在检查…",
|
|
695
|
+
"Up to date": "已是最新版本",
|
|
696
|
+
"Checked {time}": "检查于 {time}",
|
|
697
|
+
"Could not check for updates.": "没能检查更新。",
|
|
698
|
+
"Update checks are off.": "已关闭更新检查。",
|
|
699
|
+
"Installed from a local folder; update it there.": "从本地文件夹安装,请在那里更新。",
|
|
700
|
+
"Updates are not offered for this install.": "这种安装方式不提供更新。",
|
|
701
|
+
Links: "链接",
|
|
702
|
+
"Source code": "源代码",
|
|
703
|
+
"Release notes": "发布说明",
|
|
704
|
+
Open: "打开",
|
|
670
705
|
// Feedback and errors
|
|
671
706
|
Feedback: "反馈",
|
|
672
707
|
"Report a problem": "报告问题",
|
|
@@ -802,6 +837,7 @@ var HOST_ERRORS = [
|
|
|
802
837
|
];
|
|
803
838
|
var HOST_ZH = {
|
|
804
839
|
"Request failed": "请求失败",
|
|
840
|
+
"Permissions are not available in this DeepSeek Harness": "这个 DeepSeek Harness 不支持权限设置",
|
|
805
841
|
"Avatar images must be PNG, JPEG, WebP, or GIF": "头像图片必须是 PNG、JPEG、WebP 或 GIF",
|
|
806
842
|
"Avatar image is too large": "头像图片太大了",
|
|
807
843
|
"A Bot needs a name": "Bot 需要一个名字",
|
|
@@ -1077,8 +1113,8 @@ function bodyOf(def, paint, flat = false) {
|
|
|
1077
1113
|
return "";
|
|
1078
1114
|
}
|
|
1079
1115
|
}
|
|
1080
|
-
var eyeMarkup = ([cx, cy, w,
|
|
1081
|
-
var happyEyeMarkup = ([cx, cy, w,
|
|
1116
|
+
var eyeMarkup = ([cx, cy, w, h30, tilt]) => `<g transform="translate(${cx} ${cy}) rotate(${tilt})"><rect class="bt-eye" x="${-w / 2}" y="${-h30 / 2}" width="${w}" height="${h30}" rx="${w / 2}"/></g>`;
|
|
1117
|
+
var happyEyeMarkup = ([cx, cy, w, h30, tilt]) => `<g transform="translate(${cx} ${cy}) rotate(${tilt})"><path class="bt-eye-happy" opacity="0" d="M${-w * 0.62} ${h30 * 0.14}Q0 ${-h30 * 0.42} ${w * 0.62} ${h30 * 0.14}" fill="none" stroke="#000" stroke-width="${w * 0.5}" stroke-linecap="round"/></g>`;
|
|
1082
1118
|
var maskBox = (size) => `maskUnits="userSpaceOnUse" x="${-size}" y="${-size}" width="${size * 3}" height="${size * 3}"`;
|
|
1083
1119
|
var holes = (def, eyes) => `<rect x="${-def.size}" y="${-def.size}" width="${def.size * 3}" height="${def.size * 3}" fill="#fff"/>${eyes}`;
|
|
1084
1120
|
var MASK_ID = "%MASK%";
|
|
@@ -1453,12 +1489,12 @@ function liveSprings(apply2) {
|
|
|
1453
1489
|
const dt = Math.min(0.5, Math.max(1e-3, (now - last) / 1e3));
|
|
1454
1490
|
last = now;
|
|
1455
1491
|
const steps = Math.ceil(dt * 240);
|
|
1456
|
-
const
|
|
1492
|
+
const h30 = dt / steps;
|
|
1457
1493
|
let moving = false;
|
|
1458
1494
|
for (const value of values.values()) {
|
|
1459
1495
|
for (let index = 0; index < steps; index += 1) {
|
|
1460
|
-
value.v += (-value.k * (value.x - value.to) - value.c * value.v) *
|
|
1461
|
-
value.x += value.v *
|
|
1496
|
+
value.v += (-value.k * (value.x - value.to) - value.c * value.v) * h30;
|
|
1497
|
+
value.x += value.v * h30;
|
|
1462
1498
|
}
|
|
1463
1499
|
if (Math.abs(value.x - value.to) < 0.05 && Math.abs(value.v) < 1) {
|
|
1464
1500
|
value.x = value.to;
|
|
@@ -2014,6 +2050,8 @@ button.bt-q-opt:active{background:var(--bt-active)}
|
|
|
2014
2050
|
.bt-set-body{gap:20px}
|
|
2015
2051
|
.bt-drawer-model{display:flex;flex-direction:column}
|
|
2016
2052
|
.bt-drawer-model .bt-note{margin-top:6px}
|
|
2053
|
+
.bt-drawer-permission{display:flex;flex-direction:column}
|
|
2054
|
+
.bt-seg button.bt-perm-danger,.bt-seg button.bt-perm-danger[aria-pressed=true]{color:#e02135}
|
|
2017
2055
|
|
|
2018
2056
|
/* Settings dialog: a 198px page list beside the page. */
|
|
2019
2057
|
.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 +2151,8 @@ button.bt-set-row:hover{background:var(--bt-hover)}
|
|
|
2113
2151
|
.bt-voice-status{font-size:13px;line-height:18px;color:var(--bt-ink-2);min-height:18px}
|
|
2114
2152
|
.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
2153
|
.bt-voice-note{font-size:12px;color:#e02135}
|
|
2154
|
+
.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}
|
|
2155
|
+
.bt-voice-setup:hover{background:var(--bt-active)}
|
|
2116
2156
|
.bt-voice-bar{display:flex;gap:16px;padding:0 0 32px}
|
|
2117
2157
|
.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
2158
|
.bt-voice-btn:hover{background:var(--bt-active)}
|
|
@@ -2871,6 +2911,7 @@ function PinMarkIcon() {
|
|
|
2871
2911
|
}
|
|
2872
2912
|
var UnreadIcon = lineIcon(["M10 3.5a4.5 4.5 0 0 0-4.5 4.5v3L4 13.5h12L14.5 11V8", "M8.3 16a1.8 1.8 0 0 0 3.4 0", "M15.5 3.2a1.6 1.6 0 1 1 0 3.2 1.6 1.6 0 0 1 0-3.2z"]);
|
|
2873
2913
|
var PencilIcon = lineIcon(["M9 4H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-3", "M14.3 3.7a1.6 1.6 0 0 1 2.3 2.3L10.5 12l-3 .8.8-3z"]);
|
|
2914
|
+
var InfoIcon = lineIcon(["M10 3.5a6.5 6.5 0 1 1 0 13 6.5 6.5 0 0 1 0-13z", "M10 9.25v4", "M10 6.6h.01"]);
|
|
2874
2915
|
var StarLineIcon = lineIcon(["M10 3l2.1 4.3 4.7.7-3.4 3.3.8 4.7L10 13.8 5.8 16l.8-4.7L3.2 8l4.7-.7z"]);
|
|
2875
2916
|
var CopyIcon = lineIcon(["M8.5 7h6A1.5 1.5 0 0 1 16 8.5v6a1.5 1.5 0 0 1-1.5 1.5h-6A1.5 1.5 0 0 1 7 14.5v-6A1.5 1.5 0 0 1 8.5 7z", "M4 12.5V5.5A1.5 1.5 0 0 1 5.5 4H13"]);
|
|
2876
2917
|
var HideIcon = lineIcon(["M3 3l14 14", "M8.6 5.2A7.6 7.6 0 0 1 10 5c4.5 0 7 5 7 5a12.6 12.6 0 0 1-2 2.6M12.2 14.5A6.8 6.8 0 0 1 10 15c-4.5 0-7-5-7-5a12.4 12.4 0 0 1 3-3.4", "M8.5 8.5a2 2 0 0 0 3 3"]);
|
|
@@ -3513,12 +3554,22 @@ var SECRET_CSS = `
|
|
|
3513
3554
|
@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
3555
|
`;
|
|
3515
3556
|
|
|
3557
|
+
// src/client/turn-reasoning.js
|
|
3558
|
+
function turnReasoning(steps, current) {
|
|
3559
|
+
const members = steps.length ? steps : [current];
|
|
3560
|
+
if (members.find((step) => stepReasoning(step) !== "")?.step !== current.step) return "";
|
|
3561
|
+
return members.filter((step) => !thinkingLive(step)).map(stepReasoning).filter(Boolean).join("\n\n");
|
|
3562
|
+
}
|
|
3563
|
+
var texts = (step) => step.blocks.filter((block) => block.kind === "text" && block.text.trim() !== "");
|
|
3564
|
+
var thinkingLive = (step) => step.status === "running" && texts(step).length === 0 && step.blocks.some((block) => block.kind === "reasoning");
|
|
3565
|
+
var stepReasoning = (step) => step.blocks.filter((block) => block.kind === "reasoning").map((block) => block.text).join("\n").trim();
|
|
3566
|
+
|
|
3516
3567
|
// src/client/cells.js
|
|
3517
3568
|
var import_react6 = require("react");
|
|
3518
3569
|
var import_react_dom = require("react-dom");
|
|
3519
3570
|
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
3520
3571
|
var mdLabels = () => ({ code: { copyLabel: t("Copy"), copiedLabel: t("Copied") }, footnotes: t("Footnotes") });
|
|
3521
|
-
function Bubbles({ texts, roster, selfId, actions, streaming, sessionId, msgKey, useUi, lead }) {
|
|
3572
|
+
function Bubbles({ texts: texts2, roster, selfId, actions, streaming, sessionId, msgKey, useUi, lead }) {
|
|
3522
3573
|
const onClick = (event) => {
|
|
3523
3574
|
const target = mentionTarget(event);
|
|
3524
3575
|
if (target === null) return;
|
|
@@ -3530,10 +3581,10 @@ function Bubbles({ texts, roster, selfId, actions, streaming, sessionId, msgKey,
|
|
|
3530
3581
|
return (0, import_react6.createElement)(
|
|
3531
3582
|
"div",
|
|
3532
3583
|
{ className: "bt-astack", "data-lead": lead ? "" : void 0, onClickCapture: onClick },
|
|
3533
|
-
|
|
3584
|
+
texts2.map((text, index) => {
|
|
3534
3585
|
const key = msgKey ? `${msgKey}:${index}` : void 0;
|
|
3535
3586
|
const picked = key ? reactions?.[index] ?? [] : [];
|
|
3536
|
-
const last = index ===
|
|
3587
|
+
const last = index === texts2.length - 1;
|
|
3537
3588
|
const live = streaming && last;
|
|
3538
3589
|
return (0, import_react6.createElement)(
|
|
3539
3590
|
"div",
|
|
@@ -3638,8 +3689,7 @@ function MessageTools({ text, name, sessionId, react, actions }) {
|
|
|
3638
3689
|
), document.body) : null
|
|
3639
3690
|
);
|
|
3640
3691
|
}
|
|
3641
|
-
function ThinkingRow({ text
|
|
3642
|
-
if (running || !text) return null;
|
|
3692
|
+
function ThinkingRow({ text }) {
|
|
3643
3693
|
return (0, import_react6.createElement)(
|
|
3644
3694
|
"details",
|
|
3645
3695
|
{ className: "bt-think" },
|
|
@@ -3647,22 +3697,22 @@ function ThinkingRow({ text, running }) {
|
|
|
3647
3697
|
(0, import_react6.createElement)("div", { className: "bt-think-text" }, text)
|
|
3648
3698
|
);
|
|
3649
3699
|
}
|
|
3650
|
-
function AssistantCell({ node, groupPart, sessionId, useRoster, useUi, actions }) {
|
|
3700
|
+
function AssistantCell({ node, groupPart, sessionId, useRoster, useUi, useTurnSteps, actions }) {
|
|
3651
3701
|
const roster = useRoster((value) => value);
|
|
3652
3702
|
const data = node.data;
|
|
3703
|
+
const steps = useTurnSteps(String(data.turn));
|
|
3653
3704
|
const hidden = Boolean(roster.roomsById[sessionId]) || isHiddenTurn(roster, sessionId, data.turn);
|
|
3654
|
-
const
|
|
3705
|
+
const texts2 = groupPart === "reasoning" ? [] : data.blocks.filter((block) => block.kind === "text" && block.text.trim() !== "").map((block) => block.text);
|
|
3655
3706
|
const running = data.status === "running";
|
|
3656
|
-
useLiveNote(actions, sessionId, `text:${node.key ?? data.seq}`, !hidden && running &&
|
|
3707
|
+
useLiveNote(actions, sessionId, `text:${node.key ?? data.seq}`, !hidden && running && texts2.length > 0 ? { text: true } : null);
|
|
3657
3708
|
if (hidden) return null;
|
|
3658
|
-
const reasoning = groupPart === "response" ? "" :
|
|
3659
|
-
|
|
3660
|
-
if (texts.length === 0 && !reasoning) return null;
|
|
3709
|
+
const reasoning = groupPart === "response" ? "" : turnReasoning(steps, data);
|
|
3710
|
+
if (texts2.length === 0 && !reasoning) return null;
|
|
3661
3711
|
return (0, import_react6.createElement)(
|
|
3662
3712
|
import_react6.Fragment,
|
|
3663
3713
|
null,
|
|
3664
|
-
reasoning ? (0, import_react6.createElement)(ThinkingRow, { text: reasoning
|
|
3665
|
-
|
|
3714
|
+
reasoning ? (0, import_react6.createElement)(ThinkingRow, { text: reasoning }) : null,
|
|
3715
|
+
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
3716
|
);
|
|
3667
3717
|
}
|
|
3668
3718
|
function EventLine({ prefix, bots, joiner = t("and"), onOpen }) {
|
|
@@ -4339,6 +4389,16 @@ function ActivityIndicator({ sessionId, useRoster, useSessionStatus, useLive, ac
|
|
|
4339
4389
|
), host);
|
|
4340
4390
|
}
|
|
4341
4391
|
|
|
4392
|
+
// src/client/dsh-offer.js
|
|
4393
|
+
var APP_OFFER = /* @__PURE__ */ new Set(["available", "downloading", "verifying", "installing", "ready"]);
|
|
4394
|
+
var bare = (value) => String(value ?? "").trim().replace(/^v/i, "").split("+")[0];
|
|
4395
|
+
var sameVersion = (a, b) => bare(a) !== "" && bare(a) === bare(b);
|
|
4396
|
+
function showsDshRow(dsh, app) {
|
|
4397
|
+
if (!dsh?.verified || !dsh.current || sameVersion(dsh.verified, dsh.current)) return false;
|
|
4398
|
+
if (dsh.kind === "npm") return true;
|
|
4399
|
+
return dsh.kind === "app" && APP_OFFER.has(app?.phase) && sameVersion(app.version, dsh.verified);
|
|
4400
|
+
}
|
|
4401
|
+
|
|
4342
4402
|
// src/client/sidebar.js
|
|
4343
4403
|
var import_react8 = require("react");
|
|
4344
4404
|
var import_react_dom3 = require("react-dom");
|
|
@@ -4667,6 +4727,7 @@ function UpdateButton({ useUpdate, actions }) {
|
|
|
4667
4727
|
const [open, setOpen] = (0, import_react8.useState)(false);
|
|
4668
4728
|
const [box, setBox] = (0, import_react8.useState)(null);
|
|
4669
4729
|
const [error, setError] = (0, import_react8.useState)("");
|
|
4730
|
+
const [dshError, setDshError] = (0, import_react8.useState)("");
|
|
4670
4731
|
const button = (0, import_react8.useRef)(null);
|
|
4671
4732
|
(0, import_react8.useEffect)(() => {
|
|
4672
4733
|
if (!open) return void 0;
|
|
@@ -4689,7 +4750,11 @@ function UpdateButton({ useUpdate, actions }) {
|
|
|
4689
4750
|
};
|
|
4690
4751
|
}, [open]);
|
|
4691
4752
|
const phase = update?.phase ?? "idle";
|
|
4692
|
-
|
|
4753
|
+
const dsh = update?.dsh;
|
|
4754
|
+
const dshPhase = dsh?.phase ?? "idle";
|
|
4755
|
+
const dshRow = showsDshRow(dsh, update?.app);
|
|
4756
|
+
if (!update?.available && !dshRow && phase === "idle" && dshPhase === "idle") return null;
|
|
4757
|
+
const cloudPhase = phase === "installing" || dshPhase === "installing" ? "installing" : phase !== "idle" ? phase : dshPhase;
|
|
4693
4758
|
const toggle = () => {
|
|
4694
4759
|
const rect = button.current?.getBoundingClientRect();
|
|
4695
4760
|
if (rect) setBox({ left: Math.max(8, Math.min(rect.left, window.innerWidth - 288)), top: rect.bottom + 6 });
|
|
@@ -4699,8 +4764,14 @@ function UpdateButton({ useUpdate, actions }) {
|
|
|
4699
4764
|
setError("");
|
|
4700
4765
|
void actions.installUpdate().catch((failure) => setError(hostText(failure?.message ?? String(failure))));
|
|
4701
4766
|
};
|
|
4702
|
-
const
|
|
4767
|
+
const startDsh = () => {
|
|
4768
|
+
setDshError("");
|
|
4769
|
+
void actions.installDshUpdate().catch((failure) => setDshError(hostText(failure?.message ?? String(failure))));
|
|
4770
|
+
};
|
|
4771
|
+
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
4772
|
const message = error || (phase === "failed" ? update.error : "");
|
|
4773
|
+
const dshMessage = dshError || (dshPhase === "failed" ? dsh?.error : "");
|
|
4774
|
+
const dshText = dshPhase === "installed" ? t("Restart DSH to start using DSH {version}.", { version: dsh.installed ?? dsh.verified }) : dsh?.available === false && dsh?.blockedBy ? t("Update DS Bot {version} first.", { version: dsh.blockedBy }) : t("You have {version}.", { version: dsh?.current });
|
|
4704
4775
|
return (0, import_react8.createElement)(
|
|
4705
4776
|
import_react8.Fragment,
|
|
4706
4777
|
null,
|
|
@@ -4708,30 +4779,52 @@ function UpdateButton({ useUpdate, actions }) {
|
|
|
4708
4779
|
ref: button,
|
|
4709
4780
|
type: "button",
|
|
4710
4781
|
className: "bt-round bt-update",
|
|
4711
|
-
"data-phase":
|
|
4782
|
+
"data-phase": cloudPhase,
|
|
4712
4783
|
"aria-label": label,
|
|
4713
4784
|
title: label,
|
|
4714
4785
|
"aria-haspopup": "dialog",
|
|
4715
4786
|
"aria-expanded": open,
|
|
4716
4787
|
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),
|
|
4788
|
+
}, (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
4789
|
open && box ? (0, import_react_dom3.createPortal)((0, import_react8.createElement)(
|
|
4719
4790
|
"div",
|
|
4720
|
-
{ className: "bt-update-pop", role: "dialog", "aria-label": t("DS Bot update"), style: box },
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4791
|
+
{ className: "bt-update-pop", role: "dialog", "aria-label": update?.available || phase !== "idle" ? t("DS Bot update") : t("DSH update"), style: box },
|
|
4792
|
+
update?.available || phase !== "idle" ? (0, import_react8.createElement)(
|
|
4793
|
+
import_react8.Fragment,
|
|
4794
|
+
null,
|
|
4795
|
+
(0, import_react8.createElement)("div", { className: "bt-update-title" }, phase === "installed" ? t("Update installed") : t("DS Bot {version}", { version: update.latest })),
|
|
4796
|
+
(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 })),
|
|
4797
|
+
message ? (0, import_react8.createElement)("div", { className: "bt-update-error", role: "alert" }, t("The update did not finish: {reason}", { reason: message })) : null,
|
|
4798
|
+
(0, import_react8.createElement)(
|
|
4799
|
+
"div",
|
|
4800
|
+
{ className: "bt-update-actions" },
|
|
4801
|
+
update.notesUrl ? (0, import_react8.createElement)("a", { className: "bt-update-notes", href: update.notesUrl, target: "_blank", rel: "noopener noreferrer" }, t("What's new")) : null,
|
|
4802
|
+
phase === "installed" ? null : (0, import_react8.createElement)("button", {
|
|
4803
|
+
type: "button",
|
|
4804
|
+
className: "bt-update-go",
|
|
4805
|
+
disabled: phase === "installing",
|
|
4806
|
+
onClick: start
|
|
4807
|
+
}, phase === "installing" ? t("Updating…") : phase === "failed" ? t("Try again") : t("Update"))
|
|
4808
|
+
)
|
|
4809
|
+
) : null,
|
|
4810
|
+
dshRow ? (0, import_react8.createElement)(
|
|
4725
4811
|
"div",
|
|
4726
|
-
{ className: "bt-update-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4812
|
+
{ className: "bt-update-dsh" },
|
|
4813
|
+
(0, import_react8.createElement)("div", { className: "bt-update-title" }, t("DSH {version}", { version: dsh.verified })),
|
|
4814
|
+
(0, import_react8.createElement)("div", { className: "bt-update-text" }, dshText),
|
|
4815
|
+
dshMessage ? (0, import_react8.createElement)("div", { className: "bt-update-error", role: "alert" }, t("The update did not finish: {reason}", { reason: dshMessage })) : null,
|
|
4816
|
+
(0, import_react8.createElement)(
|
|
4817
|
+
"div",
|
|
4818
|
+
{ className: "bt-update-actions" },
|
|
4819
|
+
dsh.kind === "app" ? dsh.available ? (0, import_react8.createElement)("button", { type: "button", className: "bt-update-go", onClick: () => void actions.openAppUpdates(dsh.verified).catch(() => {
|
|
4820
|
+
}) }, t("Open updates")) : null : !dsh.available && dshPhase === "idle" ? null : dshPhase === "installed" ? null : (0, import_react8.createElement)("button", {
|
|
4821
|
+
type: "button",
|
|
4822
|
+
className: "bt-update-go",
|
|
4823
|
+
disabled: dshPhase === "installing",
|
|
4824
|
+
onClick: startDsh
|
|
4825
|
+
}, dshPhase === "installing" ? t("Updating…") : dshPhase === "failed" ? t("Try again") : t("Update"))
|
|
4826
|
+
)
|
|
4827
|
+
) : null
|
|
4735
4828
|
), document.body) : null
|
|
4736
4829
|
);
|
|
4737
4830
|
}
|
|
@@ -5027,6 +5120,7 @@ var SIDEBAR_CSS = `
|
|
|
5027
5120
|
.bt-update-go:hover:not(:disabled){opacity:.9}
|
|
5028
5121
|
.bt-update-go:active:not(:disabled){transform:scale(.96)}
|
|
5029
5122
|
.bt-update-go:disabled{opacity:.6;cursor:default}
|
|
5123
|
+
.bt-update-dsh:not(:first-child){margin-top:12px;padding-top:12px;border-top:.5px solid var(--bt-line-2)}
|
|
5030
5124
|
@media (prefers-reduced-motion:reduce){.bt-round.bt-update,.bt-update-pop,.bt-update .bt-cloud-arrow{animation:none}}
|
|
5031
5125
|
|
|
5032
5126
|
/* The DSH Agent row and its expanding Session list. The black whale is the
|
|
@@ -5354,10 +5448,11 @@ function ComposerVoice({ sessionId, inputActions, actions, useUi }) {
|
|
|
5354
5448
|
}
|
|
5355
5449
|
}, [quote?.token]);
|
|
5356
5450
|
if (sessionId === void 0) return null;
|
|
5451
|
+
const pluginMic = actions.speech() !== void 0;
|
|
5357
5452
|
return (0, import_react10.createElement)(
|
|
5358
5453
|
import_react10.Fragment,
|
|
5359
5454
|
null,
|
|
5360
|
-
(0, import_react10.createElement)("button", {
|
|
5455
|
+
pluginMic ? null : (0, import_react10.createElement)("button", {
|
|
5361
5456
|
type: "button",
|
|
5362
5457
|
className: "bt-mic",
|
|
5363
5458
|
"aria-label": dictation.listening ? t("Stop dictation") : t("Dictate"),
|
|
@@ -5449,6 +5544,132 @@ function ComposerPlus({ sessionId, useRoster, actions }) {
|
|
|
5449
5544
|
);
|
|
5450
5545
|
}
|
|
5451
5546
|
|
|
5547
|
+
// src/client/speech.js
|
|
5548
|
+
var VOICE_BUNDLE = "@deepseek-ai/dsh-experimental-voice-input-bundle";
|
|
5549
|
+
var USABLE = /* @__PURE__ */ new Set(["ready", "standby", "waking"]);
|
|
5550
|
+
async function speechCatalog(speech) {
|
|
5551
|
+
const result = await speech.catalog();
|
|
5552
|
+
if (!result?.ok) throw new Error(result?.error?.message ?? "Speech catalog failed");
|
|
5553
|
+
const catalog = result.value;
|
|
5554
|
+
const provider = catalog.providers.find((item) => item.id === catalog.selection.providerId);
|
|
5555
|
+
return { catalog, usable: USABLE.has(provider?.preparation?.phase) };
|
|
5556
|
+
}
|
|
5557
|
+
function waveBytes(samples, rate = 16e3) {
|
|
5558
|
+
const bytes = new Uint8Array(44 + samples.length * 2);
|
|
5559
|
+
const view = new DataView(bytes.buffer);
|
|
5560
|
+
const ascii = (at, text) => {
|
|
5561
|
+
for (let index = 0; index < text.length; index += 1) bytes[at + index] = text.charCodeAt(index);
|
|
5562
|
+
};
|
|
5563
|
+
ascii(0, "RIFF");
|
|
5564
|
+
view.setUint32(4, 36 + samples.length * 2, true);
|
|
5565
|
+
ascii(8, "WAVEfmt ");
|
|
5566
|
+
view.setUint32(16, 16, true);
|
|
5567
|
+
view.setUint16(20, 1, true);
|
|
5568
|
+
view.setUint16(22, 1, true);
|
|
5569
|
+
view.setUint32(24, rate, true);
|
|
5570
|
+
view.setUint32(28, rate * 2, true);
|
|
5571
|
+
view.setUint16(32, 2, true);
|
|
5572
|
+
view.setUint16(34, 16, true);
|
|
5573
|
+
ascii(36, "data");
|
|
5574
|
+
view.setUint32(40, samples.length * 2, true);
|
|
5575
|
+
samples.forEach((sample, index) => {
|
|
5576
|
+
const value = Math.max(-1, Math.min(1, sample));
|
|
5577
|
+
view.setInt16(44 + index * 2, value < 0 ? Math.round(value * 32768) : Math.round(value * 32767), true);
|
|
5578
|
+
});
|
|
5579
|
+
return bytes;
|
|
5580
|
+
}
|
|
5581
|
+
function base64Of(bytes) {
|
|
5582
|
+
let text = "";
|
|
5583
|
+
for (let index = 0; index < bytes.length; index += 32768) text += String.fromCharCode(...bytes.subarray(index, index + 32768));
|
|
5584
|
+
return btoa(text);
|
|
5585
|
+
}
|
|
5586
|
+
var captureError = (kind, cause) => Object.assign(new Error(kind), { kind, cause });
|
|
5587
|
+
async function openCapture() {
|
|
5588
|
+
const devices = navigator.mediaDevices;
|
|
5589
|
+
if (!devices?.getUserMedia || typeof MediaRecorder === "undefined") throw captureError("unavailable");
|
|
5590
|
+
let stream;
|
|
5591
|
+
try {
|
|
5592
|
+
stream = await devices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true }, video: false });
|
|
5593
|
+
} catch (error) {
|
|
5594
|
+
throw captureError(error?.name === "NotAllowedError" ? "blocked" : "failed", error);
|
|
5595
|
+
}
|
|
5596
|
+
const audio = new AudioContext();
|
|
5597
|
+
const analyser = audio.createAnalyser();
|
|
5598
|
+
analyser.fftSize = 512;
|
|
5599
|
+
audio.createMediaStreamSource(stream).connect(analyser);
|
|
5600
|
+
const frame = new Float32Array(analyser.fftSize);
|
|
5601
|
+
const recorder = new MediaRecorder(stream);
|
|
5602
|
+
const chunks = [];
|
|
5603
|
+
recorder.ondataavailable = (event) => {
|
|
5604
|
+
if (event.data.size > 0) chunks.push(event.data);
|
|
5605
|
+
};
|
|
5606
|
+
recorder.start();
|
|
5607
|
+
let closed = false;
|
|
5608
|
+
const close = () => {
|
|
5609
|
+
if (closed) return;
|
|
5610
|
+
closed = true;
|
|
5611
|
+
if (recorder.state !== "inactive") recorder.stop();
|
|
5612
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
5613
|
+
void audio.close().catch(() => {
|
|
5614
|
+
});
|
|
5615
|
+
};
|
|
5616
|
+
return {
|
|
5617
|
+
level() {
|
|
5618
|
+
if (closed) return 0;
|
|
5619
|
+
analyser.getFloatTimeDomainData(frame);
|
|
5620
|
+
let sum = 0;
|
|
5621
|
+
for (const sample of frame) sum += sample * sample;
|
|
5622
|
+
return Math.min(1, Math.sqrt(sum / frame.length) * 4);
|
|
5623
|
+
},
|
|
5624
|
+
cancel: close,
|
|
5625
|
+
async finish(maxSeconds) {
|
|
5626
|
+
if (closed || recorder.state === "inactive") {
|
|
5627
|
+
close();
|
|
5628
|
+
return null;
|
|
5629
|
+
}
|
|
5630
|
+
try {
|
|
5631
|
+
await new Promise((resolve) => {
|
|
5632
|
+
recorder.onstop = resolve;
|
|
5633
|
+
recorder.stop();
|
|
5634
|
+
});
|
|
5635
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
5636
|
+
const blob = new Blob(chunks, { type: recorder.mimeType });
|
|
5637
|
+
if (blob.size === 0) return null;
|
|
5638
|
+
const decoded = await audio.decodeAudioData(await blob.arrayBuffer());
|
|
5639
|
+
const length = Math.max(1, Math.floor(Math.min(decoded.duration, maxSeconds) * 16e3));
|
|
5640
|
+
const offline = new OfflineAudioContext(1, length, 16e3);
|
|
5641
|
+
const source = offline.createBufferSource();
|
|
5642
|
+
source.buffer = decoded;
|
|
5643
|
+
source.connect(offline.destination);
|
|
5644
|
+
source.start();
|
|
5645
|
+
return waveBytes((await offline.startRendering()).getChannelData(0));
|
|
5646
|
+
} finally {
|
|
5647
|
+
close();
|
|
5648
|
+
}
|
|
5649
|
+
}
|
|
5650
|
+
};
|
|
5651
|
+
}
|
|
5652
|
+
async function transcribeWave(speech, catalog, wave, signal) {
|
|
5653
|
+
if (wave.byteLength > catalog.maxAudioBytes) throw captureError("too-long");
|
|
5654
|
+
const result = await speech.transcribe({ audioBase64: base64Of(wave), ...catalog.selection }, signal);
|
|
5655
|
+
if (!result?.ok) throw Object.assign(new Error(result?.error?.message ?? "Transcription failed"), { kind: "service" });
|
|
5656
|
+
return String(result.value?.text ?? "").trim();
|
|
5657
|
+
}
|
|
5658
|
+
function turnDetector({ loud = 0.12, quiet = 0.06, quietMs = 1200 } = {}) {
|
|
5659
|
+
let spokeAt = 0;
|
|
5660
|
+
let quietSince = 0;
|
|
5661
|
+
return (level, now) => {
|
|
5662
|
+
if (level >= loud) {
|
|
5663
|
+
spokeAt ||= now;
|
|
5664
|
+
quietSince = 0;
|
|
5665
|
+
return "speech";
|
|
5666
|
+
}
|
|
5667
|
+
if (!spokeAt) return "waiting";
|
|
5668
|
+
if (level < quiet) quietSince ||= now;
|
|
5669
|
+
return quietSince && now - quietSince >= quietMs ? "done" : "speech";
|
|
5670
|
+
};
|
|
5671
|
+
}
|
|
5672
|
+
|
|
5452
5673
|
// src/client/overlay-hooks.js
|
|
5453
5674
|
var import_react11 = require("react");
|
|
5454
5675
|
var escapeLayers = [];
|
|
@@ -5680,10 +5901,19 @@ var MENU_EXIT_MS = 120;
|
|
|
5680
5901
|
function groupModels(models) {
|
|
5681
5902
|
const groups = /* @__PURE__ */ new Map();
|
|
5682
5903
|
for (const model of models ?? []) {
|
|
5683
|
-
|
|
5684
|
-
groups.
|
|
5904
|
+
const provider = model.provider ?? model.providerName ?? model.ref.split("/")[0];
|
|
5905
|
+
if (!groups.has(provider)) groups.set(provider, []);
|
|
5906
|
+
groups.get(provider).push(model);
|
|
5907
|
+
}
|
|
5908
|
+
const names = /* @__PURE__ */ new Map();
|
|
5909
|
+
for (const [provider, list] of groups) {
|
|
5910
|
+
const name = list[0].providerName ?? provider;
|
|
5911
|
+
names.set(name, (names.get(name) ?? 0) + 1);
|
|
5685
5912
|
}
|
|
5686
|
-
return [...groups]
|
|
5913
|
+
return [...groups].map(([provider, list]) => {
|
|
5914
|
+
const name = list[0].providerName ?? provider;
|
|
5915
|
+
return [names.get(name) > 1 ? `${name} (${provider})` : name, list];
|
|
5916
|
+
});
|
|
5687
5917
|
}
|
|
5688
5918
|
var servedOf = (roster, ref) => (roster.models ?? []).find((model) => model.ref === ref);
|
|
5689
5919
|
function modelOf(bot, roster) {
|
|
@@ -5693,10 +5923,57 @@ function modelOf(bot, roster) {
|
|
|
5693
5923
|
const [provider, ...rest] = ref.split("/");
|
|
5694
5924
|
return { ref, name: served?.name ?? rest.join("/"), provider: served?.providerName ?? provider, served: served !== void 0 };
|
|
5695
5925
|
}
|
|
5926
|
+
var effortCache = /* @__PURE__ */ new Map();
|
|
5927
|
+
function useEfforts(served, ref, actions) {
|
|
5928
|
+
const [fetched, setFetched] = (0, import_react13.useState)(void 0);
|
|
5929
|
+
(0, import_react13.useEffect)(() => {
|
|
5930
|
+
setFetched(void 0);
|
|
5931
|
+
if (ref === void 0 || served?.reasoning !== void 0 || actions?.efforts === void 0) return void 0;
|
|
5932
|
+
let live = true;
|
|
5933
|
+
if (effortCache.has(ref)) setFetched(effortCache.get(ref));
|
|
5934
|
+
else void Promise.resolve(actions.efforts(ref)).then((value) => {
|
|
5935
|
+
const reasoning = value?.efforts ? value : null;
|
|
5936
|
+
effortCache.set(ref, reasoning);
|
|
5937
|
+
if (live) setFetched(reasoning);
|
|
5938
|
+
}).catch(() => {
|
|
5939
|
+
});
|
|
5940
|
+
return () => {
|
|
5941
|
+
live = false;
|
|
5942
|
+
};
|
|
5943
|
+
}, [ref, served?.reasoning !== void 0, actions]);
|
|
5944
|
+
return served?.reasoning ?? fetched;
|
|
5945
|
+
}
|
|
5946
|
+
function effortRows(reasoning) {
|
|
5947
|
+
if (reasoning?.efforts === void 0 || reasoning.efforts.length === 0) return [];
|
|
5948
|
+
return [
|
|
5949
|
+
{ effort: void 0, label: t("Default") },
|
|
5950
|
+
...reasoning.efforts.map((effort) => ({ effort: effort.id, label: effort.name }))
|
|
5951
|
+
];
|
|
5952
|
+
}
|
|
5953
|
+
function EffortRows({ reasoning, current, onPick }) {
|
|
5954
|
+
return (0, import_react13.createElement)(
|
|
5955
|
+
import_react13.Fragment,
|
|
5956
|
+
null,
|
|
5957
|
+
(0, import_react13.createElement)("div", { className: "bt-menu-sep", role: "separator" }),
|
|
5958
|
+
(0, import_react13.createElement)("div", { className: "bt-model-group", role: "presentation" }, t("Thinking level")),
|
|
5959
|
+
effortRows(reasoning).map((row) => (0, import_react13.createElement)(
|
|
5960
|
+
"button",
|
|
5961
|
+
{
|
|
5962
|
+
key: row.effort ?? "default",
|
|
5963
|
+
type: "button",
|
|
5964
|
+
role: "menuitemradio",
|
|
5965
|
+
"aria-checked": row.effort === current,
|
|
5966
|
+
onClick: () => onPick(row.effort)
|
|
5967
|
+
},
|
|
5968
|
+
(0, import_react13.createElement)("span", { className: "bt-menu-label" }, row.label),
|
|
5969
|
+
(0, import_react13.createElement)("span", { className: "bt-model-tick", "aria-hidden": true }, row.effort === current ? (0, import_react13.createElement)(CheckIcon) : null)
|
|
5970
|
+
))
|
|
5971
|
+
);
|
|
5972
|
+
}
|
|
5696
5973
|
function ModelRows({ roster, current, onPick }) {
|
|
5697
5974
|
return groupModels(roster.models).map(([provider, list]) => (0, import_react13.createElement)(
|
|
5698
5975
|
import_react13.Fragment,
|
|
5699
|
-
{ key: provider },
|
|
5976
|
+
{ key: list[0].provider ?? provider },
|
|
5700
5977
|
(0, import_react13.createElement)("div", { className: "bt-model-group", role: "presentation" }, provider),
|
|
5701
5978
|
list.map((model) => (0, import_react13.createElement)(
|
|
5702
5979
|
"button",
|
|
@@ -5714,13 +5991,18 @@ function ModelRows({ roster, current, onPick }) {
|
|
|
5714
5991
|
))
|
|
5715
5992
|
));
|
|
5716
5993
|
}
|
|
5717
|
-
function ModelMenu({ bot, roster, actions, run, wide = false, onPick }) {
|
|
5994
|
+
function ModelMenu({ bot, roster, actions, run, wide = false, onPick, onPickEffort }) {
|
|
5718
5995
|
const [place, setPlace] = (0, import_react13.useState)(null);
|
|
5719
5996
|
const [shownPlace, leaving] = useLinger(place, MENU_EXIT_MS);
|
|
5720
5997
|
const button = (0, import_react13.useRef)(null);
|
|
5721
5998
|
const menu = (0, import_react13.useRef)(null);
|
|
5722
5999
|
const shown = modelOf(bot, roster);
|
|
5723
6000
|
const current = bot.model ?? bot.appliedModel;
|
|
6001
|
+
const served = servedOf(roster, current);
|
|
6002
|
+
const reasoning = useEfforts(served, current, actions);
|
|
6003
|
+
const picked = reasoning?.efforts?.some((effort) => effort.id === bot.effort) ? bot.effort : void 0;
|
|
6004
|
+
const currentEffort = picked ?? reasoning?.defaultEffort;
|
|
6005
|
+
const effortLabel = currentEffort === void 0 ? reasoning?.efforts?.length > 0 ? t("Default") : void 0 : reasoning.efforts.find((effort) => effort.id === currentEffort)?.name ?? currentEffort;
|
|
5724
6006
|
const close = () => setPlace(null);
|
|
5725
6007
|
(0, import_react13.useEffect)(() => {
|
|
5726
6008
|
if (place === null) return void 0;
|
|
@@ -5764,6 +6046,11 @@ function ModelMenu({ bot, roster, actions, run, wide = false, onPick }) {
|
|
|
5764
6046
|
if (onPick) onPick(ref);
|
|
5765
6047
|
else if (ref !== current || !bot.model) void run(actions.updateBot(bot.id, { model: ref }));
|
|
5766
6048
|
};
|
|
6049
|
+
const pickEffort = (effort) => {
|
|
6050
|
+
close();
|
|
6051
|
+
if (onPickEffort) onPickEffort(effort);
|
|
6052
|
+
else if (effort !== bot.effort) void run(actions.updateBot(bot.id, { effort: effort ?? null }));
|
|
6053
|
+
};
|
|
5767
6054
|
const models = roster.models ?? [];
|
|
5768
6055
|
return (0, import_react13.createElement)(
|
|
5769
6056
|
import_react13.Fragment,
|
|
@@ -5785,7 +6072,7 @@ function ModelMenu({ bot, roster, actions, run, wide = false, onPick }) {
|
|
|
5785
6072
|
"span",
|
|
5786
6073
|
{ className: "bt-model-copy" },
|
|
5787
6074
|
(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
|
|
6075
|
+
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
6076
|
),
|
|
5790
6077
|
(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
6078
|
),
|
|
@@ -5793,6 +6080,7 @@ function ModelMenu({ bot, roster, actions, run, wide = false, onPick }) {
|
|
|
5793
6080
|
"div",
|
|
5794
6081
|
{ 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
6082
|
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.")),
|
|
6083
|
+
effortRows(reasoning).length > 0 ? (0, import_react13.createElement)(EffortRows, { reasoning, current: picked, onPick: pickEffort }) : null,
|
|
5796
6084
|
(0, import_react13.createElement)("div", { className: "bt-menu-sep", role: "separator" }),
|
|
5797
6085
|
(0, import_react13.createElement)("button", { type: "button", role: "menuitem", onClick: () => {
|
|
5798
6086
|
close();
|
|
@@ -5824,15 +6112,21 @@ function ModelChoiceDock({ sessionId, useRoster, actions }) {
|
|
|
5824
6112
|
};
|
|
5825
6113
|
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
6114
|
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
|
-
|
|
6115
|
+
...groupModels(models).map(([provider, list]) => (0, import_react13.createElement)(
|
|
6116
|
+
"div",
|
|
6117
|
+
{ key: list[0].provider ?? provider, className: "bt-model-choice-group", role: "group", "aria-label": provider },
|
|
6118
|
+
(0, import_react13.createElement)("div", { className: "bt-model-choice-provider", "aria-hidden": true }, provider),
|
|
6119
|
+
(0, import_react13.createElement)("div", { className: "bt-model-choice-chips" }, list.map((model) => (0, import_react13.createElement)("button", {
|
|
6120
|
+
key: model.ref,
|
|
6121
|
+
type: "button",
|
|
6122
|
+
className: "bt-model-chip",
|
|
6123
|
+
disabled: busy !== "",
|
|
6124
|
+
"aria-busy": busy === model.ref || void 0,
|
|
6125
|
+
"aria-label": `${model.name} · ${provider}`,
|
|
6126
|
+
title: model.ref === roster.defaultModel ? `${model.ref} · ${t("Harness default")}` : model.ref,
|
|
6127
|
+
onClick: () => pick2(model.ref)
|
|
6128
|
+
}, model.name, busy === model.ref ? (0, import_react13.createElement)("span", { className: "bt-model-spin", "aria-label": t("Saving…") }) : null)))
|
|
6129
|
+
)),
|
|
5836
6130
|
(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
6131
|
];
|
|
5838
6132
|
return (0, import_react13.createElement)(
|
|
@@ -5905,6 +6199,9 @@ var MODEL_CSS = `
|
|
|
5905
6199
|
.bt-model-none{padding:8px;font-size:13px;line-height:18px;color:var(--bt-ink-2)}
|
|
5906
6200
|
.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
6201
|
.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}
|
|
6202
|
+
.bt-model-choice-group{display:flex;flex-direction:column;gap:6px;width:100%;min-width:0}
|
|
6203
|
+
.bt-model-choice-provider{padding-top:4px;font-size:12px;line-height:16px;font-weight:500;color:var(--bt-ink-2);overflow-wrap:anywhere}
|
|
6204
|
+
.bt-model-choice-chips{display:flex;flex-wrap:wrap;gap:6px}
|
|
5908
6205
|
.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
6206
|
.bt-model-chip:hover:not(:disabled){background:var(--bt-hover)}
|
|
5910
6207
|
.bt-model-chip:focus-visible{outline:2px solid var(--bt-accent);outline-offset:1px}
|
|
@@ -7031,9 +7328,10 @@ function startModel(roster) {
|
|
|
7031
7328
|
const models = roster.models ?? [];
|
|
7032
7329
|
return (models.find((model) => model.ref === roster.defaultModel) ?? models[0])?.ref ?? "";
|
|
7033
7330
|
}
|
|
7034
|
-
function createPayload({ name, look, image, model, brief }) {
|
|
7331
|
+
function createPayload({ name, look, image, model, effort, brief }) {
|
|
7035
7332
|
const payload = { name, color: look.color, avatar: image ? { shape: look.shape, image } : { shape: look.shape } };
|
|
7036
7333
|
if (model) payload.model = model;
|
|
7334
|
+
if (effort) payload.effort = effort;
|
|
7037
7335
|
if (brief) payload.brief = brief;
|
|
7038
7336
|
return payload;
|
|
7039
7337
|
}
|
|
@@ -7051,6 +7349,7 @@ function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCr
|
|
|
7051
7349
|
const [image, setImage] = (0, import_react17.useState)(null);
|
|
7052
7350
|
const [name, setName] = (0, import_react17.useState)(start?.name ?? "");
|
|
7053
7351
|
const [model, setModel] = (0, import_react17.useState)(() => startModel(roster));
|
|
7352
|
+
const [effort, setEffort] = (0, import_react17.useState)(void 0);
|
|
7054
7353
|
const [busy, setBusy] = (0, import_react17.useState)(false);
|
|
7055
7354
|
const [error, setError] = (0, import_react17.useState)("");
|
|
7056
7355
|
const fileRef = (0, import_react17.useRef)(null);
|
|
@@ -7067,7 +7366,7 @@ function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCr
|
|
|
7067
7366
|
setError("");
|
|
7068
7367
|
setBusy(true);
|
|
7069
7368
|
try {
|
|
7070
|
-
const bot = await actions.createBot(createPayload({ name: finalName, look, image, model, brief }));
|
|
7369
|
+
const bot = await actions.createBot(createPayload({ name: finalName, look, image, model, effort, brief }));
|
|
7071
7370
|
onCreated(bot);
|
|
7072
7371
|
} catch (failure) {
|
|
7073
7372
|
setError(failure?.message ?? String(failure));
|
|
@@ -7151,7 +7450,10 @@ function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCr
|
|
|
7151
7450
|
"div",
|
|
7152
7451
|
{ className: "bt-field" },
|
|
7153
7452
|
(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:
|
|
7453
|
+
(0, import_react17.createElement)(ModelMenu, { bot: { id: "new-bot", name: finalName, model: model || void 0, effort }, roster, actions, wide: true, onPick: (ref) => {
|
|
7454
|
+
setModel(ref);
|
|
7455
|
+
setEffort(void 0);
|
|
7456
|
+
}, onPickEffort: setEffort }),
|
|
7155
7457
|
(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
7458
|
),
|
|
7157
7459
|
error ? (0, import_react17.createElement)("div", { className: "bt-error", role: "alert", style: { padding: 0 } }, error) : null,
|
|
@@ -7513,7 +7815,15 @@ function ExchangeDialog({ pair, roster, actions }) {
|
|
|
7513
7815
|
// src/client/voice.js
|
|
7514
7816
|
var import_react20 = require("react");
|
|
7515
7817
|
var plainSpeech = (text) => stripHeader(text).replace(/```[\s\S]*?```/g, " ").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/[*_`#>|~-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
7516
|
-
var VOICE_LABELS = {
|
|
7818
|
+
var VOICE_LABELS = {
|
|
7819
|
+
listening: "Listening",
|
|
7820
|
+
thinking: "Thinking",
|
|
7821
|
+
speaking: "Speaking",
|
|
7822
|
+
paused: "Paused",
|
|
7823
|
+
setup: "Voice input needs its speech model",
|
|
7824
|
+
unsupported: "Voice chat is not available in this browser"
|
|
7825
|
+
};
|
|
7826
|
+
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
7827
|
function useMicLevel(enabled) {
|
|
7518
7828
|
const [level, setLevel] = (0, import_react20.useState)(0);
|
|
7519
7829
|
(0, import_react20.useEffect)(() => {
|
|
@@ -7566,27 +7876,59 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus }
|
|
|
7566
7876
|
const entry = Array.isArray(outline) ? outline[outline.length - 1] : void 0;
|
|
7567
7877
|
return entry ? `${outline.length}|${entry.response ?? ""}` : "0|";
|
|
7568
7878
|
});
|
|
7569
|
-
const [
|
|
7879
|
+
const [speech] = (0, import_react20.useState)(() => actions.speech());
|
|
7880
|
+
const [phase, setPhase] = (0, import_react20.useState)(speech ? "starting" : speechApi() ? "listening" : "unsupported");
|
|
7881
|
+
const [catalog, setCatalog] = (0, import_react20.useState)(null);
|
|
7570
7882
|
const [heard, setHeard] = (0, import_react20.useState)("");
|
|
7571
7883
|
const [said, setSaid] = (0, import_react20.useState)("");
|
|
7884
|
+
const [note, setNote] = (0, import_react20.useState)("");
|
|
7885
|
+
const [serviceLevel, setServiceLevel] = (0, import_react20.useState)(0);
|
|
7886
|
+
const [cycle, setCycle] = (0, import_react20.useState)(0);
|
|
7572
7887
|
const waiting = (0, import_react20.useRef)(null);
|
|
7888
|
+
const reply = (0, import_react20.useRef)(lastReply);
|
|
7889
|
+
reply.current = lastReply;
|
|
7890
|
+
const turn = (0, import_react20.useRef)(0);
|
|
7573
7891
|
const paneLeft = usePaneLeft();
|
|
7574
7892
|
const close = () => {
|
|
7893
|
+
turn.current += 1;
|
|
7575
7894
|
window.speechSynthesis?.cancel();
|
|
7576
7895
|
actions.closeVoice();
|
|
7577
7896
|
};
|
|
7578
7897
|
useEscape(close);
|
|
7898
|
+
const submit = (text) => {
|
|
7899
|
+
setHeard(text);
|
|
7900
|
+
setPhase("thinking");
|
|
7901
|
+
waiting.current = reply.current.split("|")[0];
|
|
7902
|
+
void actions.send(sessionId, text.trim()).catch(() => setPhase("listening"));
|
|
7903
|
+
};
|
|
7579
7904
|
const dictation = useDictation((text, final) => {
|
|
7580
7905
|
setHeard(text);
|
|
7581
7906
|
if (!final) return;
|
|
7582
7907
|
dictation.stop();
|
|
7583
|
-
|
|
7584
|
-
waiting.current = lastReply.split("|")[0];
|
|
7585
|
-
void actions.send(sessionId, text.trim()).catch(() => setPhase("listening"));
|
|
7908
|
+
submit(text);
|
|
7586
7909
|
});
|
|
7910
|
+
(0, import_react20.useEffect)(() => {
|
|
7911
|
+
if (!note) return void 0;
|
|
7912
|
+
const timer = setTimeout(() => setNote(""), 2600);
|
|
7913
|
+
return () => clearTimeout(timer);
|
|
7914
|
+
}, [note]);
|
|
7915
|
+
(0, import_react20.useEffect)(() => {
|
|
7916
|
+
if (!speech) return void 0;
|
|
7917
|
+
let live = true;
|
|
7918
|
+
speechCatalog(speech).then(({ catalog: next, usable }) => {
|
|
7919
|
+
if (!live) return;
|
|
7920
|
+
setCatalog(next);
|
|
7921
|
+
setPhase(usable ? "listening" : "setup");
|
|
7922
|
+
}, () => {
|
|
7923
|
+
if (live) setPhase("setup");
|
|
7924
|
+
});
|
|
7925
|
+
return () => {
|
|
7926
|
+
live = false;
|
|
7927
|
+
};
|
|
7928
|
+
}, [speech]);
|
|
7587
7929
|
const listen = phase === "listening";
|
|
7588
7930
|
(0, import_react20.useEffect)(() => {
|
|
7589
|
-
if (!listen || dictation.listening) return void 0;
|
|
7931
|
+
if (speech || !listen || dictation.listening) return void 0;
|
|
7590
7932
|
const timer = setTimeout(() => dictation.start({ continuous: false, onEnd: () => {
|
|
7591
7933
|
} }), 250);
|
|
7592
7934
|
return () => clearTimeout(timer);
|
|
@@ -7594,20 +7936,82 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus }
|
|
|
7594
7936
|
(0, import_react20.useEffect)(() => {
|
|
7595
7937
|
if (phase !== "listening") dictation.stop();
|
|
7596
7938
|
}, [phase]);
|
|
7939
|
+
(0, import_react20.useEffect)(() => {
|
|
7940
|
+
if (!speech || !catalog || !listen) return void 0;
|
|
7941
|
+
const own = turn.current;
|
|
7942
|
+
let capture = null;
|
|
7943
|
+
let frame = 0;
|
|
7944
|
+
let stopped = false;
|
|
7945
|
+
let smooth = 0;
|
|
7946
|
+
const detect = turnDetector();
|
|
7947
|
+
const limit = Math.max(1, catalog.maxDurationSeconds - 1) * 1e3;
|
|
7948
|
+
const finish = async (current) => {
|
|
7949
|
+
setPhase("transcribing");
|
|
7950
|
+
try {
|
|
7951
|
+
const wave = await current.finish(catalog.maxDurationSeconds);
|
|
7952
|
+
const text = wave ? await transcribeWave(speech, catalog, wave, new AbortController().signal) : "";
|
|
7953
|
+
if (turn.current !== own) return;
|
|
7954
|
+
if (text) submit(text);
|
|
7955
|
+
else setPhase("listening");
|
|
7956
|
+
} catch (error) {
|
|
7957
|
+
if (turn.current !== own) return;
|
|
7958
|
+
setNote(t(CAPTURE_NOTES[error?.kind] ?? "Couldn't hear that"));
|
|
7959
|
+
setPhase("listening");
|
|
7960
|
+
}
|
|
7961
|
+
};
|
|
7962
|
+
openCapture().then((opened) => {
|
|
7963
|
+
if (stopped) {
|
|
7964
|
+
opened.cancel();
|
|
7965
|
+
return;
|
|
7966
|
+
}
|
|
7967
|
+
capture = opened;
|
|
7968
|
+
const began = performance.now();
|
|
7969
|
+
const tick = () => {
|
|
7970
|
+
const now = performance.now();
|
|
7971
|
+
const level2 = capture.level();
|
|
7972
|
+
smooth = smooth * 0.8 + level2 * 0.2;
|
|
7973
|
+
setServiceLevel(Math.round(smooth * 100) / 100);
|
|
7974
|
+
const state = detect(level2, now);
|
|
7975
|
+
if (state === "done" || state === "speech" && now - began >= limit) {
|
|
7976
|
+
const current = capture;
|
|
7977
|
+
capture = null;
|
|
7978
|
+
setServiceLevel(0);
|
|
7979
|
+
void finish(current);
|
|
7980
|
+
return;
|
|
7981
|
+
}
|
|
7982
|
+
if (state === "waiting" && now - began >= limit) {
|
|
7983
|
+
setCycle((value) => value + 1);
|
|
7984
|
+
return;
|
|
7985
|
+
}
|
|
7986
|
+
frame = requestAnimationFrame(tick);
|
|
7987
|
+
};
|
|
7988
|
+
tick();
|
|
7989
|
+
}, (error) => {
|
|
7990
|
+
if (stopped) return;
|
|
7991
|
+
setNote(t(CAPTURE_NOTES[error?.kind] ?? "Couldn't hear that"));
|
|
7992
|
+
setPhase("paused");
|
|
7993
|
+
});
|
|
7994
|
+
return () => {
|
|
7995
|
+
stopped = true;
|
|
7996
|
+
cancelAnimationFrame(frame);
|
|
7997
|
+
capture?.cancel();
|
|
7998
|
+
setServiceLevel(0);
|
|
7999
|
+
};
|
|
8000
|
+
}, [speech, catalog, listen, cycle]);
|
|
7597
8001
|
(0, import_react20.useEffect)(() => {
|
|
7598
8002
|
if (phase !== "thinking" || running || waiting.current === null) return;
|
|
7599
8003
|
const [count, text] = [lastReply.slice(0, lastReply.indexOf("|")), lastReply.slice(lastReply.indexOf("|") + 1)];
|
|
7600
8004
|
if (count === waiting.current || !text.trim()) return;
|
|
7601
8005
|
waiting.current = null;
|
|
7602
|
-
const
|
|
7603
|
-
setSaid(
|
|
8006
|
+
const spoken = plainSpeech(text);
|
|
8007
|
+
setSaid(spoken);
|
|
7604
8008
|
setHeard("");
|
|
7605
|
-
if (!window.speechSynthesis || !
|
|
8009
|
+
if (!window.speechSynthesis || !spoken) {
|
|
7606
8010
|
setPhase("listening");
|
|
7607
8011
|
return;
|
|
7608
8012
|
}
|
|
7609
|
-
const utterance = new SpeechSynthesisUtterance(
|
|
7610
|
-
utterance.lang = /[\u3400-\u9fff]/.test(
|
|
8013
|
+
const utterance = new SpeechSynthesisUtterance(spoken);
|
|
8014
|
+
utterance.lang = /[\u3400-\u9fff]/.test(spoken) ? "zh-CN" : "en-US";
|
|
7611
8015
|
utterance.onend = () => setPhase((current) => current === "speaking" ? "listening" : current);
|
|
7612
8016
|
utterance.onerror = utterance.onend;
|
|
7613
8017
|
setPhase("speaking");
|
|
@@ -7615,23 +8019,28 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus }
|
|
|
7615
8019
|
window.speechSynthesis.speak(utterance);
|
|
7616
8020
|
}, [phase, running, lastReply]);
|
|
7617
8021
|
(0, import_react20.useEffect)(() => () => window.speechSynthesis?.cancel(), []);
|
|
7618
|
-
const
|
|
7619
|
-
const
|
|
8022
|
+
const micLevel = useMicLevel(!speech && phase === "listening" && dictation.listening);
|
|
8023
|
+
const level = speech ? serviceLevel : micLevel;
|
|
8024
|
+
const markState = phase === "thinking" || phase === "transcribing" ? "thinking" : phase === "speaking" ? "sending" : "idle";
|
|
8025
|
+
const blocked = phase === "unsupported" || phase === "setup" || phase === "starting";
|
|
7620
8026
|
const togglePause = () => {
|
|
7621
8027
|
if (phase === "paused") {
|
|
7622
8028
|
setPhase("listening");
|
|
7623
8029
|
return;
|
|
7624
8030
|
}
|
|
8031
|
+
turn.current += 1;
|
|
7625
8032
|
window.speechSynthesis?.cancel();
|
|
7626
8033
|
setPhase("paused");
|
|
7627
8034
|
};
|
|
8035
|
+
const openSetup = phase === "setup" ? actions.voiceSetup() : null;
|
|
7628
8036
|
const caption = phase === "speaking" ? said : heard;
|
|
8037
|
+
const shownNote = note || dictation.note;
|
|
7629
8038
|
return (0, import_react20.createElement)(
|
|
7630
8039
|
"div",
|
|
7631
8040
|
{ className: "bt-pane bt-voice-pane", style: { left: paneLeft } },
|
|
7632
8041
|
(0, import_react20.createElement)(
|
|
7633
8042
|
"div",
|
|
7634
|
-
{ className: "bt-voice-stage", role: "dialog", "aria-label": t("Voice chat with {name}", { name }), "data-phase": phase },
|
|
8043
|
+
{ className: "bt-voice-stage", role: "dialog", "aria-label": t("Voice chat with {name}", { name }), "data-phase": phase === "transcribing" ? "thinking" : phase },
|
|
7635
8044
|
(0, import_react20.createElement)(
|
|
7636
8045
|
"div",
|
|
7637
8046
|
{ className: "bt-voice-orb", style: { "--bt-level": level } },
|
|
@@ -7643,10 +8052,14 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus }
|
|
|
7643
8052
|
(0, import_react20.createElement)(
|
|
7644
8053
|
"div",
|
|
7645
8054
|
{ className: "bt-voice-status", role: "status" },
|
|
7646
|
-
phase === "thinking" ? (0, import_react20.createElement)(Shimmer, null, t("Thinking…")) : VOICE_LABELS[phase] ? t(VOICE_LABELS[phase]) : ""
|
|
8055
|
+
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
8056
|
),
|
|
7648
8057
|
(0, import_react20.createElement)("div", { className: "bt-voice-caption", key: `${phase}:${caption.slice(0, 12)}` }, caption),
|
|
7649
|
-
|
|
8058
|
+
openSetup ? (0, import_react20.createElement)("button", { type: "button", className: "bt-voice-setup", onClick: () => {
|
|
8059
|
+
close();
|
|
8060
|
+
openSetup();
|
|
8061
|
+
} }, t("Set up voice input")) : null,
|
|
8062
|
+
shownNote ? (0, import_react20.createElement)("div", { className: "bt-voice-note" }, shownNote) : null
|
|
7650
8063
|
),
|
|
7651
8064
|
(0, import_react20.createElement)(
|
|
7652
8065
|
"div",
|
|
@@ -7655,7 +8068,7 @@ function VoiceMode({ sessionId, roster, actions, useSessions, useSessionStatus }
|
|
|
7655
8068
|
type: "button",
|
|
7656
8069
|
className: "bt-voice-btn",
|
|
7657
8070
|
"aria-pressed": phase === "paused",
|
|
7658
|
-
disabled:
|
|
8071
|
+
disabled: blocked,
|
|
7659
8072
|
"aria-label": phase === "paused" ? t("Resume listening") : t("Pause"),
|
|
7660
8073
|
onClick: togglePause
|
|
7661
8074
|
}, (0, import_react20.createElement)(MicIcon, { size: 20 }), phase === "paused" ? (0, import_react20.createElement)("span", { className: "bt-voice-slash", "aria-hidden": true }) : null),
|
|
@@ -9120,55 +9533,196 @@ var FEEDBACK_CSS = `
|
|
|
9120
9533
|
@media (prefers-reduced-motion:reduce){.bt-toast{animation:none}.bt-toast[data-leaving]{transition:none}}
|
|
9121
9534
|
`;
|
|
9122
9535
|
|
|
9123
|
-
// src/client/
|
|
9536
|
+
// src/client/about.js
|
|
9124
9537
|
var import_react25 = require("react");
|
|
9538
|
+
var CHEER_MS = 1800;
|
|
9539
|
+
var starCount = (count) => new Intl.NumberFormat(dateLocale(), { notation: "compact", maximumFractionDigits: 1 }).format(count);
|
|
9540
|
+
function botHint(update) {
|
|
9541
|
+
if (update.phase === "installing") return t("Updating DS Bot…");
|
|
9542
|
+
if (update.phase === "installed") return t("Restart DSH to start using DS Bot {version}.", { version: update.installed ?? update.latest });
|
|
9543
|
+
if (update.phase === "failed") return t("The update did not finish: {reason}", { reason: hostText(update.error ?? "") });
|
|
9544
|
+
if (update.available) return t("DS Bot {version} is available", { version: update.latest });
|
|
9545
|
+
if (update.enabled === false) return t("Update checks are off.");
|
|
9546
|
+
if (update.source === "path") return t("Installed from a local folder; update it there.");
|
|
9547
|
+
if (!update.checkedAt) return null;
|
|
9548
|
+
if (!update.latest && update.source !== "registry") return t("Updates are not offered for this install.");
|
|
9549
|
+
const checked = t("Checked {time}", { time: whenText(update.checkedAt) });
|
|
9550
|
+
return `${update.latest ? t("Up to date") : t("Could not check for updates.")} · ${checked}`;
|
|
9551
|
+
}
|
|
9552
|
+
function dshHint(dsh, shown) {
|
|
9553
|
+
if (dsh.phase === "installing") return t("Updating DSH…");
|
|
9554
|
+
if (dsh.phase === "installed") return t("Restart DSH to start using DSH {version}.", { version: dsh.installed ?? dsh.verified });
|
|
9555
|
+
if (dsh.phase === "failed") return t("The update did not finish: {reason}", { reason: hostText(dsh.error ?? "") });
|
|
9556
|
+
if (dsh.available === false && dsh.blockedBy) return t("Update DS Bot {version} first.", { version: dsh.blockedBy });
|
|
9557
|
+
if (shown) return t("DSH {version} is available", { version: dsh.verified });
|
|
9558
|
+
return t("Version {version}", { version: dsh.current });
|
|
9559
|
+
}
|
|
9560
|
+
var linkProps = (href) => ({ className: "bt-soft bt-soft-sm bt-about-link", href, target: "_blank", rel: "noopener noreferrer" });
|
|
9561
|
+
function AboutPage({ useUpdate, actions, run, Section, Row: Row3 }) {
|
|
9562
|
+
const update = useUpdate((value) => value) ?? {};
|
|
9563
|
+
const [checking, setChecking] = (0, import_react25.useState)(false);
|
|
9564
|
+
const [stars, setStars] = (0, import_react25.useState)(null);
|
|
9565
|
+
(0, import_react25.useEffect)(() => {
|
|
9566
|
+
let live = true;
|
|
9567
|
+
actions.repoStars().then((count) => {
|
|
9568
|
+
if (live && Number.isInteger(count)) setStars(count);
|
|
9569
|
+
}, () => {
|
|
9570
|
+
});
|
|
9571
|
+
return () => {
|
|
9572
|
+
live = false;
|
|
9573
|
+
};
|
|
9574
|
+
}, []);
|
|
9575
|
+
const [cheer, setCheer] = (0, import_react25.useState)(true);
|
|
9576
|
+
(0, import_react25.useEffect)(() => {
|
|
9577
|
+
if (!cheer) return void 0;
|
|
9578
|
+
const timer = setTimeout(() => setCheer(false), CHEER_MS);
|
|
9579
|
+
return () => clearTimeout(timer);
|
|
9580
|
+
}, [cheer]);
|
|
9581
|
+
const check = () => {
|
|
9582
|
+
setChecking(true);
|
|
9583
|
+
void run(actions.checkUpdate()).then((done) => {
|
|
9584
|
+
setChecking(false);
|
|
9585
|
+
if (done) setCheer(true);
|
|
9586
|
+
});
|
|
9587
|
+
};
|
|
9588
|
+
const phase = update.phase ?? "idle";
|
|
9589
|
+
const dsh = update.dsh;
|
|
9590
|
+
const dshPhase = dsh?.phase ?? "idle";
|
|
9591
|
+
(0, import_react25.useEffect)(() => {
|
|
9592
|
+
if (phase === "installed" || dshPhase === "installed") setCheer(true);
|
|
9593
|
+
}, [phase, dshPhase]);
|
|
9594
|
+
const markState = checking ? "searching" : phase === "installing" || dshPhase === "installing" ? "working" : cheer ? "done" : "idle";
|
|
9595
|
+
const dshNew = showsDshRow(dsh, update.app);
|
|
9596
|
+
const dshShown = dshNew && dsh.available;
|
|
9597
|
+
const botControl = phase === "installed" ? null : update.available || phase === "installing" ? (0, import_react25.createElement)(
|
|
9598
|
+
"button",
|
|
9599
|
+
{ type: "button", className: "bt-fb-primary", disabled: phase === "installing", onClick: () => run(actions.installUpdate()) },
|
|
9600
|
+
phase === "installing" ? t("Updating…") : phase === "failed" ? t("Try again") : t("Update")
|
|
9601
|
+
) : (0, import_react25.createElement)("button", { type: "button", className: "bt-soft bt-soft-sm", disabled: checking || update.enabled === false, onClick: check }, checking ? t("Checking…") : t("Check for updates"));
|
|
9602
|
+
const dshControl = !dshNew || dshPhase === "installed" ? null : dsh.kind === "app" ? dsh.available ? (0, import_react25.createElement)("button", { type: "button", className: "bt-soft bt-soft-sm", onClick: () => run(actions.openAppUpdates(dsh.verified)) }, t("Open updates")) : null : dsh.available || dshPhase !== "idle" ? (0, import_react25.createElement)(
|
|
9603
|
+
"button",
|
|
9604
|
+
{ type: "button", className: "bt-fb-primary", disabled: dshPhase === "installing", onClick: () => run(actions.installDshUpdate()) },
|
|
9605
|
+
dshPhase === "installing" ? t("Updating…") : dshPhase === "failed" ? t("Try again") : t("Update")
|
|
9606
|
+
) : null;
|
|
9607
|
+
const repo = update.repoUrl;
|
|
9608
|
+
return [
|
|
9609
|
+
(0, import_react25.createElement)(
|
|
9610
|
+
"div",
|
|
9611
|
+
{ key: "hero", className: "bt-about-hero" },
|
|
9612
|
+
(0, import_react25.createElement)("span", { className: "bt-about-mark" }, (0, import_react25.createElement)(BotMark, { look: MAIN_LOOK, bot: { id: "about" }, size: 52, state: markState, live: true, gaze: true, pokeable: true })),
|
|
9613
|
+
(0, import_react25.createElement)(
|
|
9614
|
+
"div",
|
|
9615
|
+
{ className: "bt-about-copy" },
|
|
9616
|
+
(0, import_react25.createElement)("span", { className: "bt-about-name" }, "DS Bot"),
|
|
9617
|
+
update.current ? (0, import_react25.createElement)("span", { className: "bt-about-version" }, t("Version {version}", { version: update.current })) : null
|
|
9618
|
+
),
|
|
9619
|
+
repo ? (0, import_react25.createElement)(
|
|
9620
|
+
"a",
|
|
9621
|
+
{
|
|
9622
|
+
className: "bt-star",
|
|
9623
|
+
href: repo,
|
|
9624
|
+
target: "_blank",
|
|
9625
|
+
rel: "noopener noreferrer",
|
|
9626
|
+
title: t("Star DS Bot on GitHub"),
|
|
9627
|
+
"aria-label": stars === null ? t("Star DS Bot on GitHub") : `${t("Star DS Bot on GitHub")} · ${t("{count} stars", { count: stars })}`
|
|
9628
|
+
},
|
|
9629
|
+
(0, import_react25.createElement)("span", { className: "bt-star-main" }, (0, import_react25.createElement)("span", { className: "bt-star-icon" }, (0, import_react25.createElement)(StarLineIcon)), "Star"),
|
|
9630
|
+
stars === null ? null : (0, import_react25.createElement)("span", { className: "bt-star-count" }, starCount(stars))
|
|
9631
|
+
) : null
|
|
9632
|
+
),
|
|
9633
|
+
repo ? (0, import_react25.createElement)("p", { key: "ask", className: "bt-about-ask" }, t("If DS Bot helps you, a star on GitHub helps more people find it.")) : null,
|
|
9634
|
+
(0, import_react25.createElement)(
|
|
9635
|
+
Section,
|
|
9636
|
+
{ key: "updates", title: t("Updates") },
|
|
9637
|
+
(0, import_react25.createElement)(Row3, { label: "DS Bot", hint: botHint(update) }, botControl, update.available && update.notesUrl ? (0, import_react25.createElement)("a", linkProps(update.notesUrl), t("What's new")) : null),
|
|
9638
|
+
dsh?.current ? (0, import_react25.createElement)(Row3, { label: "DeepSeek Harness", hint: dshHint(dsh, dshShown) }, dshControl) : null
|
|
9639
|
+
),
|
|
9640
|
+
(0, import_react25.createElement)(
|
|
9641
|
+
Section,
|
|
9642
|
+
{ key: "links", title: t("Links") },
|
|
9643
|
+
repo ? (0, import_react25.createElement)(Row3, { label: t("Source code"), hint: repo.replace(/^https:\/\//, "") }, (0, import_react25.createElement)("a", linkProps(repo), (0, import_react25.createElement)(GitHubMark), t("Open"))) : null,
|
|
9644
|
+
update.notesUrl ? (0, import_react25.createElement)(Row3, { label: t("Release notes") }, (0, import_react25.createElement)("a", linkProps(update.notesUrl), t("Open"))) : null,
|
|
9645
|
+
(0, import_react25.createElement)(Row3, { label: t("Report a problem") }, (0, import_react25.createElement)("button", { type: "button", className: "bt-soft bt-soft-sm", onClick: () => actions.openSettings("feedback") }, t("Open")))
|
|
9646
|
+
)
|
|
9647
|
+
];
|
|
9648
|
+
}
|
|
9649
|
+
var ABOUT_CSS = `
|
|
9650
|
+
.bt-about-hero{display:flex;align-items:center;gap:14px;padding:16px;border:1px solid var(--bt-line);border-radius:12px;background:var(--bt-card)}
|
|
9651
|
+
.bt-about-mark{flex:none;display:inline-flex;position:relative}
|
|
9652
|
+
.bt-about-mark::before{content:'';position:absolute;inset:-20%;border-radius:50%}
|
|
9653
|
+
body:not([data-bt-motion=quiet]) .bt-about-mark:hover .bt-mark[data-live][data-state=idle]:not(.bt-mark-poke) .bt-b{rotate:8deg;translate:5% -1%;animation:bt-nuzzle .78s ease-in-out .32s infinite}
|
|
9654
|
+
body:not([data-bt-motion=quiet]) .bt-about-mark:hover .bt-mark[data-live][data-state=idle] .bt-eye{scale:1 0;animation:none}
|
|
9655
|
+
body:not([data-bt-motion=quiet]) .bt-about-mark:hover .bt-mark[data-live][data-state=idle] .bt-eye-happy{opacity:1;transition-delay:.1s}
|
|
9656
|
+
.bt-about-copy{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
|
9657
|
+
.bt-about-name{font-size:16px;line-height:22px;font-weight:600}
|
|
9658
|
+
.bt-about-version{font-size:12px;line-height:16px;color:var(--bt-ink-2);font-variant-numeric:tabular-nums}
|
|
9659
|
+
.bt-about-ask{margin:-10px 4px 0;font-size:12px;line-height:16px;color:var(--bt-ink-3)}
|
|
9660
|
+
.bt-star{flex:none;display:inline-flex;align-items:stretch;height:30px;border-radius:999px;border:1px solid var(--bt-line-2,var(--bt-line));overflow:hidden;color:var(--bt-ink);text-decoration:none;font-size:12px;line-height:28px;transition:transform .12s ease,border-color .15s ease}
|
|
9661
|
+
.bt-star:hover{border-color:color-mix(in srgb,#e3a008 55%,var(--bt-line))}
|
|
9662
|
+
.bt-star:active{transform:scale(.96)}
|
|
9663
|
+
.bt-star-main{display:inline-flex;align-items:center;gap:6px;padding:0 12px 0 10px;background:var(--bt-hover);font-weight:500}
|
|
9664
|
+
.bt-star-icon{display:inline-flex;color:var(--bt-ink-2);transition:color .15s ease}
|
|
9665
|
+
.bt-star-icon path{transition:fill .15s ease}
|
|
9666
|
+
.bt-star:hover .bt-star-icon{color:#e3a008;animation:bt-star-pop ${SPRING_POP.duration}ms ${SPRING_POP.easing}}
|
|
9667
|
+
.bt-star:hover .bt-star-icon path{fill:currentColor}
|
|
9668
|
+
.bt-star-count{display:inline-flex;align-items:center;padding:0 11px;border-left:1px solid var(--bt-line-2,var(--bt-line));color:var(--bt-ink-2);font-variant-numeric:tabular-nums;animation:bt-star-count-in ${SPRING_POP.duration}ms ${SPRING_POP.easing},bt-veil-in .2s ease-out}
|
|
9669
|
+
@keyframes bt-star-pop{from{scale:.6;rotate:-30deg}}
|
|
9670
|
+
@keyframes bt-star-count-in{from{translate:-6px 0}}
|
|
9671
|
+
.bt-about-link{display:inline-flex;align-items:center;gap:6px;text-decoration:none}
|
|
9672
|
+
.bt-about-link svg{width:14px;height:14px}
|
|
9673
|
+
.bt-fb-primary:disabled{opacity:.6;cursor:default;filter:none}
|
|
9674
|
+
@media (prefers-reduced-motion:reduce){.bt-star:hover .bt-star-icon,.bt-star-count{animation:none}}
|
|
9675
|
+
`;
|
|
9676
|
+
|
|
9677
|
+
// src/client/settings.js
|
|
9678
|
+
var import_react26 = require("react");
|
|
9125
9679
|
var capitalize2 = (word) => word[0].toUpperCase() + word.slice(1);
|
|
9126
|
-
var SETTINGS_PAGES = [["general", "General"], ["bots", "Bots"], ["groups", "Group chats"], ["connectors", "Connectors"], ["schedules", "Scheduled tasks"], ["usage", "Usage"], ["secrets", "Secrets"], ["feedback", "Feedback"]];
|
|
9127
|
-
var PAGE_ICONS = { ...SETTINGS_ICONS, connectors: ConnectorIcon, schedules: ClockIcon, secrets: KeyIcon, feedback: FeedbackIcon };
|
|
9680
|
+
var SETTINGS_PAGES = [["general", "General"], ["bots", "Bots"], ["groups", "Group chats"], ["connectors", "Connectors"], ["schedules", "Scheduled tasks"], ["usage", "Usage"], ["secrets", "Secrets"], ["feedback", "Feedback"], ["about", "About DS Bot"]];
|
|
9681
|
+
var PAGE_ICONS = { ...SETTINGS_ICONS, connectors: ConnectorIcon, schedules: ClockIcon, secrets: KeyIcon, feedback: FeedbackIcon, about: InfoIcon };
|
|
9128
9682
|
function SetSection({ title, children }) {
|
|
9129
|
-
return (0,
|
|
9683
|
+
return (0, import_react26.createElement)("section", { className: "bt-set-section" }, (0, import_react26.createElement)("h3", null, title), (0, import_react26.createElement)("div", { className: "bt-set-card" }, children));
|
|
9130
9684
|
}
|
|
9131
9685
|
function SetRow({ label, hint, children }) {
|
|
9132
|
-
return (0,
|
|
9686
|
+
return (0, import_react26.createElement)(
|
|
9133
9687
|
"div",
|
|
9134
9688
|
{ className: "bt-set-row" },
|
|
9135
|
-
(0,
|
|
9136
|
-
children ? (0,
|
|
9689
|
+
(0, import_react26.createElement)("div", { className: "bt-set-copy" }, (0, import_react26.createElement)("span", null, label), hint ? (0, import_react26.createElement)("span", { className: "bt-set-hint" }, hint) : null),
|
|
9690
|
+
children ? (0, import_react26.createElement)("div", { className: "bt-set-control" }, children) : null
|
|
9137
9691
|
);
|
|
9138
9692
|
}
|
|
9139
9693
|
function GeneralPage({ roster, actions, run }) {
|
|
9140
9694
|
const prefs = roster.prefs ?? {};
|
|
9141
9695
|
const themes = allThemes();
|
|
9142
9696
|
const current = themes[prefs.theme] ? prefs.theme : "deepseek";
|
|
9143
|
-
const [custom, setCustom] = (0,
|
|
9144
|
-
const timer = (0,
|
|
9145
|
-
(0,
|
|
9697
|
+
const [custom, setCustom] = (0, import_react26.useState)(prefs.accent ?? "#4d6bfe");
|
|
9698
|
+
const timer = (0, import_react26.useRef)(0);
|
|
9699
|
+
(0, import_react26.useEffect)(() => () => clearTimeout(timer.current), []);
|
|
9146
9700
|
const pickCustom = (value) => {
|
|
9147
9701
|
setCustom(value);
|
|
9148
9702
|
clearTimeout(timer.current);
|
|
9149
9703
|
timer.current = setTimeout(() => run(actions.setPrefs({ accent: value })), 350);
|
|
9150
9704
|
};
|
|
9151
9705
|
return [
|
|
9152
|
-
(0,
|
|
9706
|
+
(0, import_react26.createElement)(
|
|
9153
9707
|
SetSection,
|
|
9154
9708
|
{ key: "look", title: t("Appearance") },
|
|
9155
|
-
(0,
|
|
9709
|
+
(0, import_react26.createElement)(
|
|
9156
9710
|
SetRow,
|
|
9157
9711
|
{ label: t("Theme"), hint: t("Colors for the whole team, on every device.") },
|
|
9158
|
-
(0,
|
|
9712
|
+
(0, import_react26.createElement)(
|
|
9159
9713
|
"select",
|
|
9160
9714
|
{ className: "bt-select", "aria-label": t("Theme"), value: current, onChange: (event) => run(actions.setPrefs({ theme: event.target.value })) },
|
|
9161
|
-
Object.entries(themes).map(([id, theme]) => (0,
|
|
9715
|
+
Object.entries(themes).map(([id, theme]) => (0, import_react26.createElement)("option", { key: id, value: id }, t(theme.label)))
|
|
9162
9716
|
)
|
|
9163
9717
|
),
|
|
9164
|
-
(0,
|
|
9718
|
+
(0, import_react26.createElement)(
|
|
9165
9719
|
SetRow,
|
|
9166
9720
|
{ label: t("Accent"), hint: t("Unread marks, badges, and the brand mark.") },
|
|
9167
|
-
(0,
|
|
9721
|
+
(0, import_react26.createElement)(
|
|
9168
9722
|
"div",
|
|
9169
9723
|
{ className: "bt-swatches" },
|
|
9170
|
-
(0,
|
|
9171
|
-
ACCENT_INKS.map((ink) => (0,
|
|
9724
|
+
(0, import_react26.createElement)("button", { type: "button", className: "bt-swatch bt-swatch-default", title: t("Theme accent"), "aria-label": t("Theme accent"), "aria-pressed": !prefs.accent, onClick: () => run(actions.setPrefs({ accent: null })) }),
|
|
9725
|
+
ACCENT_INKS.map((ink) => (0, import_react26.createElement)("button", {
|
|
9172
9726
|
key: ink,
|
|
9173
9727
|
type: "button",
|
|
9174
9728
|
className: "bt-swatch",
|
|
@@ -9178,21 +9732,21 @@ function GeneralPage({ roster, actions, run }) {
|
|
|
9178
9732
|
style: { background: INKS[ink][0] },
|
|
9179
9733
|
onClick: () => run(actions.setPrefs({ accent: INKS[ink][0] }))
|
|
9180
9734
|
})),
|
|
9181
|
-
(0,
|
|
9735
|
+
(0, import_react26.createElement)(
|
|
9182
9736
|
"label",
|
|
9183
9737
|
{ className: "bt-swatch bt-swatch-custom", title: t("Custom accent"), "data-on": Boolean(prefs.accent) && !ACCENT_INKS.some((ink) => INKS[ink][0] === prefs.accent) },
|
|
9184
|
-
(0,
|
|
9738
|
+
(0, import_react26.createElement)("input", { type: "color", value: custom, "aria-label": t("Custom accent"), onChange: (event) => pickCustom(event.target.value) })
|
|
9185
9739
|
)
|
|
9186
9740
|
)
|
|
9187
9741
|
)
|
|
9188
9742
|
),
|
|
9189
|
-
(0,
|
|
9743
|
+
(0, import_react26.createElement)(
|
|
9190
9744
|
SetSection,
|
|
9191
9745
|
{ key: "mode", title: t("Mode") },
|
|
9192
|
-
(0,
|
|
9746
|
+
(0, import_react26.createElement)(
|
|
9193
9747
|
SetRow,
|
|
9194
9748
|
{ label: t("Classic Agent mode") },
|
|
9195
|
-
(0,
|
|
9749
|
+
(0, import_react26.createElement)("button", { type: "button", className: "bt-soft bt-soft-sm", onClick: () => actions.setSurface("agent") }, t("Switch"))
|
|
9196
9750
|
)
|
|
9197
9751
|
)
|
|
9198
9752
|
];
|
|
@@ -9201,52 +9755,52 @@ function MainBotsSection({ roster, actions, run }) {
|
|
|
9201
9755
|
const mains = (roster.mainBotIds ?? []).map((id) => roster.byId[id]).filter(Boolean);
|
|
9202
9756
|
const [first, ...others] = mains;
|
|
9203
9757
|
const candidates = roster.bots.filter((bot) => !isMainOf(roster, bot.id));
|
|
9204
|
-
return (0,
|
|
9758
|
+
return (0, import_react26.createElement)(
|
|
9205
9759
|
SetSection,
|
|
9206
9760
|
{ title: t("Main Bot") },
|
|
9207
|
-
(0,
|
|
9761
|
+
(0, import_react26.createElement)(
|
|
9208
9762
|
SetRow,
|
|
9209
9763
|
{
|
|
9210
9764
|
label: t("Main Bot"),
|
|
9211
9765
|
hint: first ? t("First in the sidebar. Pick another Bot to hand it the role; {name} then stops being a Main Bot.", { name: first.name }) : void 0
|
|
9212
9766
|
},
|
|
9213
|
-
(0,
|
|
9767
|
+
(0, import_react26.createElement)("select", {
|
|
9214
9768
|
className: "bt-select",
|
|
9215
9769
|
"aria-label": t("Main Bot"),
|
|
9216
9770
|
value: first?.id ?? "",
|
|
9217
9771
|
onChange: (event) => run(actions.setMain(event.target.value, true, first ? { replace: first.id } : {}))
|
|
9218
|
-
}, first ? null : (0,
|
|
9772
|
+
}, first ? null : (0, import_react26.createElement)("option", { value: "" }, t("Choose a Bot…")), roster.bots.map((bot) => (0, import_react26.createElement)("option", { key: bot.id, value: bot.id }, bot.name)))
|
|
9219
9773
|
),
|
|
9220
|
-
others.map((bot) => (0,
|
|
9774
|
+
others.map((bot) => (0, import_react26.createElement)(
|
|
9221
9775
|
SetRow,
|
|
9222
9776
|
{ key: bot.id, label: bot.name, hint: t("Also a Main Bot.") },
|
|
9223
|
-
(0,
|
|
9224
|
-
(0,
|
|
9777
|
+
(0, import_react26.createElement)("button", { type: "button", className: "bt-soft bt-soft-sm", onClick: () => run(actions.setMain(bot.id, true, { primary: true })) }, t("Move to first")),
|
|
9778
|
+
(0, import_react26.createElement)("button", { type: "button", className: "bt-soft bt-soft-sm", onClick: () => run(actions.setMain(bot.id, false)) }, t("Remove Main Bot role"))
|
|
9225
9779
|
)),
|
|
9226
|
-
candidates.length > 0 ? (0,
|
|
9780
|
+
candidates.length > 0 ? (0, import_react26.createElement)(
|
|
9227
9781
|
SetRow,
|
|
9228
9782
|
{ label: t("Add a Main Bot"), hint: t("Main Bots can create and change Bots, and run every group chat.") },
|
|
9229
|
-
(0,
|
|
9783
|
+
(0, import_react26.createElement)("select", {
|
|
9230
9784
|
className: "bt-select",
|
|
9231
9785
|
"aria-label": t("Add a Main Bot"),
|
|
9232
9786
|
value: "",
|
|
9233
9787
|
onChange: (event) => {
|
|
9234
9788
|
if (event.target.value) void run(actions.setMain(event.target.value, true));
|
|
9235
9789
|
}
|
|
9236
|
-
}, (0,
|
|
9790
|
+
}, (0, import_react26.createElement)("option", { value: "" }, t("Choose a Bot…")), candidates.map((bot) => (0, import_react26.createElement)("option", { key: bot.id, value: bot.id }, bot.name)))
|
|
9237
9791
|
) : null
|
|
9238
9792
|
);
|
|
9239
9793
|
}
|
|
9240
9794
|
var MOTIONS = [["quiet", "Quiet"], ["normal", "Normal"], ["lively", "Lively"]];
|
|
9241
9795
|
function MotionSection({ roster, actions, run }) {
|
|
9242
9796
|
const motion = roster.prefs?.motion === "quiet" || roster.prefs?.motion === "lively" ? roster.prefs.motion : "normal";
|
|
9243
|
-
return (0,
|
|
9797
|
+
return (0, import_react26.createElement)(
|
|
9244
9798
|
SetSection,
|
|
9245
9799
|
{ title: t("Animation") },
|
|
9246
|
-
(0,
|
|
9800
|
+
(0, import_react26.createElement)(
|
|
9247
9801
|
SetRow,
|
|
9248
9802
|
{ label: t("Bot animation") },
|
|
9249
|
-
(0,
|
|
9803
|
+
(0, import_react26.createElement)(Segmented, { label: t("Bot animation"), value: motion, className: "bt-motion-seg" }, MOTIONS.map(([id, label]) => (0, import_react26.createElement)("button", {
|
|
9250
9804
|
key: id,
|
|
9251
9805
|
type: "button",
|
|
9252
9806
|
"aria-pressed": motion === id,
|
|
@@ -9258,69 +9812,69 @@ function MotionSection({ roster, actions, run }) {
|
|
|
9258
9812
|
);
|
|
9259
9813
|
}
|
|
9260
9814
|
function BotsPage({ roster, actions, run }) {
|
|
9261
|
-
const [creating, setCreating] = (0,
|
|
9815
|
+
const [creating, setCreating] = (0, import_react26.useState)(false);
|
|
9262
9816
|
return [
|
|
9263
|
-
(0,
|
|
9264
|
-
(0,
|
|
9265
|
-
creating ? (0,
|
|
9817
|
+
(0, import_react26.createElement)(MainBotsSection, { key: "main", roster, actions, run }),
|
|
9818
|
+
(0, import_react26.createElement)(MotionSection, { key: "motion", roster, actions, run }),
|
|
9819
|
+
creating ? (0, import_react26.createElement)(CreateBot, { key: "create", roster, actions, inline: true, onBack: () => setCreating(false), onCreated: (bot) => actions.openSettings("bots", bot.id) }) : (0, import_react26.createElement)(
|
|
9266
9820
|
"div",
|
|
9267
9821
|
{ key: "new", className: "bt-actions" },
|
|
9268
|
-
(0,
|
|
9822
|
+
(0, import_react26.createElement)("button", { type: "button", className: "bt-soft bt-soft-sm bt-with-icon", onClick: () => setCreating(true) }, (0, import_react26.createElement)(PlusIcon), t("New Bot"))
|
|
9269
9823
|
),
|
|
9270
|
-
(0,
|
|
9824
|
+
(0, import_react26.createElement)(
|
|
9271
9825
|
SetSection,
|
|
9272
9826
|
{ key: "bots", title: t("Bots · {count}", { count: roster.bots.length }) },
|
|
9273
|
-
roster.bots.map((bot) => (0,
|
|
9827
|
+
roster.bots.map((bot) => (0, import_react26.createElement)(
|
|
9274
9828
|
"button",
|
|
9275
9829
|
{ key: bot.id, type: "button", className: "bt-set-row", onClick: () => actions.openSettings("bots", bot.id) },
|
|
9276
|
-
(0,
|
|
9830
|
+
(0, import_react26.createElement)(
|
|
9277
9831
|
"span",
|
|
9278
9832
|
{ className: "bt-set-bot" },
|
|
9279
|
-
(0,
|
|
9280
|
-
(0,
|
|
9281
|
-
bot.role ? (0,
|
|
9282
|
-
isMainOf(roster, bot.id) ? (0,
|
|
9283
|
-
bot.hidden && !isMainOf(roster, bot.id) ? (0,
|
|
9833
|
+
(0, import_react26.createElement)(BotAvatar, { bot, size: 28, badge: false, live: false }),
|
|
9834
|
+
(0, import_react26.createElement)("span", { className: "bt-row-title" }, bot.name),
|
|
9835
|
+
bot.role ? (0, import_react26.createElement)("span", { className: "bt-tag" }, bot.role) : null,
|
|
9836
|
+
isMainOf(roster, bot.id) ? (0, import_react26.createElement)("span", { className: "bt-tag" }, t("Main Bot")) : null,
|
|
9837
|
+
bot.hidden && !isMainOf(roster, bot.id) ? (0, import_react26.createElement)("span", { className: "bt-tag" }, t("Hidden")) : null
|
|
9284
9838
|
),
|
|
9285
|
-
(0,
|
|
9839
|
+
(0, import_react26.createElement)("span", { className: "bt-chevron" }, (0, import_react26.createElement)(ChevronRightIcon))
|
|
9286
9840
|
))
|
|
9287
9841
|
)
|
|
9288
9842
|
];
|
|
9289
9843
|
}
|
|
9290
9844
|
function GroupsPage({ roster, actions, run }) {
|
|
9291
|
-
const [creating, setCreating] = (0,
|
|
9845
|
+
const [creating, setCreating] = (0, import_react26.useState)(false);
|
|
9292
9846
|
return [
|
|
9293
|
-
creating ? (0,
|
|
9847
|
+
creating ? (0, import_react26.createElement)(NewGroupForm, { key: "create", roster, actions, run, onCancel: () => setCreating(false), onCreated: (room2) => actions.openSettings("groups", room2.id) }) : (0, import_react26.createElement)(
|
|
9294
9848
|
"div",
|
|
9295
9849
|
{ key: "new", className: "bt-actions" },
|
|
9296
|
-
(0,
|
|
9850
|
+
(0, import_react26.createElement)("button", { type: "button", className: "bt-soft bt-soft-sm bt-with-icon", disabled: roster.bots.length < 2, title: roster.bots.length < 2 ? t("A group chat needs at least two Bots") : void 0, onClick: () => setCreating(true) }, (0, import_react26.createElement)(PlusIcon), t("Create group chat"))
|
|
9297
9851
|
),
|
|
9298
|
-
(0,
|
|
9852
|
+
(0, import_react26.createElement)(
|
|
9299
9853
|
SetSection,
|
|
9300
9854
|
{ key: "groups", title: t("Group chats · {count}", { count: roster.rooms.length }) },
|
|
9301
|
-
roster.rooms.length === 0 ? (0,
|
|
9855
|
+
roster.rooms.length === 0 ? (0, import_react26.createElement)(SetRow, { label: t("No group chats yet."), hint: t("A group chat lets several Bots work on one thing with you.") }) : roster.rooms.map((room2) => {
|
|
9302
9856
|
const admin = roster.byId[room2.admin];
|
|
9303
|
-
return (0,
|
|
9857
|
+
return (0, import_react26.createElement)(
|
|
9304
9858
|
"button",
|
|
9305
9859
|
{ key: room2.id, type: "button", className: "bt-set-row", onClick: () => actions.openSettings("groups", room2.id) },
|
|
9306
|
-
(0,
|
|
9860
|
+
(0, import_react26.createElement)(
|
|
9307
9861
|
"span",
|
|
9308
9862
|
{ className: "bt-set-bot" },
|
|
9309
|
-
(0,
|
|
9310
|
-
(0,
|
|
9311
|
-
(0,
|
|
9312
|
-
admin ? (0,
|
|
9313
|
-
room2.hidden ? (0,
|
|
9863
|
+
(0, import_react26.createElement)(RoomAvatar, { room: room2, roster, size: 28 }),
|
|
9864
|
+
(0, import_react26.createElement)("span", { className: "bt-row-title" }, room2.name),
|
|
9865
|
+
(0, import_react26.createElement)("span", { className: "bt-tag" }, t("{count} members", { count: room2.members.length })),
|
|
9866
|
+
admin ? (0, import_react26.createElement)("span", { className: "bt-tag bt-tag-admin" }, (0, import_react26.createElement)(AdminStar), admin.name) : null,
|
|
9867
|
+
room2.hidden ? (0, import_react26.createElement)("span", { className: "bt-tag" }, t("Hidden")) : null
|
|
9314
9868
|
),
|
|
9315
|
-
(0,
|
|
9869
|
+
(0, import_react26.createElement)("span", { className: "bt-chevron" }, (0, import_react26.createElement)(ChevronRightIcon))
|
|
9316
9870
|
);
|
|
9317
9871
|
})
|
|
9318
9872
|
)
|
|
9319
9873
|
];
|
|
9320
9874
|
}
|
|
9321
|
-
function SettingsDialog({ page, id, roster, actions, leaving = false }) {
|
|
9322
|
-
const [error, setError] = (0,
|
|
9323
|
-
const close = (0,
|
|
9875
|
+
function SettingsDialog({ page, id, roster, actions, useUpdate, leaving = false }) {
|
|
9876
|
+
const [error, setError] = (0, import_react26.useState)("");
|
|
9877
|
+
const close = (0, import_react26.useCallback)(() => actions.closeSettings(), []);
|
|
9324
9878
|
useEscape(close);
|
|
9325
9879
|
const run = (promise) => Promise.resolve(promise).then(() => {
|
|
9326
9880
|
setError("");
|
|
@@ -9338,32 +9892,32 @@ function SettingsDialog({ page, id, roster, actions, leaving = false }) {
|
|
|
9338
9892
|
const nav = useGlide(shown, { axis: "y" });
|
|
9339
9893
|
const pageKey = `${shown}:${bot?.id ?? room2?.id ?? ""}`;
|
|
9340
9894
|
const depth = bot || room2 ? 1 : 0;
|
|
9341
|
-
const last = (0,
|
|
9895
|
+
const last = (0, import_react26.useRef)({ key: pageKey, depth, move: "up" });
|
|
9342
9896
|
if (last.current.key !== pageKey) {
|
|
9343
9897
|
last.current = { key: pageKey, depth, move: depth > last.current.depth ? "in" : depth < last.current.depth ? "out" : "up" };
|
|
9344
9898
|
}
|
|
9345
|
-
const [usageOpened, setUsageOpened] = (0,
|
|
9899
|
+
const [usageOpened, setUsageOpened] = (0, import_react26.useState)(shown === "usage");
|
|
9346
9900
|
if (shown === "usage" && !usageOpened) setUsageOpened(true);
|
|
9347
|
-
const errorLine = error ? (0,
|
|
9348
|
-
return (0,
|
|
9901
|
+
const errorLine = error ? (0, import_react26.createElement)("div", { className: "bt-error", role: "alert", style: { padding: 0 } }, error) : null;
|
|
9902
|
+
return (0, import_react26.createElement)(
|
|
9349
9903
|
"div",
|
|
9350
9904
|
{ className: "bt-settings-layer", "data-leaving": leaving || void 0 },
|
|
9351
|
-
(0,
|
|
9352
|
-
(0,
|
|
9905
|
+
(0, import_react26.createElement)("div", { className: "bt-scrim", onMouseDown: close }),
|
|
9906
|
+
(0, import_react26.createElement)(
|
|
9353
9907
|
"div",
|
|
9354
9908
|
{ className: "bt-settings", role: "dialog", "aria-modal": true, "aria-label": t("DS Bot settings"), "data-wide": shown === "usage" ? "" : void 0 },
|
|
9355
|
-
(0,
|
|
9909
|
+
(0, import_react26.createElement)(
|
|
9356
9910
|
"nav",
|
|
9357
9911
|
{ ref: nav.box, className: "bt-settings-nav", "aria-label": t("Settings pages") },
|
|
9358
|
-
(0,
|
|
9359
|
-
SETTINGS_PAGES.map(([key, text]) => (0,
|
|
9912
|
+
(0, import_react26.createElement)("span", { ref: nav.thumb, className: "bt-thumb", "aria-hidden": true }),
|
|
9913
|
+
SETTINGS_PAGES.map(([key, text]) => (0, import_react26.createElement)("button", {
|
|
9360
9914
|
key,
|
|
9361
9915
|
type: "button",
|
|
9362
9916
|
className: "bt-settings-item",
|
|
9363
9917
|
"aria-current": shown === key ? "page" : void 0,
|
|
9364
9918
|
onClick: () => actions.openSettings(key)
|
|
9365
|
-
}, (0,
|
|
9366
|
-
(0,
|
|
9919
|
+
}, (0, import_react26.createElement)(PAGE_ICONS[key]), t(text))),
|
|
9920
|
+
(0, import_react26.createElement)("button", {
|
|
9367
9921
|
type: "button",
|
|
9368
9922
|
className: "bt-settings-item bt-settings-shell",
|
|
9369
9923
|
title: t("Models, providers, and the rest of DeepSeek Harness"),
|
|
@@ -9371,62 +9925,63 @@ function SettingsDialog({ page, id, roster, actions, leaving = false }) {
|
|
|
9371
9925
|
close();
|
|
9372
9926
|
openHarnessSettings();
|
|
9373
9927
|
}
|
|
9374
|
-
}, (0,
|
|
9928
|
+
}, (0, import_react26.createElement)(SlidersIcon), t("Harness settings"))
|
|
9375
9929
|
),
|
|
9376
|
-
(0,
|
|
9930
|
+
(0, import_react26.createElement)(
|
|
9377
9931
|
"div",
|
|
9378
9932
|
{ className: "bt-settings-main" },
|
|
9379
|
-
(0,
|
|
9933
|
+
(0, import_react26.createElement)(
|
|
9380
9934
|
"div",
|
|
9381
9935
|
{ className: "bt-settings-head" },
|
|
9382
|
-
back ? (0,
|
|
9383
|
-
(0,
|
|
9936
|
+
back ? (0, import_react26.createElement)("button", { type: "button", className: "bt-icon-btn", "aria-label": back, title: back, onClick: () => actions.openSettings(shown) }, (0, import_react26.createElement)(ChevronLeftIcon)) : null,
|
|
9937
|
+
(0, import_react26.createElement)("h2", { key: bot?.id ?? room2?.id ?? shown }, bot?.name ?? room2?.name ?? label)
|
|
9384
9938
|
),
|
|
9385
|
-
shown === "usage" ? null : (0,
|
|
9939
|
+
shown === "usage" ? null : (0, import_react26.createElement)(
|
|
9386
9940
|
"div",
|
|
9387
9941
|
{ key: pageKey, className: "bt-settings-scroll bt-settings-page", "data-move": last.current.move },
|
|
9388
9942
|
errorLine,
|
|
9389
|
-
shown === "general" ? (0,
|
|
9390
|
-
bot ? (0,
|
|
9391
|
-
shown === "bots" && !bot ? (0,
|
|
9392
|
-
room2 ? (0,
|
|
9393
|
-
shown === "groups" && !room2 ? (0,
|
|
9394
|
-
shown === "connectors" ? (0,
|
|
9395
|
-
shown === "schedules" ? (0,
|
|
9396
|
-
shown === "secrets" ? (0,
|
|
9397
|
-
shown === "feedback" ? (0,
|
|
9943
|
+
shown === "general" ? (0, import_react26.createElement)(GeneralPage, { roster, actions, run }) : null,
|
|
9944
|
+
bot ? (0, import_react26.createElement)("div", { className: "bt-settings-form" }, (0, import_react26.createElement)(BotSettingsForm, { key: bot.id, bot, roster, actions, run })) : null,
|
|
9945
|
+
shown === "bots" && !bot ? (0, import_react26.createElement)(BotsPage, { roster, actions, run }) : null,
|
|
9946
|
+
room2 ? (0, import_react26.createElement)("div", { className: "bt-settings-form" }, (0, import_react26.createElement)(GroupSettingsForm, { key: room2.id, room: room2, roster, actions, run })) : null,
|
|
9947
|
+
shown === "groups" && !room2 ? (0, import_react26.createElement)(GroupsPage, { roster, actions, run }) : null,
|
|
9948
|
+
shown === "connectors" ? (0, import_react26.createElement)(ConnectorsPage, { roster, actions }) : null,
|
|
9949
|
+
shown === "schedules" ? (0, import_react26.createElement)(SchedulesPage, { roster, actions, start: id ?? void 0 }) : null,
|
|
9950
|
+
shown === "secrets" ? (0, import_react26.createElement)(SecretsPage, { roster, actions, run, Section: SetSection }) : null,
|
|
9951
|
+
shown === "feedback" ? (0, import_react26.createElement)(FeedbackPage, { actions, run, Section: SetSection, Row: SetRow }) : null,
|
|
9952
|
+
shown === "about" ? (0, import_react26.createElement)(AboutPage, { useUpdate, actions, run, Section: SetSection, Row: SetRow }) : null
|
|
9398
9953
|
),
|
|
9399
|
-
usageOpened ? (0,
|
|
9954
|
+
usageOpened ? (0, import_react26.createElement)(
|
|
9400
9955
|
"div",
|
|
9401
9956
|
{ key: "usage", className: "bt-settings-scroll bt-settings-usage", "data-off": shown === "usage" ? void 0 : "", "aria-hidden": shown === "usage" ? void 0 : true },
|
|
9402
9957
|
shown === "usage" ? errorLine : null,
|
|
9403
|
-
(0,
|
|
9958
|
+
(0, import_react26.createElement)(UsagePage, { roster, actions, focusId: usageFocus })
|
|
9404
9959
|
) : null
|
|
9405
9960
|
),
|
|
9406
|
-
(0,
|
|
9961
|
+
(0, import_react26.createElement)("button", { type: "button", className: "bt-settings-close", "aria-label": t("Close settings"), onClick: close }, (0, import_react26.createElement)(CloseIcon))
|
|
9407
9962
|
)
|
|
9408
9963
|
);
|
|
9409
9964
|
}
|
|
9410
9965
|
|
|
9411
9966
|
// src/client/context-menu.js
|
|
9412
|
-
var
|
|
9967
|
+
var import_react27 = require("react");
|
|
9413
9968
|
function ContextMenu({ menu, roster, actions, leaving = false }) {
|
|
9414
9969
|
useEscape(() => actions.closeMenu());
|
|
9415
|
-
(0,
|
|
9970
|
+
(0, import_react27.useEffect)(() => {
|
|
9416
9971
|
const onDown = (event) => {
|
|
9417
9972
|
if (!(event.target instanceof Element) || !event.target.closest(".bt-menu")) actions.closeMenu();
|
|
9418
9973
|
};
|
|
9419
9974
|
window.addEventListener("mousedown", onDown);
|
|
9420
9975
|
return () => window.removeEventListener("mousedown", onDown);
|
|
9421
9976
|
}, []);
|
|
9422
|
-
const ref = (0,
|
|
9423
|
-
const [place, setPlace] = (0,
|
|
9424
|
-
(0,
|
|
9977
|
+
const ref = (0, import_react27.useRef)(null);
|
|
9978
|
+
const [place, setPlace] = (0, import_react27.useState)({ left: menu.x, top: menu.y });
|
|
9979
|
+
(0, import_react27.useLayoutEffect)(() => {
|
|
9425
9980
|
const box = ref.current?.getBoundingClientRect();
|
|
9426
9981
|
if (!box) return;
|
|
9427
9982
|
setPlace({ left: Math.max(8, Math.min(menu.x, window.innerWidth - box.width - 8)), top: Math.max(8, Math.min(menu.y, window.innerHeight - box.height - 8)) });
|
|
9428
9983
|
}, [menu.x, menu.y]);
|
|
9429
|
-
if (menu.kind === "agent" || menu.kind === "agent-session") return (0,
|
|
9984
|
+
if (menu.kind === "agent" || menu.kind === "agent-session") return (0, import_react27.createElement)(AgentMenu, { menu, roster, actions, leaving });
|
|
9430
9985
|
const bot = roster.byId[menu.id];
|
|
9431
9986
|
const room2 = roster.roomsById[menu.id];
|
|
9432
9987
|
const entry = bot ?? room2;
|
|
@@ -9457,14 +10012,14 @@ function ContextMenu({ menu, roster, actions, leaving = false }) {
|
|
|
9457
10012
|
isMainBot ? null : [room2 ? t("Delete group") : t("Delete"), TrashIcon, () => bot ? actions.deleteBot(bot.id) : actions.deleteRoom(menu.id), true]
|
|
9458
10013
|
]
|
|
9459
10014
|
].map((group) => group.filter(Boolean)).filter((group) => group.length > 0);
|
|
9460
|
-
return (0,
|
|
10015
|
+
return (0, import_react27.createElement)(
|
|
9461
10016
|
"div",
|
|
9462
10017
|
{ ref, className: "bt-menu", role: "menu", "aria-label": t("{name} actions", { name: bot?.name ?? room2.name }), style: place, "data-leaving": leaving || void 0 },
|
|
9463
|
-
groups.map((group, index) => (0,
|
|
9464
|
-
|
|
10018
|
+
groups.map((group, index) => (0, import_react27.createElement)(
|
|
10019
|
+
import_react27.Fragment,
|
|
9465
10020
|
{ key: index },
|
|
9466
|
-
index > 0 ? (0,
|
|
9467
|
-
group.map(([label, Icon, run, danger]) => (0,
|
|
10021
|
+
index > 0 ? (0, import_react27.createElement)("div", { className: "bt-menu-sep", role: "separator" }) : null,
|
|
10022
|
+
group.map(([label, Icon, run, danger]) => (0, import_react27.createElement)("button", {
|
|
9468
10023
|
key: label,
|
|
9469
10024
|
type: "button",
|
|
9470
10025
|
role: "menuitem",
|
|
@@ -9473,14 +10028,14 @@ function ContextMenu({ menu, roster, actions, leaving = false }) {
|
|
|
9473
10028
|
actions.closeMenu();
|
|
9474
10029
|
void Promise.resolve(run()).catch((error) => actions.reportError(error, "menu"));
|
|
9475
10030
|
}
|
|
9476
|
-
}, (0,
|
|
10031
|
+
}, (0, import_react27.createElement)(Icon), label))
|
|
9477
10032
|
))
|
|
9478
10033
|
);
|
|
9479
10034
|
}
|
|
9480
10035
|
function AgentMenu({ menu, roster, actions, leaving }) {
|
|
9481
|
-
const ref = (0,
|
|
9482
|
-
const [place, setPlace] = (0,
|
|
9483
|
-
(0,
|
|
10036
|
+
const ref = (0, import_react27.useRef)(null);
|
|
10037
|
+
const [place, setPlace] = (0, import_react27.useState)({ left: menu.x, top: menu.y });
|
|
10038
|
+
(0, import_react27.useLayoutEffect)(() => {
|
|
9484
10039
|
const box = ref.current?.getBoundingClientRect();
|
|
9485
10040
|
if (!box) return;
|
|
9486
10041
|
setPlace({ left: Math.max(8, Math.min(menu.x, window.innerWidth - box.width - 8)), top: Math.max(8, Math.min(menu.y, window.innerHeight - box.height - 8)) });
|
|
@@ -9502,14 +10057,14 @@ function AgentMenu({ menu, roster, actions, leaving }) {
|
|
|
9502
10057
|
[t("Archive"), TrashIcon, () => actions.archiveAgentSession(agent.id, menu.id), true]
|
|
9503
10058
|
]
|
|
9504
10059
|
];
|
|
9505
|
-
return (0,
|
|
10060
|
+
return (0, import_react27.createElement)(
|
|
9506
10061
|
"div",
|
|
9507
10062
|
{ ref, className: "bt-menu", role: "menu", "aria-label": menu.kind === "agent" ? "DSH Agent" : t("Session actions"), style: place, "data-leaving": leaving || void 0 },
|
|
9508
|
-
groups.map((group, index) => (0,
|
|
9509
|
-
|
|
10063
|
+
groups.map((group, index) => (0, import_react27.createElement)(
|
|
10064
|
+
import_react27.Fragment,
|
|
9510
10065
|
{ key: index },
|
|
9511
|
-
index > 0 ? (0,
|
|
9512
|
-
group.map(([label, Icon, run, danger]) => (0,
|
|
10066
|
+
index > 0 ? (0, import_react27.createElement)("div", { className: "bt-menu-sep", role: "separator" }) : null,
|
|
10067
|
+
group.map(([label, Icon, run, danger]) => (0, import_react27.createElement)("button", {
|
|
9513
10068
|
key: label,
|
|
9514
10069
|
type: "button",
|
|
9515
10070
|
role: "menuitem",
|
|
@@ -9518,7 +10073,7 @@ function AgentMenu({ menu, roster, actions, leaving }) {
|
|
|
9518
10073
|
actions.closeMenu();
|
|
9519
10074
|
void Promise.resolve(run()).catch((error) => console.warn("[ds-bot]", error));
|
|
9520
10075
|
}
|
|
9521
|
-
}, (0,
|
|
10076
|
+
}, (0, import_react27.createElement)(Icon), label))
|
|
9522
10077
|
))
|
|
9523
10078
|
);
|
|
9524
10079
|
}
|
|
@@ -9589,7 +10144,7 @@ function migrateStorage(storage = globalThis.localStorage) {
|
|
|
9589
10144
|
}
|
|
9590
10145
|
|
|
9591
10146
|
// src/client/details-drawer.js
|
|
9592
|
-
var
|
|
10147
|
+
var import_react28 = require("react");
|
|
9593
10148
|
function transcriptOf(list, id, name, hiddenTurns = []) {
|
|
9594
10149
|
const outline = list.projectionsBySession?.[id]?.values?.turnOutline ?? list.byId[id]?.projectionValues?.turnOutline;
|
|
9595
10150
|
if (!Array.isArray(outline)) return "";
|
|
@@ -9611,45 +10166,79 @@ var hostOf = (url) => {
|
|
|
9611
10166
|
};
|
|
9612
10167
|
var shortUrl = (url) => String(url ?? "").replace(/^https?:\/\//i, "").replace(/^www\./i, "").replace(/\/$/, "");
|
|
9613
10168
|
var EMPTY_TABS = { ids: [], active: null };
|
|
10169
|
+
var PERMISSION_LABELS = { "read-only": "Read only", "workspace-write": "Workspace", "danger-full-access": "Full access" };
|
|
10170
|
+
var DANGER = /* @__PURE__ */ new Set(["danger-full-access"]);
|
|
10171
|
+
function PermissionRow({ bot, roster, actions, run }) {
|
|
10172
|
+
const [pending, setPending] = (0, import_react28.useState)(null);
|
|
10173
|
+
const catalog = roster.permissions;
|
|
10174
|
+
const saved = bot.permission ?? catalog?.defaultPreset;
|
|
10175
|
+
(0, import_react28.useEffect)(() => {
|
|
10176
|
+
if (pending === saved) setPending(null);
|
|
10177
|
+
}, [pending, saved]);
|
|
10178
|
+
if (!catalog?.options?.length) return null;
|
|
10179
|
+
const current = pending ?? saved;
|
|
10180
|
+
const pick2 = (value) => {
|
|
10181
|
+
if (value === current && bot.permission !== void 0) return;
|
|
10182
|
+
setPending(value);
|
|
10183
|
+
void run(actions.updateBot(bot.id, { permission: value })).catch(() => setPending(null));
|
|
10184
|
+
};
|
|
10185
|
+
return (0, import_react28.createElement)(
|
|
10186
|
+
"div",
|
|
10187
|
+
{ className: "bt-drawer-permission" },
|
|
10188
|
+
(0, import_react28.createElement)("div", { className: "bt-section-title" }, t("Permissions")),
|
|
10189
|
+
(0, import_react28.createElement)(
|
|
10190
|
+
Segmented,
|
|
10191
|
+
{ label: t("Permissions"), value: current },
|
|
10192
|
+
catalog.options.map((option) => (0, import_react28.createElement)("button", {
|
|
10193
|
+
key: option.value,
|
|
10194
|
+
type: "button",
|
|
10195
|
+
"aria-pressed": option.value === current,
|
|
10196
|
+
title: option.description,
|
|
10197
|
+
onClick: () => pick2(option.value),
|
|
10198
|
+
className: DANGER.has(option.value) ? "bt-perm-danger" : void 0
|
|
10199
|
+
}, PERMISSION_LABELS[option.value] ? t(PERMISSION_LABELS[option.value]) : option.name))
|
|
10200
|
+
)
|
|
10201
|
+
);
|
|
10202
|
+
}
|
|
9614
10203
|
function BrowserCards({ bot, sessionId, roster, actions, useBrowserTabs, useBrowserPages }) {
|
|
9615
10204
|
const state = useBrowserTabs((map) => map[bot.id] ?? EMPTY_TABS);
|
|
9616
10205
|
const pages = useBrowserPages((map) => map);
|
|
9617
|
-
(0,
|
|
10206
|
+
(0, import_react28.useEffect)(() => {
|
|
9618
10207
|
actions.browserEnsure?.(bot.id);
|
|
9619
10208
|
}, [bot.id]);
|
|
9620
10209
|
if (roster.browser !== true || !actions.browserShow) {
|
|
9621
|
-
return (0,
|
|
10210
|
+
return (0, import_react28.createElement)("div", { className: "bt-muted" }, t("The browser is in the DSH Desktop app."));
|
|
9622
10211
|
}
|
|
9623
10212
|
if (state.ids.length === 0) {
|
|
9624
|
-
return (0,
|
|
10213
|
+
return (0, import_react28.createElement)(
|
|
9625
10214
|
"div",
|
|
9626
10215
|
{ className: "bt-browser-empty-mark" },
|
|
9627
|
-
(0,
|
|
9628
|
-
(0,
|
|
9629
|
-
(0,
|
|
9630
|
-
(0,
|
|
10216
|
+
(0, import_react28.createElement)(BotMark, { bot, size: 40, state: "idle", live: true }),
|
|
10217
|
+
(0, import_react28.createElement)("div", { className: "bt-browser-empty-title" }, t("{name}'s browser", { name: bot.name })),
|
|
10218
|
+
(0, import_react28.createElement)("div", { className: "bt-muted" }, t("Open a page here and {name} can read it with you. Logins and cookies stay with {name}; other Bots never see them.", { name: bot.name })),
|
|
10219
|
+
(0, import_react28.createElement)("button", { type: "button", className: "bt-browser-retry", onClick: () => actions.openBrowser(sessionId) }, t("Open browser"))
|
|
9631
10220
|
);
|
|
9632
10221
|
}
|
|
9633
|
-
return (0,
|
|
10222
|
+
return (0, import_react28.createElement)(
|
|
9634
10223
|
"div",
|
|
9635
10224
|
{ className: "bt-bcards" },
|
|
9636
10225
|
state.ids.map((tabId, index) => {
|
|
9637
10226
|
const page = pages[tabId] ?? {};
|
|
9638
10227
|
const label = page.title || shortUrl(page.url) || t("New tab");
|
|
9639
|
-
return (0,
|
|
10228
|
+
return (0, import_react28.createElement)(
|
|
9640
10229
|
"div",
|
|
9641
10230
|
{ key: tabId, className: "bt-bcard", "data-active": state.active === tabId || void 0, style: { animationDelay: `${index * 30}ms` } },
|
|
9642
|
-
(0,
|
|
10231
|
+
(0, import_react28.createElement)(
|
|
9643
10232
|
"button",
|
|
9644
10233
|
{ type: "button", className: "bt-bcard-shot", "aria-label": t("Open {title}", { title: label }), title: page.url, onClick: () => actions.browserPick(sessionId, tabId) },
|
|
9645
|
-
page.excerpt ? (0,
|
|
10234
|
+
page.excerpt ? (0, import_react28.createElement)(
|
|
9646
10235
|
"span",
|
|
9647
10236
|
{ className: "bt-bcard-preview" },
|
|
9648
|
-
(0,
|
|
9649
|
-
(0,
|
|
9650
|
-
) : (0,
|
|
10237
|
+
(0, import_react28.createElement)("span", { className: "bt-bcard-ptitle" }, label),
|
|
10238
|
+
(0, import_react28.createElement)("span", { className: "bt-bcard-excerpt" }, page.excerpt)
|
|
10239
|
+
) : (0, import_react28.createElement)("span", { className: "bt-bcard-blank" }, (0, import_react28.createElement)(GlobeIcon), (0, import_react28.createElement)("span", null, label))
|
|
9651
10240
|
),
|
|
9652
|
-
(0,
|
|
10241
|
+
(0, import_react28.createElement)("button", {
|
|
9653
10242
|
type: "button",
|
|
9654
10243
|
className: "bt-bcard-x",
|
|
9655
10244
|
"aria-label": t("Close tab"),
|
|
@@ -9658,62 +10247,62 @@ function BrowserCards({ bot, sessionId, roster, actions, useBrowserTabs, useBrow
|
|
|
9658
10247
|
void actions.browserCloseTab(bot.id, tabId);
|
|
9659
10248
|
}
|
|
9660
10249
|
}, "×"),
|
|
9661
|
-
(0,
|
|
10250
|
+
(0, import_react28.createElement)(
|
|
9662
10251
|
"div",
|
|
9663
10252
|
{ className: "bt-bcard-row" },
|
|
9664
|
-
(0,
|
|
9665
|
-
(0,
|
|
9666
|
-
page.url ? (0,
|
|
10253
|
+
(0, import_react28.createElement)(Favicon, { src: page.favicon, className: "bt-bcard-fav" }),
|
|
10254
|
+
(0, import_react28.createElement)("span", { className: "bt-bcard-title" }, label),
|
|
10255
|
+
page.url ? (0, import_react28.createElement)("span", { className: "bt-bcard-host" }, hostOf(page.url)) : null
|
|
9667
10256
|
)
|
|
9668
10257
|
);
|
|
9669
10258
|
}),
|
|
9670
|
-
(0,
|
|
10259
|
+
(0, import_react28.createElement)("button", { type: "button", className: "bt-bcard-new", style: { animationDelay: `${state.ids.length * 30}ms` }, onClick: () => actions.browserNewTab(sessionId) }, (0, import_react28.createElement)(PlusIcon), t("New tab"))
|
|
9671
10260
|
);
|
|
9672
10261
|
}
|
|
9673
10262
|
function About({ text, title = "About" }) {
|
|
9674
|
-
const [open, setOpen] = (0,
|
|
10263
|
+
const [open, setOpen] = (0, import_react28.useState)(false);
|
|
9675
10264
|
const long = text.split("\n").length > 5 || text.length > 240;
|
|
9676
|
-
const body = (0,
|
|
10265
|
+
const body = (0, import_react28.useRef)(null);
|
|
9677
10266
|
useMorph(body, open);
|
|
9678
|
-
return (0,
|
|
10267
|
+
return (0, import_react28.createElement)(
|
|
9679
10268
|
"div",
|
|
9680
10269
|
null,
|
|
9681
|
-
(0,
|
|
9682
|
-
(0,
|
|
9683
|
-
long ? (0,
|
|
10270
|
+
(0, import_react28.createElement)("div", { className: "bt-section-title" }, t(title)),
|
|
10271
|
+
(0, import_react28.createElement)("div", { ref: body, className: "bt-about", "data-open": open || void 0 }, text),
|
|
10272
|
+
long ? (0, import_react28.createElement)("button", { type: "button", className: "bt-more", onClick: () => setOpen((value) => !value) }, open ? t("Show less") : t("Show more")) : null
|
|
9684
10273
|
);
|
|
9685
10274
|
}
|
|
9686
10275
|
function GroupOverview({ room: room2, roster, actions, usage, openSettings }) {
|
|
9687
10276
|
const members = room2.members.map((id) => roster.byId[id]).filter(Boolean);
|
|
9688
10277
|
return [
|
|
9689
|
-
room2.notice ? (0,
|
|
9690
|
-
(0,
|
|
10278
|
+
room2.notice ? (0, import_react28.createElement)(About, { key: "notice", title: "Notice", text: room2.notice }) : (0, import_react28.createElement)("button", { key: "notice", type: "button", className: "bt-more", onClick: openSettings }, t("Add a notice in group settings")),
|
|
10279
|
+
(0, import_react28.createElement)(
|
|
9691
10280
|
"div",
|
|
9692
10281
|
{ key: "members" },
|
|
9693
|
-
(0,
|
|
9694
|
-
(0,
|
|
10282
|
+
(0, import_react28.createElement)("div", { className: "bt-section-title" }, t("Members · {count}", { count: members.length })),
|
|
10283
|
+
(0, import_react28.createElement)("div", { className: "bt-members", role: "list" }, members.map((member) => (0, import_react28.createElement)(
|
|
9695
10284
|
"div",
|
|
9696
10285
|
{ key: member.id, className: "bt-member", role: "listitem" },
|
|
9697
|
-
(0,
|
|
10286
|
+
(0, import_react28.createElement)(
|
|
9698
10287
|
"button",
|
|
9699
10288
|
{ type: "button", className: "bt-member-name", title: t("Open {name}'s chat", { name: member.name }), onClick: () => actions.openSession(member.id) },
|
|
9700
|
-
(0,
|
|
9701
|
-
(0,
|
|
10289
|
+
(0, import_react28.createElement)(BotAvatar, { bot: member, size: 26, main: isMainOf(roster, member.id), admin: room2.admin === member.id, live: false }),
|
|
10290
|
+
(0, import_react28.createElement)("span", null, member.name)
|
|
9702
10291
|
),
|
|
9703
|
-
room2.admin === member.id ? (0,
|
|
10292
|
+
room2.admin === member.id ? (0, import_react28.createElement)("span", { className: "bt-chip bt-chip-admin" }, t("Admin")) : null
|
|
9704
10293
|
)))
|
|
9705
10294
|
),
|
|
9706
|
-
(0,
|
|
10295
|
+
(0, import_react28.createElement)(
|
|
9707
10296
|
"div",
|
|
9708
10297
|
{ key: "mode" },
|
|
9709
|
-
(0,
|
|
9710
|
-
(0,
|
|
10298
|
+
(0, import_react28.createElement)("div", { className: "bt-section-title" }, t("Who answers")),
|
|
10299
|
+
(0, import_react28.createElement)("div", { className: "bt-kv" }, (0, import_react28.createElement)("span", null, t(ROOM_MODES.find(([id]) => id === shownMode(room2, roster))?.[1] ?? "Everyone")))
|
|
9711
10300
|
),
|
|
9712
|
-
usage ? (0,
|
|
10301
|
+
usage ? (0, import_react28.createElement)(
|
|
9713
10302
|
"div",
|
|
9714
10303
|
{ key: "usage" },
|
|
9715
|
-
(0,
|
|
9716
|
-
(0,
|
|
10304
|
+
(0, import_react28.createElement)("div", { className: "bt-section-title" }, t("Usage")),
|
|
10305
|
+
(0, import_react28.createElement)("div", { className: "bt-kv" }, (0, import_react28.createElement)("span", null, room2.name), (0, import_react28.createElement)("span", null, usage))
|
|
9717
10306
|
) : null
|
|
9718
10307
|
];
|
|
9719
10308
|
}
|
|
@@ -9721,15 +10310,15 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a
|
|
|
9721
10310
|
const owner = roster.byId[sessionId];
|
|
9722
10311
|
const part = owner ? currentPart(owner) : sessionId;
|
|
9723
10312
|
const isMainBot = isMainOf(roster, owner?.id);
|
|
9724
|
-
const [tab, setTab] = (0,
|
|
10313
|
+
const [tab, setTab] = (0, import_react28.useState)("details");
|
|
9725
10314
|
const room2 = roster.roomsById[sessionId];
|
|
9726
|
-
const [view, setView] = (0,
|
|
9727
|
-
const [editing, setEditing] = (0,
|
|
9728
|
-
const [draftName, setDraftName] = (0,
|
|
9729
|
-
const [copied, setCopied] = (0,
|
|
9730
|
-
const [error, setError] = (0,
|
|
10315
|
+
const [view, setView] = (0, import_react28.useState)(startTab === "settings" && (owner || room2) ? "settings" : "overview");
|
|
10316
|
+
const [editing, setEditing] = (0, import_react28.useState)(Boolean(startRenaming && (roster.byId[sessionId] || roster.roomsById[sessionId])));
|
|
10317
|
+
const [draftName, setDraftName] = (0, import_react28.useState)(roster.byId[sessionId]?.name ?? (roster.roomsById[sessionId]?.named ? roster.roomsById[sessionId].name : ""));
|
|
10318
|
+
const [copied, setCopied] = (0, import_react28.useState)(false);
|
|
10319
|
+
const [error, setError] = (0, import_react28.useState)("");
|
|
9731
10320
|
const glide = useGlide(tab);
|
|
9732
|
-
const last = (0,
|
|
10321
|
+
const last = (0, import_react28.useRef)({ view, tab, move: void 0 });
|
|
9733
10322
|
if (last.current.view !== view) last.current = { view, tab, move: view === "settings" ? "in" : "out" };
|
|
9734
10323
|
else if (last.current.tab !== tab) last.current = { view, tab, move: "tab" };
|
|
9735
10324
|
const move = last.current.move;
|
|
@@ -9754,36 +10343,36 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a
|
|
|
9754
10343
|
setTimeout(() => setCopied(false), 1600);
|
|
9755
10344
|
});
|
|
9756
10345
|
};
|
|
9757
|
-
const errorLine = error ? (0,
|
|
10346
|
+
const errorLine = error ? (0, import_react28.createElement)("div", { className: "bt-error", role: "alert", style: { padding: 0 } }, error) : null;
|
|
9758
10347
|
if ((bot || room2) && view === "settings") {
|
|
9759
10348
|
return [
|
|
9760
|
-
(0,
|
|
10349
|
+
(0, import_react28.createElement)(
|
|
9761
10350
|
"div",
|
|
9762
10351
|
{ key: "head", className: "bt-subhead", "data-move": move },
|
|
9763
|
-
(0,
|
|
9764
|
-
(0,
|
|
9765
|
-
hasStrip ? null : (0,
|
|
10352
|
+
(0, import_react28.createElement)("button", { type: "button", className: "bt-icon-btn", "aria-label": t("Back to details"), onClick: () => setView("overview") }, (0, import_react28.createElement)(ChevronLeftIcon)),
|
|
10353
|
+
(0, import_react28.createElement)("span", { className: "bt-subhead-title" }, bot ? t("Settings") : t("Group settings")),
|
|
10354
|
+
hasStrip ? null : (0, import_react28.createElement)("button", { type: "button", className: "bt-icon-btn", "aria-label": t("Close details"), onClick: close }, (0, import_react28.createElement)(CloseIcon))
|
|
9766
10355
|
),
|
|
9767
|
-
(0,
|
|
10356
|
+
(0, import_react28.createElement)(
|
|
9768
10357
|
"div",
|
|
9769
10358
|
{ key: "set-body", className: "bt-drawer-body bt-set-body", "data-move": move },
|
|
9770
10359
|
errorLine,
|
|
9771
|
-
bot ? (0,
|
|
10360
|
+
bot ? (0, import_react28.createElement)(BotSettingsForm, { bot, roster, actions, run }) : (0, import_react28.createElement)(GroupSettingsForm, { room: room2, roster, actions, run, onDeleted: close })
|
|
9772
10361
|
)
|
|
9773
10362
|
];
|
|
9774
10363
|
}
|
|
9775
|
-
const top = (0,
|
|
10364
|
+
const top = (0, import_react28.createElement)(
|
|
9776
10365
|
"div",
|
|
9777
10366
|
{ className: "bt-drawer-top", "data-move": side },
|
|
9778
|
-
bot || room2 ? (0,
|
|
9779
|
-
bot && roster.browser === true && actions.openBrowser ? (0,
|
|
9780
|
-
bot || room2 ? (0,
|
|
9781
|
-
hasStrip ? null : (0,
|
|
10367
|
+
bot || room2 ? (0, import_react28.createElement)("button", { type: "button", className: "bt-round", "aria-label": bot ? t("Edit Bot") : t("Group settings"), title: bot ? t("Edit Bot") : t("Group settings"), "data-info-row": "settings", onClick: () => setView("settings") }, (0, import_react28.createElement)(GearIcon)) : null,
|
|
10368
|
+
bot && roster.browser === true && actions.openBrowser ? (0, import_react28.createElement)("button", { type: "button", className: "bt-round", "aria-label": t("Open browser"), title: t("Open this Bot's browser"), onClick: () => actions.openBrowser(sessionId) }, (0, import_react28.createElement)(GlobeIcon)) : null,
|
|
10369
|
+
bot || room2 ? (0, import_react28.createElement)("button", { type: "button", className: "bt-round", "aria-label": copied ? t("Copied conversation") : t("Share conversation"), title: copied ? t("Copied") : t("Copy conversation as Markdown"), onClick: share }, copied ? (0, import_react28.createElement)("span", { key: "copied", className: "bt-pop-in" }, "✓") : (0, import_react28.createElement)(ShareIcon)) : null,
|
|
10370
|
+
hasStrip ? null : (0, import_react28.createElement)("button", { type: "button", className: "bt-round", "aria-label": t("Close details"), onClick: close }, (0, import_react28.createElement)(CloseIcon))
|
|
9782
10371
|
);
|
|
9783
10372
|
if (!bot && !room2) {
|
|
9784
10373
|
return [
|
|
9785
|
-
(0,
|
|
9786
|
-
(0,
|
|
10374
|
+
(0, import_react28.createElement)(import_react28.Fragment, { key: "top" }, top),
|
|
10375
|
+
(0, import_react28.createElement)("div", { key: "body", className: "bt-drawer-body" }, (0, import_react28.createElement)("div", { className: "bt-muted" }, t("This conversation is not part of your Bot team.")))
|
|
9787
10376
|
];
|
|
9788
10377
|
}
|
|
9789
10378
|
const current = bot?.name ?? room2.name;
|
|
@@ -9797,12 +10386,12 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a
|
|
|
9797
10386
|
const subtitle = bot ? bot.role : roomSubtitle(room2, roster);
|
|
9798
10387
|
const tabs = bot ? ["details", "browser"] : ["details"];
|
|
9799
10388
|
return [
|
|
9800
|
-
(0,
|
|
9801
|
-
(0,
|
|
10389
|
+
(0, import_react28.createElement)(import_react28.Fragment, { key: "top" }, top),
|
|
10390
|
+
(0, import_react28.createElement)(
|
|
9802
10391
|
"div",
|
|
9803
10392
|
{ key: "id", className: "bt-drawer-id", "data-move": side },
|
|
9804
|
-
bot ? (0,
|
|
9805
|
-
editing ? (0,
|
|
10393
|
+
bot ? (0, import_react28.createElement)(BotAvatar, { bot, size: 64, state: botState(running, doing, roster.questions?.[part] !== void 0), main: isMainBot, badge: false, gaze: true, pokeable: true }) : (0, import_react28.createElement)(RoomAvatar, { room: room2, roster, size: 64 }),
|
|
10394
|
+
editing ? (0, import_react28.createElement)("input", {
|
|
9806
10395
|
className: "bt-drawer-name-input",
|
|
9807
10396
|
autoFocus: true,
|
|
9808
10397
|
value: draftName,
|
|
@@ -9817,48 +10406,49 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a
|
|
|
9817
10406
|
setEditing(false);
|
|
9818
10407
|
}
|
|
9819
10408
|
}
|
|
9820
|
-
}) : (0,
|
|
10409
|
+
}) : (0, import_react28.createElement)("button", { type: "button", className: "bt-drawer-name", "aria-label": t("Rename {name}", { name: current }), title: t("Rename"), onClick: () => {
|
|
9821
10410
|
setDraftName(room2 && !room2.named ? "" : current);
|
|
9822
10411
|
setEditing(true);
|
|
9823
10412
|
} }, current),
|
|
9824
|
-
subtitle ? (0,
|
|
10413
|
+
subtitle ? (0, import_react28.createElement)("span", { className: "bt-drawer-role" }, subtitle) : null
|
|
9825
10414
|
),
|
|
9826
|
-
(0,
|
|
10415
|
+
(0, import_react28.createElement)(
|
|
9827
10416
|
"div",
|
|
9828
10417
|
{ key: "tabs", ref: glide.box, className: "bt-tabs", role: "tablist", "data-move": side },
|
|
9829
|
-
(0,
|
|
9830
|
-
tabs.map((key) => (0,
|
|
10418
|
+
(0, import_react28.createElement)("span", { ref: glide.thumb, className: "bt-thumb", "aria-hidden": true }),
|
|
10419
|
+
tabs.map((key) => (0, import_react28.createElement)("button", { key, type: "button", role: "tab", className: "bt-tab", "aria-selected": tab === key, onClick: () => setTab(key) }, t(TAB_LABELS[key])))
|
|
9831
10420
|
),
|
|
9832
|
-
(0,
|
|
10421
|
+
(0, import_react28.createElement)(
|
|
9833
10422
|
"div",
|
|
9834
10423
|
{ key: `body:${tab}`, className: "bt-drawer-body", "data-move": move },
|
|
9835
10424
|
errorLine,
|
|
9836
|
-
room2 && tab === "details" ? (0,
|
|
10425
|
+
room2 && tab === "details" ? (0, import_react28.createElement)(GroupOverview, { room: room2, roster, actions, usage, openSettings: () => setView("settings") }) : null,
|
|
9837
10426
|
bot && tab === "details" ? [
|
|
9838
|
-
(0,
|
|
10427
|
+
(0, import_react28.createElement)(
|
|
9839
10428
|
"div",
|
|
9840
10429
|
{ key: "model", className: "bt-drawer-model" },
|
|
9841
|
-
(0,
|
|
9842
|
-
(0,
|
|
9843
|
-
modelOf(bot, roster) ? null : (0,
|
|
10430
|
+
(0, import_react28.createElement)("div", { className: "bt-section-title" }, t("Model")),
|
|
10431
|
+
(0, import_react28.createElement)(ModelMenu, { bot, roster, actions, run, wide: true }),
|
|
10432
|
+
modelOf(bot, roster) ? null : (0, import_react28.createElement)("div", { className: "bt-note" }, t("Pick a model and {name} can start.", { name: bot.name }))
|
|
9844
10433
|
),
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
10434
|
+
(0, import_react28.createElement)(PermissionRow, { key: "permission", bot, roster, actions, run }),
|
|
10435
|
+
bot.instructions ? (0, import_react28.createElement)(About, { key: "about", text: bot.instructions }) : (0, import_react28.createElement)("button", { key: "about", type: "button", className: "bt-more", onClick: () => setView("settings") }, t("Add instructions in Bot settings")),
|
|
10436
|
+
roster.memory === false ? null : (0, import_react28.createElement)(MemoryRow, { key: "memory", bot, actions }),
|
|
10437
|
+
usage ? (0, import_react28.createElement)(
|
|
9848
10438
|
"div",
|
|
9849
10439
|
{ key: "usage" },
|
|
9850
|
-
(0,
|
|
9851
|
-
(0,
|
|
10440
|
+
(0, import_react28.createElement)("div", { className: "bt-section-title" }, t("Usage")),
|
|
10441
|
+
(0, import_react28.createElement)("div", { className: "bt-kv" }, (0, import_react28.createElement)("span", null, bot.name), (0, import_react28.createElement)("span", null, usage))
|
|
9852
10442
|
) : null,
|
|
9853
|
-
(0,
|
|
10443
|
+
(0, import_react28.createElement)(ScheduleSummary, { key: "routines", bot, actions })
|
|
9854
10444
|
] : null,
|
|
9855
|
-
bot && tab === "browser" ? (0,
|
|
10445
|
+
bot && tab === "browser" ? (0, import_react28.createElement)(BrowserCards, { bot, sessionId, roster, actions, useBrowserTabs, useBrowserPages }) : null
|
|
9856
10446
|
)
|
|
9857
10447
|
];
|
|
9858
10448
|
}
|
|
9859
10449
|
|
|
9860
10450
|
// src/client/browser-pane.js
|
|
9861
|
-
var
|
|
10451
|
+
var import_react29 = require("react");
|
|
9862
10452
|
var import_react_dom7 = require("react-dom");
|
|
9863
10453
|
var PANEL_LINGER_MS = 300;
|
|
9864
10454
|
var WIDTH_KEY = STORAGE.browserWidth;
|
|
@@ -9873,10 +10463,10 @@ var EMPTY_TABS2 = { ids: [], active: null };
|
|
|
9873
10463
|
var SPRING_LEAD2 = springEasing(576, 39.4);
|
|
9874
10464
|
var SPRING_TRAIL2 = springEasing(144, 19.7);
|
|
9875
10465
|
var icon = (paths) => function BrowserIcon() {
|
|
9876
|
-
return (0,
|
|
10466
|
+
return (0, import_react29.createElement)(
|
|
9877
10467
|
"svg",
|
|
9878
10468
|
{ width: 16, height: 16, viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true },
|
|
9879
|
-
paths.map((d, index) => (0,
|
|
10469
|
+
paths.map((d, index) => (0, import_react29.createElement)("path", { key: index, d }))
|
|
9880
10470
|
);
|
|
9881
10471
|
};
|
|
9882
10472
|
var GlobeIcon = icon(["M10 3a7 7 0 1 0 0 14 7 7 0 0 0 0-14z", "M3 10h14", "M10 3c2 2.2 2.9 4.5 2.9 7S12 14.8 10 17c-2-2.2-2.9-4.5-2.9-7S8 5.2 10 3z"]);
|
|
@@ -9888,9 +10478,9 @@ var CloseIcon2 = icon(["M5.5 5.5l9 9M14.5 5.5l-9 9"]);
|
|
|
9888
10478
|
var PlusTabIcon = icon(["M10 5v10M5 10h10"]);
|
|
9889
10479
|
var LockIcon2 = icon(["M6.5 9V7a3.5 3.5 0 0 1 7 0v2", "M5.5 9h9v7h-9z"]);
|
|
9890
10480
|
function Favicon({ src, className }) {
|
|
9891
|
-
const [failed, setFailed] = (0,
|
|
9892
|
-
if (!src || failed === src) return (0,
|
|
9893
|
-
return (0,
|
|
10481
|
+
const [failed, setFailed] = (0, import_react29.useState)(null);
|
|
10482
|
+
if (!src || failed === src) return (0, import_react29.createElement)("span", { className: `${className} bt-ptab-globe` }, (0, import_react29.createElement)(GlobeIcon));
|
|
10483
|
+
return (0, import_react29.createElement)("img", { className, src, alt: "", "aria-hidden": true, onError: () => setFailed(src) });
|
|
9894
10484
|
}
|
|
9895
10485
|
var savedWidth = () => {
|
|
9896
10486
|
try {
|
|
@@ -9949,11 +10539,11 @@ var reducedMotion2 = () => typeof matchMedia === "function" && matchMedia("(pref
|
|
|
9949
10539
|
var tabLabel = (page) => page.title || shortAddress(page.url) || t("New tab");
|
|
9950
10540
|
function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions, useBrowserPages }) {
|
|
9951
10541
|
const pages = useBrowserPages((map) => map);
|
|
9952
|
-
const wrap = (0,
|
|
9953
|
-
const ind = (0,
|
|
9954
|
-
const spot = (0,
|
|
9955
|
-
const under = (0,
|
|
9956
|
-
(0,
|
|
10542
|
+
const wrap = (0, import_react29.useRef)(null);
|
|
10543
|
+
const ind = (0, import_react29.useRef)(null);
|
|
10544
|
+
const spot = (0, import_react29.useRef)(null);
|
|
10545
|
+
const under = (0, import_react29.useRef)(null);
|
|
10546
|
+
(0, import_react29.useLayoutEffect)(() => {
|
|
9957
10547
|
const box = wrap.current;
|
|
9958
10548
|
const capsule = ind.current;
|
|
9959
10549
|
if (!box || !capsule) return void 0;
|
|
@@ -9982,7 +10572,7 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions,
|
|
|
9982
10572
|
trail.cancel();
|
|
9983
10573
|
};
|
|
9984
10574
|
});
|
|
9985
|
-
(0,
|
|
10575
|
+
(0, import_react29.useEffect)(() => {
|
|
9986
10576
|
const box = wrap.current;
|
|
9987
10577
|
if (!box) return void 0;
|
|
9988
10578
|
const observer = new ResizeObserver(() => {
|
|
@@ -9996,13 +10586,13 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions,
|
|
|
9996
10586
|
observer.observe(box);
|
|
9997
10587
|
return () => observer.disconnect();
|
|
9998
10588
|
}, []);
|
|
9999
|
-
return (0,
|
|
10589
|
+
return (0, import_react29.createElement)(
|
|
10000
10590
|
"div",
|
|
10001
10591
|
{ className: "bt-ptabs", role: "tablist", "aria-label": t("Panel tabs") },
|
|
10002
|
-
(0,
|
|
10592
|
+
(0, import_react29.createElement)(
|
|
10003
10593
|
"div",
|
|
10004
10594
|
{ className: "bt-ptabs-in", ref: wrap },
|
|
10005
|
-
(0,
|
|
10595
|
+
(0, import_react29.createElement)(
|
|
10006
10596
|
"button",
|
|
10007
10597
|
{
|
|
10008
10598
|
type: "button",
|
|
@@ -10012,13 +10602,13 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions,
|
|
|
10012
10602
|
title: bot.name,
|
|
10013
10603
|
onClick: () => onView("details")
|
|
10014
10604
|
},
|
|
10015
|
-
(0,
|
|
10016
|
-
view === "details" ? (0,
|
|
10605
|
+
(0, import_react29.createElement)(BotMark, { bot, size: 20, state: reading ? "searching" : "idle" }),
|
|
10606
|
+
view === "details" ? (0, import_react29.createElement)("span", { className: "bt-ptab-name" }, bot.name) : null
|
|
10017
10607
|
),
|
|
10018
10608
|
ids.map((tabId) => {
|
|
10019
10609
|
const page = pages[tabId] ?? EMPTY_PAGE;
|
|
10020
10610
|
const on = view === "browser" && tabId === active;
|
|
10021
|
-
return (0,
|
|
10611
|
+
return (0, import_react29.createElement)(
|
|
10022
10612
|
"button",
|
|
10023
10613
|
{
|
|
10024
10614
|
key: tabId,
|
|
@@ -10039,9 +10629,9 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions,
|
|
|
10039
10629
|
}
|
|
10040
10630
|
}
|
|
10041
10631
|
},
|
|
10042
|
-
(0,
|
|
10043
|
-
(0,
|
|
10044
|
-
(0,
|
|
10632
|
+
(0, import_react29.createElement)(Favicon, { src: page.favicon, className: "bt-ptab-fav" }),
|
|
10633
|
+
(0, import_react29.createElement)("span", { className: "bt-ptab-title" }, tabLabel(page)),
|
|
10634
|
+
(0, import_react29.createElement)("span", {
|
|
10045
10635
|
className: "bt-ptab-x",
|
|
10046
10636
|
role: "button",
|
|
10047
10637
|
"aria-label": t("Close tab"),
|
|
@@ -10053,20 +10643,20 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions,
|
|
|
10053
10643
|
}, "×")
|
|
10054
10644
|
);
|
|
10055
10645
|
}),
|
|
10056
|
-
(0,
|
|
10646
|
+
(0, import_react29.createElement)("span", { ref: ind, className: "bt-ptab-ind", "aria-hidden": true })
|
|
10057
10647
|
),
|
|
10058
10648
|
// New tab and Close stay outside the scrolling tabs, so a narrow panel keeps both.
|
|
10059
|
-
(0,
|
|
10060
|
-
(0,
|
|
10649
|
+
(0, import_react29.createElement)("button", { type: "button", className: "bt-ptab-new", "aria-label": t("New tab"), title: t("New tab"), onClick: () => actions.browserNewTab(subject) }, (0, import_react29.createElement)(PlusTabIcon)),
|
|
10650
|
+
(0, import_react29.createElement)("button", { type: "button", className: "bt-icon-btn bt-ptab-close", "aria-label": t("Close panel"), title: t("Close panel"), onClick: () => actions.closePanel() }, (0, import_react29.createElement)(CloseIcon2))
|
|
10061
10651
|
);
|
|
10062
10652
|
}
|
|
10063
10653
|
function BrowserView({ bot, tabId, leaving, landed, actions, useBrowserPages }) {
|
|
10064
10654
|
const page = useBrowserPages((pages) => pages[tabId ?? ""]) ?? EMPTY_PAGE;
|
|
10065
|
-
const frame = (0,
|
|
10066
|
-
const input = (0,
|
|
10067
|
-
const [draft, setDraft] = (0,
|
|
10655
|
+
const frame = (0, import_react29.useRef)(null);
|
|
10656
|
+
const input = (0, import_react29.useRef)(null);
|
|
10657
|
+
const [draft, setDraft] = (0, import_react29.useState)(null);
|
|
10068
10658
|
const showGuest = landed && !leaving && Boolean(page.url) && !page.error;
|
|
10069
|
-
(0,
|
|
10659
|
+
(0, import_react29.useLayoutEffect)(() => {
|
|
10070
10660
|
const element = frame.current;
|
|
10071
10661
|
if (!element || leaving || !tabId) {
|
|
10072
10662
|
void actions.browserShow(null);
|
|
@@ -10092,7 +10682,7 @@ function BrowserView({ bot, tabId, leaving, landed, actions, useBrowserPages })
|
|
|
10092
10682
|
window.removeEventListener("resize", schedule);
|
|
10093
10683
|
};
|
|
10094
10684
|
}, [tabId, showGuest, leaving]);
|
|
10095
|
-
(0,
|
|
10685
|
+
(0, import_react29.useEffect)(() => {
|
|
10096
10686
|
setDraft(null);
|
|
10097
10687
|
if (landed && !page.url) input.current?.focus();
|
|
10098
10688
|
}, [tabId, landed]);
|
|
@@ -10108,27 +10698,27 @@ function BrowserView({ bot, tabId, leaving, landed, actions, useBrowserPages })
|
|
|
10108
10698
|
};
|
|
10109
10699
|
const editing = draft !== null;
|
|
10110
10700
|
const secure = /^https:/i.test(page.url);
|
|
10111
|
-
return (0,
|
|
10701
|
+
return (0, import_react29.createElement)(
|
|
10112
10702
|
"div",
|
|
10113
10703
|
{ className: "bt-panel-view bt-panel-browser", "aria-label": t("{name}'s browser", { name: bot.name }) },
|
|
10114
|
-
(0,
|
|
10704
|
+
(0, import_react29.createElement)(
|
|
10115
10705
|
"div",
|
|
10116
10706
|
{ className: "bt-browser-bar" },
|
|
10117
|
-
(0,
|
|
10118
|
-
(0,
|
|
10119
|
-
(0,
|
|
10707
|
+
(0, import_react29.createElement)("button", { type: "button", className: "bt-icon-btn", "aria-label": t("Back"), title: t("Back"), disabled: !page.canGoBack, onClick: () => actions.browserCommand(tabId, "back") }, (0, import_react29.createElement)(BackIcon)),
|
|
10708
|
+
(0, import_react29.createElement)("button", { type: "button", className: "bt-icon-btn", "aria-label": t("Forward"), title: t("Forward"), disabled: !page.canGoForward, onClick: () => actions.browserCommand(tabId, "forward") }, (0, import_react29.createElement)(ForwardIcon)),
|
|
10709
|
+
(0, import_react29.createElement)("button", {
|
|
10120
10710
|
type: "button",
|
|
10121
10711
|
className: "bt-icon-btn",
|
|
10122
10712
|
disabled: !page.url,
|
|
10123
10713
|
"aria-label": page.loading ? t("Stop") : t("Reload"),
|
|
10124
10714
|
title: page.loading ? t("Stop") : t("Reload"),
|
|
10125
10715
|
onClick: () => actions.browserCommand(tabId, page.loading ? "stop" : "reload")
|
|
10126
|
-
}, (0,
|
|
10127
|
-
(0,
|
|
10716
|
+
}, (0, import_react29.createElement)(page.loading ? StopIcon : ReloadIcon)),
|
|
10717
|
+
(0, import_react29.createElement)(
|
|
10128
10718
|
"form",
|
|
10129
10719
|
{ className: "bt-browser-address", onSubmit: go, "data-editing": editing || void 0 },
|
|
10130
|
-
(0,
|
|
10131
|
-
(0,
|
|
10720
|
+
(0, import_react29.createElement)("span", { className: "bt-browser-scheme", "aria-hidden": true }, (0, import_react29.createElement)(secure && !editing ? LockIcon2 : GlobeIcon)),
|
|
10721
|
+
(0, import_react29.createElement)("input", {
|
|
10132
10722
|
ref: input,
|
|
10133
10723
|
type: "text",
|
|
10134
10724
|
spellCheck: false,
|
|
@@ -10152,25 +10742,25 @@ function BrowserView({ bot, tabId, leaving, landed, actions, useBrowserPages })
|
|
|
10152
10742
|
}
|
|
10153
10743
|
})
|
|
10154
10744
|
),
|
|
10155
|
-
page.loading ? (0,
|
|
10745
|
+
page.loading ? (0, import_react29.createElement)("span", { className: "bt-browser-progress", "aria-hidden": true }) : null
|
|
10156
10746
|
),
|
|
10157
|
-
(0,
|
|
10747
|
+
(0, import_react29.createElement)(
|
|
10158
10748
|
"div",
|
|
10159
10749
|
{ ref: frame, className: "bt-browser-frame", "data-guest": showGuest || void 0 },
|
|
10160
|
-
page.error ? (0,
|
|
10750
|
+
page.error ? (0, import_react29.createElement)(
|
|
10161
10751
|
"div",
|
|
10162
10752
|
{ className: "bt-browser-empty" },
|
|
10163
|
-
(0,
|
|
10164
|
-
(0,
|
|
10165
|
-
page.url ? (0,
|
|
10753
|
+
(0, import_react29.createElement)("div", { className: "bt-browser-empty-title" }, t("This page didn’t load")),
|
|
10754
|
+
(0, import_react29.createElement)("div", { className: "bt-browser-empty-text" }, t(page.error)),
|
|
10755
|
+
page.url ? (0, import_react29.createElement)("button", { type: "button", className: "bt-browser-retry", onClick: () => {
|
|
10166
10756
|
void actions.browserNavigate(tabId, page.url);
|
|
10167
10757
|
} }, t("Try again")) : null
|
|
10168
|
-
) : !page.url ? (0,
|
|
10758
|
+
) : !page.url ? (0, import_react29.createElement)(
|
|
10169
10759
|
"div",
|
|
10170
10760
|
{ className: "bt-browser-empty" },
|
|
10171
|
-
(0,
|
|
10172
|
-
(0,
|
|
10173
|
-
(0,
|
|
10761
|
+
(0, import_react29.createElement)(BotMark, { bot, size: 48, state: "idle", live: true }),
|
|
10762
|
+
(0, import_react29.createElement)("div", { className: "bt-browser-empty-title" }, t("{name}'s browser", { name: bot.name })),
|
|
10763
|
+
(0, import_react29.createElement)("div", { className: "bt-browser-empty-text" }, t("Open a page here and {name} can read it with you. Logins and cookies stay with {name}; other Bots never see them.", { name: bot.name }))
|
|
10174
10764
|
) : null
|
|
10175
10765
|
)
|
|
10176
10766
|
);
|
|
@@ -10182,41 +10772,41 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu
|
|
|
10182
10772
|
const tabState = useBrowserTabs((map) => bot ? map[bot.id] ?? EMPTY_TABS2 : EMPTY_TABS2);
|
|
10183
10773
|
const active = tabState.active;
|
|
10184
10774
|
const page = useBrowserPages((pages) => (active ? pages[active] : null) ?? EMPTY_PAGE);
|
|
10185
|
-
const root = (0,
|
|
10186
|
-
const fly = (0,
|
|
10187
|
-
const [landed, setLanded] = (0,
|
|
10188
|
-
const [settling, setSettling] = (0,
|
|
10189
|
-
const [width, setWidth] = (0,
|
|
10190
|
-
const [viewport, setViewport] = (0,
|
|
10191
|
-
const [dragging, setDragging] = (0,
|
|
10192
|
-
const [resizing, setResizing] = (0,
|
|
10775
|
+
const root = (0, import_react29.useRef)(null);
|
|
10776
|
+
const fly = (0, import_react29.useRef)(null);
|
|
10777
|
+
const [landed, setLanded] = (0, import_react29.useState)(false);
|
|
10778
|
+
const [settling, setSettling] = (0, import_react29.useState)(false);
|
|
10779
|
+
const [width, setWidth] = (0, import_react29.useState)(savedWidth);
|
|
10780
|
+
const [viewport, setViewport] = (0, import_react29.useState)(() => window.innerWidth);
|
|
10781
|
+
const [dragging, setDragging] = (0, import_react29.useState)(false);
|
|
10782
|
+
const [resizing, setResizing] = (0, import_react29.useState)(false);
|
|
10193
10783
|
const instant = dragging || resizing;
|
|
10194
|
-
const [reading, setReading] = (0,
|
|
10784
|
+
const [reading, setReading] = (0, import_react29.useState)(false);
|
|
10195
10785
|
const panelWidth = fitWidth(width ?? (view === "browser" ? DEFAULT_WIDTH : DETAILS_WIDTH), widthRange(view, viewport));
|
|
10196
|
-
(0,
|
|
10786
|
+
(0, import_react29.useLayoutEffect)(() => {
|
|
10197
10787
|
if (view === "browser" && width === null) setWidth(DEFAULT_WIDTH);
|
|
10198
10788
|
}, [view, width]);
|
|
10199
|
-
(0,
|
|
10789
|
+
(0, import_react29.useLayoutEffect)(() => {
|
|
10200
10790
|
chatRoom.hold(instant);
|
|
10201
10791
|
}, [instant]);
|
|
10202
|
-
const [last, setLast] = (0,
|
|
10792
|
+
const [last, setLast] = (0, import_react29.useState)({ view, width: panelWidth });
|
|
10203
10793
|
if (last.view !== view || last.width !== panelWidth) {
|
|
10204
10794
|
setLast({ view, width: panelWidth });
|
|
10205
10795
|
if (last.view !== view) setSettling(last.width !== panelWidth && !instant);
|
|
10206
10796
|
}
|
|
10207
|
-
const shownView = (0,
|
|
10208
|
-
(0,
|
|
10797
|
+
const shownView = (0, import_react29.useRef)(view);
|
|
10798
|
+
(0, import_react29.useLayoutEffect)(() => {
|
|
10209
10799
|
const switched = settling && shownView.current !== view;
|
|
10210
10800
|
shownView.current = view;
|
|
10211
10801
|
if (switched) chatRoom.hold(true);
|
|
10212
10802
|
chatRoom.set(leaving ? null : panelWidth);
|
|
10213
10803
|
if (switched) chatRoom.hold(false);
|
|
10214
10804
|
}, [view, panelWidth, leaving]);
|
|
10215
|
-
(0,
|
|
10805
|
+
(0, import_react29.useEffect)(() => () => {
|
|
10216
10806
|
chatRoom.set(null);
|
|
10217
10807
|
chatRoom.hold(false);
|
|
10218
10808
|
}, []);
|
|
10219
|
-
(0,
|
|
10809
|
+
(0, import_react29.useEffect)(() => {
|
|
10220
10810
|
let timer;
|
|
10221
10811
|
const onResize = () => {
|
|
10222
10812
|
setResizing(true);
|
|
@@ -10230,28 +10820,28 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu
|
|
|
10230
10820
|
window.removeEventListener("resize", onResize);
|
|
10231
10821
|
};
|
|
10232
10822
|
}, []);
|
|
10233
|
-
(0,
|
|
10823
|
+
(0, import_react29.useEffect)(() => {
|
|
10234
10824
|
if (!settling) return void 0;
|
|
10235
10825
|
const timer = setTimeout(() => setSettling(false), SPRING_SHAPE.duration + 40);
|
|
10236
10826
|
return () => clearTimeout(timer);
|
|
10237
10827
|
}, [settling, view]);
|
|
10238
|
-
(0,
|
|
10828
|
+
(0, import_react29.useEffect)(() => {
|
|
10239
10829
|
const timer = setTimeout(() => setLanded(true), 360);
|
|
10240
10830
|
return () => clearTimeout(timer);
|
|
10241
10831
|
}, []);
|
|
10242
|
-
(0,
|
|
10832
|
+
(0, import_react29.useEffect)(() => {
|
|
10243
10833
|
if (canBrowse) actions.browserEnsure(bot.id);
|
|
10244
10834
|
}, [bot?.id, canBrowse]);
|
|
10245
|
-
(0,
|
|
10835
|
+
(0, import_react29.useEffect)(() => {
|
|
10246
10836
|
if (view === "browser" && (active === null || !tabState.ids.includes(active))) actions.setPanelView("details");
|
|
10247
10837
|
}, [view, active, tabState.ids.length]);
|
|
10248
|
-
(0,
|
|
10838
|
+
(0, import_react29.useEffect)(() => {
|
|
10249
10839
|
if (view !== "browser" || leaving) void actions.browserShow?.(null);
|
|
10250
10840
|
}, [view, leaving]);
|
|
10251
|
-
(0,
|
|
10841
|
+
(0, import_react29.useEffect)(() => () => {
|
|
10252
10842
|
void actions.browserShow?.(null);
|
|
10253
10843
|
}, []);
|
|
10254
|
-
(0,
|
|
10844
|
+
(0, import_react29.useEffect)(() => {
|
|
10255
10845
|
if (!page.readAt || Date.now() - page.readAt > READ_FLASH_MS) return void 0;
|
|
10256
10846
|
setReading(true);
|
|
10257
10847
|
const timer = setTimeout(() => setReading(false), READ_FLASH_MS);
|
|
@@ -10288,7 +10878,7 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu
|
|
|
10288
10878
|
return actions[name](...args);
|
|
10289
10879
|
};
|
|
10290
10880
|
}
|
|
10291
|
-
(0,
|
|
10881
|
+
(0, import_react29.useLayoutEffect)(() => {
|
|
10292
10882
|
const move = fly.current;
|
|
10293
10883
|
if (!move) return;
|
|
10294
10884
|
fly.current = null;
|
|
@@ -10371,7 +10961,7 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu
|
|
|
10371
10961
|
for (const name of ends) grip.addEventListener(name, onEnd);
|
|
10372
10962
|
};
|
|
10373
10963
|
const hasStrip = canBrowse && tabState.ids.length > 0;
|
|
10374
|
-
return (0,
|
|
10964
|
+
return (0, import_react29.createElement)(
|
|
10375
10965
|
"aside",
|
|
10376
10966
|
{
|
|
10377
10967
|
ref: root,
|
|
@@ -10388,15 +10978,15 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu
|
|
|
10388
10978
|
if (event.target === event.currentTarget) setLanded(true);
|
|
10389
10979
|
}
|
|
10390
10980
|
},
|
|
10391
|
-
leaving ? null : (0,
|
|
10981
|
+
leaving ? null : (0, import_react29.createElement)("div", { className: "bt-panel-grip", role: "separator", "aria-orientation": "vertical", "aria-label": t("Resize panel"), onPointerDown: startResize }),
|
|
10392
10982
|
// Over the whole window while dragging: the cursor stays a resize cursor and the
|
|
10393
10983
|
// pages underneath (the chat, the guest) see no pointer.
|
|
10394
|
-
dragging ? (0, import_react_dom7.createPortal)((0,
|
|
10395
|
-
hasStrip ? (0,
|
|
10396
|
-
view === "browser" && bot ? (0,
|
|
10984
|
+
dragging ? (0, import_react_dom7.createPortal)((0, import_react29.createElement)("div", { className: "bt-drag-shield", "aria-hidden": true }), document.body) : null,
|
|
10985
|
+
hasStrip ? (0, import_react29.createElement)(PanelStrip, { bot, subject: pane.id, view, ids: tabState.ids, active, reading, onView: switchView, actions: panelActions, useBrowserPages }) : null,
|
|
10986
|
+
view === "browser" && bot ? (0, import_react29.createElement)(BrowserView, { key: `browser:${active}`, bot, tabId: active, leaving, landed: landed && !settling, actions, useBrowserPages }) : (0, import_react29.createElement)(
|
|
10397
10987
|
"div",
|
|
10398
10988
|
{ key: `details:${pane.id}:${pane.renaming ? "rename" : ""}:${pane.tab ?? ""}`, className: "bt-panel-view" },
|
|
10399
|
-
(0,
|
|
10989
|
+
(0, import_react29.createElement)(DetailsDrawer, {
|
|
10400
10990
|
sessionId: pane.id,
|
|
10401
10991
|
startRenaming: pane.renaming,
|
|
10402
10992
|
startTab: pane.tab,
|
|
@@ -10492,24 +11082,24 @@ var BROWSER_CSS = `
|
|
|
10492
11082
|
`;
|
|
10493
11083
|
|
|
10494
11084
|
// src/client/overlays.js
|
|
10495
|
-
var
|
|
11085
|
+
var import_react30 = require("react");
|
|
10496
11086
|
var import_react_dom8 = require("react-dom");
|
|
10497
11087
|
var DIALOG_EXIT_MS = 160;
|
|
10498
11088
|
var MENU_EXIT_MS3 = 120;
|
|
10499
|
-
function Overlays({ useRoster, useUi, useSessions, useSessionStatus, useActivity, useRegistry, useBrowserPages, useBrowserTabs, actions }) {
|
|
11089
|
+
function Overlays({ useRoster, useUi, useSessions, useSessionStatus, useActivity, useRegistry, useBrowserPages, useBrowserTabs, useUpdate, actions }) {
|
|
10500
11090
|
const roster = useRoster((value) => value);
|
|
10501
11091
|
const ui = useUi((value) => value);
|
|
10502
11092
|
useRegistry((value) => value);
|
|
10503
11093
|
const current = useSessions((list) => Object.values(list.byId).find((session) => (session?.retainedBy?.mainView ?? 0) > 0)?.id ?? null);
|
|
10504
|
-
const previous = (0,
|
|
10505
|
-
(0,
|
|
11094
|
+
const previous = (0, import_react30.useRef)(current);
|
|
11095
|
+
(0, import_react30.useEffect)(() => {
|
|
10506
11096
|
if (previous.current === current) return;
|
|
10507
11097
|
previous.current = current;
|
|
10508
11098
|
if (current) actions.followPanel?.(current);
|
|
10509
11099
|
}, [current]);
|
|
10510
11100
|
const open = ui.details ? { id: ui.details, view: ui.view ?? "details", renaming: ui.renaming, tab: ui.tab } : null;
|
|
10511
|
-
const [kept, setKept] = (0,
|
|
10512
|
-
(0,
|
|
11101
|
+
const [kept, setKept] = (0, import_react30.useState)(open);
|
|
11102
|
+
(0, import_react30.useEffect)(() => {
|
|
10513
11103
|
if (open) {
|
|
10514
11104
|
setKept(open);
|
|
10515
11105
|
return void 0;
|
|
@@ -10521,17 +11111,17 @@ function Overlays({ useRoster, useUi, useSessions, useSessionStatus, useActivity
|
|
|
10521
11111
|
const [settings, settingsLeaving] = useLinger(ui.settings, DIALOG_EXIT_MS);
|
|
10522
11112
|
const [memory, memoryLeaving] = useLinger(ui.memory && roster.byId[ui.memory] ? ui.memory : null, DIALOG_EXIT_MS);
|
|
10523
11113
|
const [menu, menuLeaving] = useLinger(ui.menu, MENU_EXIT_MS3);
|
|
10524
|
-
return (0,
|
|
10525
|
-
|
|
11114
|
+
return (0, import_react30.createElement)(
|
|
11115
|
+
import_react30.Fragment,
|
|
10526
11116
|
null,
|
|
10527
|
-
pane ? (0, import_react_dom8.createPortal)((0,
|
|
10528
|
-
ui.newChat ? (0, import_react_dom8.createPortal)((0,
|
|
10529
|
-
ui.palette ? (0, import_react_dom8.createPortal)((0,
|
|
10530
|
-
ui.exchange ? (0, import_react_dom8.createPortal)((0,
|
|
10531
|
-
ui.voice ? (0, import_react_dom8.createPortal)((0,
|
|
10532
|
-
menu ? (0, import_react_dom8.createPortal)((0,
|
|
10533
|
-
settings ? (0, import_react_dom8.createPortal)((0,
|
|
10534
|
-
memory && roster.byId[memory] ? (0, import_react_dom8.createPortal)((0,
|
|
11117
|
+
pane ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(BotPanel, { pane, leaving: open === null, roster, actions, useSessions, useSessionStatus, useActivity, useBrowserTabs, useBrowserPages }), document.body) : null,
|
|
11118
|
+
ui.newChat ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(NewChat, { key: ui.newChat, mode: ui.newChat, roster, actions }), document.body) : null,
|
|
11119
|
+
ui.palette ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(CommandPalette, { key: ui.palette.token, start: ui.palette.query, token: ui.palette.token, roster, actions, useSessions }), document.body) : null,
|
|
11120
|
+
ui.exchange ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(ExchangeDialog, { pair: ui.exchange, roster, actions }), document.body) : null,
|
|
11121
|
+
ui.voice ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(VoiceMode, { key: ui.voice, sessionId: ui.voice, roster, actions, useSessions, useSessionStatus }), document.body) : null,
|
|
11122
|
+
menu ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(ContextMenu, { key: `${menu.kind ?? ""}:${menu.id}:${menu.x}:${menu.y}`, menu, roster, actions, leaving: menuLeaving }), document.body) : null,
|
|
11123
|
+
settings ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(SettingsDialog, { page: settings.page, id: settings.id, roster, actions, useUpdate, leaving: settingsLeaving }), document.body) : null,
|
|
11124
|
+
memory && roster.byId[memory] ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(MemoryDialog, { key: memory, bot: roster.byId[memory], actions, leaving: memoryLeaving }), document.body) : null
|
|
10535
11125
|
);
|
|
10536
11126
|
}
|
|
10537
11127
|
|
|
@@ -11362,7 +11952,7 @@ function writeMode(mode, storage = globalThis.localStorage) {
|
|
|
11362
11952
|
}
|
|
11363
11953
|
|
|
11364
11954
|
// src/client/index.js
|
|
11365
|
-
var inject = ["sessions", "uiWorkspace", "slots", "theme", "layout", "configForms"];
|
|
11955
|
+
var inject = ["sessions", "uiWorkspace", "uiConversation", "slots", "theme", "layout", "configForms"];
|
|
11366
11956
|
function apply(ctx) {
|
|
11367
11957
|
ctx.effect(() => installLocale(ctx.get("locale")), "ds-bot: interface copy");
|
|
11368
11958
|
const roster = createSource(EMPTY_ROSTER);
|
|
@@ -11532,8 +12122,8 @@ function apply(ctx) {
|
|
|
11532
12122
|
if (stopped) return;
|
|
11533
12123
|
const value = await call("update-check", { fresh: false }).catch(() => null);
|
|
11534
12124
|
if (stopped) return;
|
|
11535
|
-
if (value) update.set(value);
|
|
11536
|
-
timer = setTimeout(loop, value?.phase === "installing" ? 2e3 : 60 * 6e4);
|
|
12125
|
+
if (value) update.set((previous) => ({ ...value, app: previous.app }));
|
|
12126
|
+
timer = setTimeout(loop, value?.phase === "installing" || value?.dsh?.phase === "installing" ? 2e3 : 60 * 6e4);
|
|
11537
12127
|
};
|
|
11538
12128
|
wakeUpdate = loop;
|
|
11539
12129
|
void loop();
|
|
@@ -11544,6 +12134,22 @@ function apply(ctx) {
|
|
|
11544
12134
|
};
|
|
11545
12135
|
};
|
|
11546
12136
|
}, "ds-bot: update check");
|
|
12137
|
+
const appUpdates = window.dshDesktop?.protocolVersion === 1 ? window.dshDesktop.updates ?? null : null;
|
|
12138
|
+
if (appUpdates?.status && appUpdates.subscribe) {
|
|
12139
|
+
ctx.effect(() => {
|
|
12140
|
+
let stopped = false;
|
|
12141
|
+
const take = (app) => {
|
|
12142
|
+
if (!stopped && app) update.set((previous) => ({ ...previous, app }));
|
|
12143
|
+
};
|
|
12144
|
+
void appUpdates.status().then(take, () => {
|
|
12145
|
+
});
|
|
12146
|
+
const unsubscribe = appUpdates.subscribe(take);
|
|
12147
|
+
return () => {
|
|
12148
|
+
stopped = true;
|
|
12149
|
+
unsubscribe?.();
|
|
12150
|
+
};
|
|
12151
|
+
}, "ds-bot: app update status");
|
|
12152
|
+
}
|
|
11547
12153
|
const bridge = window.dshDesktop?.protocolVersion === 1 ? window.dshDesktop.browser ?? null : null;
|
|
11548
12154
|
const browsers = bridge ? createBotBrowsers({ bridge, createSource }) : null;
|
|
11549
12155
|
if (browsers) {
|
|
@@ -11906,6 +12512,7 @@ function apply(ctx) {
|
|
|
11906
12512
|
updateBot: (id, patch) => after(call("update-bot", { id, ...patch })),
|
|
11907
12513
|
refreshModels: () => after(call("models")).catch(() => {
|
|
11908
12514
|
}),
|
|
12515
|
+
efforts: (ref) => call("efforts", { ref }),
|
|
11909
12516
|
deleteBot: (id) => after(call("delete-bot", { id })),
|
|
11910
12517
|
duplicateBot: (id) => after(call("duplicate-bot", { id })),
|
|
11911
12518
|
setPrefs: (prefs) => after(call("set-prefs", prefs)),
|
|
@@ -11992,11 +12599,33 @@ function apply(ctx) {
|
|
|
11992
12599
|
actions.signIn = () => ui.set((state) => ({ ...state, signIn: (state.signIn ?? 0) + 1 }));
|
|
11993
12600
|
actions.closeSignIn = () => ui.set((state) => ({ ...state, signIn: null }));
|
|
11994
12601
|
actions.accountT = () => ctx.get("locale").bind("settings.account");
|
|
12602
|
+
actions.speech = () => ctx.get("remote.speech") ?? void 0;
|
|
12603
|
+
actions.voiceSetup = () => {
|
|
12604
|
+
const navigation = ctx.get("pluginNavigation");
|
|
12605
|
+
return navigation ? () => navigation.openBundle(VOICE_BUNDLE) : null;
|
|
12606
|
+
};
|
|
11995
12607
|
actions.seatAccount = (on) => ui.set((state) => state.accountSeated === on ? state : { ...state, accountSeated: on });
|
|
11996
12608
|
actions.installUpdate = async () => {
|
|
11997
|
-
|
|
12609
|
+
const value = await call("update-install");
|
|
12610
|
+
update.set((previous) => ({ ...previous, ...value }));
|
|
11998
12611
|
wakeUpdate();
|
|
11999
12612
|
};
|
|
12613
|
+
actions.installDshUpdate = async () => {
|
|
12614
|
+
const dsh = await call("dsh-update-install");
|
|
12615
|
+
update.set((previous) => ({ ...previous, dsh }));
|
|
12616
|
+
wakeUpdate();
|
|
12617
|
+
};
|
|
12618
|
+
actions.openAppUpdates = async (verified) => {
|
|
12619
|
+
const app = await appUpdates?.status?.();
|
|
12620
|
+
if (app) update.set((previous) => ({ ...previous, app }));
|
|
12621
|
+
if (sameVersion(app?.version, verified)) await appUpdates.open();
|
|
12622
|
+
};
|
|
12623
|
+
actions.checkUpdate = async () => {
|
|
12624
|
+
const value = await call("update-check", { fresh: true });
|
|
12625
|
+
update.set((previous) => ({ ...value, app: previous.app }));
|
|
12626
|
+
wakeUpdate();
|
|
12627
|
+
};
|
|
12628
|
+
actions.repoStars = () => call("repo-stars");
|
|
12000
12629
|
actions.noteTurnTime = (sessionId, turn, key, time) => {
|
|
12001
12630
|
if (sessionId === void 0 || !Number.isFinite(turn) || !Number.isFinite(time)) return;
|
|
12002
12631
|
turnClock.set((state) => {
|
|
@@ -12037,6 +12666,18 @@ function apply(ctx) {
|
|
|
12037
12666
|
const browserPages = browsers?.pages ?? createSource({});
|
|
12038
12667
|
const browserTabs = browsers?.tabs ?? createSource({});
|
|
12039
12668
|
const face = () => ({ actions, hooks: { roster, ui, activity, memoryNews, registry, turnClock, live, browserPages, browserTabs, surface, update } });
|
|
12669
|
+
const assistantFace = (sessionId) => {
|
|
12670
|
+
const binding = ctx.sessions.binding(sessionId);
|
|
12671
|
+
if (binding === void 0) throw new Error(`ds-bot: unknown session "${sessionId}"`);
|
|
12672
|
+
const chat = ctx.uiConversation.binding(binding).target("chat");
|
|
12673
|
+
return { ...face(), keyedHooks: {
|
|
12674
|
+
turnSteps: (turn) => {
|
|
12675
|
+
const snapshot = chat.getSnapshot();
|
|
12676
|
+
if (snapshot === void 0) throw new Error("ds-bot: Chat target is unavailable");
|
|
12677
|
+
return snapshot.nodes.turnDataSource(Number(turn), "assistant-step");
|
|
12678
|
+
}
|
|
12679
|
+
} };
|
|
12680
|
+
};
|
|
12040
12681
|
const shadow = (name, options, component) => ctx.slots.inject(name, () => ctx.slots.register({ name, ...options }, component));
|
|
12041
12682
|
const installShell = () => [
|
|
12042
12683
|
installCompiledStyles(SHELL_CSS, "shell"),
|
|
@@ -12046,6 +12687,7 @@ function apply(ctx) {
|
|
|
12046
12687
|
installCompiledStyles(USAGE_CSS, "usage"),
|
|
12047
12688
|
installCompiledStyles(SCHEDULE_CSS, "schedules"),
|
|
12048
12689
|
installCompiledStyles(FEEDBACK_CSS, "feedback"),
|
|
12690
|
+
installCompiledStyles(ABOUT_CSS, "about"),
|
|
12049
12691
|
browsers ? installCompiledStyles(BROWSER_CSS, "browser") : null,
|
|
12050
12692
|
installTheme(),
|
|
12051
12693
|
shadow("sidebar.workspaces", { priority: -10, inject: face }, TeamSidebar),
|
|
@@ -12071,7 +12713,7 @@ function apply(ctx) {
|
|
|
12071
12713
|
// the mode is held at `verbose` while this group is mounted.
|
|
12072
12714
|
holdFlatTranscript(ctx.configForms.get("ui-chat")),
|
|
12073
12715
|
shadow("conversation.session.header", { priority: -10, inject: face }, HeaderPill),
|
|
12074
|
-
shadow("conversation.chat.node", { key: "assistant-step", priority: -10, locale: "chat", inject:
|
|
12716
|
+
shadow("conversation.chat.node", { key: "assistant-step", priority: -10, locale: "chat", inject: assistantFace }, AssistantCell),
|
|
12075
12717
|
shadow("conversation.chat.node", { key: "turn-trigger", priority: -10, locale: "chat", inject: face }, TriggerCell),
|
|
12076
12718
|
shadow("conversation.chat.node", { key: "tool-call", priority: -10, locale: "chat", inject: face }, ToolCell),
|
|
12077
12719
|
shadow("conversation.chat.node", { key: "turn-process", priority: -10, locale: "chat", inject: face }, TurnProcessCell),
|