@khorsheed/dsh-room 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/README.en.md +29 -62
- package/README.i18n.yaml +2 -2
- package/README.md +28 -61
- package/lib/client.js +13097 -4182
- package/lib/index.js +4045 -730
- package/lib/journal-B4FOGO6S.js +482 -0
- package/lib/journal-Rid9AAnD.js +485 -0
- package/lib/tool.js +112 -166
- package/lib/typert.host.js +2031 -374
- package/lib/typert.remote-client.d.ts +34 -10
- package/lib/typert.remote-client.js +1599 -210
- package/lib/types/agent-setup.d.ts +1 -1
- package/lib/types/client/InviteAgentAction.js +4 -16
- package/lib/types/client/InviteDialog.d.ts +28 -6
- package/lib/types/client/InviteDialog.js +76 -12
- package/lib/types/client/MemberCard.d.ts +5 -1
- package/lib/types/client/MemberCard.js +4 -4
- package/lib/types/client/MembersView.d.ts +1 -1
- package/lib/types/client/MembersView.js +58 -22
- package/lib/types/client/RemoveMemberConfirm.d.ts +40 -0
- package/lib/types/client/RemoveMemberConfirm.js +85 -0
- package/lib/types/client/RoomApprovalView.d.ts +11 -0
- package/lib/types/client/RoomApprovalView.js +28 -0
- package/lib/types/client/RoomComposer.d.ts +1 -1
- package/lib/types/client/RoomComposer.js +195 -29
- package/lib/types/client/RoomDecisionView.d.ts +22 -0
- package/lib/types/client/RoomDecisionView.js +35 -0
- package/lib/types/client/RoomHelpView.d.ts +17 -0
- package/lib/types/client/RoomHelpView.js +56 -0
- package/lib/types/client/RoomInboxView.d.ts +10 -0
- package/lib/types/client/RoomInboxView.js +14 -0
- package/lib/types/client/RoomQuestionView.d.ts +13 -0
- package/lib/types/client/RoomQuestionView.js +61 -0
- package/lib/types/client/RoomRelayView.js +1 -1
- package/lib/types/client/RoomRunView.d.ts +3 -1
- package/lib/types/client/RoomRunView.js +12 -5
- package/lib/types/client/RoomSettingsView.d.ts +19 -0
- package/lib/types/client/RoomSettingsView.js +30 -0
- package/lib/types/client/RoomSpeechView.d.ts +22 -8
- package/lib/types/client/RoomSpeechView.js +94 -11
- package/lib/types/client/RoomStatsLine.d.ts +26 -5
- package/lib/types/client/RoomStatsLine.js +53 -7
- package/lib/types/client/RoomTaskCapsule.d.ts +19 -0
- package/lib/types/client/RoomTaskCapsule.js +37 -0
- package/lib/types/client/TasksTab.d.ts +41 -0
- package/lib/types/client/TasksTab.js +43 -0
- package/lib/types/client/TasksView.d.ts +44 -0
- package/lib/types/client/TasksView.js +149 -0
- package/lib/types/client/TeamView.d.ts +33 -0
- package/lib/types/client/TeamView.js +210 -0
- package/lib/types/client/at-menu.d.ts +127 -0
- package/lib/types/client/at-menu.js +173 -0
- package/lib/types/client/draft-chips.d.ts +67 -0
- package/lib/types/client/draft-chips.js +95 -0
- package/lib/types/client/executions.d.ts +0 -30
- package/lib/types/client/executions.js +4 -34
- package/lib/types/client/icons-local.d.ts +25 -0
- package/lib/types/client/icons-local.js +10 -0
- package/lib/types/client/icons.d.ts +2 -1
- package/lib/types/client/icons.js +1 -0
- package/lib/types/client/index.js +276 -72
- package/lib/types/client/locales.d.ts +482 -52
- package/lib/types/client/locales.js +490 -60
- package/lib/types/client/nodes.d.ts +112 -17
- package/lib/types/client/nodes.js +197 -26
- package/lib/types/client/slots.d.ts +117 -47
- package/lib/types/client/task-tag.d.ts +32 -0
- package/lib/types/client/task-tag.js +33 -0
- package/lib/types/client/tasks.d.ts +50 -0
- package/lib/types/client/tasks.js +82 -0
- package/lib/types/client/team.d.ts +73 -0
- package/lib/types/client/team.js +187 -0
- package/lib/types/decisions.d.ts +49 -0
- package/lib/types/decisions.js +101 -0
- package/lib/types/deliveries.d.ts +17 -1
- package/lib/types/deliveries.js +82 -6
- package/lib/types/dispatch.d.ts +92 -4
- package/lib/types/dispatch.js +477 -109
- package/lib/types/help.d.ts +137 -0
- package/lib/types/help.js +216 -0
- package/lib/types/helps.d.ts +26 -0
- package/lib/types/helps.js +47 -0
- package/lib/types/index.d.ts +292 -52
- package/lib/types/index.js +1415 -199
- package/lib/types/jobs.d.ts +22 -0
- package/lib/types/jobs.js +77 -0
- package/lib/types/journal.d.ts +17 -27
- package/lib/types/journal.js +120 -43
- package/lib/types/messaging.d.ts +65 -0
- package/lib/types/messaging.js +160 -0
- package/lib/types/plan-service.js +7 -1
- package/lib/types/report-gate.d.ts +81 -0
- package/lib/types/report-gate.js +89 -0
- package/lib/types/tool-definition.d.ts +43 -20
- package/lib/types/tool-definition.js +145 -212
- package/lib/types/tool.d.ts +2 -2
- package/lib/types/tool.js +3 -2
- package/lib/types/types.d.ts +755 -78
- package/locale/zh.json +1 -1
- package/package.json +3 -3
- package/lib/types/client/RoomActivityDock.d.ts +0 -19
- package/lib/types/client/RoomActivityDock.js +0 -105
- package/lib/types/client/RoomDockCapsules.d.ts +0 -64
- package/lib/types/client/RoomDockCapsules.js +0 -277
- package/lib/types/client/RoomPlanTab.d.ts +0 -15
- package/lib/types/client/RoomPlanTab.js +0 -13
- package/lib/types/client/RoomTaskLineView.d.ts +0 -13
- package/lib/types/client/RoomTaskLineView.js +0 -21
|
@@ -10,11 +10,13 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
10
10
|
* degrade). Unlike the official row there is no window-fold fallback — the
|
|
11
11
|
* takeover has no business re-deriving what the host already projects.
|
|
12
12
|
*
|
|
13
|
-
* Semantics: the figures are the ROOM SESSION's own — its main
|
|
14
|
-
* turns and billing.
|
|
15
|
-
*
|
|
13
|
+
* Semantics: the projected figures are the ROOM SESSION's own — its main
|
|
14
|
+
* agent's turns and billing. Members consume in their own child sessions, so
|
|
15
|
+
* when the room has member runs the row adds a members chip (how many
|
|
16
|
+
* members worked, their billed tokens) whose breakdown lists each member's
|
|
17
|
+
* model, rounds, tokens and run time from the room's execution records.
|
|
16
18
|
*/
|
|
17
|
-
import { Fragment, memo, useLayoutEffect, useRef, useState } from 'react';
|
|
19
|
+
import { Fragment, memo, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
18
20
|
import { Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
19
21
|
import css from './RoomStatsLine.module.css';
|
|
20
22
|
/**
|
|
@@ -75,7 +77,47 @@ export function cacheHitPercent(usage) {
|
|
|
75
77
|
? null
|
|
76
78
|
: Math.round(usage.cacheReadTokens / denominator * 100);
|
|
77
79
|
}
|
|
78
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Per-member usage from the room's execution records: every run but the
|
|
82
|
+
* main agent's (its turns are the projected figures already), in roster
|
|
83
|
+
* order. Members without a run do not appear.
|
|
84
|
+
* @param state - the replayed room state.
|
|
85
|
+
* @returns one row per member that ran.
|
|
86
|
+
*/
|
|
87
|
+
export function memberUsage(state) {
|
|
88
|
+
if (state === undefined)
|
|
89
|
+
return [];
|
|
90
|
+
const main = new Set(state.members.filter(member => member.kind === 'main-agent').map(member => member.name));
|
|
91
|
+
const rows = new Map();
|
|
92
|
+
for (const run of state.executions ?? []) {
|
|
93
|
+
if (main.has(run.member))
|
|
94
|
+
continue;
|
|
95
|
+
const row = rows.get(run.member) ?? { member: run.member, rounds: 0, tokens: 0, elapsedMs: 0 };
|
|
96
|
+
row.rounds += 1;
|
|
97
|
+
row.tokens += run.tokens ?? 0;
|
|
98
|
+
row.elapsedMs += run.elapsedMs ?? 0;
|
|
99
|
+
if (run.model !== undefined)
|
|
100
|
+
row.model = run.model;
|
|
101
|
+
rows.set(run.member, row);
|
|
102
|
+
}
|
|
103
|
+
const order = state.members.map(member => member.name);
|
|
104
|
+
return [...rows.values()].sort((a, b) => order.indexOf(a.member) - order.indexOf(b.member));
|
|
105
|
+
}
|
|
106
|
+
export const RoomStatsLine = memo(function RoomStatsLine({ useProjection, state, t }) {
|
|
107
|
+
const members = memberUsage(state);
|
|
108
|
+
const [open, setOpen] = useState(false);
|
|
109
|
+
const membersRef = useRef(null);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (!open)
|
|
112
|
+
return;
|
|
113
|
+
const close = (event) => {
|
|
114
|
+
if (event instanceof KeyboardEvent ? event.key === 'Escape' : membersRef.current?.contains(event.target) !== true)
|
|
115
|
+
setOpen(false);
|
|
116
|
+
};
|
|
117
|
+
document.addEventListener('pointerdown', close);
|
|
118
|
+
document.addEventListener('keydown', close);
|
|
119
|
+
return () => { document.removeEventListener('pointerdown', close); document.removeEventListener('keydown', close); };
|
|
120
|
+
}, [open]);
|
|
79
121
|
const stats = useProjection('sessionStats');
|
|
80
122
|
const usage = useProjection('tokenUsage');
|
|
81
123
|
// Pipe-separated groups, mirroring the official assembly; a group with no
|
|
@@ -132,7 +174,11 @@ export const RoomStatsLine = memo(function RoomStatsLine({ useProjection, t }) {
|
|
|
132
174
|
observer.observe(el);
|
|
133
175
|
return () => { observer.disconnect(); };
|
|
134
176
|
}, [line]);
|
|
135
|
-
if (groups.length === 0)
|
|
177
|
+
if (groups.length === 0 && members.length === 0)
|
|
136
178
|
return null;
|
|
137
|
-
|
|
179
|
+
const own = groups.length === 0 ? null : (_jsx(Tooltip, { label: line, side: "top", delayMs: 500, disabled: !truncated, children: _jsx("div", { ref: rootRef, className: css.root, children: groups.map((group, i) => (_jsxs(Fragment, { children: [i > 0 && _jsxs(_Fragment, { children: [_jsx("span", { className: css.sep, "aria-hidden": true, children: "|" }), ' '] }), _jsx("span", { children: group })] }, group))) }) }));
|
|
180
|
+
if (members.length === 0)
|
|
181
|
+
return own;
|
|
182
|
+
const total = members.reduce((sum, row) => sum + row.tokens, 0);
|
|
183
|
+
return (_jsxs("div", { className: css.withMembers, ref: membersRef, children: [own, own !== null && _jsx("span", { className: css.sep, "aria-hidden": true, children: "|" }), _jsxs("button", { type: "button", className: css.membersChip, "aria-expanded": open, onClick: () => { setOpen(value => !value); }, children: [total > 0 ? t('stats.members', { count: members.length, tokens: formatTokens(total) }) : t('stats.membersNoTokens', { count: members.length }), " \u25BE"] }), open && (_jsx("div", { className: css.breakdown, role: "dialog", "aria-label": t('stats.membersTitle'), children: _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: t('stats.col.member') }), _jsx("th", { children: t('stats.col.model') }), _jsx("th", { className: css.num, children: t('stats.col.rounds') }), _jsx("th", { className: css.num, children: t('stats.col.tokens') }), _jsx("th", { className: css.num, children: t('stats.col.time') })] }) }), _jsx("tbody", { children: members.map(row => (_jsxs("tr", { children: [_jsx("td", { className: css.name, children: row.member }), _jsx("td", { children: row.model ?? '—' }), _jsx("td", { className: css.num, children: row.rounds }), _jsx("td", { className: css.num, children: row.tokens > 0 ? formatTokens(row.tokens) : '—' }), _jsx("td", { className: css.num, children: row.elapsedMs > 0 ? formatDuration(row.elapsedMs) : '—' })] }, row.member))) })] }) }))] }));
|
|
138
184
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The task capsule above the room composer: 「● N 个任务进行中」 plus, when
|
|
3
|
+
* something waits on the human (an uncertain outcome, a held message), an
|
|
4
|
+
* amber 「⚠ N 件需要处理」. Both open the task page in the right sidebar
|
|
5
|
+
* (the second straight at the first task that needs the human). A fresh
|
|
6
|
+
* room — no task, no goal — shows a single 「+ 邀请成员」 capsule instead.
|
|
7
|
+
* Without the sidebar the capsule cannot open anything, so uncertain
|
|
8
|
+
* outcomes fall back to the inline reconcile view.
|
|
9
|
+
*/
|
|
10
|
+
import { type ReactNode } from 'react';
|
|
11
|
+
import type { RoomState } from '../types.ts';
|
|
12
|
+
import type { RoomComposerProps } from './slots.ts';
|
|
13
|
+
type Props = Pick<RoomComposerProps, 'openTasks' | 'reconcileDelivery' | 'openPlanSession' | 'invite' | 'listProviders' | 'roomCwd' | 'browseDirectory' | 'modelSurface' | 'renderHarnessModelPicker' | 't'> & {
|
|
14
|
+
readonly state: RoomState;
|
|
15
|
+
};
|
|
16
|
+
/** The capsule row. */
|
|
17
|
+
export declare function RoomTaskCapsule({ state, openTasks, reconcileDelivery, openPlanSession, invite, listProviders, roomCwd, browseDirectory, modelSurface, renderHarnessModelPicker, t, }: Props): ReactNode;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=RoomTaskCapsule.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The task capsule above the room composer: 「● N 个任务进行中」 plus, when
|
|
4
|
+
* something waits on the human (an uncertain outcome, a held message), an
|
|
5
|
+
* amber 「⚠ N 件需要处理」. Both open the task page in the right sidebar
|
|
6
|
+
* (the second straight at the first task that needs the human). A fresh
|
|
7
|
+
* room — no task, no goal — shows a single 「+ 邀请成员」 capsule instead.
|
|
8
|
+
* Without the sidebar the capsule cannot open anything, so uncertain
|
|
9
|
+
* outcomes fall back to the inline reconcile view.
|
|
10
|
+
*/
|
|
11
|
+
import { useState } from 'react';
|
|
12
|
+
import { InviteDialog, inviteRequest } from './InviteDialog.js';
|
|
13
|
+
import { RoomRecoveryView } from './RoomRecoveryView.js';
|
|
14
|
+
import { attentionCount, needsAttention } from './tasks.js';
|
|
15
|
+
import css from './RoomTaskCapsule.module.css';
|
|
16
|
+
/** The capsule row. */
|
|
17
|
+
export function RoomTaskCapsule({ state, openTasks, reconcileDelivery, openPlanSession, invite, listProviders, roomCwd, browseDirectory, modelSurface, renderHarnessModelPicker, t, }) {
|
|
18
|
+
const [inviteOpen, setInviteOpen] = useState(false);
|
|
19
|
+
const [providers, setProviders] = useState(undefined);
|
|
20
|
+
const jobs = state.jobs ?? [];
|
|
21
|
+
if (jobs.length === 0 && state.goal === undefined && state.plan === undefined) {
|
|
22
|
+
const submitInvite = (values) => invite(inviteRequest(values));
|
|
23
|
+
return _jsxs("div", { className: css.root, children: [_jsx("div", { className: css.capsules, children: _jsx("button", { type: "button", className: css.capsule, onClick: () => {
|
|
24
|
+
setProviders(undefined);
|
|
25
|
+
setInviteOpen(true);
|
|
26
|
+
void listProviders().then(setProviders);
|
|
27
|
+
}, children: _jsx("span", { className: css.guide, children: t('members.invite') }) }) }), inviteOpen && _jsx(InviteDialog, { mode: "invite", providers: providers?.providers, presets: providers?.presets, sessionPreset: providers?.sessionPreset, 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 })] });
|
|
28
|
+
}
|
|
29
|
+
if (openTasks === undefined) {
|
|
30
|
+
return reconcileDelivery === undefined ? null
|
|
31
|
+
: _jsx(RoomRecoveryView, { deliveries: state.deliveries ?? [], members: state.members, reconcile: reconcileDelivery, openSession: openPlanSession, t: t });
|
|
32
|
+
}
|
|
33
|
+
const running = jobs.filter(job => job.status === 'running').length;
|
|
34
|
+
const attention = attentionCount(state);
|
|
35
|
+
const firstAttention = jobs.find(job => needsAttention(state, job));
|
|
36
|
+
return _jsx("div", { className: css.root, children: _jsxs("div", { className: css.capsules, children: [_jsxs("button", { type: "button", className: css.capsule, "data-testid": "room-task-capsule", ...running > 0 ? { 'data-running': '' } : {}, onClick: () => { openTasks(); }, children: [running > 0 && _jsx("span", { className: css.dot, "aria-hidden": true }), running > 0 ? t('job.capsule.running', { count: running }) : t('job.capsule.idle', { count: jobs.length })] }), attention > 0 && _jsx("button", { type: "button", className: css.capsule, "data-attention": "", onClick: () => { openTasks(firstAttention?.id); }, children: t('job.capsule.attention', { count: attention }) })] }) });
|
|
37
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The room's collaboration page as one right-sidebar page (kind
|
|
3
|
+
* `room-tasks`): a 任务 / 团队 switch over the task page and the team view,
|
|
4
|
+
* so the two never open as separate sidebar tabs. Opened from the composer's
|
|
5
|
+
* task capsule (`view` tasks, an optional `task` param opens that task) and
|
|
6
|
+
* from the members tab's 团队 button (`view` team, an optional `member` param
|
|
7
|
+
* reveals that member); every navigation (revision bump) applies again.
|
|
8
|
+
* 「追问」 / 「+ 新任务」 tag the main composer and, on a narrow window where
|
|
9
|
+
* the sidebar covers the conversation, close this page so the composer is in
|
|
10
|
+
* reach.
|
|
11
|
+
*/
|
|
12
|
+
import { type ReactNode } from 'react';
|
|
13
|
+
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
14
|
+
import type { RoomStore } from './room-store.ts';
|
|
15
|
+
import type { RoomTaskTags } from './task-tag.ts';
|
|
16
|
+
import { type TasksViewActions } from './TasksView.tsx';
|
|
17
|
+
export declare const ROOM_TASKS_KIND = "room-tasks";
|
|
18
|
+
export declare const ROOM_TASKS_TAB_ID = "@khorsheed/dsh-room/tasks";
|
|
19
|
+
declare module '@deepseek-ai/dsh-client-ui-sidebar-right/client' {
|
|
20
|
+
interface SidebarRightTabParamsMap {
|
|
21
|
+
'room-tasks': {
|
|
22
|
+
readonly view?: 'tasks' | 'team';
|
|
23
|
+
readonly task?: string;
|
|
24
|
+
readonly member?: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** What the task page needs injected. */
|
|
29
|
+
export interface RoomTasksInjected extends Omit<TasksViewActions, 'compose'> {
|
|
30
|
+
readonly roomStore: RoomStore;
|
|
31
|
+
readonly taskTags: RoomTaskTags;
|
|
32
|
+
/** Keep an outside session on the member list (the team view's 加入房间成员). */
|
|
33
|
+
readonly linkSession?: ((target: string, title: string) => void) | undefined;
|
|
34
|
+
/** Take it off the list. */
|
|
35
|
+
readonly unlinkSession?: ((target: string) => void) | undefined;
|
|
36
|
+
}
|
|
37
|
+
type Props = PropsRuntime<'sidebar.right.pane.tab'> & InjectFace<RoomTasksInjected> & PropsLocale<'room'>;
|
|
38
|
+
/** The sidebar page body. */
|
|
39
|
+
export declare function RoomTasksTab({ sessionId, roomStore, taskTags, useTabInfo, linkSession, unlinkSession, t, ...actions }: Props): ReactNode;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=TasksTab.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The room's collaboration page as one right-sidebar page (kind
|
|
4
|
+
* `room-tasks`): a 任务 / 团队 switch over the task page and the team view,
|
|
5
|
+
* so the two never open as separate sidebar tabs. Opened from the composer's
|
|
6
|
+
* task capsule (`view` tasks, an optional `task` param opens that task) and
|
|
7
|
+
* from the members tab's 团队 button (`view` team, an optional `member` param
|
|
8
|
+
* reveals that member); every navigation (revision bump) applies again.
|
|
9
|
+
* 「追问」 / 「+ 新任务」 tag the main composer and, on a narrow window where
|
|
10
|
+
* the sidebar covers the conversation, close this page so the composer is in
|
|
11
|
+
* reach.
|
|
12
|
+
*/
|
|
13
|
+
import { useEffect, useState, useSyncExternalStore } from 'react';
|
|
14
|
+
import { TasksView } from './TasksView.js';
|
|
15
|
+
import { TeamView } from './TeamView.js';
|
|
16
|
+
import css from './TasksTab.module.css';
|
|
17
|
+
export const ROOM_TASKS_KIND = 'room-tasks';
|
|
18
|
+
export const ROOM_TASKS_TAB_ID = '@khorsheed/dsh-room/tasks';
|
|
19
|
+
/** Below this window width the sidebar covers the conversation. */
|
|
20
|
+
const NARROW = '(max-width: 720px)';
|
|
21
|
+
/** The sidebar page body. */
|
|
22
|
+
export function RoomTasksTab({ sessionId, roomStore, taskTags, useTabInfo, linkSession, unlinkSession, t, ...actions }) {
|
|
23
|
+
const state = useSyncExternalStore(roomStore.subscribe, () => roomStore.getCached(sessionId));
|
|
24
|
+
useEffect(() => { void roomStore.refresh(sessionId); }, [roomStore, sessionId]);
|
|
25
|
+
const { tab } = useTabInfo();
|
|
26
|
+
const params = tab.navigation.params;
|
|
27
|
+
const task = typeof params?.task === 'string' ? params.task : undefined;
|
|
28
|
+
const member = typeof params?.member === 'string' ? params.member : undefined;
|
|
29
|
+
const asked = params?.view === 'team' ? 'team' : 'tasks';
|
|
30
|
+
// The switch is the human's between navigations; a navigation sets it again.
|
|
31
|
+
const [view, setView] = useState(asked);
|
|
32
|
+
useEffect(() => { setView(asked); }, [asked, tab.navigation.revision]);
|
|
33
|
+
if (state === undefined)
|
|
34
|
+
return null;
|
|
35
|
+
const compose = (tag) => {
|
|
36
|
+
taskTags.set(sessionId, tag);
|
|
37
|
+
if (typeof window !== 'undefined' && window.matchMedia?.(NARROW).matches === true)
|
|
38
|
+
tab.actions.close();
|
|
39
|
+
};
|
|
40
|
+
return (_jsxs("div", { className: css.page, children: [_jsx("div", { className: css.switch, role: "tablist", "aria-label": t('collab.title'), children: ['tasks', 'team'].map(entry => (_jsx("button", { type: "button", role: "tab", className: css.switchTab, "aria-selected": view === entry, onClick: () => { setView(entry); }, children: t(entry === 'tasks' ? 'job.title' : 'team.title') }, entry))) }), _jsx("div", { className: css.body, role: "tabpanel", children: view === 'tasks'
|
|
41
|
+
? _jsx(TasksView, { state: state, focus: task === undefined ? undefined : { task, revision: tab.navigation.revision }, compose: compose, ...actions, t: t })
|
|
42
|
+
: _jsx(TeamView, { state: state, focus: member === undefined ? undefined : { member, revision: tab.navigation.revision }, openSession: (id) => { actions.openSession?.(id); }, linkSession: linkSession, unlinkSession: unlinkSession, t: t }) })] }));
|
|
43
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The task page (right sidebar): every task in the room — one human request
|
|
3
|
+
* and the subtask tree it set in motion — with filters, and a detail view
|
|
4
|
+
* per task: what the human said (and how follow-ups were filed), stop the
|
|
5
|
+
* whole task, follow up through the main composer's tag, what needs the
|
|
6
|
+
* human (uncertain outcomes, held messages), the formal goal it belongs to,
|
|
7
|
+
* the subtask tree, and a member swimlane timeline. The room goal sits on
|
|
8
|
+
* top of the list. The page has no input of its own: 「追问」 and
|
|
9
|
+
* 「+ 新任务」 tag the main composer.
|
|
10
|
+
*/
|
|
11
|
+
import { type ReactNode } from 'react';
|
|
12
|
+
import type { RoomState } from '../types.ts';
|
|
13
|
+
import type { RoomComposerProps, RoomMutationOutcome } from './slots.ts';
|
|
14
|
+
type T = RoomComposerProps['t'];
|
|
15
|
+
/** What the task page can do. */
|
|
16
|
+
export interface TasksViewActions {
|
|
17
|
+
readonly setGoal?: ((text: string) => Promise<RoomMutationOutcome>) | undefined;
|
|
18
|
+
readonly stopJob?: ((task: string) => Promise<RoomMutationOutcome>) | undefined;
|
|
19
|
+
/** Stop a member's running round (only the one with that run id, when given). */
|
|
20
|
+
readonly stopDelivery?: ((member: string, deliveryId?: string) => void) | undefined;
|
|
21
|
+
readonly reconcile?: RoomComposerProps['reconcileDelivery'];
|
|
22
|
+
readonly confirmRelay?: ((relayId: string) => Promise<void>) | undefined;
|
|
23
|
+
readonly dismissRelay?: ((relayId: string) => Promise<void>) | undefined;
|
|
24
|
+
readonly planCommand?: ((command: string) => Promise<RoomMutationOutcome>) | undefined;
|
|
25
|
+
readonly openSession?: ((sessionId: string) => void) | undefined;
|
|
26
|
+
/** Tag the main composer (「任务 N」 or 「新任务」) and hand it focus. */
|
|
27
|
+
readonly compose?: ((tag: {
|
|
28
|
+
readonly task: string;
|
|
29
|
+
readonly label: string;
|
|
30
|
+
}) => void) | undefined;
|
|
31
|
+
}
|
|
32
|
+
export interface TasksViewProps extends TasksViewActions {
|
|
33
|
+
readonly state: RoomState;
|
|
34
|
+
/** Open this task (a navigation param); each revision re-opens. */
|
|
35
|
+
readonly focus?: {
|
|
36
|
+
readonly task: string;
|
|
37
|
+
readonly revision: number;
|
|
38
|
+
} | undefined;
|
|
39
|
+
readonly t: T;
|
|
40
|
+
}
|
|
41
|
+
/** The task page body. */
|
|
42
|
+
export declare function TasksView({ state, focus, t, ...actions }: TasksViewProps): ReactNode;
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=TasksView.d.ts.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The task page (right sidebar): every task in the room — one human request
|
|
4
|
+
* and the subtask tree it set in motion — with filters, and a detail view
|
|
5
|
+
* per task: what the human said (and how follow-ups were filed), stop the
|
|
6
|
+
* whole task, follow up through the main composer's tag, what needs the
|
|
7
|
+
* human (uncertain outcomes, held messages), the formal goal it belongs to,
|
|
8
|
+
* the subtask tree, and a member swimlane timeline. The room goal sits on
|
|
9
|
+
* top of the list. The page has no input of its own: 「追问」 and
|
|
10
|
+
* 「+ 新任务」 tag the main composer.
|
|
11
|
+
*/
|
|
12
|
+
import { useEffect, useState } from 'react';
|
|
13
|
+
import { executionRows } from './executions.js';
|
|
14
|
+
import { formatClock, formatDurationMs } from './format.js';
|
|
15
|
+
import { memberColor } from './member-color.js';
|
|
16
|
+
import { RoomPlanView } from './RoomPlanView.js';
|
|
17
|
+
import { RoomRecoveryView } from './RoomRecoveryView.js';
|
|
18
|
+
import { TASK_FILTERS, filterJobs, isOpenDelivery, jobDeliveries, jobElapsed, needsAttention, pendingRelays, runningMembers, segmentsOf, subtaskTree, } from './tasks.js';
|
|
19
|
+
import { IconChevronLeftOutlineMedium } from './icons.js';
|
|
20
|
+
import css from './TasksView.module.css';
|
|
21
|
+
const memberOf = (state, id) => state.members.find(member => member.id === id) ?? state.members.find(member => member.name === id);
|
|
22
|
+
function Avatars({ state, ids }) {
|
|
23
|
+
return _jsx("span", { className: css.avatars, children: ids.map(id => {
|
|
24
|
+
const name = memberOf(state, id)?.name ?? id;
|
|
25
|
+
return _jsx("span", { className: css.avatar, style: { '--member-color': memberColor(name) }, title: name, children: name.slice(0, 1).toUpperCase() }, id);
|
|
26
|
+
}) });
|
|
27
|
+
}
|
|
28
|
+
/** The task's time split into queue / held / exec, across all its deliveries. */
|
|
29
|
+
function Bar({ rows, from, to, now }) {
|
|
30
|
+
const span = Math.max(1, to - from);
|
|
31
|
+
return _jsx("span", { className: css.bar, "aria-hidden": true, children: rows.flatMap(row => segmentsOf(row, now)).map((segment, index) => _jsx("span", { className: css.seg, "data-kind": segment.kind, style: { left: `${(segment.from - from) / span * 100}%`, width: `${Math.max(0.6, (segment.to - segment.from) / span * 100)}%` } }, index)) });
|
|
32
|
+
}
|
|
33
|
+
function useNow(live) {
|
|
34
|
+
const [now, setNow] = useState(Date.now);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!live)
|
|
37
|
+
return;
|
|
38
|
+
setNow(Date.now());
|
|
39
|
+
const timer = setInterval(() => { setNow(Date.now()); }, 1000);
|
|
40
|
+
return () => { clearInterval(timer); };
|
|
41
|
+
}, [live]);
|
|
42
|
+
return now;
|
|
43
|
+
}
|
|
44
|
+
/** The room goal, editable in place. */
|
|
45
|
+
function Goal({ goal, setGoal, t }) {
|
|
46
|
+
const [draft, setDraft] = useState(null);
|
|
47
|
+
const [error, setError] = useState(null);
|
|
48
|
+
const save = async () => {
|
|
49
|
+
if (draft === null || setGoal === undefined)
|
|
50
|
+
return;
|
|
51
|
+
const outcome = await setGoal(draft.trim());
|
|
52
|
+
if (outcome.ok)
|
|
53
|
+
setDraft(null);
|
|
54
|
+
else
|
|
55
|
+
setError(outcome.message);
|
|
56
|
+
};
|
|
57
|
+
if (draft !== null) {
|
|
58
|
+
return _jsxs("div", { className: css.goal, children: [_jsx("input", { className: css.goalInput, value: draft, placeholder: t('goal.placeholder'), autoFocus: true, "aria-label": t('job.goal'), onChange: event => { setDraft(event.target.value); }, onKeyDown: event => { if (event.key === 'Enter')
|
|
59
|
+
void save();
|
|
60
|
+
else if (event.key === 'Escape')
|
|
61
|
+
setDraft(null); } }), _jsx("button", { type: "button", className: css.link, onClick: () => { void save(); }, children: t('goal.save') }), _jsx("button", { type: "button", className: css.link, onClick: () => { setDraft(null); }, children: t('goal.cancel') }), error !== null && _jsx("span", { role: "alert", className: css.error, children: error })] });
|
|
62
|
+
}
|
|
63
|
+
return _jsxs("div", { className: css.goal, children: [_jsx("span", { className: css.goalKey, children: t('job.goal') }), _jsx("span", { className: goal === undefined ? css.muted : css.goalText, children: goal ?? t('job.goalNone') }), setGoal !== undefined && _jsx("button", { type: "button", className: css.link, onClick: () => { setDraft(goal ?? ''); setError(null); }, children: goal === undefined ? t('goal.set') : t('goal.edit') })] });
|
|
64
|
+
}
|
|
65
|
+
function statusOf(state, job) {
|
|
66
|
+
return needsAttention(state, job) ? 'attention' : job.status;
|
|
67
|
+
}
|
|
68
|
+
/** The one-line summary under a row's title: who it is waiting on, or what needs the human. */
|
|
69
|
+
function waitingLine(state, job, t) {
|
|
70
|
+
if (needsAttention(state, job))
|
|
71
|
+
return t('job.need');
|
|
72
|
+
const running = runningMembers(state, job).map(id => memberOf(state, id)?.name ?? id);
|
|
73
|
+
return running.length === 0 ? undefined : t('job.waiting', { member: running.join('、') });
|
|
74
|
+
}
|
|
75
|
+
function JobRow({ state, job, now, open, t }) {
|
|
76
|
+
const status = statusOf(state, job);
|
|
77
|
+
const waiting = waitingLine(state, job, t);
|
|
78
|
+
return _jsxs("button", { type: "button", className: css.row, "data-task": job.id, onClick: open, children: [_jsxs("span", { className: css.rowHead, children: [_jsx("span", { className: css.dot, "data-status": status, "aria-hidden": true }), _jsx("span", { className: css.rowTitle, title: job.title, children: job.title }), _jsx("span", { className: css.elapsed, children: formatDurationMs(jobElapsed(job, now)) })] }), _jsxs("span", { className: css.rowMeta, children: [_jsx(Avatars, { state: state, ids: job.members }), _jsx("span", { className: css.tag, "data-status": status, children: t(`job.status.${status}`) }), waiting !== undefined && _jsx("span", { className: status === 'attention' ? css.need : css.now, children: waiting }), job.goalId !== undefined && job.goalId === state.plan?.id && _jsx("span", { className: css.tag, "data-status": "goal", children: t('job.goalTag', { objective: state.plan.objective }) })] }), _jsx(Bar, { rows: jobDeliveries(state, job), from: job.startedAt, to: job.endedAt ?? now, now: now })] });
|
|
79
|
+
}
|
|
80
|
+
function SubtaskTree({ state, nodes, now, actions, t }) {
|
|
81
|
+
const executions = new Map(executionRows(state).map(row => [row.id, row]));
|
|
82
|
+
const render = (node) => {
|
|
83
|
+
const row = node.delivery;
|
|
84
|
+
const member = memberOf(state, row.memberId);
|
|
85
|
+
const name = member?.name ?? row.memberId;
|
|
86
|
+
const segments = segmentsOf(row, now);
|
|
87
|
+
const from = segments[0]?.from ?? row.queuedAt;
|
|
88
|
+
const to = segments.at(-1)?.to ?? from;
|
|
89
|
+
const tokens = executions.get(row.id)?.tokens;
|
|
90
|
+
return _jsxs("li", { children: [_jsxs("div", { className: css.node, "data-delivery": row.id, children: [_jsx("span", { className: css.nodeState, "data-status": row.status, "aria-label": t(`team.status.${row.status}`) }), _jsx("span", { className: css.avatar, style: { '--member-color': memberColor(name) }, "aria-hidden": true, children: name.slice(0, 1).toUpperCase() }), _jsx("span", { className: css.nodeName, children: name }), _jsxs("span", { className: css.nodeText, title: row.text, children: [row.kind === 'notify' ? `${t('job.notify')} · ` : '', row.text.replace(/\s+/g, ' ')] }), _jsx(Bar, { rows: [row], from: from, to: Math.max(to, from + 1), now: now }), _jsxs("span", { className: css.elapsed, children: [formatDurationMs(Math.max(0, to - from)), tokens === undefined ? '' : ` · ${Intl.NumberFormat(undefined, { notation: 'compact', maximumFractionDigits: 1 }).format(tokens)} tok`] }), row.status === 'running' && actions.stopDelivery !== undefined && member !== undefined
|
|
91
|
+
? _jsx("button", { type: "button", className: css.small, onClick: () => { actions.stopDelivery?.(member.name, row.id); }, children: t('job.node.stop') })
|
|
92
|
+
: member?.childSessionId !== undefined && actions.openSession !== undefined && _jsx("button", { type: "button", className: css.small, onClick: () => { actions.openSession?.(member.childSessionId); }, children: t('job.node.session') })] }), row.error !== undefined && _jsx("p", { className: css.nodeError, children: row.error }), node.children.length > 0 && _jsx("ul", { className: css.tree, children: node.children.map(render) })] }, row.id);
|
|
93
|
+
};
|
|
94
|
+
return _jsx("ul", { className: css.tree, "data-root": true, children: nodes.map(render) });
|
|
95
|
+
}
|
|
96
|
+
/** One lane per member, each delivery's queue / held / exec stretches along the task's span. */
|
|
97
|
+
function Timeline({ state, job, now, t }) {
|
|
98
|
+
const rows = jobDeliveries(state, job);
|
|
99
|
+
const lanes = [...new Set(rows.map(row => row.memberId))];
|
|
100
|
+
const to = job.endedAt ?? now;
|
|
101
|
+
return _jsxs("div", { className: css.timeline, children: [lanes.map(id => {
|
|
102
|
+
const name = memberOf(state, id)?.name ?? id;
|
|
103
|
+
return _jsxs("div", { className: css.lane, children: [_jsx("span", { className: css.laneName, title: name, children: name }), _jsx(Bar, { rows: rows.filter(row => row.memberId === id), from: job.startedAt, to: to, now: now })] }, id);
|
|
104
|
+
}), _jsxs("div", { className: css.axis, children: [_jsx("span", { children: formatClock(job.startedAt) }), _jsx("span", { children: formatDurationMs(Math.max(0, to - job.startedAt)) })] }), _jsx("div", { className: css.legend, children: ['queue', 'held', 'exec'].map(kind => _jsxs("span", { children: [_jsx("i", { className: css.seg, "data-kind": kind }), t(`job.seg.${kind}`)] }, kind)) })] });
|
|
105
|
+
}
|
|
106
|
+
function JobDetail({ state, job, now, actions, back, t }) {
|
|
107
|
+
const [confirming, setConfirming] = useState(false);
|
|
108
|
+
const [busy, setBusy] = useState(false);
|
|
109
|
+
const [message, setMessage] = useState(null);
|
|
110
|
+
const status = statusOf(state, job);
|
|
111
|
+
const rows = jobDeliveries(state, job);
|
|
112
|
+
const open = rows.filter(isOpenDelivery);
|
|
113
|
+
const relays = pendingRelays(state).filter(relay => relay.intent?.task === job.id);
|
|
114
|
+
const label = t('job.number', { number: job.number });
|
|
115
|
+
const stop = async () => {
|
|
116
|
+
if (actions.stopJob === undefined || busy)
|
|
117
|
+
return;
|
|
118
|
+
setBusy(true);
|
|
119
|
+
try {
|
|
120
|
+
const outcome = await actions.stopJob(job.id);
|
|
121
|
+
setMessage(outcome.ok ? null : outcome.message);
|
|
122
|
+
setConfirming(false);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
setBusy(false);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const stopping = [...new Set(open.map(row => memberOf(state, row.memberId)?.name ?? row.memberId))];
|
|
129
|
+
return _jsxs("div", { className: css.detail, "data-task": job.id, children: [_jsxs("button", { type: "button", className: css.back, onClick: back, children: [_jsx(IconChevronLeftOutlineMedium, { size: 18 }), t('job.back')] }), _jsxs("div", { className: css.card, children: [_jsxs("strong", { className: css.cardTitle, children: [label, " \u00B7 ", job.title] }), _jsxs("div", { className: css.rowMeta, children: [_jsx("span", { className: css.tag, "data-status": status, children: t(`job.status.${status}`) }), _jsx("span", { className: css.muted, children: t('job.elapsed', { time: formatDurationMs(jobElapsed(job, now)) }) }), _jsx(Avatars, { state: state, ids: job.members })] }), _jsx("ul", { className: css.messages, children: job.messages.map(entry => _jsxs("li", { children: [_jsx("span", { className: css.muted, children: formatClock(entry.time) }), _jsxs("span", { className: css.messageText, children: [t('job.you'), entry.text] }), entry.by !== undefined && _jsx("span", { className: css.attr, children: entry.by === 'model'
|
|
130
|
+
? t('job.by.model', { probability: (entry.probability ?? 0).toFixed(2) })
|
|
131
|
+
: t(`job.by.${entry.by}`) })] }, entry.id)) }), _jsx(Bar, { rows: rows, from: job.startedAt, to: job.endedAt ?? now, now: now })] }), _jsxs("div", { className: css.actions, children: [open.length > 0 && actions.stopJob !== undefined && !confirming
|
|
132
|
+
&& _jsx("button", { type: "button", className: css.danger, onClick: () => { setConfirming(true); }, children: t('job.stop') }), actions.compose !== undefined && _jsx("button", { type: "button", className: css.primary, onClick: () => { actions.compose?.({ task: job.id, label }); }, children: t('job.followUp') }), actions.compose !== undefined && _jsx("span", { className: css.muted, children: t('job.followUpHint', { label }) })] }), confirming && _jsxs("div", { className: css.confirm, role: "alertdialog", "aria-label": t('job.stop'), children: [_jsx("p", { children: t('job.stopConfirm', { members: stopping.join('、') }) }), _jsxs("div", { className: css.actions, children: [_jsx("button", { type: "button", className: css.danger, disabled: busy, onClick: () => { void stop(); }, children: t('job.stopYes') }), _jsx("button", { type: "button", className: css.small, onClick: () => { setConfirming(false); }, children: t('goal.cancel') })] })] }), message !== null && _jsx("p", { role: "alert", className: css.error, children: message }), actions.reconcile !== undefined && _jsx(RoomRecoveryView, { deliveries: rows, members: state.members, reconcile: actions.reconcile, openSession: actions.openSession, t: t }), relays.map(relay => _jsxs("div", { className: css.need, children: [_jsx("p", { children: t('job.relay', { from: relay.from, to: relay.to }) }), _jsx("p", { className: css.messageText, children: relay.content }), _jsxs("div", { className: css.actions, children: [actions.confirmRelay !== undefined && _jsx("button", { type: "button", className: css.small, onClick: () => { void actions.confirmRelay?.(relay.id); }, children: t('job.relayConfirm') }), actions.dismissRelay !== undefined && _jsx("button", { type: "button", className: css.small, onClick: () => { void actions.dismissRelay?.(relay.id); }, children: t('job.relayDismiss') })] })] }, relay.id)), job.goalId !== undefined && job.goalId === state.plan?.id && actions.planCommand !== undefined && _jsxs("section", { className: css.section, children: [_jsx("h4", { children: t('job.goalTag', { objective: state.plan.objective }) }), _jsx(RoomPlanView, { plan: state.plan, members: state.members, command: actions.planCommand, openSession: actions.openSession, stopMember: actions.stopDelivery, t: t })] }), _jsxs("section", { className: css.section, children: [_jsx("h4", { children: t('job.subtasks') }), _jsx(SubtaskTree, { state: state, nodes: subtaskTree(state, job), now: now, actions: actions, t: t })] }), _jsxs("section", { className: css.section, children: [_jsx("h4", { children: t('job.timeline') }), _jsx(Timeline, { state: state, job: job, now: now, t: t })] })] });
|
|
133
|
+
}
|
|
134
|
+
/** The task page body. */
|
|
135
|
+
export function TasksView({ state, focus, t, ...actions }) {
|
|
136
|
+
const jobs = state.jobs ?? [];
|
|
137
|
+
const [filter, setFilter] = useState(() => jobs.some(job => job.status === 'running' || needsAttention(state, job)) ? 'running' : 'all');
|
|
138
|
+
const [selected, setSelected] = useState(focus?.task ?? null);
|
|
139
|
+
useEffect(() => { if (focus !== undefined)
|
|
140
|
+
setSelected(focus.task); }, [focus?.task, focus?.revision]);
|
|
141
|
+
const now = useNow(jobs.some(job => job.status === 'running' || job.status === 'attention'));
|
|
142
|
+
const job = jobs.find(entry => entry.id === selected);
|
|
143
|
+
if (job !== undefined)
|
|
144
|
+
return _jsx("div", { className: css.page, children: _jsx(JobDetail, { state: state, job: job, now: now, actions: actions, back: () => { setSelected(null); }, t: t }) });
|
|
145
|
+
const shown = filterJobs(state, filter);
|
|
146
|
+
// A formal goal no task has picked up yet still needs a home.
|
|
147
|
+
const looseGoal = state.plan !== undefined && !jobs.some(entry => entry.goalId === state.plan?.id);
|
|
148
|
+
return _jsxs("div", { className: css.page, children: [_jsx(Goal, { goal: state.goal, setGoal: actions.setGoal, t: t }), _jsxs("div", { className: css.filters, role: "group", "aria-label": t('job.title'), children: [TASK_FILTERS.map(value => _jsxs("button", { type: "button", className: css.filter, "data-kind": value, "aria-pressed": filter === value, onClick: () => { setFilter(value); }, children: [t(`job.filter.${value}`), _jsx("span", { className: css.count, children: filterJobs(state, value).length })] }, value)), actions.compose !== undefined && _jsx("button", { type: "button", className: css.newTask, onClick: () => { actions.compose?.({ task: 'new', label: t('job.tagNew') }); }, children: t('job.new') })] }), _jsxs("div", { className: css.list, children: [shown.map(entry => _jsx(JobRow, { state: state, job: entry, now: now, open: () => { setSelected(entry.id); }, t: t }, entry.id)), shown.length === 0 && _jsx("p", { className: css.muted, children: jobs.length === 0 ? t('job.empty') : t('job.emptyFilter') })] }), looseGoal && actions.planCommand !== undefined && _jsxs("section", { className: css.section, children: [_jsx("h4", { children: t('job.goalTag', { objective: state.plan.objective }) }), _jsx(RoomPlanView, { plan: state.plan, members: state.members, command: actions.planCommand, openSession: actions.openSession, stopMember: actions.stopDelivery, t: t })] })] });
|
|
149
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The team view: the room's organization in the right sidebar, opened from
|
|
3
|
+
* the members tab. One layout that adapts to its own width — narrow, an
|
|
4
|
+
* indented tree (you → coordinator → members) whose nodes expand into the
|
|
5
|
+
* deliveries they received; wide (≥ 600px), the same tree laid out
|
|
6
|
+
* top-down with drawn edges (solid = tasks handed over, with the report
|
|
7
|
+
* count coming back; dashed = FYIs off the tree) and the selected member's
|
|
8
|
+
* details below. Opening it with a `member` param selects and reveals that
|
|
9
|
+
* member (the cards' 在团队中定位).
|
|
10
|
+
*/
|
|
11
|
+
import { type ReactNode } from 'react';
|
|
12
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
13
|
+
import type { RoomState } from '../types.ts';
|
|
14
|
+
import type { MembersViewProps } from './slots.ts';
|
|
15
|
+
type T = MembersViewProps['t'];
|
|
16
|
+
/** What a link control needs. */
|
|
17
|
+
interface LinkActions {
|
|
18
|
+
readonly linkSession?: ((target: string, title: string) => void) | undefined;
|
|
19
|
+
readonly unlinkSession?: ((target: string) => void) | undefined;
|
|
20
|
+
}
|
|
21
|
+
/** The team view body. */
|
|
22
|
+
export declare function TeamView({ state, focus, openSession, linkSession, unlinkSession, t }: LinkActions & {
|
|
23
|
+
readonly state: RoomState;
|
|
24
|
+
/** A member name to select and reveal (the navigation param). */
|
|
25
|
+
readonly focus?: {
|
|
26
|
+
readonly member: string;
|
|
27
|
+
readonly revision: number;
|
|
28
|
+
} | undefined;
|
|
29
|
+
readonly openSession: (sessionId: SessionId) => void;
|
|
30
|
+
readonly t: T;
|
|
31
|
+
}): ReactNode;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=TeamView.d.ts.map
|