@khorsheed/dsh-room 0.2.1 → 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 +59 -0
- package/README.en.md +29 -62
- package/README.i18n.yaml +2 -2
- package/README.md +28 -61
- package/lib/client.js +34057 -6483
- 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 +2517 -363
- package/lib/typert.remote-client.d.ts +53 -9
- package/lib/typert.remote-client.js +1909 -280
- 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 +228 -37
- 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/preset-visibility.d.ts +12 -2
- package/lib/types/client/preset-visibility.js +54 -13
- package/lib/types/client/slots.d.ts +124 -51
- 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 +27 -29
- package/lib/invariant.js +0 -26
- 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
- package/lib/types/invariant.d.ts +0 -16
- package/lib/types/invariant.js +0 -24
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The in-card removal confirmation: replaces the browser's native confirm
|
|
4
|
+
* with a layer anchored on the member's own card that states what removing
|
|
5
|
+
* THIS member will actually do — stop its running round, cancel its queued
|
|
6
|
+
* messages, tell whoever waits for its
|
|
7
|
+
* report — and then does exactly that (RoomService.removeMember). An idle
|
|
8
|
+
* member with nothing pending gets one sentence. The current coordinator
|
|
9
|
+
* cannot be removed: the layer says why and how instead of offering a
|
|
10
|
+
* button that would only fail. Esc and an outside click cancel; focus opens
|
|
11
|
+
* on 取消 so a stray Enter never removes anyone.
|
|
12
|
+
*/
|
|
13
|
+
import { useEffect, useRef, useState } from 'react';
|
|
14
|
+
import css from './RemoveMemberConfirm.module.css';
|
|
15
|
+
/**
|
|
16
|
+
* Read a removal's consequences from the room state the members tab holds.
|
|
17
|
+
* @param state - the replayed room state.
|
|
18
|
+
* @param member - the member about to be removed.
|
|
19
|
+
* @returns the impact summary.
|
|
20
|
+
*/
|
|
21
|
+
export function removalImpact(state, member) {
|
|
22
|
+
const run = state.runs.find(entry => entry.member === member.name && entry.state === 'running');
|
|
23
|
+
const id = member.id;
|
|
24
|
+
const open = (state.deliveries ?? []).filter(row => row.memberId === id && (row.status === 'queued' || row.status === 'running'));
|
|
25
|
+
const coordinator = state.coordinator === undefined
|
|
26
|
+
? state.members.find(entry => entry.kind === 'main-agent')
|
|
27
|
+
: state.members.find(entry => entry.id === state.coordinator.memberId);
|
|
28
|
+
const waiting = new Set();
|
|
29
|
+
for (const row of open) {
|
|
30
|
+
const target = row.plan !== undefined ? coordinator : state.members.find(entry => entry.id !== undefined && entry.id === row.replyTo);
|
|
31
|
+
if (target !== undefined && target.name !== member.name)
|
|
32
|
+
waiting.add(target.name);
|
|
33
|
+
}
|
|
34
|
+
const current = open.find(row => row.status === 'running' && (run?.runId === undefined || row.id === run.runId));
|
|
35
|
+
return {
|
|
36
|
+
running: run === undefined ? undefined : (current?.text.trim().split('\n', 1)[0] ?? '').slice(0, 60),
|
|
37
|
+
queued: open.filter(row => row.status === 'queued').length,
|
|
38
|
+
waiting: [...waiting],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** The confirmation layer. */
|
|
42
|
+
export function RemoveMemberConfirm({ member, impact, coordinator, onConfirm, onCancel, t, }) {
|
|
43
|
+
const ref = useRef(null);
|
|
44
|
+
const [busy, setBusy] = useState(false);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const onKey = (event) => { if (event.key === 'Escape')
|
|
47
|
+
onCancel(); };
|
|
48
|
+
const onDown = (event) => {
|
|
49
|
+
if (ref.current !== null && !ref.current.contains(event.target))
|
|
50
|
+
onCancel();
|
|
51
|
+
};
|
|
52
|
+
document.addEventListener('keydown', onKey);
|
|
53
|
+
document.addEventListener('mousedown', onDown);
|
|
54
|
+
return () => {
|
|
55
|
+
document.removeEventListener('keydown', onKey);
|
|
56
|
+
document.removeEventListener('mousedown', onDown);
|
|
57
|
+
};
|
|
58
|
+
}, [onCancel]);
|
|
59
|
+
const title = t('remove.title', { member: member.name });
|
|
60
|
+
if (coordinator) {
|
|
61
|
+
return (_jsxs("div", { ref: ref, className: css.layer, role: "dialog", "aria-label": title, children: [_jsx("p", { className: css.title, children: title }), _jsx("p", { className: css.body, children: t('remove.coordinator') }), _jsx("div", { className: css.actions, children: _jsx("button", { type: "button", className: css.secondary, autoFocus: true, onClick: onCancel, children: t('remove.ok') }) })] }));
|
|
62
|
+
}
|
|
63
|
+
const lines = [];
|
|
64
|
+
if (impact.running !== undefined) {
|
|
65
|
+
lines.push(impact.running === '' ? t('remove.running') : t('remove.runningTask', { task: impact.running }));
|
|
66
|
+
}
|
|
67
|
+
if (impact.queued > 0)
|
|
68
|
+
lines.push(t('remove.queued', { count: impact.queued }));
|
|
69
|
+
if (impact.waiting.length > 0)
|
|
70
|
+
lines.push(t('remove.waiting', { members: impact.waiting.join('、') }));
|
|
71
|
+
const confirm = async () => {
|
|
72
|
+
if (busy)
|
|
73
|
+
return;
|
|
74
|
+
setBusy(true);
|
|
75
|
+
try {
|
|
76
|
+
await onConfirm();
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
setBusy(false);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
return (_jsxs("div", { ref: ref, className: css.layer, role: "dialog", "aria-label": title, children: [_jsx("p", { className: css.title, children: title }), lines.length === 0
|
|
83
|
+
? _jsx("p", { className: css.body, children: t('remove.idle') })
|
|
84
|
+
: _jsx("ul", { className: css.list, children: lines.map((line, index) => _jsx("li", { children: line }, index)) }), _jsx("p", { className: css.note, children: t('remove.keep') }), _jsxs("div", { className: css.actions, children: [_jsx("button", { type: "button", className: css.secondary, autoFocus: true, onClick: onCancel, children: t('remove.cancel') }), _jsx("button", { type: "button", className: css.danger, disabled: busy, onClick: () => { void confirm(); }, children: impact.running !== undefined ? t('remove.stopAndRemove') : t('remove.confirm') })] })] }));
|
|
85
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A member's tool approval ('room-approval' keyed renderer): one line in the
|
|
3
|
+
* room's conversation — 「⚠ coco 要你确认:调用 video_render」 with the reason
|
|
4
|
+
* — and [允许一次] [拒绝] while it waits. The answer folds in place through the
|
|
5
|
+
* journal (allowed / rejected / cancelled when the call ended first).
|
|
6
|
+
*/
|
|
7
|
+
import { type ReactNode } from 'react';
|
|
8
|
+
import type { RoomApprovalViewProps } from './slots.ts';
|
|
9
|
+
/** The approval line. */
|
|
10
|
+
export declare function RoomApprovalView({ node, answerApproval, t }: RoomApprovalViewProps): ReactNode;
|
|
11
|
+
//# sourceMappingURL=RoomApprovalView.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* A member's tool approval ('room-approval' keyed renderer): one line in the
|
|
4
|
+
* room's conversation — 「⚠ coco 要你确认:调用 video_render」 with the reason
|
|
5
|
+
* — and [允许一次] [拒绝] while it waits. The answer folds in place through the
|
|
6
|
+
* journal (allowed / rejected / cancelled when the call ended first).
|
|
7
|
+
*/
|
|
8
|
+
import { useState } from 'react';
|
|
9
|
+
import css from './RoomRelayView.module.css';
|
|
10
|
+
/** The approval line. */
|
|
11
|
+
export function RoomApprovalView({ node, answerApproval, t }) {
|
|
12
|
+
const data = node.data;
|
|
13
|
+
const [busy, setBusy] = useState(false);
|
|
14
|
+
const [error, setError] = useState(undefined);
|
|
15
|
+
const answer = async (outcome) => {
|
|
16
|
+
if (busy)
|
|
17
|
+
return;
|
|
18
|
+
setBusy(true);
|
|
19
|
+
try {
|
|
20
|
+
setError(await answerApproval(data.approvalId, outcome));
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
setBusy(false);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const pending = data.state === 'pending';
|
|
27
|
+
return (_jsxs("div", { className: css.row, "data-state": pending ? 'pending' : 'resolved', children: [_jsxs("span", { className: css.text, children: [t('approval.line', { member: data.member, tool: data.tool }), data.reason === undefined ? '' : `(${data.reason})`, pending ? '' : ` ${t(`approval.state.${data.state}`)}`, error === undefined ? '' : ` ${error}`] }), pending && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.action, disabled: busy, onClick: () => { void answer('allowed-once'); }, children: t('approval.allow') }), _jsx("button", { type: "button", className: css.action, disabled: busy, onClick: () => { void answer('rejected'); }, children: t('approval.reject') })] }))] }));
|
|
28
|
+
}
|
|
@@ -17,5 +17,5 @@ import type { RoomComposerMatch, RoomComposerProps } from './slots.ts';
|
|
|
17
17
|
*/
|
|
18
18
|
export declare function selectRoomComposer(owner: ComposerChainProps, isRoomCached: (sessionId: SessionId) => boolean): RoomComposerMatch | null;
|
|
19
19
|
/** The room composer takeover component. */
|
|
20
|
-
export declare function RoomComposer({ sessionId, roomStore, submit, stop,
|
|
20
|
+
export declare function RoomComposer({ sessionId, roomStore, submit, stop, openTasks, taskTags, openPlanSession, reconcileDelivery, roomCwd, invite, listProviders, browseDirectory, modelSurface, renderHarnessModelPicker, modelDirectory, renderMemberConfiguration, renderMemberInbox, stopMember, openReference, listSessions, insertSessionReference, useSession, useProjection, useInput, inputActions, t, }: RoomComposerProps): ReactNode;
|
|
21
21
|
//# sourceMappingURL=RoomComposer.d.ts.map
|
|
@@ -1,31 +1,45 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Room composer: the `conversation.composer` chain takeover for room
|
|
4
4
|
* sessions. Replaces the official input bar while the selector holds (the
|
|
5
5
|
* official bar is display:none underneath), so it owns the whole interaction:
|
|
6
6
|
* a textarea (Enter sends, Shift+Enter newlines), a send button, and the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* @ menu in two groups: this room's members (pure addressing — invitation
|
|
8
|
+
* lives in the members tab) and other sessions (the host's session-reference
|
|
9
|
+
* candidates; a pick inserts the host's own session chip, the mention the
|
|
10
|
+
* room's agent asks for help through — see at-menu.ts). Addressing is the union of two
|
|
9
11
|
* channels: leading `@name` tokens (the host's parseMentions grammar,
|
|
10
12
|
* stripped from the dispatched text) and the mention menu's picks (a pick
|
|
11
13
|
* records explicit addressing wherever the `@name` sits in the sentence —
|
|
12
14
|
* the text is dispatched verbatim, the picks ride the postMessage request's
|
|
13
15
|
* `targets`). Hand-typed mid-sentence mentions without a menu pick stay
|
|
14
|
-
* prose: they address nobody.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* machine
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
16
|
+
* prose: they address nobody. EVERY send — bare text included — routes
|
|
17
|
+
* through room's own dispatch (the postMessage Remote; the host routes bare
|
|
18
|
+
* text to the current coordinator), never through `inputActions.submit()`:
|
|
19
|
+
* the machine's busy admission would enqueue behind a running coordinator
|
|
20
|
+
* and serialize what the dispatch engine deliberately runs in parallel. A
|
|
21
|
+
* structured rejection (unknown targets) shows as an inline error line.
|
|
22
|
+
*
|
|
23
|
+
* The DRAFT, though, lives in the session's official input machine: the
|
|
24
|
+
* chain occupant's standard session kit (`useInput`/`inputActions`) is this
|
|
25
|
+
* component's draft store, so the takeover and the display:none fallback
|
|
26
|
+
* share one draft — text typed during the first-pull election window
|
|
27
|
+
* carries over, drafts persist across navigation, and external draft
|
|
28
|
+
* writers (quote-anything's 引用到当前会话) land here with zero
|
|
29
|
+
* room-specific code. Reference chips in the machine (a quote, any trigger
|
|
30
|
+
* source's pick) render as their own row above the textarea — label, click
|
|
31
|
+
* to preview through the chip's source, × to remove — and the textarea holds
|
|
32
|
+
* only the typed text around them; edits map back as detect-span
|
|
33
|
+
* replacements, so typing never flattens a chip (draft-chips.ts). A host
|
|
34
|
+
* without the machine's chip view or `insertText` keeps the old plain form:
|
|
35
|
+
* chips project to their clipboard text and editing flattens them.
|
|
22
36
|
*
|
|
23
37
|
* Taking over the chain inherits the official bar's environment duties (the
|
|
24
38
|
* member-channel rule, local-agent 84a2ed0): a pending interaction yields (the
|
|
25
39
|
* selector declines while `owner.pendingInteraction` is set, so the priority-1
|
|
26
40
|
* ApprovalPanel elects), and everything the hidden fallback tree carried is
|
|
27
41
|
* re-homed INTO this component — the main agent's turn Stop (the send circle
|
|
28
|
-
* swaps while `running`), the
|
|
42
|
+
* swaps while `running`), the task capsule (RoomTaskCapsule), the main
|
|
29
43
|
* agent's todo strip (RoomTodoStrip, the `todos` projection), the queued-
|
|
30
44
|
* messages strip (RoomQueueStrip, the `inbox` projection — the 0.1.5 session
|
|
31
45
|
* snapshot's queue on the old line), the session
|
|
@@ -34,18 +48,25 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
34
48
|
* itself is single-owner and cannot be re-hosted, so the picker is our own
|
|
35
49
|
* trigger on the same directory; a host without ui-model-selection gets no
|
|
36
50
|
* picker).
|
|
51
|
+
*
|
|
52
|
+
* The task page's 「追问」 / 「+ 新任务」 put a removable chip on the card
|
|
53
|
+
* (「任务 N」 / 「新任务」, the shared RoomTaskTags store) and focus the
|
|
54
|
+
* textarea; the next send carries it as postMessage's `task` and clears it.
|
|
55
|
+
* Removing the chip leaves the filing to dsh.
|
|
37
56
|
*/
|
|
38
57
|
import { coordinatorMember } from '../journal.js';
|
|
39
|
-
import { useRef, useState, useSyncExternalStore, } from 'react';
|
|
58
|
+
import { useEffect, useRef, useState, useSyncExternalStore, } from 'react';
|
|
59
|
+
import { chipDetectIndex, draftSegments, freeText, planFreeEdit } from './draft-chips.js';
|
|
60
|
+
import { detectOffset, memberMention, otherRows, rankByMatch, sessionMention, sessionTitle, shortTime } from './at-menu.js';
|
|
40
61
|
import { memberColor } from './member-color.js';
|
|
41
62
|
import { RoomStatsLine } from './RoomStatsLine.js';
|
|
42
|
-
import {
|
|
63
|
+
import { RoomTaskCapsule } from './RoomTaskCapsule.js';
|
|
43
64
|
import { RoomTodoStrip } from './RoomTodoStrip.js';
|
|
44
|
-
import { RoomActivityDock } from './RoomActivityDock.js';
|
|
45
65
|
import { RoomQueueStrip } from './RoomQueueStrip.js';
|
|
46
|
-
import { RoomRecoveryView } from './RoomRecoveryView.js';
|
|
47
66
|
import { RoomModelPicker } from './RoomModelPicker.js';
|
|
67
|
+
import { IconChevronRightOutlineMedium } from './icons.js';
|
|
48
68
|
import css from './RoomComposer.module.css';
|
|
69
|
+
const NO_TAGS = () => () => { };
|
|
49
70
|
const EMPTY_LEGACY_QUEUE = [];
|
|
50
71
|
const QUEUE_PREVIEW_CHARS = 200;
|
|
51
72
|
/** The queue row's flat preview: attachment blocks dropped, the rest joined and whitespace-collapsed, 200 chars max (the official QueueDock's rule). */
|
|
@@ -90,10 +111,24 @@ export function selectRoomComposer(owner, isRoomCached) {
|
|
|
90
111
|
const sessionId = owner.sessionId ?? owner.session?.sessionId;
|
|
91
112
|
return sessionId !== undefined && isRoomCached(sessionId) ? { room: true } : null;
|
|
92
113
|
}
|
|
114
|
+
const NO_CHIPS = [];
|
|
93
115
|
/** The room composer takeover component. */
|
|
94
|
-
export function RoomComposer({ sessionId, roomStore, submit, stop,
|
|
116
|
+
export function RoomComposer({ sessionId, roomStore, submit, stop, openTasks, taskTags, openPlanSession, reconcileDelivery, roomCwd, invite, listProviders, browseDirectory, modelSurface, renderHarnessModelPicker, modelDirectory, renderMemberConfiguration, renderMemberInbox, stopMember, openReference, listSessions, insertSessionReference, useSession, useProjection, useInput, inputActions, t, }) {
|
|
95
117
|
const state = useSyncExternalStore(roomStore.subscribe, () => roomStore.getCached(sessionId));
|
|
96
|
-
const
|
|
118
|
+
const tag = useSyncExternalStore(taskTags?.subscribe ?? NO_TAGS, () => taskTags?.get(sessionId));
|
|
119
|
+
// The draft is the session's official input machine (the module header):
|
|
120
|
+
// shared with the hidden fallback bar, persistent across navigation, and
|
|
121
|
+
// writable by external drafters (quote) with zero room-specific code.
|
|
122
|
+
const draft = useInput(input => input.draft);
|
|
123
|
+
// The machine's chips (absent before the occurrence view shipped) and the
|
|
124
|
+
// span-guarded text edit (absent on older faces): both present = chip mode.
|
|
125
|
+
const occurrences = useInput(input => input.occurrences);
|
|
126
|
+
const chipEdit = inputActions;
|
|
127
|
+
const chipMode = occurrences !== undefined && occurrences.length > 0
|
|
128
|
+
&& chipEdit.insertText !== undefined && chipEdit.captureInsertion !== undefined;
|
|
129
|
+
const chips = chipMode ? occurrences : NO_CHIPS;
|
|
130
|
+
// What the textarea shows: the typed text around the chips.
|
|
131
|
+
const value = chipMode ? freeText(draft, chips) : draft;
|
|
97
132
|
const [busy, setBusy] = useState(false);
|
|
98
133
|
const [error, setError] = useState(null);
|
|
99
134
|
const [mention, setMention] = useState(null);
|
|
@@ -101,10 +136,59 @@ export function RoomComposer({ sessionId, roomStore, submit, stop, addTask, clos
|
|
|
101
136
|
`@name` sits (see the module header); cleared with the draft on send. */
|
|
102
137
|
const [picked, setPicked] = useState([]);
|
|
103
138
|
const areaRef = useRef(null);
|
|
139
|
+
// IME composition (the official ui-primitives observeComposition rule):
|
|
140
|
+
// Enter while composing, or the one that closes a composition (Safari
|
|
141
|
+
// fires compositionend before its keydown), commits the candidate — it
|
|
142
|
+
// never sends. keyCode 229 covers engines without isComposing.
|
|
143
|
+
const composing = useRef(false);
|
|
144
|
+
const justComposed = useRef(false);
|
|
104
145
|
const members = state?.members ?? [];
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
146
|
+
// The other-sessions group: the host's candidates for the open query (the
|
|
147
|
+
// recent list plus the query's own lookup, merged). A room starts folded;
|
|
148
|
+
// its arrow (or →) shows its members, and a typed query matches members by name.
|
|
149
|
+
const [sessionPool, setSessionPool] = useState([]);
|
|
150
|
+
const [expanded, setExpanded] = useState(new Set());
|
|
151
|
+
const toggleRoom = (id, open) => {
|
|
152
|
+
setExpanded((current) => {
|
|
153
|
+
const next = new Set(current);
|
|
154
|
+
if (open ?? !next.has(id))
|
|
155
|
+
next.add(id);
|
|
156
|
+
else
|
|
157
|
+
next.delete(id);
|
|
158
|
+
return next;
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
const query = mention?.query;
|
|
162
|
+
// The injected face is rebuilt on every render (and the room re-renders
|
|
163
|
+
// every second while anything runs): read it through a ref so only a new
|
|
164
|
+
// query cancels the lookup — keyed on the function, no lookup ever finished.
|
|
165
|
+
const listRef = useRef(listSessions);
|
|
166
|
+
listRef.current = listSessions;
|
|
167
|
+
const canList = listSessions !== undefined;
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
const list = listRef.current;
|
|
170
|
+
if (query === undefined || list === undefined)
|
|
171
|
+
return;
|
|
172
|
+
const abort = new AbortController();
|
|
173
|
+
const timer = setTimeout(() => {
|
|
174
|
+
list(query, abort.signal).then((found) => { if (!abort.signal.aborted)
|
|
175
|
+
setSessionPool(found); }).catch(() => { });
|
|
176
|
+
}, query === '' ? 0 : 120);
|
|
177
|
+
return () => { clearTimeout(timer); abort.abort(); };
|
|
178
|
+
}, [query, canList]);
|
|
179
|
+
// Outside sessions kept on the member list sit in the members group.
|
|
180
|
+
const linked = state?.linked ?? [];
|
|
181
|
+
const excluded = new Set([
|
|
182
|
+
sessionId, ...linked.map(entry => entry.sessionId),
|
|
183
|
+
...members.flatMap(member => member.childSessionId === undefined ? [] : [member.childSessionId]),
|
|
184
|
+
]);
|
|
185
|
+
const memberRows = mention === null ? [] : [
|
|
186
|
+
...rankByMatch(mention.query, members, member => member.name).map(member => ({ kind: 'member', member })),
|
|
187
|
+
...rankByMatch(mention.query, linked, entry => entry.title)
|
|
188
|
+
.map(entry => ({ kind: 'session', candidate: { sessionId: entry.sessionId, label: entry.title, mention: sessionMention(entry.sessionId, entry.title), linked: true } })),
|
|
189
|
+
];
|
|
190
|
+
const others = mention === null ? [] : otherRows(mention.query, sessionPool, excluded, expanded);
|
|
191
|
+
const candidates = [...memberRows, ...others];
|
|
108
192
|
// The inherited environment state: the room's own main-agent turn flag
|
|
109
193
|
// (drives the Send/Stop swap) and the still-queued inbox rows (the official
|
|
110
194
|
// queue dock's data, re-homed as a read-only strip). alpha.2 moved the
|
|
@@ -124,25 +208,99 @@ export function RoomComposer({ sessionId, roomStore, submit, stop, addTask, clos
|
|
|
124
208
|
.filter(row => row.placement === 'queued')
|
|
125
209
|
.map(row => ({ id: String(row.id), preview: row.preview }))
|
|
126
210
|
: nextTurn.map(row => ({ id: String(row.id), preview: previewOf(row.content) }));
|
|
211
|
+
// The coordinator's own room deliveries: a round a restart interrupted
|
|
212
|
+
// blocks the ones after it until the human reconciles or stops its task —
|
|
213
|
+
// say so, instead of a silent queue.
|
|
214
|
+
const coordinatorRows = (state?.deliveries ?? []).filter(row => row.memberId === coordinator?.id && (row.status === 'queued' || row.status === 'uncertain'));
|
|
215
|
+
const blocked = coordinatorRows.some(row => row.status === 'uncertain');
|
|
216
|
+
const coordinatorQueue = coordinatorRows.map(row => ({
|
|
217
|
+
id: row.id,
|
|
218
|
+
preview: row.status === 'uncertain' ? t('job.queueUncertain', { text: row.text }) : blocked ? t('job.queueBlocked', { text: row.text }) : row.text,
|
|
219
|
+
}));
|
|
127
220
|
const resize = (area) => {
|
|
128
221
|
area.style.height = 'auto';
|
|
129
222
|
area.style.height = `${area.scrollHeight}px`;
|
|
130
223
|
};
|
|
224
|
+
// A new tag (the task page's 追问 / 新任务) hands the textarea focus.
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
if (tag !== undefined)
|
|
227
|
+
areaRef.current?.focus();
|
|
228
|
+
}, [tag?.revision]);
|
|
229
|
+
// External draft writes (a quote insertion, the election window's
|
|
230
|
+
// carry-over) never fire onChange — keep the height in sync with the draft.
|
|
231
|
+
useEffect(() => {
|
|
232
|
+
if (areaRef.current !== null)
|
|
233
|
+
resize(areaRef.current);
|
|
234
|
+
}, [draft]);
|
|
235
|
+
// Write the textarea's text back: chip mode maps the change onto the
|
|
236
|
+
// machine as span edits (each against the then-current revision, last
|
|
237
|
+
// first), so the chips survive; otherwise the whole draft is replaced.
|
|
238
|
+
const writeText = (next) => {
|
|
239
|
+
if (!chipMode) {
|
|
240
|
+
inputActions.setDraft(next);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
for (const edit of planFreeEdit(draft, chips, next)) {
|
|
244
|
+
const draftRev = chipEdit.captureInsertion().draftRev;
|
|
245
|
+
if (!chipEdit.insertText(edit.text, { start: edit.start, end: edit.end, draftRev }))
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
// Remove one chip: delete its single detect placeholder.
|
|
250
|
+
const removeChip = (k) => {
|
|
251
|
+
const start = chipDetectIndex(draft, chips, k);
|
|
252
|
+
chipEdit.insertText?.('', { start, end: start + 1, draftRev: chipEdit.captureInsertion().draftRev });
|
|
253
|
+
areaRef.current?.focus();
|
|
254
|
+
};
|
|
131
255
|
const onChange = (event) => {
|
|
132
|
-
const
|
|
133
|
-
|
|
256
|
+
const next = event.target.value;
|
|
257
|
+
writeText(next);
|
|
134
258
|
resize(event.target);
|
|
135
|
-
const active = detectMention(
|
|
259
|
+
const active = detectMention(next, event.target.selectionStart ?? next.length);
|
|
136
260
|
setMention(active === null ? null : { ...active, index: 0 });
|
|
137
261
|
};
|
|
262
|
+
// A session pick: the host's own session chip replaces the typed `@query`
|
|
263
|
+
// (detect coordinates: the chips before it each fold to one placeholder);
|
|
264
|
+
// a host without the insertion seam gets the mention as text.
|
|
265
|
+
const applySession = (candidate) => {
|
|
266
|
+
setExpanded(new Set());
|
|
267
|
+
if (mention === null)
|
|
268
|
+
return;
|
|
269
|
+
const caret = areaRef.current?.selectionStart ?? value.length;
|
|
270
|
+
setMention(null);
|
|
271
|
+
if (insertSessionReference !== undefined && chipEdit.captureInsertion !== undefined) {
|
|
272
|
+
const segments = chipMode ? draftSegments(draft, chips) : [value];
|
|
273
|
+
const span = { start: detectOffset(segments, mention.start), end: detectOffset(segments, caret), draftRev: chipEdit.captureInsertion().draftRev };
|
|
274
|
+
if (insertSessionReference(candidate, span)) {
|
|
275
|
+
requestAnimationFrame(() => areaRef.current?.focus());
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
writeText(`${value.slice(0, mention.start)}${candidate.mention} ${value.slice(caret)}`);
|
|
280
|
+
};
|
|
281
|
+
const pick = (row) => {
|
|
282
|
+
if (row.kind === 'member')
|
|
283
|
+
applyMention(row.member.name);
|
|
284
|
+
else if (row.kind === 'session')
|
|
285
|
+
applySession(row.candidate);
|
|
286
|
+
else {
|
|
287
|
+
// A room's member: the room's own chip under a 「成员(房间)」 label.
|
|
288
|
+
const named = memberMention(row.candidate, row.member);
|
|
289
|
+
applySession({ ...row.candidate, mention: named.mention, displayTitle: named.label });
|
|
290
|
+
}
|
|
291
|
+
};
|
|
138
292
|
const applyMention = (name) => {
|
|
139
293
|
if (mention === null)
|
|
140
294
|
return;
|
|
141
|
-
const caret = areaRef.current?.selectionStart ??
|
|
142
|
-
const next = `${
|
|
143
|
-
|
|
295
|
+
const caret = areaRef.current?.selectionStart ?? value.length;
|
|
296
|
+
const next = `${value.slice(0, mention.start)}@${name} ${value.slice(caret)}`;
|
|
297
|
+
writeText(next);
|
|
144
298
|
setMention(null);
|
|
145
299
|
setPicked(picked.includes(name) ? picked : [...picked, name]);
|
|
300
|
+
// The machine tracks no caret: restore it behind the spliced pick once
|
|
301
|
+
// the store round-trip has re-rendered the textarea.
|
|
302
|
+
const restored = mention.start + name.length + 2;
|
|
303
|
+
requestAnimationFrame(() => areaRef.current?.setSelectionRange(restored, restored));
|
|
146
304
|
};
|
|
147
305
|
const send = async () => {
|
|
148
306
|
const text = draft.trim();
|
|
@@ -153,16 +311,23 @@ export function RoomComposer({ sessionId, roomStore, submit, stop, addTask, clos
|
|
|
153
311
|
try {
|
|
154
312
|
// Two-argument form while nothing was menu-picked: the injected face's
|
|
155
313
|
// third parameter is the pick channel only.
|
|
156
|
-
const outcome =
|
|
157
|
-
? await submit(sessionId, text, picked)
|
|
158
|
-
:
|
|
314
|
+
const outcome = tag !== undefined
|
|
315
|
+
? await submit(sessionId, text, picked, tag.task)
|
|
316
|
+
: picked.length > 0
|
|
317
|
+
? await submit(sessionId, text, picked)
|
|
318
|
+
: await submit(sessionId, text);
|
|
159
319
|
if (!outcome.ok) {
|
|
160
320
|
setError(outcome.message);
|
|
161
321
|
return;
|
|
162
322
|
}
|
|
163
|
-
setDraft('');
|
|
323
|
+
inputActions.setDraft('');
|
|
324
|
+
// Persist the cleared draft when the host's kit exposes the flush (the
|
|
325
|
+
// send bypasses the machine's own submit, which would do it).
|
|
326
|
+
const flushDraft = inputActions.persistDraft;
|
|
327
|
+
flushDraft?.();
|
|
164
328
|
setMention(null);
|
|
165
329
|
setPicked([]);
|
|
330
|
+
taskTags?.set(sessionId, undefined);
|
|
166
331
|
}
|
|
167
332
|
catch {
|
|
168
333
|
setError(t('composer.error.generic'));
|
|
@@ -172,6 +337,11 @@ export function RoomComposer({ sessionId, roomStore, submit, stop, addTask, clos
|
|
|
172
337
|
}
|
|
173
338
|
};
|
|
174
339
|
const onKeyDown = (event) => {
|
|
340
|
+
// oxlint-disable-next-line typescript/no-deprecated -- IME 229 covers engines without isComposing.
|
|
341
|
+
const ime = composing.current || justComposed.current || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;
|
|
342
|
+
justComposed.current = false;
|
|
343
|
+
if (ime)
|
|
344
|
+
return;
|
|
175
345
|
if (mention !== null) {
|
|
176
346
|
if (event.key === 'Escape') {
|
|
177
347
|
event.preventDefault();
|
|
@@ -189,9 +359,18 @@ export function RoomComposer({ sessionId, roomStore, submit, stop, addTask, clos
|
|
|
189
359
|
setMention({ ...mention, index: (mention.index + candidates.length - 1) % candidates.length });
|
|
190
360
|
return;
|
|
191
361
|
}
|
|
362
|
+
const active = candidates[mention.index];
|
|
363
|
+
if ((event.key === 'ArrowRight' || event.key === 'ArrowLeft') && active.kind !== 'member' && (active.kind === 'roomMember' || active.candidate.room === true)) {
|
|
364
|
+
event.preventDefault();
|
|
365
|
+
toggleRoom(active.candidate.sessionId, event.key === 'ArrowRight');
|
|
366
|
+
if (event.key === 'ArrowLeft' && active.kind === 'roomMember') {
|
|
367
|
+
setMention({ ...mention, index: candidates.findIndex(row => row.kind === 'session' && row.candidate.sessionId === active.candidate.sessionId) });
|
|
368
|
+
}
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
192
371
|
if (event.key === 'Enter' || event.key === 'Tab') {
|
|
193
372
|
event.preventDefault();
|
|
194
|
-
|
|
373
|
+
pick(active);
|
|
195
374
|
return;
|
|
196
375
|
}
|
|
197
376
|
}
|
|
@@ -201,11 +380,23 @@ export function RoomComposer({ sessionId, roomStore, submit, stop, addTask, clos
|
|
|
201
380
|
void send();
|
|
202
381
|
}
|
|
203
382
|
};
|
|
204
|
-
return (_jsxs("div", { className: css.root, children: [_jsxs("div", { className: css.dock, children: [!external && useProjection !== undefined && _jsx(RoomTodoStrip, { useProjection: useProjection, t: t }),
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
383
|
+
return (_jsxs("div", { className: css.root, children: [_jsxs("div", { className: css.dock, children: [!external && useProjection !== undefined && _jsx(RoomTodoStrip, { useProjection: useProjection, t: t }), state !== undefined && _jsx(RoomTaskCapsule, { state: state, openTasks: openTasks, reconcileDelivery: reconcileDelivery, openPlanSession: openPlanSession, roomCwd: roomCwd, invite: invite, listProviders: () => listProviders(sessionId), browseDirectory: browseDirectory, modelSurface: modelSurface, renderHarnessModelPicker: renderHarnessModelPicker, t: t }), _jsx(RoomQueueStrip, { items: [...external ? [] : queued, ...coordinatorQueue], t: t })] }), error !== null && _jsx("div", { className: css.error, role: "alert", children: error }), external && coordinator.childSessionId !== undefined && renderMemberInbox?.(coordinator.childSessionId), _jsxs("div", { className: css.card, children: [_jsxs("div", { className: css.coordinator, style: { '--member-color': memberColor(coordinator?.name ?? 'dsh') }, children: [_jsx("span", { className: css.coordinatorDot, "aria-hidden": true }), _jsx("span", { className: css.coordinatorName, children: coordinator?.name ?? 'dsh' }), _jsxs("span", { children: ["\u00B7 ", t('coordinator.label'), " \u00B7 ", external ? coordinator.provider : 'DSH'] })] }), mention !== null && candidates.length > 0 && (_jsxs("ul", { className: css.menu, role: "listbox", "aria-label": t('composer.at.label'), children: [candidates.map((row, index) => (_jsxs("li", { children: [index === 0 && memberRows.length > 0 && others.length > 0 && _jsx("div", { className: css.menuGroup, children: t('composer.at.members') }), index === memberRows.length && (_jsx("div", { className: css.menuGroup, children: t(mention.query === '' ? 'composer.at.recent' : 'composer.at.sessions') })), _jsx("button", { type: "button", role: "option", "aria-selected": index === mention.index, className: index === mention.index ? css.itemActive : css.item, "data-kid": row.kind === 'roomMember' && row.nested === true ? '' : undefined, onMouseDown: (event) => {
|
|
384
|
+
event.preventDefault();
|
|
385
|
+
pick(row);
|
|
386
|
+
}, children: row.kind === 'roomMember'
|
|
387
|
+
? _jsxs(_Fragment, { children: [_jsx("span", { className: css.dot, style: { background: memberColor(row.member.name) }, "aria-hidden": true }), _jsxs("span", { className: css.itemText, children: [row.member.name, row.nested !== true && _jsxs("span", { className: css.itemContext, children: [" \u00B7 ", sessionTitle(row.candidate)] })] }), _jsx("span", { className: css.hint, children: row.member.kind === 'main-agent' ? t('member.kind.main') : row.member.provider ?? '' })] })
|
|
388
|
+
: row.kind === 'member'
|
|
389
|
+
? _jsxs(_Fragment, { children: [_jsx("span", { className: css.dot, style: { background: memberColor(row.member.name) }, "aria-hidden": true }), _jsx("span", { className: css.itemText, children: row.member.name }), _jsx("span", { className: css.hint, children: row.member.kind === 'main-agent' ? t('member.kind.main') : row.member.provider ?? '' })] })
|
|
390
|
+
: _jsxs(_Fragment, { children: [_jsx("span", { className: css.sessionMark, "aria-hidden": true, children: "@" }), _jsx("span", { className: css.itemText, children: sessionTitle(row.candidate) }), row.candidate.room === true && (row.candidate.members?.length ?? 0) > 0 && (_jsx("span", { role: "button", tabIndex: -1, className: css.expand, "data-open": expanded.has(row.candidate.sessionId) ? '' : undefined, "aria-label": t(expanded.has(row.candidate.sessionId) ? 'composer.at.collapse' : 'composer.at.expand'), onMouseDown: (event) => {
|
|
391
|
+
event.preventDefault();
|
|
392
|
+
event.stopPropagation();
|
|
393
|
+
toggleRoom(row.candidate.sessionId);
|
|
394
|
+
}, children: _jsx(IconChevronRightOutlineMedium, { size: 14 }) })), _jsx("span", { className: css.hint, children: row.candidate.linked === true
|
|
395
|
+
? t('team.external.linked')
|
|
396
|
+
: row.candidate.room === true
|
|
397
|
+
? t('composer.at.room')
|
|
398
|
+
: row.candidate.createdAt === undefined ? t('composer.at.session') : t('composer.at.sessionAt', { time: shortTime(row.candidate.createdAt, Date.now()) }) })] }) })] }, row.kind === 'member' ? `m:${row.member.name}` : row.kind === 'session' ? `s:${row.candidate.sessionId}` : `r:${row.candidate.sessionId}:${row.member.name}`))), mention.query === '' && others.length > 0 && _jsx("li", { className: css.menuMore, children: t('composer.at.more') })] })), tag !== undefined && (_jsx("div", { className: css.tagRow, children: _jsxs("span", { className: css.tag, "data-new": tag.task === 'new' ? '' : undefined, children: [tag.label, _jsx("button", { type: "button", className: css.tagRemove, "aria-label": t('job.tagRemove'), title: t('job.tagRemove'), onClick: () => { taskTags?.set(sessionId, undefined); }, children: "\u2715" })] }) })), chips.length > 0 && (_jsx("div", { className: css.chipRow, children: chips.map((chip, k) => (_jsxs("span", { className: css.chip, children: [_jsxs("button", { type: "button", className: css.chipLabel, title: chip.label, onClick: () => { openReference?.(chip.source, chip.ref); }, children: [_jsx("span", { "aria-hidden": true, className: css.chipAt, children: "@" }), _jsx("span", { className: css.chipText, children: chip.label })] }), _jsx("button", { type: "button", className: css.tagRemove, "aria-label": t('composer.chipRemove', { label: chip.label }), title: t('composer.chipRemove', { label: chip.label }), onMouseDown: (event) => { event.preventDefault(); }, onClick: () => { removeChip(k); }, children: "\u2715" })] }, chip.occurrenceId))) })), _jsx("textarea", { ref: areaRef, className: css.input, rows: 2, value: value, placeholder: tag === undefined ? t('composer.placeholder', { name: coordinator?.name ?? 'dsh' }) : tag.task === 'new' ? t('job.tagPlaceholderNew') : t('job.tagPlaceholder', { label: tag.label }), onChange: onChange, onKeyDown: onKeyDown, onCompositionStart: () => { composing.current = true; }, onCompositionEnd: () => { composing.current = false; justComposed.current = true; }, onKeyUp: () => { justComposed.current = false; }, onBlur: () => { composing.current = false; justComposed.current = false; } }), _jsxs("div", { className: css.row, children: [external && coordinator.childSessionId !== undefined && renderMemberConfiguration?.(coordinator.childSessionId), !external && modelDirectory !== undefined && (_jsx(RoomModelPicker, { directory: modelDirectory, onError: setError, t: t })), running && (_jsx("button", { type: "button", className: css.primary, "aria-label": t('composer.stop'), onMouseDown: (event) => { event.preventDefault(); }, onClick: () => { if (external)
|
|
208
399
|
stopMember?.(coordinator.name);
|
|
209
400
|
else
|
|
210
|
-
stop(); }, children: _jsx("svg", { viewBox: "0 0 16 16", width: "16", height: "16", "aria-hidden": true, children: _jsx("rect", { x: "3", y: "3", width: "10", height: "10", rx: "3", fill: "currentColor" }) }) })), _jsx("button", { type: "button", className: css.primary, "aria-label": t('composer.send'), disabled: busy || draft.trim() === '', onMouseDown: (event) => { event.preventDefault(); }, onClick: () => { void send(); }, children: _jsx("svg", { viewBox: "0 0 16 16", width: "16", height: "16", "aria-hidden": true, children: _jsx("path", { d: "M8.3125 0.980183C8.66767 1.0531 8.97902 1.20418 9.2627 1.43233C9.48724 1.61297 9.73029 1.85793 9.97949 2.10714L14.707 6.83468L13.293 8.24874L9 3.95577V15.0417H7V3.95577L2.70703 8.24874L1.29297 6.83468L6.02051 2.10714C6.26971 1.85793 6.51277 1.61297 6.7373 1.43233C6.97662 1.23986 7.28445 1.04402 7.6875 0.980183C7.8973 0.947006 8.1031 0.95516 8.3125 0.980183Z", fill: "currentColor" }) }) })] })] }), !external && useProjection !== undefined && _jsx(RoomStatsLine, { useProjection: useProjection, t: t })] }));
|
|
401
|
+
stop(); }, children: _jsx("svg", { viewBox: "0 0 16 16", width: "16", height: "16", "aria-hidden": true, children: _jsx("rect", { x: "3", y: "3", width: "10", height: "10", rx: "3", fill: "currentColor" }) }) })), _jsx("button", { type: "button", className: css.primary, "aria-label": t('composer.send'), disabled: busy || draft.trim() === '', onMouseDown: (event) => { event.preventDefault(); }, onClick: () => { void send(); }, children: _jsx("svg", { viewBox: "0 0 16 16", width: "16", height: "16", "aria-hidden": true, children: _jsx("path", { d: "M8.3125 0.980183C8.66767 1.0531 8.97902 1.20418 9.2627 1.43233C9.48724 1.61297 9.73029 1.85793 9.97949 2.10714L14.707 6.83468L13.293 8.24874L9 3.95577V15.0417H7V3.95577L2.70703 8.24874L1.29297 6.83468L6.02051 2.10714C6.26971 1.85793 6.51277 1.61297 6.7373 1.43233C6.97662 1.23986 7.28445 1.04402 7.6875 0.980183C7.8973 0.947006 8.1031 0.95516 8.3125 0.980183Z", fill: "currentColor" }) }) })] })] }), !external && useProjection !== undefined && _jsx(RoomStatsLine, { useProjection: useProjection, state: state, t: t })] }));
|
|
211
402
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One decision the room made on its own ('room-decision' keyed renderer): a
|
|
3
|
+
* single dim line in the room's conversation — the scenario's outcome, the
|
|
4
|
+
* rule that decided or the model's probability and latency, and 查看判断 for a
|
|
5
|
+
* decision that called the decision model (it opens Context Lens at that
|
|
6
|
+
* request). The row reads like the relay / approval status lines.
|
|
7
|
+
*/
|
|
8
|
+
import type { ReactNode } from 'react';
|
|
9
|
+
import type { RoomDecisionEvent } from '../types.ts';
|
|
10
|
+
import type { RoomDecisionViewProps } from './slots.ts';
|
|
11
|
+
type Translate = RoomDecisionViewProps['t'];
|
|
12
|
+
/**
|
|
13
|
+
* The line's text for one decision.
|
|
14
|
+
* @param decision - the recorded decision.
|
|
15
|
+
* @param t - the room translate.
|
|
16
|
+
* @returns the outcome sentence.
|
|
17
|
+
*/
|
|
18
|
+
export declare function decisionLine(decision: RoomDecisionEvent, t: Translate): string;
|
|
19
|
+
/** The decision row. */
|
|
20
|
+
export declare function RoomDecisionView({ node, openDecision, t }: RoomDecisionViewProps): ReactNode;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=RoomDecisionView.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import css from './RoomRelayView.module.css';
|
|
3
|
+
/**
|
|
4
|
+
* The line's text for one decision.
|
|
5
|
+
* @param decision - the recorded decision.
|
|
6
|
+
* @param t - the room translate.
|
|
7
|
+
* @returns the outcome sentence.
|
|
8
|
+
*/
|
|
9
|
+
export function decisionLine(decision, t) {
|
|
10
|
+
const failed = decision.call?.failure !== undefined;
|
|
11
|
+
if (decision.scenario === 'task-filing') {
|
|
12
|
+
if (decision.rule === 'unavailable')
|
|
13
|
+
return t(failed ? 'decision.filing.failed' : 'decision.filing.unavailable');
|
|
14
|
+
return decision.about.task === undefined
|
|
15
|
+
? t('decision.filing.new')
|
|
16
|
+
: t('decision.filing.task', { number: decision.about.task.number, title: decision.about.task.title });
|
|
17
|
+
}
|
|
18
|
+
const from = decision.about.from ?? '';
|
|
19
|
+
const to = decision.about.to ?? '';
|
|
20
|
+
if (decision.rule === 'unavailable')
|
|
21
|
+
return t(failed ? 'decision.report.failed' : 'decision.report.unavailable', { from, to });
|
|
22
|
+
if (decision.rule !== undefined)
|
|
23
|
+
return t('decision.report.rule', { from, to, rule: t(`decision.rule.${decision.rule}`) });
|
|
24
|
+
return t(decision.outcome === 'later' ? 'decision.report.later' : 'decision.report.now', { from, to });
|
|
25
|
+
}
|
|
26
|
+
/** The decision row. */
|
|
27
|
+
export function RoomDecisionView({ node, openDecision, t }) {
|
|
28
|
+
const decision = node.data.decision;
|
|
29
|
+
const call = decision.call;
|
|
30
|
+
const meta = [
|
|
31
|
+
...decision.probability === undefined ? [] : [decision.probability.toFixed(2)],
|
|
32
|
+
...call?.latencyMs === undefined ? [] : [`${(call.latencyMs / 1000).toFixed(1)}s`],
|
|
33
|
+
].join(' · ');
|
|
34
|
+
return (_jsxs("div", { className: css.row, "data-decision": decision.scenario, children: [_jsx("span", { "aria-hidden": true, children: call?.failure !== undefined ? '⚠' : '🧭' }), _jsx("span", { className: css.text, children: decisionLine(decision, t) }), meta !== '' && _jsx("span", { children: meta }), call !== undefined && openDecision !== undefined && (_jsx("button", { type: "button", className: css.action, onClick: () => { openDecision(decision.id); }, children: t('decision.open') }))] }));
|
|
35
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-session help, on both ends.
|
|
3
|
+
*
|
|
4
|
+
* - `room-help` (the asking room): one line per thread — who asked which
|
|
5
|
+
* session, where it stands — that opens to the request and every message
|
|
6
|
+
* on the thread (the helper's questions, its reply with the handoff, the
|
|
7
|
+
* asker's follow-ups), plus a jump into the helper's session.
|
|
8
|
+
* - `room-help-request` (the helper's session): the request the room queued
|
|
9
|
+
* there, as a card naming the asking room and member, with a jump back.
|
|
10
|
+
*/
|
|
11
|
+
import { type ReactNode } from 'react';
|
|
12
|
+
import type { RoomHelpRequestViewProps, RoomHelpViewProps } from './slots.ts';
|
|
13
|
+
/** The thread row in the asking room. */
|
|
14
|
+
export declare function RoomHelpView({ node, sessionId, roomStore, openSession, linkSession, dismissNudge, t }: RoomHelpViewProps): ReactNode;
|
|
15
|
+
/** The request card in the helper's session. */
|
|
16
|
+
export declare function RoomHelpRequestView({ node, openSession, t }: RoomHelpRequestViewProps): ReactNode;
|
|
17
|
+
//# sourceMappingURL=RoomHelpView.d.ts.map
|