@khorsheed/dsh-room 0.2.2 → 0.2.3
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/CHANGELOG.md +53 -0
- package/README.en.md +29 -62
- package/README.i18n.yaml +2 -2
- package/README.md +28 -61
- package/lib/client.js +13097 -4182
- package/lib/index.js +4045 -730
- package/lib/journal-B4FOGO6S.js +482 -0
- package/lib/journal-Rid9AAnD.js +485 -0
- package/lib/tool.js +112 -166
- package/lib/typert.host.js +2031 -374
- package/lib/typert.remote-client.d.ts +34 -10
- package/lib/typert.remote-client.js +1599 -210
- package/lib/types/agent-setup.d.ts +1 -1
- package/lib/types/client/InviteAgentAction.js +4 -16
- package/lib/types/client/InviteDialog.d.ts +28 -6
- package/lib/types/client/InviteDialog.js +76 -12
- package/lib/types/client/MemberCard.d.ts +5 -1
- package/lib/types/client/MemberCard.js +4 -4
- package/lib/types/client/MembersView.d.ts +1 -1
- package/lib/types/client/MembersView.js +58 -22
- package/lib/types/client/RemoveMemberConfirm.d.ts +40 -0
- package/lib/types/client/RemoveMemberConfirm.js +85 -0
- package/lib/types/client/RoomApprovalView.d.ts +11 -0
- package/lib/types/client/RoomApprovalView.js +28 -0
- package/lib/types/client/RoomComposer.d.ts +1 -1
- package/lib/types/client/RoomComposer.js +195 -29
- package/lib/types/client/RoomDecisionView.d.ts +22 -0
- package/lib/types/client/RoomDecisionView.js +35 -0
- package/lib/types/client/RoomHelpView.d.ts +17 -0
- package/lib/types/client/RoomHelpView.js +56 -0
- package/lib/types/client/RoomInboxView.d.ts +10 -0
- package/lib/types/client/RoomInboxView.js +14 -0
- package/lib/types/client/RoomQuestionView.d.ts +13 -0
- package/lib/types/client/RoomQuestionView.js +61 -0
- package/lib/types/client/RoomRelayView.js +1 -1
- package/lib/types/client/RoomRunView.d.ts +3 -1
- package/lib/types/client/RoomRunView.js +12 -5
- package/lib/types/client/RoomSettingsView.d.ts +19 -0
- package/lib/types/client/RoomSettingsView.js +30 -0
- package/lib/types/client/RoomSpeechView.d.ts +22 -8
- package/lib/types/client/RoomSpeechView.js +94 -11
- package/lib/types/client/RoomStatsLine.d.ts +26 -5
- package/lib/types/client/RoomStatsLine.js +53 -7
- package/lib/types/client/RoomTaskCapsule.d.ts +19 -0
- package/lib/types/client/RoomTaskCapsule.js +37 -0
- package/lib/types/client/TasksTab.d.ts +41 -0
- package/lib/types/client/TasksTab.js +43 -0
- package/lib/types/client/TasksView.d.ts +44 -0
- package/lib/types/client/TasksView.js +149 -0
- package/lib/types/client/TeamView.d.ts +33 -0
- package/lib/types/client/TeamView.js +210 -0
- package/lib/types/client/at-menu.d.ts +127 -0
- package/lib/types/client/at-menu.js +173 -0
- package/lib/types/client/draft-chips.d.ts +67 -0
- package/lib/types/client/draft-chips.js +95 -0
- package/lib/types/client/executions.d.ts +0 -30
- package/lib/types/client/executions.js +4 -34
- package/lib/types/client/icons-local.d.ts +25 -0
- package/lib/types/client/icons-local.js +10 -0
- package/lib/types/client/icons.d.ts +2 -1
- package/lib/types/client/icons.js +1 -0
- package/lib/types/client/index.js +276 -72
- package/lib/types/client/locales.d.ts +482 -52
- package/lib/types/client/locales.js +490 -60
- package/lib/types/client/nodes.d.ts +112 -17
- package/lib/types/client/nodes.js +197 -26
- package/lib/types/client/slots.d.ts +117 -47
- package/lib/types/client/task-tag.d.ts +32 -0
- package/lib/types/client/task-tag.js +33 -0
- package/lib/types/client/tasks.d.ts +50 -0
- package/lib/types/client/tasks.js +82 -0
- package/lib/types/client/team.d.ts +73 -0
- package/lib/types/client/team.js +187 -0
- package/lib/types/decisions.d.ts +49 -0
- package/lib/types/decisions.js +101 -0
- package/lib/types/deliveries.d.ts +17 -1
- package/lib/types/deliveries.js +82 -6
- package/lib/types/dispatch.d.ts +92 -4
- package/lib/types/dispatch.js +477 -109
- package/lib/types/help.d.ts +137 -0
- package/lib/types/help.js +216 -0
- package/lib/types/helps.d.ts +26 -0
- package/lib/types/helps.js +47 -0
- package/lib/types/index.d.ts +292 -52
- package/lib/types/index.js +1415 -199
- package/lib/types/jobs.d.ts +22 -0
- package/lib/types/jobs.js +77 -0
- package/lib/types/journal.d.ts +17 -27
- package/lib/types/journal.js +120 -43
- package/lib/types/messaging.d.ts +65 -0
- package/lib/types/messaging.js +160 -0
- package/lib/types/plan-service.js +7 -1
- package/lib/types/report-gate.d.ts +81 -0
- package/lib/types/report-gate.js +89 -0
- package/lib/types/tool-definition.d.ts +43 -20
- package/lib/types/tool-definition.js +145 -212
- package/lib/types/tool.d.ts +2 -2
- package/lib/types/tool.js +3 -2
- package/lib/types/types.d.ts +755 -78
- package/locale/zh.json +1 -1
- package/package.json +3 -3
- package/lib/types/client/RoomActivityDock.d.ts +0 -19
- package/lib/types/client/RoomActivityDock.js +0 -105
- package/lib/types/client/RoomDockCapsules.d.ts +0 -64
- package/lib/types/client/RoomDockCapsules.js +0 -277
- package/lib/types/client/RoomPlanTab.d.ts +0 -15
- package/lib/types/client/RoomPlanTab.js +0 -13
- package/lib/types/client/RoomTaskLineView.d.ts +0 -13
- package/lib/types/client/RoomTaskLineView.js +0 -21
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Cross-session help, on both ends.
|
|
4
|
+
*
|
|
5
|
+
* - `room-help` (the asking room): one line per thread — who asked which
|
|
6
|
+
* session, where it stands — that opens to the request and every message
|
|
7
|
+
* on the thread (the helper's questions, its reply with the handoff, the
|
|
8
|
+
* asker's follow-ups), plus a jump into the helper's session.
|
|
9
|
+
* - `room-help-request` (the helper's session): the request the room queued
|
|
10
|
+
* there, as a card naming the asking room and member, with a jump back.
|
|
11
|
+
*/
|
|
12
|
+
import { useEffect, useState, useSyncExternalStore } from 'react';
|
|
13
|
+
import { NUDGE_AFTER } from '../helps.js';
|
|
14
|
+
import css from './RoomHelpView.module.css';
|
|
15
|
+
const NO_STORE = () => () => { };
|
|
16
|
+
/** Default minutes before an unanswered request turns amber (the room setting's default). */
|
|
17
|
+
const REMINDER_MINUTES = 30;
|
|
18
|
+
/** The thread row in the asking room. */
|
|
19
|
+
export function RoomHelpView({ node, sessionId, roomStore, openSession, linkSession, dismissNudge, t }) {
|
|
20
|
+
const help = node.data.help;
|
|
21
|
+
const [open, setOpen] = useState(false);
|
|
22
|
+
// An unanswered request (queued, or the helper still on it) past the room's
|
|
23
|
+
// reminder turns amber with how long it has waited; nothing is cancelled.
|
|
24
|
+
const state = useSyncExternalStore(roomStore?.subscribe ?? NO_STORE, () => roomStore?.getCached(sessionId));
|
|
25
|
+
const minutes = state?.settings?.helpReminderMinutes ?? REMINDER_MINUTES;
|
|
26
|
+
// The third ask of one plain session, once answered: a one-time hint to keep
|
|
27
|
+
// it on the member list (unless it is already there, or the hint was dismissed).
|
|
28
|
+
const target = help.target.sessionId;
|
|
29
|
+
const asks = (state?.helps ?? []).filter(entry => entry.target.sessionId === target && entry.target.room !== true);
|
|
30
|
+
const nudge = help.state === 'replied' && help.target.room !== true && linkSession !== undefined
|
|
31
|
+
&& asks.length >= NUDGE_AFTER && asks[NUDGE_AFTER - 1]?.id === help.id
|
|
32
|
+
&& state?.linked?.some(entry => entry.sessionId === target) !== true
|
|
33
|
+
&& state?.helpNudgesDismissed?.includes(target) !== true;
|
|
34
|
+
const waiting = help.state === 'queued' || help.state === 'running';
|
|
35
|
+
const [now, setNow] = useState(() => Date.now());
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!waiting)
|
|
38
|
+
return;
|
|
39
|
+
const timer = setInterval(() => { setNow(Date.now()); }, 30_000);
|
|
40
|
+
return () => { clearInterval(timer); };
|
|
41
|
+
}, [waiting]);
|
|
42
|
+
const waited = Math.floor((now - help.since) / 60_000);
|
|
43
|
+
const overdue = waiting && waited >= minutes;
|
|
44
|
+
return (_jsxs("div", { className: css.thread, "data-state": help.state, "data-overdue": overdue ? '' : undefined, children: [_jsxs("div", { className: css.line, children: [_jsx("span", { "aria-hidden": true, className: css.icon, children: help.state === 'replied' ? '✓' : help.state === 'failed' ? '!' : '↗' }), _jsx("span", { className: css.text, children: t('help.title', { from: help.fromName, target: help.target.title }) }), _jsxs("span", { className: css.tag, "data-state": overdue ? 'overdue' : help.state, children: [t(`help.state.${help.state}`), help.state === 'failed' && help.error !== undefined ? `:${help.error}` : '', overdue && t('help.waited', { minutes: waited })] }), _jsx("button", { type: "button", className: css.action, "aria-expanded": open, onClick: () => { setOpen(value => !value); }, children: t(open ? 'help.collapse' : 'help.expand') }), _jsx("button", { type: "button", className: css.action, onClick: () => { openSession(help.target.sessionId); }, children: t('help.open') })] }), nudge && (_jsxs("div", { className: css.nudge, children: [_jsx("span", { children: t('help.nudge', { count: asks.length }) }), _jsx("button", { type: "button", className: css.action, onClick: () => { linkSession(target, help.target.title); }, children: t('help.nudgeLink') }), dismissNudge !== undefined && _jsx("button", { type: "button", className: css.action, onClick: () => { dismissNudge(target); }, children: t('help.nudgeDismiss') })] })), open && (_jsxs("div", { className: css.body, children: [_jsxs("div", { className: css.entry, children: [_jsx("span", { className: css.label, children: t('help.request') }), _jsx("p", { className: css.copy, children: help.text })] }), help.messages.map((message, index) => (_jsxs("div", { className: css.entry, "data-kind": message.kind, children: [_jsx("span", { className: css.label, children: message.kind === 'message'
|
|
45
|
+
? t('help.msg.message', { from: help.fromName })
|
|
46
|
+
: t(message.kind === 'reply' ? (message.fallback === true ? 'help.msg.fallback' : 'help.msg.reply') : 'help.msg.question') }), _jsx("p", { className: css.copy, children: message.text }), message.handoff !== undefined && _jsxs("p", { className: css.handoff, children: [_jsx("b", { children: t('help.handoff') }), message.handoff] })] }, index)))] }))] }));
|
|
47
|
+
}
|
|
48
|
+
/** Lines past which the request card folds. */
|
|
49
|
+
const FOLD_LINES = 6;
|
|
50
|
+
/** The request card in the helper's session. */
|
|
51
|
+
export function RoomHelpRequestView({ node, openSession, t }) {
|
|
52
|
+
const data = node.data;
|
|
53
|
+
const [open, setOpen] = useState(false);
|
|
54
|
+
const long = data.text.split('\n').length > FOLD_LINES || data.text.length > 360;
|
|
55
|
+
return (_jsxs("div", { className: css.card, children: [_jsxs("div", { className: css.cardHead, children: [_jsx("span", { "aria-hidden": true, className: css.icon, children: "\u2198" }), _jsxs("span", { className: css.cardTitle, children: [t(data.op === 'message' ? 'help.card.message' : 'help.card.request', { room: data.roomTitle, from: data.from }), data.member !== undefined && t('help.card.to', { member: data.member })] }), data.held === true && _jsx("span", { className: css.tag, children: t('help.card.held') }), _jsx("button", { type: "button", className: css.action, onClick: () => { openSession(data.room); }, children: t('help.card.back') })] }), _jsx("p", { className: css.cardBody, "data-folded": long && !open ? 'true' : undefined, children: data.text }), long && (_jsx("button", { type: "button", className: css.more, onClick: () => { setOpen(value => !value); }, children: t(open ? 'help.card.less' : 'help.card.more') }))] }));
|
|
56
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Held-reports line ('room-inbox' keyed renderer): the report gate kept some
|
|
3
|
+
* results in a member's inbox instead of waking it — one dim line saying so,
|
|
4
|
+
* with the model's verdict and a 现在交给 button that wakes the member now.
|
|
5
|
+
*/
|
|
6
|
+
import { type ReactNode } from 'react';
|
|
7
|
+
import type { RoomInboxViewProps } from './slots.ts';
|
|
8
|
+
/** The held-reports line. */
|
|
9
|
+
export declare function RoomInboxView({ node, releaseReports, t }: RoomInboxViewProps): ReactNode;
|
|
10
|
+
//# sourceMappingURL=RoomInboxView.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Held-reports line ('room-inbox' keyed renderer): the report gate kept some
|
|
4
|
+
* results in a member's inbox instead of waking it — one dim line saying so,
|
|
5
|
+
* with the model's verdict and a 现在交给 button that wakes the member now.
|
|
6
|
+
*/
|
|
7
|
+
import { useState } from 'react';
|
|
8
|
+
import css from './RoomInboxView.module.css';
|
|
9
|
+
/** The held-reports line. */
|
|
10
|
+
export function RoomInboxView({ node, releaseReports, t }) {
|
|
11
|
+
const data = node.data;
|
|
12
|
+
const [busy, setBusy] = useState(false);
|
|
13
|
+
return (_jsxs("div", { className: css.row, role: "status", children: [_jsxs("span", { className: css.text, children: [t('inbox.line', { member: data.member, count: data.held.length }), data.probability !== undefined && _jsx("span", { className: css.why, children: t('inbox.why', { p: data.probability.toFixed(2) }) })] }), _jsx("button", { type: "button", className: css.action, disabled: busy, onClick: () => { setBusy(true); void releaseReports(data.member).finally(() => { setBusy(false); }); }, children: t('inbox.release', { member: data.member }) })] }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A member's question card ('room-question' keyed renderer): the questions a
|
|
3
|
+
* CLI member asked through its bridge's `ask_user_question`, answered by
|
|
4
|
+
* picking — one option (or several on a multi-pick question) plus an optional
|
|
5
|
+
* 其他 free-text line — instead of a numbered list in the member's reply that
|
|
6
|
+
* the human answers by typing. Submitting resolves the member's waiting call;
|
|
7
|
+
* the card then folds to the given answers (or 已过期 when the call ended).
|
|
8
|
+
*/
|
|
9
|
+
import { type ReactNode } from 'react';
|
|
10
|
+
import type { RoomQuestionViewProps } from './slots.ts';
|
|
11
|
+
/** The question card. */
|
|
12
|
+
export declare function RoomQuestionView({ node, answerQuestion, t }: RoomQuestionViewProps): ReactNode;
|
|
13
|
+
//# sourceMappingURL=RoomQuestionView.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* A member's question card ('room-question' keyed renderer): the questions a
|
|
4
|
+
* CLI member asked through its bridge's `ask_user_question`, answered by
|
|
5
|
+
* picking — one option (or several on a multi-pick question) plus an optional
|
|
6
|
+
* 其他 free-text line — instead of a numbered list in the member's reply that
|
|
7
|
+
* the human answers by typing. Submitting resolves the member's waiting call;
|
|
8
|
+
* the card then folds to the given answers (or 已过期 when the call ended).
|
|
9
|
+
*/
|
|
10
|
+
import { useState } from 'react';
|
|
11
|
+
import { memberColor } from './member-color.js';
|
|
12
|
+
import css from './RoomQuestionView.module.css';
|
|
13
|
+
/** The question card. */
|
|
14
|
+
export function RoomQuestionView({ node, answerQuestion, t }) {
|
|
15
|
+
const data = node.data;
|
|
16
|
+
const [drafts, setDrafts] = useState({});
|
|
17
|
+
const [busy, setBusy] = useState(false);
|
|
18
|
+
const [error, setError] = useState(undefined);
|
|
19
|
+
const pending = data.state === 'pending';
|
|
20
|
+
const draftOf = (id) => drafts[id] ?? { selected: [], custom: '' };
|
|
21
|
+
const setDraft = (id, next) => { setDrafts(current => ({ ...current, [id]: next })); };
|
|
22
|
+
const answered = (id) => draftOf(id).selected.length > 0 || draftOf(id).custom.trim() !== '';
|
|
23
|
+
const complete = data.questions.every(question => answered(question.id));
|
|
24
|
+
const pick = (id, label, multi) => {
|
|
25
|
+
const draft = draftOf(id);
|
|
26
|
+
const has = draft.selected.includes(label);
|
|
27
|
+
setDraft(id, { ...draft, selected: multi ? (has ? draft.selected.filter(entry => entry !== label) : [...draft.selected, label]) : (has ? [] : [label]) });
|
|
28
|
+
};
|
|
29
|
+
const submit = async () => {
|
|
30
|
+
if (busy || !complete)
|
|
31
|
+
return;
|
|
32
|
+
setBusy(true);
|
|
33
|
+
try {
|
|
34
|
+
const answers = data.questions.map((question) => {
|
|
35
|
+
const draft = draftOf(question.id);
|
|
36
|
+
const custom = draft.custom.trim();
|
|
37
|
+
return { id: question.id, selected: draft.selected, ...custom === '' ? {} : { custom } };
|
|
38
|
+
});
|
|
39
|
+
setError(await answerQuestion(data.questionId, answers));
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
setBusy(false);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/** The folded answer text of one question. */
|
|
46
|
+
const answerText = (id) => {
|
|
47
|
+
const answer = data.answers?.find(entry => entry.id === id);
|
|
48
|
+
return [...answer?.selected ?? [], ...answer?.custom === undefined ? [] : [answer.custom]].join(';');
|
|
49
|
+
};
|
|
50
|
+
return (_jsxs("div", { className: css.card, "data-state": data.state, style: { '--member-color': memberColor(data.member) }, children: [_jsxs("div", { className: css.head, children: [_jsx("span", { className: css.avatar, "aria-hidden": true, children: data.member.slice(0, 1).toUpperCase() }), _jsx("span", { className: css.title, children: t('question.title', { member: data.member }) }), !pending && _jsx("span", { className: css.status, children: t(data.state === 'answered' ? 'question.answered' : 'question.cancelled') })] }), data.questions.map((question) => {
|
|
51
|
+
const multi = question.multiSelect === true;
|
|
52
|
+
const draft = draftOf(question.id);
|
|
53
|
+
return (_jsxs("fieldset", { className: css.question, disabled: !pending || busy, children: [_jsxs("legend", { className: css.prompt, children: [question.header !== undefined && _jsx("span", { className: css.chip, children: question.header }), _jsx("span", { children: question.question }), pending && multi && _jsx("span", { className: css.hint, children: t('question.multi') })] }), pending ? (_jsxs(_Fragment, { children: [(question.options ?? []).map(option => (_jsxs("label", { className: css.option, "data-picked": draft.selected.includes(option.label), children: [_jsx("input", { type: multi ? 'checkbox' : 'radio', name: `${data.questionId}-${question.id}`, checked: draft.selected.includes(option.label), onChange: () => { pick(question.id, option.label, multi); }, onClick: (event) => {
|
|
54
|
+
// A picked radio unpicks on a second click (the draft allows none).
|
|
55
|
+
if (!multi && draft.selected.includes(option.label)) {
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
pick(question.id, option.label, multi);
|
|
58
|
+
}
|
|
59
|
+
} }), _jsxs("span", { className: css.optionText, children: [_jsx("span", { className: css.optionLabel, children: option.label }), option.description !== undefined && _jsx("span", { className: css.optionDescription, children: option.description })] })] }, option.label))), _jsx("input", { className: css.other, value: draft.custom, "aria-label": t('question.other'), placeholder: (question.options ?? []).length > 0 ? `${t('question.other')}:${t('question.otherPlaceholder')}` : t('question.otherPlaceholder'), onChange: (event) => { setDraft(question.id, { ...draft, custom: event.target.value }); } })] })) : data.state === 'answered' && (_jsx("div", { className: css.answer, children: t('question.answer', { answer: answerText(question.id) }) }))] }, question.id));
|
|
60
|
+
}), pending && (_jsxs("div", { className: css.actions, children: [error !== undefined && _jsx("span", { className: css.error, children: error }), _jsx("button", { type: "button", className: css.submit, disabled: !complete || busy, onClick: () => { void submit(); }, children: t('question.submit') })] }))] }));
|
|
61
|
+
}
|
|
@@ -16,5 +16,5 @@ export function RoomRelayView({ node, confirmRelay, dismissRelay, t }) {
|
|
|
16
16
|
break;
|
|
17
17
|
case 'pending': break;
|
|
18
18
|
}
|
|
19
|
-
return (_jsxs("div", { className: css.row, "data-state": data.state, children: [_jsxs("span", { className: css.text, children: [t('relay.line', { from: data.from, to: data.to, content: data.content }), suffix === '' ? '' : ` ${suffix}`] }), data.state === 'pending' && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.action, onClick: () => { void confirmRelay(data.relayId); }, children: t('relay.confirm') }), _jsx("button", { type: "button", className: css.action, onClick: () => { void dismissRelay(data.relayId); }, children: t('relay.dismiss') })] }))] }));
|
|
19
|
+
return (_jsxs("div", { className: css.row, "data-state": data.state, children: [_jsxs("span", { className: css.text, children: [data.reason !== undefined && data.state === 'pending' && _jsx("span", { className: css.reason, children: t(`relay.reason.${data.reason}`) }), t('relay.line', { from: data.from, to: data.to, content: data.content }), suffix === '' ? '' : ` ${suffix}`] }), data.state === 'pending' && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.action, onClick: () => { void confirmRelay(data.relayId); }, children: data.reason === 'hop-limit' ? t('relay.continue') : t('relay.confirm') }), _jsx("button", { type: "button", className: css.action, onClick: () => { void dismissRelay(data.relayId); }, children: data.reason === 'hop-limit' ? t('relay.stop') : t('relay.dismiss') })] }))] }));
|
|
20
20
|
}
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* no handle), and a trailing stop button cancels through the Remote. A
|
|
7
7
|
* failed run stays as a dim error row with the journaled reason (truncated,
|
|
8
8
|
* the full text on hover); done/cancelled are hidden by the Definition and
|
|
9
|
-
* never reach here.
|
|
9
|
+
* never reach here. A RUNNING member's row takes the speech card's shape —
|
|
10
|
+
* avatar and light card, the row as the card's header, the live output
|
|
11
|
+
* below — so the reply card that replaces it lands in the same place.
|
|
10
12
|
*/
|
|
11
13
|
import { type ReactNode } from 'react';
|
|
12
14
|
import type { RoomRunViewProps } from './slots.ts';
|
|
@@ -7,13 +7,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
* no handle), and a trailing stop button cancels through the Remote. A
|
|
8
8
|
* failed run stays as a dim error row with the journaled reason (truncated,
|
|
9
9
|
* the full text on hover); done/cancelled are hidden by the Definition and
|
|
10
|
-
* never reach here.
|
|
10
|
+
* never reach here. A RUNNING member's row takes the speech card's shape —
|
|
11
|
+
* avatar and light card, the row as the card's header, the live output
|
|
12
|
+
* below — so the reply card that replaces it lands in the same place.
|
|
11
13
|
*/
|
|
12
14
|
import { useEffect, useState, useSyncExternalStore } from 'react';
|
|
13
15
|
import { StateDot, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
14
16
|
import { IconStopFillMedium } from './icons.js';
|
|
15
17
|
import { formatDurationMs } from './format.js';
|
|
18
|
+
import { memberColor } from './member-color.js';
|
|
16
19
|
import css from './RoomRunView.module.css';
|
|
20
|
+
import speech from './RoomSpeechView.module.css';
|
|
17
21
|
/** The member run row. */
|
|
18
22
|
export function RoomRunView({ node, sessionId, roomStore, openSession, cancelMember, renderMemberOutput, t }) {
|
|
19
23
|
const state = useSyncExternalStore(roomStore.subscribe, () => roomStore.getCached(sessionId));
|
|
@@ -43,8 +47,11 @@ export function RoomRunView({ node, sessionId, roomStore, openSession, cancelMem
|
|
|
43
47
|
jump();
|
|
44
48
|
}
|
|
45
49
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
const row = (_jsxs("div", { className: css.run, "data-state": failed ? 'failed' : 'running', role: "button", tabIndex: 0, onClick: jump, onKeyDown: onKeyDown, children: [_jsx(StateDot, { state: failed ? 'error' : 'ongoing' }), _jsx("span", { className: css.title, children: failed ? t('run.failed', { member: data.member }) : t('run.working', { member: data.member }) }), _jsxs("span", { className: css.elapsed, "aria-hidden": true, children: ["\u00B7 ", elapsed === undefined ? t('run.durationUnknown') : formatDurationMs(elapsed)] }), failed && data.error !== undefined && (_jsx("span", { className: css.reason, title: data.error, children: data.error })), !failed && (_jsx(Tooltip, { label: t('run.stop'), side: "bottom", children: _jsx("button", { type: "button", className: css.stop, "aria-label": t('run.stop'), onClick: (event) => {
|
|
51
|
+
event.stopPropagation();
|
|
52
|
+
void cancelMember(data.member);
|
|
53
|
+
}, children: _jsx(IconStopFillMedium, {}) }) }))] }));
|
|
54
|
+
if (failed)
|
|
55
|
+
return _jsx("div", { children: row });
|
|
56
|
+
return (_jsxs("div", { className: speech.speech, style: { '--member-color': memberColor(data.member) }, children: [_jsx("span", { className: speech.avatar, "aria-hidden": true, children: data.member.slice(0, 1).toUpperCase() }), _jsxs("div", { className: `${speech.card} ${css.liveCard}`, children: [row, childSessionId !== undefined && renderMemberOutput?.(childSessionId, data.startedAt)] })] }));
|
|
50
57
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The members tab's 协作设置, built on the host primitives so it reads like
|
|
3
|
+
* the host's own settings: a full-row DisclosureRow (closed by default, the
|
|
4
|
+
* current values as its collapsed summary), SegmentedControls for the
|
|
5
|
+
* choices and Switches. Two groups: 协作 (how members message each other,
|
|
6
|
+
* the hop limit of one chain of messages, pause) and 自动判断 — the room's
|
|
7
|
+
* decision scenarios (decisions.ts), one switch each; report timing carries
|
|
8
|
+
* its threshold and longest wait in a box of its own.
|
|
9
|
+
*/
|
|
10
|
+
import { type ReactNode } from 'react';
|
|
11
|
+
import type { RoomSettings } from '../types.ts';
|
|
12
|
+
import type { MembersViewProps, RoomMutationOutcome } from './slots.ts';
|
|
13
|
+
/** The settings block. */
|
|
14
|
+
export declare function RoomSettingsView({ settings, onChange, t }: {
|
|
15
|
+
readonly settings: RoomSettings;
|
|
16
|
+
readonly onChange: (patch: Partial<RoomSettings>) => Promise<RoomMutationOutcome>;
|
|
17
|
+
readonly t: MembersViewProps['t'];
|
|
18
|
+
}): ReactNode;
|
|
19
|
+
//# sourceMappingURL=RoomSettingsView.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The members tab's 协作设置, built on the host primitives so it reads like
|
|
4
|
+
* the host's own settings: a full-row DisclosureRow (closed by default, the
|
|
5
|
+
* current values as its collapsed summary), SegmentedControls for the
|
|
6
|
+
* choices and Switches. Two groups: 协作 (how members message each other,
|
|
7
|
+
* the hop limit of one chain of messages, pause) and 自动判断 — the room's
|
|
8
|
+
* decision scenarios (decisions.ts), one switch each; report timing carries
|
|
9
|
+
* its threshold and longest wait in a box of its own.
|
|
10
|
+
*/
|
|
11
|
+
import { useState } from 'react';
|
|
12
|
+
import { DisclosureRow, SegmentedControl, Switch } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
13
|
+
import { IconAgentPresetOutlineMedium } from './icons.js';
|
|
14
|
+
import css from './RoomSettingsView.module.css';
|
|
15
|
+
/** The settings block. */
|
|
16
|
+
export function RoomSettingsView({ settings, onChange, t }) {
|
|
17
|
+
const [open, setOpen] = useState(false);
|
|
18
|
+
const [error, setError] = useState(null);
|
|
19
|
+
const change = (patch) => {
|
|
20
|
+
void onChange(patch).then(outcome => { setError(outcome.ok ? null : outcome.message); });
|
|
21
|
+
};
|
|
22
|
+
const judged = settings.reportTiming === 'judge';
|
|
23
|
+
const step = (value, delta, min, max) => Math.min(max, Math.max(min, Math.round((value + delta) * 10) / 10));
|
|
24
|
+
const summary = [
|
|
25
|
+
settings.memberMessages === 'free' ? t('settings.free') : t('settings.confirm'),
|
|
26
|
+
t('settings.hopSummary', { count: settings.hopLimit }),
|
|
27
|
+
...settings.paused ? [t('settings.pausedSummary')] : [],
|
|
28
|
+
].join(' · ');
|
|
29
|
+
return (_jsx("div", { className: css.settings, children: _jsx(DisclosureRow, { icon: _jsx(IconAgentPresetOutlineMedium, {}), title: t('settings.title'), open: open, expandable: true, expandOnRowClick: true, keepContentWhenOpen: true, onToggle: () => { setOpen(value => !value); }, collapsedContent: _jsx("span", { className: settings.paused ? css.valuesPaused : css.values, children: summary }), children: _jsxs("div", { className: css.rows, children: [_jsx("div", { className: css.group, children: t('settings.groupCollab') }), _jsxs("div", { className: css.row, children: [_jsxs("span", { className: css.label, children: [_jsx("b", { children: t('settings.messages') }), _jsx("span", { children: t('settings.messagesHint') })] }), _jsx(SegmentedControl, { id: "room-settings-messages", label: t('settings.messages'), value: settings.memberMessages, options: [{ value: 'free', label: t('settings.free') }, { value: 'confirm', label: t('settings.confirm') }], onChange: (memberMessages) => { change({ memberMessages }); } })] }), _jsxs("div", { className: css.row, children: [_jsxs("span", { className: css.label, children: [_jsx("b", { children: t('settings.hops') }), _jsx("span", { children: t('settings.hopsHint') })] }), _jsxs("span", { className: css.stepper, children: [_jsx("button", { type: "button", "aria-label": t('settings.hopsLess'), disabled: settings.hopLimit <= 2, onClick: () => { change({ hopLimit: settings.hopLimit - 1 }); }, children: "\u2212" }), _jsx("b", { "aria-live": "polite", children: settings.hopLimit }), _jsx("button", { type: "button", "aria-label": t('settings.hopsMore'), disabled: settings.hopLimit >= 50, onClick: () => { change({ hopLimit: settings.hopLimit + 1 }); }, children: "\uFF0B" })] })] }), _jsxs("div", { className: css.row, children: [_jsxs("span", { className: css.label, children: [_jsx("b", { children: t('settings.pause') }), _jsx("span", { children: t('settings.pauseHint') })] }), _jsx(Switch, { checked: settings.paused, label: t('settings.pause'), onChange: (paused) => { change({ paused }); } })] }), _jsxs("div", { className: css.row, children: [_jsxs("span", { className: css.label, children: [_jsx("b", { children: t('settings.helpReminder') }), _jsx("span", { children: t('settings.helpReminderHint') })] }), _jsxs("span", { className: css.stepper, children: [_jsx("button", { type: "button", "aria-label": t('settings.less'), disabled: settings.helpReminderMinutes <= 10, onClick: () => { change({ helpReminderMinutes: settings.helpReminderMinutes - 10 }); }, children: "\u2212" }), _jsx("b", { "aria-live": "polite", children: t('settings.minutes', { count: settings.helpReminderMinutes }) }), _jsx("button", { type: "button", "aria-label": t('settings.more'), disabled: settings.helpReminderMinutes >= 120, onClick: () => { change({ helpReminderMinutes: settings.helpReminderMinutes + 10 }); }, children: "\uFF0B" })] })] }), _jsx("div", { className: css.group, children: t('settings.groupAuto') }), _jsxs("div", { className: css.row, children: [_jsxs("span", { className: css.label, children: [_jsx("b", { children: t('settings.autoFiling') }), _jsx("span", { children: t('settings.autoFilingHint') })] }), _jsx(Switch, { checked: settings.autoTaskFiling, label: t('settings.autoFiling'), onChange: (autoTaskFiling) => { change({ autoTaskFiling }); } })] }), _jsxs("div", { className: css.row, children: [_jsxs("span", { className: css.label, children: [_jsx("b", { children: t('settings.autoReport') }), _jsx("span", { children: t('settings.autoReportHint') })] }), _jsx(Switch, { checked: judged, label: t('settings.autoReport'), onChange: (on) => { change({ reportTiming: on ? 'judge' : 'each' }); } })] }), judged && (_jsxs("div", { className: css.subbox, children: [_jsxs("div", { className: css.subrow, children: [_jsxs("span", { className: css.label, children: [_jsx("b", { children: t('settings.reportThreshold') }), _jsx("span", { children: t('settings.reportThresholdHint') })] }), _jsxs("span", { className: css.stepper, children: [_jsx("button", { type: "button", "aria-label": t('settings.less'), disabled: settings.reportThreshold <= 0.1, onClick: () => { change({ reportThreshold: step(settings.reportThreshold, -0.1, 0.1, 0.9) }); }, children: "\u2212" }), _jsx("b", { "aria-live": "polite", children: settings.reportThreshold.toFixed(1) }), _jsx("button", { type: "button", "aria-label": t('settings.more'), disabled: settings.reportThreshold >= 0.9, onClick: () => { change({ reportThreshold: step(settings.reportThreshold, 0.1, 0.1, 0.9) }); }, children: "\uFF0B" })] })] }), _jsxs("div", { className: css.subrow, children: [_jsxs("span", { className: css.label, children: [_jsx("b", { children: t('settings.reportWait') }), _jsx("span", { children: t('settings.reportWaitHint') })] }), _jsxs("span", { className: css.stepper, children: [_jsx("button", { type: "button", "aria-label": t('settings.less'), disabled: settings.reportWaitMinutes <= 1, onClick: () => { change({ reportWaitMinutes: settings.reportWaitMinutes - 1 }); }, children: "\u2212" }), _jsx("b", { "aria-live": "polite", children: t('settings.minutes', { count: settings.reportWaitMinutes }) }), _jsx("button", { type: "button", "aria-label": t('settings.more'), disabled: settings.reportWaitMinutes >= 30, onClick: () => { change({ reportWaitMinutes: settings.reportWaitMinutes + 1 }); }, children: "\uFF0B" })] })] })] })), error !== null && _jsx("p", { className: css.error, role: "alert", children: error })] }) }) }));
|
|
30
|
+
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Member speech node ('room-speech' keyed renderer): a
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* Member speech node ('room-speech' keyed renderer): a conversation entry —
|
|
3
|
+
* the member's color avatar beside a light card that blocks off one member's
|
|
4
|
+
* turn (several agents take turns in a room, so each turn reads as its own
|
|
5
|
+
* block): a header (name, public role, provider, time), the markdown body —
|
|
6
|
+
* the official MarkdownText, typographically identical to main-agent speech —
|
|
7
|
+
* and a quiet footer row. A
|
|
8
|
+
* body past {@link COLLAPSE_CHARS} may fold to ~10 lines under a bg-base fade
|
|
9
|
+
* with an expand/collapse toggle (the fade expands too). The newest reply to
|
|
10
|
+
* the room starts unfolded — it is what the human is reading — and folds once
|
|
11
|
+
* a newer reply lands; reports to another member and older replies start
|
|
12
|
+
* folded. The human's own expand/collapse wins and is remembered per speech
|
|
13
|
+
* (see {@link foldChoices}). The action row replicates the
|
|
9
14
|
* official MessageIconActions chrome: copy, child-session jump (when the
|
|
10
15
|
* speech carries its handle), honest duration, and a hover-revealed clock.
|
|
11
16
|
* Deliberately absent: branch (forking the room session has undefined
|
|
@@ -13,6 +18,15 @@
|
|
|
13
18
|
*/
|
|
14
19
|
import { type ReactNode } from 'react';
|
|
15
20
|
import type { RoomSpeechViewProps } from './slots.ts';
|
|
21
|
+
/**
|
|
22
|
+
* The human's expand/collapse choices for one room's speeches, by speech seq.
|
|
23
|
+
* A per-viewer convenience in browser storage: unreadable storage (private
|
|
24
|
+
* window, blocked site data) only means the default folding applies.
|
|
25
|
+
*/
|
|
26
|
+
export declare const foldChoices: {
|
|
27
|
+
read(sessionId: string): Record<string, boolean>;
|
|
28
|
+
write(sessionId: string, seq: number, expanded: boolean): void;
|
|
29
|
+
};
|
|
16
30
|
/** The member speech row. */
|
|
17
|
-
export declare function RoomSpeechView({ node, sessionId, roomStore, openSession, t }: RoomSpeechViewProps): ReactNode;
|
|
31
|
+
export declare function RoomSpeechView({ node, sessionId, roomStore, openSession, openDetail, t }: RoomSpeechViewProps): ReactNode;
|
|
18
32
|
//# sourceMappingURL=RoomSpeechView.d.ts.map
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* Member speech node ('room-speech' keyed renderer): a
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
3
|
+
* Member speech node ('room-speech' keyed renderer): a conversation entry —
|
|
4
|
+
* the member's color avatar beside a light card that blocks off one member's
|
|
5
|
+
* turn (several agents take turns in a room, so each turn reads as its own
|
|
6
|
+
* block): a header (name, public role, provider, time), the markdown body —
|
|
7
|
+
* the official MarkdownText, typographically identical to main-agent speech —
|
|
8
|
+
* and a quiet footer row. A
|
|
9
|
+
* body past {@link COLLAPSE_CHARS} may fold to ~10 lines under a bg-base fade
|
|
10
|
+
* with an expand/collapse toggle (the fade expands too). The newest reply to
|
|
11
|
+
* the room starts unfolded — it is what the human is reading — and folds once
|
|
12
|
+
* a newer reply lands; reports to another member and older replies start
|
|
13
|
+
* folded. The human's own expand/collapse wins and is remembered per speech
|
|
14
|
+
* (see {@link foldChoices}). The action row replicates the
|
|
10
15
|
* official MessageIconActions chrome: copy, child-session jump (when the
|
|
11
16
|
* speech carries its handle), honest duration, and a hover-revealed clock.
|
|
12
17
|
* Deliberately absent: branch (forking the room session has undefined
|
|
@@ -14,9 +19,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
14
19
|
*/
|
|
15
20
|
import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
|
|
16
21
|
import { MarkdownText, Tooltip, writeClipboard } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
17
|
-
import { IconCheckOutlineMedium, IconCopyOutlineMedium, IconLinkOutlineMedium } from './icons.js';
|
|
22
|
+
import { IconCheckOutlineMedium, IconChevronDownOutlineMedium, IconCopyOutlineMedium, IconLinkOutlineMedium } from './icons.js';
|
|
23
|
+
import { IconRequestDetailOutline16 } from './icons-local.js';
|
|
18
24
|
import { memberColor } from './member-color.js';
|
|
19
25
|
import { formatClock, formatDurationMs } from './format.js';
|
|
26
|
+
import { formatTokens } from './RoomStatsLine.js';
|
|
20
27
|
import css from './RoomSpeechView.module.css';
|
|
21
28
|
/**
|
|
22
29
|
* Speeches longer than this start collapsed. Chars, not rendered lines: a
|
|
@@ -24,6 +31,35 @@ import css from './RoomSpeechView.module.css';
|
|
|
24
31
|
* lines of prose.
|
|
25
32
|
*/
|
|
26
33
|
const COLLAPSE_CHARS = 600;
|
|
34
|
+
/** Browser storage key of one room's remembered expand/collapse choices. */
|
|
35
|
+
const FOLD_KEY_PREFIX = 'dsh-room:speech-fold:';
|
|
36
|
+
/** Choices kept per room (newest speeches win): bounds the stored map. */
|
|
37
|
+
const FOLD_KEEP = 200;
|
|
38
|
+
/**
|
|
39
|
+
* The human's expand/collapse choices for one room's speeches, by speech seq.
|
|
40
|
+
* A per-viewer convenience in browser storage: unreadable storage (private
|
|
41
|
+
* window, blocked site data) only means the default folding applies.
|
|
42
|
+
*/
|
|
43
|
+
export const foldChoices = {
|
|
44
|
+
read(sessionId) {
|
|
45
|
+
try {
|
|
46
|
+
const raw = globalThis.localStorage?.getItem(FOLD_KEY_PREFIX + sessionId);
|
|
47
|
+
const parsed = raw == null ? undefined : JSON.parse(raw);
|
|
48
|
+
return typeof parsed === 'object' && parsed !== null ? parsed : {};
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
write(sessionId, seq, expanded) {
|
|
55
|
+
try {
|
|
56
|
+
const choices = { ...foldChoices.read(sessionId), [String(seq)]: expanded };
|
|
57
|
+
const kept = Object.keys(choices).map(Number).sort((a, b) => b - a).slice(0, FOLD_KEEP);
|
|
58
|
+
globalThis.localStorage?.setItem(FOLD_KEY_PREFIX + sessionId, JSON.stringify(Object.fromEntries(kept.map(key => [key, choices[key]]))));
|
|
59
|
+
}
|
|
60
|
+
catch { /* a convenience: never fail the view */ }
|
|
61
|
+
},
|
|
62
|
+
};
|
|
27
63
|
/** Localized Markdown chrome (code-fence copy buttons, footnotes) over the room namespace. */
|
|
28
64
|
function markdownLabels(t) {
|
|
29
65
|
return {
|
|
@@ -32,10 +68,11 @@ function markdownLabels(t) {
|
|
|
32
68
|
};
|
|
33
69
|
}
|
|
34
70
|
/** The member speech row. */
|
|
35
|
-
export function RoomSpeechView({ node, sessionId, roomStore, openSession, t }) {
|
|
71
|
+
export function RoomSpeechView({ node, sessionId, roomStore, openSession, openDetail, t }) {
|
|
36
72
|
const data = node.data;
|
|
37
73
|
const [copied, setCopied] = useState(false);
|
|
38
|
-
|
|
74
|
+
// The human's own choice for this speech (remembered), else the default.
|
|
75
|
+
const [chosen, setChosen] = useState(() => foldChoices.read(sessionId)[String(node.data.seq)]);
|
|
39
76
|
const copyTimer = useRef(null);
|
|
40
77
|
useEffect(() => () => {
|
|
41
78
|
if (copyTimer.current !== null)
|
|
@@ -47,8 +84,27 @@ export function RoomSpeechView({ node, sessionId, roomStore, openSession, t }) {
|
|
|
47
84
|
? undefined
|
|
48
85
|
: member.kind === 'main-agent' ? t('member.kind.main') : member.provider;
|
|
49
86
|
const child = data.childSessionId;
|
|
87
|
+
// The run's billed tokens, as the host's 用量 chip shows them (recorded
|
|
88
|
+
// when the run settles; absent for runs the producer reported none for).
|
|
89
|
+
const execution = data.runId === undefined ? undefined : state?.executions?.find(entry => entry.id === data.runId);
|
|
90
|
+
// The billed total is the sum of the disjoint buckets when they were
|
|
91
|
+
// recorded (an older record's total may leave the cache out).
|
|
92
|
+
const usage = execution?.usage;
|
|
93
|
+
const tokens = usage === undefined ? execution?.tokens
|
|
94
|
+
: usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0) + usage.outputTokens;
|
|
95
|
+
// The newest speech keeps its action row shown; older rows reveal on hover
|
|
96
|
+
// (the host's turn-tail rule). A speech newer than the cached state's is the newest.
|
|
97
|
+
const newestSpeech = data.seq >= (state?.latestSpeechSeq ?? data.seq);
|
|
50
98
|
const collapsible = data.text.length > COLLAPSE_CHARS;
|
|
99
|
+
// The newest reply to the room reads unfolded. A reply newer than the
|
|
100
|
+
// cached state's (the refresh is debounced) is the newest already.
|
|
101
|
+
const newestReply = data.reportedTo === undefined && data.seq >= (state?.latestReplySeq ?? data.seq);
|
|
102
|
+
const expanded = chosen ?? newestReply;
|
|
51
103
|
const clamped = collapsible && !expanded;
|
|
104
|
+
const toggle = (next) => {
|
|
105
|
+
setChosen(next);
|
|
106
|
+
foldChoices.write(sessionId, data.seq, next);
|
|
107
|
+
};
|
|
52
108
|
const labels = useMemo(() => markdownLabels(t), [t]);
|
|
53
109
|
const copy = () => {
|
|
54
110
|
void writeClipboard(data.text).then((ok) => {
|
|
@@ -58,5 +114,32 @@ export function RoomSpeechView({ node, sessionId, roomStore, openSession, t }) {
|
|
|
58
114
|
copyTimer.current = setTimeout(() => { setCopied(false); }, 1000);
|
|
59
115
|
});
|
|
60
116
|
};
|
|
61
|
-
|
|
117
|
+
const color = memberColor(data.member);
|
|
118
|
+
return (_jsxs("div", { className: css.speech, "data-time-hover-root": "", "data-actions-reveal": newestSpeech ? 'always' : 'hover', style: { '--member-color': color }, children: [_jsx("span", { className: css.avatar, "aria-hidden": true, children: data.member.slice(0, 1).toUpperCase() }), _jsxs("div", { className: css.stack, children: [_jsxs("div", { className: css.card, children: [_jsxs("div", { className: css.identity, children: [_jsx("span", { className: css.name, children: data.member }), member?.role !== undefined && _jsx("span", { className: css.role, children: member.role }), provider !== undefined && provider !== '' && _jsx("span", { className: css.provider, children: provider }), data.interrupted !== undefined && _jsx("span", { className: css.interrupted, children: t(data.interrupted === 'cancelled' ? 'speech.stopped' : 'speech.failed') })] }), _jsxs("div", { className: clamped ? `${css.body} ${css.clamped}` : css.body, children: [_jsx(MarkdownText, { text: data.text, labels: labels }), clamped && _jsx("div", { className: css.fade, "aria-hidden": true, onClick: () => { toggle(true); } }), collapsible && (_jsx("div", { className: css.foldBar, children: _jsx(Tooltip, { label: expanded ? t('speech.collapse') : t('speech.expand'), side: "bottom", children: _jsx("button", { type: "button", className: css.foldToggle, "aria-label": expanded ? t('speech.collapse') : t('speech.expand'), "aria-expanded": expanded, onClick: () => { toggle(!expanded); }, children: _jsx(IconChevronDownOutlineMedium, {}) }) }) }))] }), data.reportedTo !== undefined && (_jsx("span", { className: css.reported, children: t('speech.reportedTo', { member: data.reportedTo }) }))] }), _jsxs("div", { className: css.actions, children: [_jsx(Tooltip, { label: copied ? t('action.copied') : t('action.copy'), side: "bottom", children: _jsx("button", { type: "button", className: css.action, "aria-label": copied ? t('action.copied') : t('action.copy'), onClick: copy, children: copied ? _jsx(IconCheckOutlineMedium, {}) : _jsx(IconCopyOutlineMedium, {}) }) }), child !== undefined && (_jsx(Tooltip, { label: t('speech.jump'), side: "bottom", children: _jsx("button", { type: "button", className: css.action, "aria-label": t('speech.jump'), onClick: () => { openSession(child); }, children: _jsx(IconLinkOutlineMedium, {}) }) })), child !== undefined && data.messageId !== undefined && openDetail !== undefined && (_jsx(Tooltip, { label: t('speech.detail'), side: "bottom", children: _jsx("button", { type: "button", className: css.action, "aria-label": t('speech.detail'), onClick: () => { openDetail(child, data.messageId); }, children: _jsx(IconRequestDetailOutline16, {}) }) })), tokens !== undefined && tokens > 0
|
|
119
|
+
? _jsx(UsageChip, { tokens: tokens, execution: execution, provider: member?.kind === 'cli' ? member.provider : undefined, durationMs: data.durationMs, t: t })
|
|
120
|
+
: data.durationMs !== undefined && _jsx("span", { className: css.meta, children: formatDurationMs(data.durationMs) }), _jsx("span", { className: css.meta, children: formatClock(data.time) })] })] })] }));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 用量 with its breakdown on hover or focus — the host's turn usage panel:
|
|
124
|
+
* total, provider / model, cache hit, the disjoint input buckets, output, and
|
|
125
|
+
* this round's run time. Rows the harness did not report are left out.
|
|
126
|
+
*/
|
|
127
|
+
function UsageChip({ tokens, execution, provider, durationMs, t }) {
|
|
128
|
+
const [open, setOpen] = useState(false);
|
|
129
|
+
const usage = execution?.usage;
|
|
130
|
+
const billed = usage === undefined ? 0 : usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0);
|
|
131
|
+
const exact = (n) => n.toLocaleString('en-US');
|
|
132
|
+
const route = [provider, execution?.model].filter(Boolean).join('/');
|
|
133
|
+
const rows = [
|
|
134
|
+
...route === '' ? [] : [[t('usage.route'), route]],
|
|
135
|
+
...usage !== undefined && billed > 0 ? [[t('usage.cacheHit'), `${Math.round((usage.cacheReadTokens ?? 0) / billed * 1000) / 10}%`]] : [],
|
|
136
|
+
...usage === undefined ? [] : [
|
|
137
|
+
[t('usage.uncached'), `${exact(usage.inputTokens)} tok`],
|
|
138
|
+
...usage.cacheReadTokens === undefined ? [] : [[t('usage.cacheRead'), `${exact(usage.cacheReadTokens)} tok`]],
|
|
139
|
+
...usage.cacheWriteTokens === undefined ? [] : [[t('usage.cacheWrite'), `${exact(usage.cacheWriteTokens)} tok`]],
|
|
140
|
+
[t('usage.output'), `${exact(usage.outputTokens)} tok`],
|
|
141
|
+
],
|
|
142
|
+
...durationMs === undefined ? [] : [[t('usage.duration'), formatDurationMs(durationMs)]],
|
|
143
|
+
];
|
|
144
|
+
return (_jsxs("span", { className: css.usage, onMouseEnter: () => { setOpen(true); }, onMouseLeave: () => { setOpen(false); }, children: [_jsx("button", { type: "button", className: css.usageChip, "aria-expanded": open, onFocus: () => { setOpen(true); }, onBlur: () => { setOpen(false); }, children: t('speech.usage', { tokens: formatTokens(tokens) }) }), open && (_jsxs("span", { className: css.usagePanel, role: "tooltip", children: [_jsxs("span", { className: css.usageHead, children: [_jsx("span", { children: t('usage.title') }), _jsxs("span", { children: [exact(tokens), " tok"] })] }), rows.map(([label, value]) => _jsxs("span", { className: css.usageRow, children: [_jsx("span", { children: label }), _jsx("span", { children: value })] }, label))] }))] }));
|
|
62
145
|
}
|
|
@@ -9,14 +9,17 @@
|
|
|
9
9
|
* degrade). Unlike the official row there is no window-fold fallback — the
|
|
10
10
|
* takeover has no business re-deriving what the host already projects.
|
|
11
11
|
*
|
|
12
|
-
* Semantics: the figures are the ROOM SESSION's own — its main
|
|
13
|
-
* turns and billing.
|
|
14
|
-
*
|
|
12
|
+
* Semantics: the projected figures are the ROOM SESSION's own — its main
|
|
13
|
+
* agent's turns and billing. Members consume in their own child sessions, so
|
|
14
|
+
* when the room has member runs the row adds a members chip (how many
|
|
15
|
+
* members worked, their billed tokens) whose breakdown lists each member's
|
|
16
|
+
* model, rounds, tokens and run time from the room's execution records.
|
|
15
17
|
*/
|
|
16
18
|
import { type ReactNode } from 'react';
|
|
17
19
|
import type { UseProjection } from '@deepseek-ai/dsh-api-session-controller/client';
|
|
18
20
|
import type { TokenUsageProjection } from '@deepseek-ai/dsh-token-meter/client';
|
|
19
21
|
import type { RoomComposerProps } from './slots.ts';
|
|
22
|
+
import type { RoomState } from '../types.ts';
|
|
20
23
|
/**
|
|
21
24
|
* Compact token count: 517 / 12.2K / 517K / 1.2M (one decimal under three
|
|
22
25
|
* digits). Copied from the official StatsLine.
|
|
@@ -52,10 +55,28 @@ export declare function billedInputTokens(usage: TokenUsageProjection): number;
|
|
|
52
55
|
* @returns rounded integer percent, or null when no input was billed.
|
|
53
56
|
*/
|
|
54
57
|
export declare function cacheHitPercent(usage: TokenUsageProjection): number | null;
|
|
55
|
-
/**
|
|
58
|
+
/** One member's share of the room's work. */
|
|
59
|
+
export interface MemberUsage {
|
|
60
|
+
readonly member: string;
|
|
61
|
+
readonly model?: string;
|
|
62
|
+
readonly rounds: number;
|
|
63
|
+
readonly tokens: number;
|
|
64
|
+
readonly elapsedMs: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Per-member usage from the room's execution records: every run but the
|
|
68
|
+
* main agent's (its turns are the projected figures already), in roster
|
|
69
|
+
* order. Members without a run do not appear.
|
|
70
|
+
* @param state - the replayed room state.
|
|
71
|
+
* @returns one row per member that ran.
|
|
72
|
+
*/
|
|
73
|
+
export declare function memberUsage(state: RoomState | undefined): readonly MemberUsage[];
|
|
74
|
+
/** Props: the projection read seat, the room state, and the room locale seat. */
|
|
56
75
|
export interface RoomStatsLineProps {
|
|
57
76
|
readonly useProjection: UseProjection;
|
|
77
|
+
/** The room's replayed state: the members chip reads its execution records. */
|
|
78
|
+
readonly state?: RoomState | undefined;
|
|
58
79
|
readonly t: RoomComposerProps['t'];
|
|
59
80
|
}
|
|
60
|
-
export declare const RoomStatsLine: import("react").MemoExoticComponent<({ useProjection, t }: RoomStatsLineProps) => ReactNode>;
|
|
81
|
+
export declare const RoomStatsLine: import("react").MemoExoticComponent<({ useProjection, state, t }: RoomStatsLineProps) => ReactNode>;
|
|
61
82
|
//# sourceMappingURL=RoomStatsLine.d.ts.map
|