@khorsheed/dsh-room 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.i18n.yaml +6 -0
- package/README.md +48 -0
- package/README.zh.md +48 -0
- package/cordis.patch.yml +6 -0
- package/lib/client.js +11481 -0
- package/lib/index.js +3231 -0
- package/lib/invariant.js +26 -0
- package/lib/journal-CuRnWk66.js +435 -0
- package/lib/tool.js +350 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +2615 -0
- package/lib/typert.remote-client.d.ts +55 -0
- package/lib/typert.remote-client.js +1711 -0
- package/lib/types/adapter.d.ts +42 -0
- package/lib/types/adapter.js +41 -0
- package/lib/types/agent-setup.d.ts +141 -0
- package/lib/types/agent-setup.js +145 -0
- package/lib/types/client/InviteAgentAction.d.ts +13 -0
- package/lib/types/client/InviteAgentAction.js +45 -0
- package/lib/types/client/InviteDialog.d.ts +90 -0
- package/lib/types/client/InviteDialog.js +216 -0
- package/lib/types/client/MemberCard.d.ts +33 -0
- package/lib/types/client/MemberCard.js +32 -0
- package/lib/types/client/MembersView.d.ts +5 -0
- package/lib/types/client/MembersView.js +168 -0
- package/lib/types/client/RoomComposer.d.ts +21 -0
- package/lib/types/client/RoomComposer.js +211 -0
- package/lib/types/client/RoomDockCapsules.d.ts +64 -0
- package/lib/types/client/RoomDockCapsules.js +277 -0
- package/lib/types/client/RoomEventView.d.ts +11 -0
- package/lib/types/client/RoomEventView.js +17 -0
- package/lib/types/client/RoomModelPicker.d.ts +18 -0
- package/lib/types/client/RoomModelPicker.js +134 -0
- package/lib/types/client/RoomPlanView.d.ts +17 -0
- package/lib/types/client/RoomPlanView.js +88 -0
- package/lib/types/client/RoomQueueStrip.d.ts +26 -0
- package/lib/types/client/RoomQueueStrip.js +28 -0
- package/lib/types/client/RoomRecoveryView.d.ts +11 -0
- package/lib/types/client/RoomRecoveryView.js +38 -0
- package/lib/types/client/RoomRelayView.d.ts +12 -0
- package/lib/types/client/RoomRelayView.js +20 -0
- package/lib/types/client/RoomRunView.d.ts +15 -0
- package/lib/types/client/RoomRunView.js +50 -0
- package/lib/types/client/RoomSpeechView.d.ts +18 -0
- package/lib/types/client/RoomSpeechView.js +62 -0
- package/lib/types/client/RoomStatsLine.d.ts +61 -0
- package/lib/types/client/RoomStatsLine.js +138 -0
- package/lib/types/client/RoomTaskLineView.d.ts +13 -0
- package/lib/types/client/RoomTaskLineView.js +21 -0
- package/lib/types/client/RoomTodoStrip.d.ts +24 -0
- package/lib/types/client/RoomTodoStrip.js +64 -0
- package/lib/types/client/format.d.ts +31 -0
- package/lib/types/client/format.js +48 -0
- package/lib/types/client/icons.d.ts +27 -0
- package/lib/types/client/icons.js +11 -0
- package/lib/types/client/index.d.ts +39 -0
- package/lib/types/client/index.js +471 -0
- package/lib/types/client/locales.d.ts +422 -0
- package/lib/types/client/locales.js +413 -0
- package/lib/types/client/member-color.d.ts +13 -0
- package/lib/types/client/member-color.js +22 -0
- package/lib/types/client/name-pool.d.ts +17 -0
- package/lib/types/client/name-pool.js +25 -0
- package/lib/types/client/nodes.d.ts +136 -0
- package/lib/types/client/nodes.js +212 -0
- package/lib/types/client/preset-visibility.d.ts +91 -0
- package/lib/types/client/preset-visibility.js +113 -0
- package/lib/types/client/request-ids.d.ts +7 -0
- package/lib/types/client/request-ids.js +21 -0
- package/lib/types/client/room-store.d.ts +137 -0
- package/lib/types/client/room-store.js +244 -0
- package/lib/types/client/slots.d.ts +319 -0
- package/lib/types/client/slots.js +1 -0
- package/lib/types/deliveries.d.ts +5 -0
- package/lib/types/deliveries.js +20 -0
- package/lib/types/dispatch.d.ts +105 -0
- package/lib/types/dispatch.js +467 -0
- package/lib/types/index.d.ts +301 -0
- package/lib/types/index.js +1200 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/invariant.js +24 -0
- package/lib/types/journal.d.ts +117 -0
- package/lib/types/journal.js +434 -0
- package/lib/types/plan-service.d.ts +34 -0
- package/lib/types/plan-service.js +266 -0
- package/lib/types/plan.d.ts +168 -0
- package/lib/types/plan.js +494 -0
- package/lib/types/tool.d.ts +56 -0
- package/lib/types/tool.js +389 -0
- package/lib/types/types.d.ts +813 -0
- package/lib/types/types.js +1 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +131 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Room composer: the `conversation.composer` chain takeover for room
|
|
4
|
+
* sessions. Replaces the official input bar while the selector holds (the
|
|
5
|
+
* official bar is display:none underneath), so it owns the whole interaction:
|
|
6
|
+
* a textarea (Enter sends, Shift+Enter newlines), a send button, and the
|
|
7
|
+
* @-completion menu listing ONLY existing roster members (pure addressing —
|
|
8
|
+
* invitation lives in the members tab). Addressing is the union of two
|
|
9
|
+
* channels: leading `@name` tokens (the host's parseMentions grammar,
|
|
10
|
+
* stripped from the dispatched text) and the mention menu's picks (a pick
|
|
11
|
+
* records explicit addressing wherever the `@name` sits in the sentence —
|
|
12
|
+
* the text is dispatched verbatim, the picks ride the postMessage request's
|
|
13
|
+
* `targets`). Hand-typed mid-sentence mentions without a menu pick stay
|
|
14
|
+
* prose: they address nobody. A bare message (neither channel) is NOT room
|
|
15
|
+
* business: it is released to the official submit
|
|
16
|
+
* path — the takeover writes the text into the session's official input
|
|
17
|
+
* machine (`inputActions.setDraft` + `inputActions.submit()`, the same entry
|
|
18
|
+
* the InputBar's Enter key drives), so it becomes an ordinary main-agent
|
|
19
|
+
* turn; sent while that turn runs, the machine's busy admission enqueues it
|
|
20
|
+
* exactly as the official bar's does. A structured rejection (unknown
|
|
21
|
+
* targets) shows as an inline error line.
|
|
22
|
+
*
|
|
23
|
+
* Taking over the chain inherits the official bar's environment duties (the
|
|
24
|
+
* member-channel rule, local-agent 84a2ed0): a pending interaction yields (the
|
|
25
|
+
* selector declines while `owner.pendingInteraction` is set, so the priority-1
|
|
26
|
+
* ApprovalPanel elects), and everything the hidden fallback tree carried is
|
|
27
|
+
* re-homed INTO this component — the main agent's turn Stop (the send circle
|
|
28
|
+
* swaps while `running`), the dock capsules (RoomDockCapsules), the main
|
|
29
|
+
* agent's todo strip (RoomTodoStrip, the `todos` projection), the queued-
|
|
30
|
+
* messages strip (RoomQueueStrip, the `inbox` projection — the 0.1.5 session
|
|
31
|
+
* snapshot's queue on the old line), the session
|
|
32
|
+
* stats row (RoomStatsLine below the card), and the main agent's model seat
|
|
33
|
+
* (RoomModelPicker over the official per-session ModelDirectory — the seat
|
|
34
|
+
* itself is single-owner and cannot be re-hosted, so the picker is our own
|
|
35
|
+
* trigger on the same directory; a host without ui-model-selection gets no
|
|
36
|
+
* picker).
|
|
37
|
+
*/
|
|
38
|
+
import { coordinatorMember } from '../journal.js';
|
|
39
|
+
import { useRef, useState, useSyncExternalStore, } from 'react';
|
|
40
|
+
import { memberColor } from './member-color.js';
|
|
41
|
+
import { RoomStatsLine } from './RoomStatsLine.js';
|
|
42
|
+
import { RoomDockCapsules } from './RoomDockCapsules.js';
|
|
43
|
+
import { RoomTodoStrip } from './RoomTodoStrip.js';
|
|
44
|
+
import { RoomPlanView } from './RoomPlanView.js';
|
|
45
|
+
import { RoomQueueStrip } from './RoomQueueStrip.js';
|
|
46
|
+
import { RoomRecoveryView } from './RoomRecoveryView.js';
|
|
47
|
+
import { RoomModelPicker } from './RoomModelPicker.js';
|
|
48
|
+
import css from './RoomComposer.module.css';
|
|
49
|
+
const EMPTY_LEGACY_QUEUE = [];
|
|
50
|
+
const QUEUE_PREVIEW_CHARS = 200;
|
|
51
|
+
/** The queue row's flat preview: attachment blocks dropped, the rest joined and whitespace-collapsed, 200 chars max (the official QueueDock's rule). */
|
|
52
|
+
function previewOf(content) {
|
|
53
|
+
const flat = content
|
|
54
|
+
.filter(block => block.type !== 'image' && block.type !== 'file')
|
|
55
|
+
.map(block => (block.type === 'text' ? block.text : `[${block.type}]`))
|
|
56
|
+
.join(' ').replace(/\s+/g, ' ').trim();
|
|
57
|
+
const chars = Array.from(flat);
|
|
58
|
+
return chars.length > QUEUE_PREVIEW_CHARS ? `${chars.slice(0, QUEUE_PREVIEW_CHARS).join('')}…` : flat;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Detect an active mention being typed: the text before the caret ends with
|
|
62
|
+
* `@partial` whose `@` follows the line start or whitespace — a mid-sentence
|
|
63
|
+
* mention completes as happily as a leading one. `\S` covers CJK member
|
|
64
|
+
* names (K酱). A menu pick of one of these candidates is explicit addressing
|
|
65
|
+
* even though the host's parseMentions reads only leading tokens.
|
|
66
|
+
*/
|
|
67
|
+
function detectMention(draft, caret) {
|
|
68
|
+
const head = draft.slice(0, caret);
|
|
69
|
+
const match = /(?:^|\s)@(\S*)$/.exec(head);
|
|
70
|
+
if (match === null)
|
|
71
|
+
return null;
|
|
72
|
+
return { query: match[1], start: caret - match[1].length - 1 };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Pure composer-chain selector: elect exactly the cached-room sessions.
|
|
76
|
+
* Pending interactions (questions/approvals) belong to the official
|
|
77
|
+
* ApprovalPanel — a chain entry at priority 1. Election runs ascending, so
|
|
78
|
+
* this -10 entry would shadow it: decline and let the interaction render
|
|
79
|
+
* (the member-channel rule, local-agent 84a2ed0). A cache miss declines too —
|
|
80
|
+
* the freshly opened room shows the official bar for the first pull's
|
|
81
|
+
* duration (accepted, see room-store.ts).
|
|
82
|
+
* @param owner - the composer chain currency dispatched by ConversationRoot.
|
|
83
|
+
* @param isRoomCached - the room store's cache probe (injected: the selector
|
|
84
|
+
* must stay a pure function of the owner props plus stable state reads).
|
|
85
|
+
* @returns the room match, or null to pass the election down the chain.
|
|
86
|
+
*/
|
|
87
|
+
export function selectRoomComposer(owner, isRoomCached) {
|
|
88
|
+
if (owner.pendingInteraction !== undefined)
|
|
89
|
+
return null;
|
|
90
|
+
const sessionId = owner.sessionId ?? owner.session?.sessionId;
|
|
91
|
+
return sessionId !== undefined && isRoomCached(sessionId) ? { room: true } : null;
|
|
92
|
+
}
|
|
93
|
+
/** The room composer takeover component. */
|
|
94
|
+
export function RoomComposer({ sessionId, roomStore, submit, stop, addTask, closeTask, setGoal, planCommand, openPlanSession, reconcileDelivery, roomCwd, invite, listProviders, listNames, browseDirectory, modelSurface, renderHarnessModelPicker, roomChrome, modelDirectory, renderMemberConfiguration, renderMemberInbox, stopMember, useSession, useProjection, t, }) {
|
|
95
|
+
const state = useSyncExternalStore(roomStore.subscribe, () => roomStore.getCached(sessionId));
|
|
96
|
+
const [draft, setDraft] = useState('');
|
|
97
|
+
const [busy, setBusy] = useState(false);
|
|
98
|
+
const [error, setError] = useState(null);
|
|
99
|
+
const [mention, setMention] = useState(null);
|
|
100
|
+
/** Menu-picked addressees: a pick is explicit addressing wherever the
|
|
101
|
+
`@name` sits (see the module header); cleared with the draft on send. */
|
|
102
|
+
const [picked, setPicked] = useState([]);
|
|
103
|
+
const areaRef = useRef(null);
|
|
104
|
+
const members = state?.members ?? [];
|
|
105
|
+
const candidates = mention === null
|
|
106
|
+
? []
|
|
107
|
+
: members.filter(member => member.name.startsWith(mention.query));
|
|
108
|
+
// The inherited environment state: the room's own main-agent turn flag
|
|
109
|
+
// (drives the Send/Stop swap) and the still-queued inbox rows (the official
|
|
110
|
+
// queue dock's data, re-homed as a read-only strip). alpha.2 moved the
|
|
111
|
+
// queue off the session snapshot into the `inbox` projection; 0.1.5 serves
|
|
112
|
+
// no `inbox` key — the undefined read falls back to the legacy snapshot
|
|
113
|
+
// queue.
|
|
114
|
+
const nativeRunning = useSession(snapshot => snapshot.running) ?? false;
|
|
115
|
+
const coordinator = state === undefined ? undefined : coordinatorMember(state);
|
|
116
|
+
const external = coordinator?.kind === 'cli';
|
|
117
|
+
const running = external ? state?.runs.some(run => run.member === coordinator.name && run.state === 'running') ?? false : nativeRunning;
|
|
118
|
+
const legacyQueue = useSession(snapshot => snapshot.queue)
|
|
119
|
+
?? EMPTY_LEGACY_QUEUE;
|
|
120
|
+
const inbox = (useProjection === undefined ? undefined : useProjection('inbox'));
|
|
121
|
+
const nextTurn = inbox?.['next-turn'];
|
|
122
|
+
const queued = nextTurn === undefined
|
|
123
|
+
? legacyQueue
|
|
124
|
+
.filter(row => row.placement === 'queued')
|
|
125
|
+
.map(row => ({ id: String(row.id), preview: row.preview }))
|
|
126
|
+
: nextTurn.map(row => ({ id: String(row.id), preview: previewOf(row.content) }));
|
|
127
|
+
const resize = (area) => {
|
|
128
|
+
area.style.height = 'auto';
|
|
129
|
+
area.style.height = `${area.scrollHeight}px`;
|
|
130
|
+
};
|
|
131
|
+
const onChange = (event) => {
|
|
132
|
+
const value = event.target.value;
|
|
133
|
+
setDraft(value);
|
|
134
|
+
resize(event.target);
|
|
135
|
+
const active = detectMention(value, event.target.selectionStart ?? value.length);
|
|
136
|
+
setMention(active === null ? null : { ...active, index: 0 });
|
|
137
|
+
};
|
|
138
|
+
const applyMention = (name) => {
|
|
139
|
+
if (mention === null)
|
|
140
|
+
return;
|
|
141
|
+
const caret = areaRef.current?.selectionStart ?? draft.length;
|
|
142
|
+
const next = `${draft.slice(0, mention.start)}@${name} ${draft.slice(caret)}`;
|
|
143
|
+
setDraft(next);
|
|
144
|
+
setMention(null);
|
|
145
|
+
setPicked(picked.includes(name) ? picked : [...picked, name]);
|
|
146
|
+
};
|
|
147
|
+
const send = async () => {
|
|
148
|
+
const text = draft.trim();
|
|
149
|
+
if (text === '' || busy)
|
|
150
|
+
return;
|
|
151
|
+
setBusy(true);
|
|
152
|
+
setError(null);
|
|
153
|
+
try {
|
|
154
|
+
// Two-argument form while nothing was menu-picked: the injected face's
|
|
155
|
+
// third parameter is the pick channel only.
|
|
156
|
+
const outcome = picked.length > 0
|
|
157
|
+
? await submit(sessionId, text, picked)
|
|
158
|
+
: await submit(sessionId, text);
|
|
159
|
+
if (!outcome.ok) {
|
|
160
|
+
setError(outcome.message);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
setDraft('');
|
|
164
|
+
setMention(null);
|
|
165
|
+
setPicked([]);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
setError(t('composer.error.generic'));
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
setBusy(false);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const onKeyDown = (event) => {
|
|
175
|
+
if (mention !== null) {
|
|
176
|
+
if (event.key === 'Escape') {
|
|
177
|
+
event.preventDefault();
|
|
178
|
+
setMention(null);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (candidates.length > 0) {
|
|
182
|
+
if (event.key === 'ArrowDown') {
|
|
183
|
+
event.preventDefault();
|
|
184
|
+
setMention({ ...mention, index: (mention.index + 1) % candidates.length });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (event.key === 'ArrowUp') {
|
|
188
|
+
event.preventDefault();
|
|
189
|
+
setMention({ ...mention, index: (mention.index + candidates.length - 1) % candidates.length });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (event.key === 'Enter' || event.key === 'Tab') {
|
|
193
|
+
event.preventDefault();
|
|
194
|
+
applyMention(candidates[mention.index].name);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
200
|
+
event.preventDefault();
|
|
201
|
+
void send();
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
return (_jsxs("div", { className: css.root, children: [_jsxs("div", { className: css.dock, children: [!external && useProjection !== undefined && _jsx(RoomTodoStrip, { useProjection: useProjection, t: t }), _jsx(RoomDockCapsules, { sessionId: sessionId, roomStore: roomStore, addTask: addTask, closeTask: closeTask, setGoal: setGoal, formalPlans: planCommand !== undefined, roomCwd: roomCwd, invite: invite, listProviders: listProviders, listNames: listNames, browseDirectory: browseDirectory, modelSurface: modelSurface, renderHarnessModelPicker: renderHarnessModelPicker, roomChrome: roomChrome, t: t }), state !== undefined && planCommand !== undefined && _jsx(RoomPlanView, { plan: state.plan, members: state.members, command: planCommand, openSession: openPlanSession, stopMember: stopMember, t: t }), state !== undefined && reconcileDelivery !== undefined && _jsx(RoomRecoveryView, { deliveries: state.deliveries ?? [], members: state.members, reconcile: reconcileDelivery, openSession: openPlanSession, t: t }), _jsx(RoomQueueStrip, { items: [...external ? [] : queued, ...(state?.deliveries ?? []).filter(row => row.memberId === coordinator?.id && (row.status === 'queued' || row.status === 'uncertain')).map(row => ({ id: row.id, preview: row.status === 'uncertain' ? `${row.error ?? 'Outcome unknown'}: ${row.text}` : row.text }))], 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, children: [t('coordinator.label'), " \u00B7 ", coordinator?.name ?? 'dsh', external ? ` · ${coordinator.provider}` : ' · DSH'] }), mention !== null && candidates.length > 0 && (_jsx("ul", { className: css.menu, role: "listbox", "aria-label": "members", children: candidates.map((member, index) => (_jsx("li", { children: _jsxs("button", { type: "button", role: "option", "aria-selected": index === mention.index, className: index === mention.index ? css.itemActive : css.item, onMouseDown: (event) => {
|
|
205
|
+
event.preventDefault();
|
|
206
|
+
applyMention(member.name);
|
|
207
|
+
}, children: [_jsx("span", { className: css.dot, style: { background: memberColor(member.name) }, "aria-hidden": true }), _jsx("span", { children: member.name }), _jsx("span", { className: css.hint, children: member.kind === 'main-agent' ? t('member.kind.main') : member.provider ?? '' })] }) }, member.name))) })), _jsx("textarea", { ref: areaRef, className: css.input, rows: 2, value: draft, placeholder: t('composer.placeholder'), onChange: onChange, onKeyDown: onKeyDown }), _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
|
+
stopMember?.(coordinator.name);
|
|
209
|
+
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 })] }));
|
|
211
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The room dock capsules, rendered by the RoomComposer itself above the input
|
|
3
|
+
* card — the seat of the old task-board strip (NOT a `conversation.input.dock`
|
|
4
|
+
* entry: that slot lives inside the official composer fallback, which the
|
|
5
|
+
* room's composer takeover hides). One collapsed row of two capsules plus a
|
|
6
|
+
* quick-add button:
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* [◔33% 插件API v2上线] [☑ 当前进度 1/3 · ●ada 在做] +
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* The row is ANCHORED just above the input card: an expanded panel renders
|
|
13
|
+
* above the capsule row (top-to-bottom: panel → capsules → input card) and
|
|
14
|
+
* grows upward only, so folding/unfolding never shifts the row under the
|
|
15
|
+
* user's pointer.
|
|
16
|
+
*
|
|
17
|
+
* The GOAL capsule's only visual focus is the progress ring: an SVG circle
|
|
18
|
+
* (tertiary track + business-primary progress arc) followed by the percent
|
|
19
|
+
* and the truncated goal text; without a goal it renders the 「+ 设定目标」
|
|
20
|
+
* guide state (no ring). It expands into the goal card: the full text with an
|
|
21
|
+
* inline [编辑], a progress bar with the done/total fraction, and the recent
|
|
22
|
+
* advance records (who finished what, relative time).
|
|
23
|
+
*
|
|
24
|
+
* The TASK capsule leads with the checklist icon (IconChecklistOutlineMedium from
|
|
25
|
+
* the official primitives — never a Unicode glyph), then the open-task count
|
|
26
|
+
* and the members currently running (color dot + name). It expands into the
|
|
27
|
+
* Linear-style task panel: a member filter chip row (color-dot capsules, the
|
|
28
|
+
* selected one takes a tinted fill of the member color — no outline) with the
|
|
29
|
+
* +添加 trigger at its right end, small-caps tertiary group headers, and task
|
|
30
|
+
* rows (colored status icon — business-primary half-ring for in_progress,
|
|
31
|
+
* tertiary empty ring for pending, filled check for done, filled error × for
|
|
32
|
+
* failed — + title + member chip + right-aligned tertiary status/relative
|
|
33
|
+
* time + a row-end [完成]; a failed row's action is [关闭], dismissing to
|
|
34
|
+
* cancelled — the run did not finish, so it never closes done).
|
|
35
|
+
* Closing a task plays a restrained completion beat (the check pops, the
|
|
36
|
+
* title's strike draws in, <300ms, reduced-motion safe) — the row remounts on
|
|
37
|
+
* a status change (`key = id:status`) so the CSS animation replays. A blocked
|
|
38
|
+
* row greys with a 「等 ada」 tag until its `blockedBy` member has no open
|
|
39
|
+
* task left (display only: nothing auto-dispatches). The trailing + opens
|
|
40
|
+
* the panel with the add form already expanded (member select + title +
|
|
41
|
+
* optional "等谁"). Panels close on a second capsule click or a click
|
|
42
|
+
* outside; no overlay.
|
|
43
|
+
*
|
|
44
|
+
* Visual layer: the capsules ride the official chip register (28px, r24,
|
|
45
|
+
* interactive hover fill, 13/20/500 label) with a visible outline (the
|
|
46
|
+
* InputBar card's `l2-darkmode-thin` pair, firming to `l3` on hover/expand);
|
|
47
|
+
* the expanded cards are the
|
|
48
|
+
* official Menu surface (`--dsw-specific-menu`, r12, inverted hairline,
|
|
49
|
+
* shadow-lv3); the task capsule sweeps a restrained glare band while tasks
|
|
50
|
+
* run (the ToolRow pattern, reduced-motion safe). Every token reference
|
|
51
|
+
* carries the light-theme literal as fallback; no divider lines anywhere.
|
|
52
|
+
*
|
|
53
|
+
* FRESH-ROOM STATE: a room with neither a goal nor any task (membership does
|
|
54
|
+
* not count — the main agent is always there) has no use for the guide/task
|
|
55
|
+
* pair, which would be pure noise. The collapsed row then renders a single
|
|
56
|
+
* 「+ 邀请成员」 capsule (same chip family) that opens the invite dialog
|
|
57
|
+
* straight from the dock. The moment a goal is set or a task exists the pair
|
|
58
|
+
* returns, and the invite entry retires — the members tab already carries it.
|
|
59
|
+
*/
|
|
60
|
+
import { type ReactNode } from 'react';
|
|
61
|
+
import type { RoomDockCapsulesProps } from './slots.ts';
|
|
62
|
+
/** The dock capsules (goal + tasks) with their expanded cards. */
|
|
63
|
+
export declare function RoomDockCapsules({ sessionId, roomStore, addTask, closeTask, setGoal, formalPlans, roomCwd, invite, listProviders, browseDirectory, modelSurface, renderHarnessModelPicker, t, }: RoomDockCapsulesProps): ReactNode;
|
|
64
|
+
//# sourceMappingURL=RoomDockCapsules.d.ts.map
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The room dock capsules, rendered by the RoomComposer itself above the input
|
|
4
|
+
* card — the seat of the old task-board strip (NOT a `conversation.input.dock`
|
|
5
|
+
* entry: that slot lives inside the official composer fallback, which the
|
|
6
|
+
* room's composer takeover hides). One collapsed row of two capsules plus a
|
|
7
|
+
* quick-add button:
|
|
8
|
+
*
|
|
9
|
+
* ```
|
|
10
|
+
* [◔33% 插件API v2上线] [☑ 当前进度 1/3 · ●ada 在做] +
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* The row is ANCHORED just above the input card: an expanded panel renders
|
|
14
|
+
* above the capsule row (top-to-bottom: panel → capsules → input card) and
|
|
15
|
+
* grows upward only, so folding/unfolding never shifts the row under the
|
|
16
|
+
* user's pointer.
|
|
17
|
+
*
|
|
18
|
+
* The GOAL capsule's only visual focus is the progress ring: an SVG circle
|
|
19
|
+
* (tertiary track + business-primary progress arc) followed by the percent
|
|
20
|
+
* and the truncated goal text; without a goal it renders the 「+ 设定目标」
|
|
21
|
+
* guide state (no ring). It expands into the goal card: the full text with an
|
|
22
|
+
* inline [编辑], a progress bar with the done/total fraction, and the recent
|
|
23
|
+
* advance records (who finished what, relative time).
|
|
24
|
+
*
|
|
25
|
+
* The TASK capsule leads with the checklist icon (IconChecklistOutlineMedium from
|
|
26
|
+
* the official primitives — never a Unicode glyph), then the open-task count
|
|
27
|
+
* and the members currently running (color dot + name). It expands into the
|
|
28
|
+
* Linear-style task panel: a member filter chip row (color-dot capsules, the
|
|
29
|
+
* selected one takes a tinted fill of the member color — no outline) with the
|
|
30
|
+
* +添加 trigger at its right end, small-caps tertiary group headers, and task
|
|
31
|
+
* rows (colored status icon — business-primary half-ring for in_progress,
|
|
32
|
+
* tertiary empty ring for pending, filled check for done, filled error × for
|
|
33
|
+
* failed — + title + member chip + right-aligned tertiary status/relative
|
|
34
|
+
* time + a row-end [完成]; a failed row's action is [关闭], dismissing to
|
|
35
|
+
* cancelled — the run did not finish, so it never closes done).
|
|
36
|
+
* Closing a task plays a restrained completion beat (the check pops, the
|
|
37
|
+
* title's strike draws in, <300ms, reduced-motion safe) — the row remounts on
|
|
38
|
+
* a status change (`key = id:status`) so the CSS animation replays. A blocked
|
|
39
|
+
* row greys with a 「等 ada」 tag until its `blockedBy` member has no open
|
|
40
|
+
* task left (display only: nothing auto-dispatches). The trailing + opens
|
|
41
|
+
* the panel with the add form already expanded (member select + title +
|
|
42
|
+
* optional "等谁"). Panels close on a second capsule click or a click
|
|
43
|
+
* outside; no overlay.
|
|
44
|
+
*
|
|
45
|
+
* Visual layer: the capsules ride the official chip register (28px, r24,
|
|
46
|
+
* interactive hover fill, 13/20/500 label) with a visible outline (the
|
|
47
|
+
* InputBar card's `l2-darkmode-thin` pair, firming to `l3` on hover/expand);
|
|
48
|
+
* the expanded cards are the
|
|
49
|
+
* official Menu surface (`--dsw-specific-menu`, r12, inverted hairline,
|
|
50
|
+
* shadow-lv3); the task capsule sweeps a restrained glare band while tasks
|
|
51
|
+
* run (the ToolRow pattern, reduced-motion safe). Every token reference
|
|
52
|
+
* carries the light-theme literal as fallback; no divider lines anywhere.
|
|
53
|
+
*
|
|
54
|
+
* FRESH-ROOM STATE: a room with neither a goal nor any task (membership does
|
|
55
|
+
* not count — the main agent is always there) has no use for the guide/task
|
|
56
|
+
* pair, which would be pure noise. The collapsed row then renders a single
|
|
57
|
+
* 「+ 邀请成员」 capsule (same chip family) that opens the invite dialog
|
|
58
|
+
* straight from the dock. The moment a goal is set or a task exists the pair
|
|
59
|
+
* returns, and the invite entry retires — the members tab already carries it.
|
|
60
|
+
*/
|
|
61
|
+
import { useEffect, useRef, useState, useSyncExternalStore, } from 'react';
|
|
62
|
+
import { IconChecklistOutlineMedium } from './icons.js';
|
|
63
|
+
import { taskProgress } from '../journal.js';
|
|
64
|
+
import { formatRelativeTime } from './format.js';
|
|
65
|
+
import { memberColor } from './member-color.js';
|
|
66
|
+
import { InviteDialog } from './InviteDialog.js';
|
|
67
|
+
import css from './RoomDockCapsules.module.css';
|
|
68
|
+
/** Glyph tooltip text per status. */
|
|
69
|
+
function statusLabel(status, t) {
|
|
70
|
+
switch (status) {
|
|
71
|
+
case 'pending': return t('tasks.status.pending');
|
|
72
|
+
case 'in_progress': return t('tasks.status.in_progress');
|
|
73
|
+
case 'done': return t('tasks.status.done');
|
|
74
|
+
case 'cancelled': return t('tasks.status.cancelled');
|
|
75
|
+
case 'failed': return t('tasks.status.failed');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/* The Linear-style status icons: colored, 14px, centered in the 16px cell. */
|
|
79
|
+
/** Done: filled success circle, check cut in the card surface tone. */
|
|
80
|
+
function DoneGlyph() {
|
|
81
|
+
return (_jsxs("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.glyphDone, children: [_jsx("circle", { cx: "7", cy: "7", r: "7", fill: "currentColor" }), _jsx("path", { d: "M4.2 7.3L6.2 9.3L9.8 5.2", stroke: "var(--dsw-specific-menu, var(--dsw-alias-bg-base, #ffffff))", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" })] }));
|
|
82
|
+
}
|
|
83
|
+
/** In-progress: business-primary half ring; CSS spins the svg. */
|
|
84
|
+
function ProgressGlyph() {
|
|
85
|
+
return (_jsx("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.glyphProgress, children: _jsx("circle", { cx: "7", cy: "7", r: "5.5", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeDasharray: `${Math.PI * 5.5} ${2 * Math.PI * 5.5}` }) }));
|
|
86
|
+
}
|
|
87
|
+
/** Pending: the tertiary empty ring. */
|
|
88
|
+
function PendingGlyph() {
|
|
89
|
+
return (_jsx("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.glyphPending, children: _jsx("circle", { cx: "7", cy: "7", r: "5.5", stroke: "currentColor", strokeWidth: "1.4" }) }));
|
|
90
|
+
}
|
|
91
|
+
/** Cancelled: the pending ring in the dimmer caption tone. */
|
|
92
|
+
function CancelledGlyph() {
|
|
93
|
+
return (_jsx("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.glyphCancelled, children: _jsx("circle", { cx: "7", cy: "7", r: "5.5", stroke: "currentColor", strokeWidth: "1.4" }) }));
|
|
94
|
+
}
|
|
95
|
+
/** Failed: filled error circle, × cut in the card surface tone (DoneGlyph's cutout idiom). */
|
|
96
|
+
function FailedGlyph() {
|
|
97
|
+
return (_jsxs("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.glyphFailed, children: [_jsx("circle", { cx: "7", cy: "7", r: "7", fill: "currentColor" }), _jsx("path", { d: "M4.8 4.8L9.2 9.2M9.2 4.8L4.8 9.2", stroke: "var(--dsw-specific-menu, var(--dsw-alias-bg-base, #ffffff))", strokeWidth: "1.4", strokeLinecap: "round" })] }));
|
|
98
|
+
}
|
|
99
|
+
function StatusGlyph({ status }) {
|
|
100
|
+
switch (status) {
|
|
101
|
+
case 'done': return _jsx(DoneGlyph, {});
|
|
102
|
+
case 'in_progress': return _jsx(ProgressGlyph, {});
|
|
103
|
+
case 'pending': return _jsx(PendingGlyph, {});
|
|
104
|
+
case 'cancelled': return _jsx(CancelledGlyph, {});
|
|
105
|
+
case 'failed': return _jsx(FailedGlyph, {});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The goal capsule's progress ring — the collapsed row's single visual focus:
|
|
110
|
+
* a tertiary track arc plus the business-primary progress arc, -90° rotated
|
|
111
|
+
* so the arc starts at twelve o'clock.
|
|
112
|
+
*/
|
|
113
|
+
function GoalRing({ fraction }) {
|
|
114
|
+
const clamped = Math.min(1, Math.max(0, fraction));
|
|
115
|
+
const R = 5.5;
|
|
116
|
+
const C = 2 * Math.PI * R;
|
|
117
|
+
return (_jsxs("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", className: css.ring, children: [_jsx("circle", { cx: "7", cy: "7", r: R, className: css.ringTrack, strokeWidth: "2" }), _jsx("circle", { cx: "7", cy: "7", r: R, className: css.ringFill, strokeWidth: "2", strokeLinecap: "round", strokeDasharray: `${C * clamped} ${C}`, transform: "rotate(-90 7 7)" })] }));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* A task is BLOCKED while the member its `blockedBy` names still has an open
|
|
121
|
+
* (pending/in_progress) task — the row greys until the wait is over. The task
|
|
122
|
+
* ITSELF never counts as its own blocker (a "等谁" pointing at the owner
|
|
123
|
+
* would otherwise grey forever). Display only: nothing auto-dispatches when
|
|
124
|
+
* it clears.
|
|
125
|
+
*/
|
|
126
|
+
function isBlocked(task, tasks) {
|
|
127
|
+
return task.blockedBy !== undefined
|
|
128
|
+
&& tasks.some(entry => entry.id !== task.id && entry.member === task.blockedBy
|
|
129
|
+
&& (entry.status === 'pending' || entry.status === 'in_progress'));
|
|
130
|
+
}
|
|
131
|
+
/** How many recent advance records the goal card lists. */
|
|
132
|
+
const RECENT_ADVANCES = 5;
|
|
133
|
+
/** One task row of the task panel. */
|
|
134
|
+
function TaskRow({ task, tasks, now, closeTask, t }) {
|
|
135
|
+
const blocked = isBlocked(task, tasks);
|
|
136
|
+
const open = task.status === 'pending' || task.status === 'in_progress';
|
|
137
|
+
return (_jsxs("li", { className: css.item, "data-status": task.status, "data-blocked": blocked || undefined, children: [_jsx("span", { className: css.glyph, title: statusLabel(task.status, t), children: _jsx(StatusGlyph, { status: task.status }) }), _jsx("span", { className: css.content, children: _jsx("span", { className: css.title, children: task.title }) }), _jsxs("span", { className: css.memberChip, children: [_jsx("span", { className: css.memberDot, style: { background: memberColor(task.member) }, "aria-hidden": true }), task.member] }), blocked && _jsx("span", { className: css.blockedTag, children: t('tasks.blocked', { member: task.blockedBy ?? '' }) }), _jsxs("span", { className: css.meta, children: [statusLabel(task.status, t), " \u00B7 ", formatRelativeTime(task.updatedAt, now, t)] }), open && (_jsx("button", { type: "button", className: css.action, onClick: () => { void closeTask(task.id); }, children: t('tasks.close') })), task.status === 'failed' && (_jsx("button", { type: "button", className: css.action, onClick: () => { void closeTask(task.id, 'cancelled'); }, children: t('tasks.dismiss') }))] }));
|
|
138
|
+
}
|
|
139
|
+
/** The dock capsules (goal + tasks) with their expanded cards. */
|
|
140
|
+
export function RoomDockCapsules({ sessionId, roomStore, addTask, closeTask, setGoal, formalPlans = false, roomCwd, invite, listProviders, browseDirectory, modelSurface, renderHarnessModelPicker, t, }) {
|
|
141
|
+
const state = useSyncExternalStore(roomStore.subscribe, () => roomStore.getCached(sessionId));
|
|
142
|
+
const [open, setOpen] = useState(null);
|
|
143
|
+
const [addOpen, setAddOpen] = useState(false);
|
|
144
|
+
const [filter, setFilter] = useState('');
|
|
145
|
+
const [editingGoal, setEditingGoal] = useState(false);
|
|
146
|
+
const [goalDraft, setGoalDraft] = useState('');
|
|
147
|
+
const [draftTitle, setDraftTitle] = useState('');
|
|
148
|
+
const [draftMember, setDraftMember] = useState('');
|
|
149
|
+
const [draftBlockedBy, setDraftBlockedBy] = useState('');
|
|
150
|
+
const [error, setError] = useState(null);
|
|
151
|
+
const [inviteOpen, setInviteOpen] = useState(false);
|
|
152
|
+
const [providers, setProviders] = useState(undefined);
|
|
153
|
+
const rootRef = useRef(null);
|
|
154
|
+
// Collapse on any press outside the dock (no overlay; a second click on the
|
|
155
|
+
// capsule toggles too).
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (open === null)
|
|
158
|
+
return;
|
|
159
|
+
const onPointerDown = (event) => {
|
|
160
|
+
if (rootRef.current !== null && event.target instanceof Node
|
|
161
|
+
&& !rootRef.current.contains(event.target)) {
|
|
162
|
+
setOpen(null);
|
|
163
|
+
setAddOpen(false);
|
|
164
|
+
setEditingGoal(false);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
document.addEventListener('mousedown', onPointerDown);
|
|
168
|
+
return () => { document.removeEventListener('mousedown', onPointerDown); };
|
|
169
|
+
}, [open]);
|
|
170
|
+
// A cache miss (first pull in flight) or a non-room: the dock vanishes.
|
|
171
|
+
if (state === undefined || roomStore.isRoomCached(sessionId) !== true)
|
|
172
|
+
return null;
|
|
173
|
+
// Fresh room (no goal, no tasks — membership does not count, the main
|
|
174
|
+
// agent is always there): the guide/task pair would be noise, so the row
|
|
175
|
+
// carries a single 「+ 邀请成员」 capsule that opens the invite dialog
|
|
176
|
+
// straight from the dock. The pair returns the moment a goal or a task
|
|
177
|
+
// exists; the invite entry then retires (the members tab carries it).
|
|
178
|
+
const fresh = state.goal === undefined && state.tasks.length === 0;
|
|
179
|
+
if (fresh) {
|
|
180
|
+
const openInvite = () => {
|
|
181
|
+
setProviders(undefined);
|
|
182
|
+
setInviteOpen(true);
|
|
183
|
+
void listProviders().then(setProviders);
|
|
184
|
+
};
|
|
185
|
+
const submitInvite = (values) => invite({
|
|
186
|
+
provider: values.provider,
|
|
187
|
+
name: values.name,
|
|
188
|
+
// Blank instructions are omitted, not sent: the host rejects a
|
|
189
|
+
// present-but-blank role, and an absent one simply carries no preset.
|
|
190
|
+
...values.instructions === '' ? {} : { instructions: values.instructions },
|
|
191
|
+
...values.cwd === '' ? {} : { cwd: values.cwd },
|
|
192
|
+
...values.firstTask === '' ? {} : { firstTask: values.firstTask },
|
|
193
|
+
...values.model === '' ? {} : { model: values.model },
|
|
194
|
+
});
|
|
195
|
+
return (_jsxs("section", { ref: rootRef, className: css.root, "aria-label": t('goal.label'), children: [_jsx("div", { className: css.capsules, children: _jsx("button", { type: "button", className: css.capsule, onClick: openInvite, children: _jsx("span", { className: css.capsuleGuide, children: t('members.invite') }) }) }), inviteOpen && (_jsx(InviteDialog, { mode: "invite", providers: providers?.providers, localAgentAvailable: providers?.localAgentAvailable ?? true, inheritedCwd: roomCwd, existingNames: state.members.map(entry => entry.name), browseDirectory: browseDirectory, modelSurface: modelSurface, renderHarnessModelPicker: renderHarnessModelPicker, onSubmit: submitInvite, onClose: () => { setInviteOpen(false); }, t: t }))] }));
|
|
196
|
+
}
|
|
197
|
+
const now = Date.now();
|
|
198
|
+
const progress = taskProgress(state.tasks);
|
|
199
|
+
const runners = [...new Set(state.tasks.filter(task => task.status === 'in_progress').map(task => task.member))];
|
|
200
|
+
const members = state.members;
|
|
201
|
+
const chosen = members.some(member => member.name === draftMember)
|
|
202
|
+
? draftMember
|
|
203
|
+
: (members[0]?.name ?? '');
|
|
204
|
+
const advances = state.tasks
|
|
205
|
+
.filter(task => task.status === 'done')
|
|
206
|
+
.slice()
|
|
207
|
+
.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
208
|
+
const fraction = progress.total === 0 ? 0 : progress.done / progress.total;
|
|
209
|
+
const toggle = (panel) => {
|
|
210
|
+
if (open === panel) {
|
|
211
|
+
setOpen(null);
|
|
212
|
+
setAddOpen(false);
|
|
213
|
+
setEditingGoal(false);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
setOpen(panel);
|
|
217
|
+
setAddOpen(false);
|
|
218
|
+
setEditingGoal(false);
|
|
219
|
+
setError(null);
|
|
220
|
+
if (panel === 'goal' && state.goal === undefined) {
|
|
221
|
+
setGoalDraft('');
|
|
222
|
+
setEditingGoal(true);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const saveGoal = async () => {
|
|
226
|
+
const outcome = await setGoal(goalDraft);
|
|
227
|
+
if (!outcome.ok) {
|
|
228
|
+
setError(outcome.message);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
setEditingGoal(false);
|
|
232
|
+
setError(null);
|
|
233
|
+
};
|
|
234
|
+
const add = async () => {
|
|
235
|
+
const title = draftTitle.trim();
|
|
236
|
+
if (title === '' || chosen === '')
|
|
237
|
+
return;
|
|
238
|
+
const outcome = await addTask(chosen, title, draftBlockedBy === '' ? undefined : draftBlockedBy);
|
|
239
|
+
if (!outcome.ok) {
|
|
240
|
+
setError(outcome.message);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
setDraftTitle('');
|
|
244
|
+
setDraftBlockedBy('');
|
|
245
|
+
setError(null);
|
|
246
|
+
};
|
|
247
|
+
const onAddKeyDown = (event) => {
|
|
248
|
+
if (event.key === 'Enter') {
|
|
249
|
+
event.preventDefault();
|
|
250
|
+
void add();
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
const visibleTasks = filter === ''
|
|
254
|
+
? undefined
|
|
255
|
+
: state.tasks.filter(task => task.member === filter);
|
|
256
|
+
return (_jsxs("section", { ref: rootRef, className: css.root, "aria-label": t('goal.label'), children: [!formalPlans && open === 'goal' && (_jsxs("div", { className: css.card, children: [editingGoal ? (_jsxs("div", { className: css.goalEditRow, children: [_jsx("input", { className: css.input, value: goalDraft, placeholder: t('goal.placeholder'), onChange: event => { setGoalDraft(event.target.value); }, onKeyDown: (event) => {
|
|
257
|
+
if (event.key === 'Enter') {
|
|
258
|
+
event.preventDefault();
|
|
259
|
+
void saveGoal();
|
|
260
|
+
}
|
|
261
|
+
} }), _jsx("button", { type: "button", className: css.action, onClick: () => { void saveGoal(); }, children: t('goal.save') }), state.goal !== undefined && (_jsx("button", { type: "button", className: css.action, onClick: () => { setEditingGoal(false); }, children: t('goal.cancel') }))] })) : (_jsxs("div", { className: css.goalRow, children: [_jsx("span", { className: css.goalText, children: state.goal }), _jsx("button", { type: "button", className: css.action, onClick: () => {
|
|
262
|
+
setGoalDraft(state.goal ?? '');
|
|
263
|
+
setEditingGoal(true);
|
|
264
|
+
}, children: t('goal.edit') })] })), _jsxs("div", { className: css.progressRow, children: [_jsx("div", { className: css.progressTrack, role: "progressbar", "aria-valuemin": 0, "aria-valuemax": progress.total, "aria-valuenow": progress.done, children: _jsx("div", { className: css.progressFill, style: { width: `${fraction * 100}%` } }) }), _jsxs("span", { className: css.progressMeta, children: [progress.done, "/", progress.total] })] }), _jsx("div", { className: css.recentLabel, children: t('goal.recent') }), advances.length === 0 ? (_jsx("div", { className: css.meta, children: t('goal.noAdvances') })) : (_jsx("ul", { className: css.list, children: advances.slice(0, RECENT_ADVANCES).map(task => (_jsxs("li", { className: css.advanceRow, children: [t('advance.line', { member: task.member, title: task.title }), _jsxs("span", { className: css.meta, children: [" \u00B7 ", formatRelativeTime(task.updatedAt, now, t)] })] }, task.id))) })), error !== null && _jsx("div", { className: css.error, role: "alert", children: error })] })), open === 'tasks' && (_jsxs("div", { className: css.card, children: [_jsxs("div", { className: css.panelBar, children: [_jsxs("div", { className: css.chips, role: "group", "aria-label": t(formalPlans ? 'tasks.chat' : 'tasks.capsule'), children: [_jsx("button", { type: "button", className: css.chip, "data-active": filter === '' || undefined, onClick: () => { setFilter(''); }, children: t('tasks.filter.all') }), members.map(member => (_jsxs("button", { type: "button", className: css.chip, "data-active": filter === member.name || undefined, style: { '--room-chip-color': memberColor(member.name) }, onClick: () => { setFilter(member.name); }, children: [_jsx("span", { className: css.memberDot, style: { background: memberColor(member.name) }, "aria-hidden": true }), member.name] }, member.name)))] }), !addOpen && (_jsxs("button", { type: "button", className: css.addTrigger, onClick: () => { setAddOpen(true); }, children: ["\uFF0B ", t('tasks.addTrigger')] }))] }), _jsx("div", { className: css.groups, children: filter === ''
|
|
265
|
+
? members.map((member) => {
|
|
266
|
+
const own = state.tasks.filter(task => task.member === member.name);
|
|
267
|
+
if (own.length === 0)
|
|
268
|
+
return null;
|
|
269
|
+
return (_jsxs("div", { className: css.group, children: [_jsx("div", { className: css.head, children: member.name }), _jsx("ul", { className: css.list, children: own.map(task => (_jsx(TaskRow, { task: task, tasks: state.tasks, now: now, closeTask: closeTask, t: t }, `${task.id}:${task.status}`))) })] }, member.name));
|
|
270
|
+
})
|
|
271
|
+
: (_jsx("ul", { className: css.list, children: (visibleTasks ?? []).map(task => (_jsx(TaskRow, { task: task, tasks: state.tasks, now: now, closeTask: closeTask, t: t }, `${task.id}:${task.status}`))) })) }), addOpen && (_jsxs("div", { className: css.addRow, children: [_jsx("select", { className: css.memberSelect, "aria-label": t('tasks.addMember'), value: chosen, onChange: event => { setDraftMember(event.target.value); }, children: members.map(member => (_jsx("option", { value: member.name, children: member.name }, member.name))) }), _jsx("input", { className: css.input, value: draftTitle, placeholder: t('tasks.addPlaceholder'), onChange: event => { setDraftTitle(event.target.value); }, onKeyDown: onAddKeyDown }), _jsxs("select", { className: css.memberSelect, "aria-label": t('tasks.addBlockedBy'), value: draftBlockedBy, onChange: event => { setDraftBlockedBy(event.target.value); }, children: [_jsx("option", { value: "", children: t('tasks.addBlockedByNone') }), members.map(member => (_jsx("option", { value: member.name, children: member.name }, member.name)))] }), _jsx("button", { type: "button", className: css.action, disabled: draftTitle.trim() === '' || chosen === '', onClick: () => { void add(); }, children: t('tasks.add') })] })), error !== null && _jsx("div", { className: css.error, role: "alert", children: error })] })), _jsxs("div", { className: css.capsules, children: [!formalPlans && _jsx("button", { type: "button", className: css.capsule, "aria-expanded": open === 'goal', "aria-label": t('goal.label'), onClick: () => { toggle('goal'); }, children: state.goal === undefined ? (_jsx("span", { className: css.capsuleGuide, children: t('goal.set') })) : (_jsxs(_Fragment, { children: [_jsx(GoalRing, { fraction: fraction }), _jsxs("span", { className: css.capsulePercent, children: [Math.round(fraction * 100), "%"] }), _jsx("span", { className: css.capsuleText, children: state.goal })] })) }), _jsxs("button", { type: "button", className: css.capsule, "aria-expanded": open === 'tasks', "aria-label": t(formalPlans ? 'tasks.chat' : 'tasks.capsule'), "data-running": runners.length > 0 || undefined, onClick: () => { toggle('tasks'); }, children: [_jsx(IconChecklistOutlineMedium, { size: 14, className: css.checklistIcon }), progress.total === 0 ? (_jsx("span", { className: css.capsuleText, children: t(formalPlans ? 'tasks.chat' : 'tasks.capsule') })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: css.capsuleText, children: t(formalPlans ? 'tasks.chat' : 'tasks.capsule') }), _jsxs("span", { className: css.capsuleCount, children: [progress.done, "/", progress.total] }), runners.length > 0 && (_jsxs("span", { className: css.runners, children: [runners.map(name => (_jsxs("span", { className: css.runner, children: [_jsx("span", { className: css.memberDot, style: { background: memberColor(name) }, "aria-hidden": true }), name] }, name))), _jsx("span", { className: css.runnerSuffix, children: t('tasks.doing') })] }))] }))] }), _jsx("button", { type: "button", className: css.addCapsule, "aria-label": t('tasks.addTrigger'), onClick: () => {
|
|
272
|
+
setOpen('tasks');
|
|
273
|
+
setAddOpen(true);
|
|
274
|
+
setEditingGoal(false);
|
|
275
|
+
setError(null);
|
|
276
|
+
}, children: "\uFF0B" })] })] }));
|
|
277
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boundary node ('room-event' keyed renderer): one dim
|
|
3
|
+
* compaction-marker-style line per member join/leave. The human's own
|
|
4
|
+
* @-messages are NOT here — postMessage appends them as standard
|
|
5
|
+
* `user/message` events, so the official user bubble carries them.
|
|
6
|
+
*/
|
|
7
|
+
import type { ReactNode } from 'react';
|
|
8
|
+
import type { RoomEventViewProps } from './slots.ts';
|
|
9
|
+
/** The boundary dim line. */
|
|
10
|
+
export declare function RoomEventView({ node, t }: RoomEventViewProps): ReactNode;
|
|
11
|
+
//# sourceMappingURL=RoomEventView.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import css from './RoomEventView.module.css';
|
|
3
|
+
/** The boundary dim line. */
|
|
4
|
+
export function RoomEventView({ node, t }) {
|
|
5
|
+
const data = node.data;
|
|
6
|
+
let text;
|
|
7
|
+
switch (data.sub) {
|
|
8
|
+
case 'member-added':
|
|
9
|
+
text = t('event.joined', { member: data.member ?? '', provider: data.provider ?? '' })
|
|
10
|
+
+ (data.invitedBy === 'agent' ? t('event.joinedByAgent') : '');
|
|
11
|
+
break;
|
|
12
|
+
case 'member-removed':
|
|
13
|
+
text = t('event.left', { member: data.member ?? '' });
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
return _jsx("div", { className: css.row, children: text });
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
import type { RoomModelDirectory } from './slots.ts';
|
|
3
|
+
interface RoomModelPickerProps {
|
|
4
|
+
/** The session's shared official directory (same instance the /model popup reads). */
|
|
5
|
+
readonly directory: RoomModelDirectory;
|
|
6
|
+
/** Selection failures ride the composer's inline error line. */
|
|
7
|
+
readonly onError: (message: string) => void;
|
|
8
|
+
readonly t: PropsLocale<'room'>['t'];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Render the main-agent model seat: trigger (current model name + effort in
|
|
12
|
+
* the caption tone + chevron) and, while open, the two-level menu.
|
|
13
|
+
* @param props - the shared directory, the error sink, and the locale seat.
|
|
14
|
+
* @returns the picker.
|
|
15
|
+
*/
|
|
16
|
+
export declare function RoomModelPicker({ directory, onError, t }: RoomModelPickerProps): import("react").JSX.Element;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=RoomModelPicker.d.ts.map
|