@qilin-agent/client-ui-subagent 0.0.0-stage → 3.1.4
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/LICENSE +21 -0
- package/README.i18n.yaml +64 -0
- package/README.md +117 -2
- package/README.zh.md +118 -0
- package/lib/client.js +970 -0
- package/lib/index.js +11 -0
- package/lib/types/client/SubagentHeaderLineage.d.ts +30 -0
- package/lib/types/client/SubagentReadOnlyComposer.d.ts +17 -0
- package/lib/types/client/index.d.ts +19 -0
- package/lib/types/client/locales.d.ts +49 -0
- package/lib/types/client/sidebar-chat/index.d.ts +62 -0
- package/lib/types/index.d.ts +9 -0
- package/package.json +81 -3
package/lib/client.js
ADDED
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@qilin-agent/client-ui-subagent",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
+
let react = require("react");
|
|
9
|
+
let react_dom = require("react-dom");
|
|
10
|
+
let _qilin_agent_client_ui_primitives = require("@qilin-agent/client-ui-primitives");
|
|
11
|
+
//#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-subagent/src/client/SubagentHeaderLineage.module.css.mjs
|
|
12
|
+
const css$2 = ".fO_A5q_root{align-items:center;gap:10px;min-width:0;display:inline-flex;position:relative}.fO_A5q_switcherRoot{min-width:0;margin-left:6px}.fO_A5q_trigger,.fO_A5q_switcherTrigger{min-height:28px;color:var(--qilin-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:6px;align-items:center;padding:3px 2px;font-size:12px;line-height:18px;display:inline-flex}.fO_A5q_trigger{gap:4px}.fO_A5q_switcherTrigger{min-width:0;max-width:244px;color:var(--qilin-alias-label-primary);gap:4px;font-weight:500}.fO_A5q_ancestorSwitcherTrigger{color:var(--qilin-alias-label-tertiary);font-weight:400}.fO_A5q_switcherTitle{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.fO_A5q_switcherTrigger svg{flex:none}.fO_A5q_activitySlot{flex:none;justify-content:center;align-items:center;width:14px;height:14px;display:inline-flex}.fO_A5q_trigger:hover,.fO_A5q_trigger:focus-visible{color:var(--qilin-alias-label-secondary)}.fO_A5q_switcherTrigger:hover,.fO_A5q_switcherTrigger:focus-visible{color:var(--qilin-alias-label-primary)}.fO_A5q_ancestorSwitcherTrigger:hover,.fO_A5q_ancestorSwitcherTrigger:focus-visible{color:var(--qilin-alias-label-tertiary)}.fO_A5q_trigger svg,.fO_A5q_switcherTrigger svg{transition:transform .12s}.fO_A5q_triggerOpen{transform:rotate(180deg)}.fO_A5q_menu{z-index:100;box-sizing:border-box;background:var(--qilin-specific-menu);width:336px;max-width:min(400px,100vw - 32px);max-height:min(560px,100vh - 140px);backdrop-filter:var(--qilin-menu-backdrop-filter);--qilin-scrollbar-thumb:var(--qilin-alias-scrollbar-bg-l2);--qilin-scrollbar-thumb-hover:var(--qilin-alias-scrollbar-hover-l2);--qilin-elevation-stroke-color:var(--qilin-alias-border-l1);box-shadow:var(--qilin-elevation-prominent);border-radius:16px;flex-direction:column;padding:3px;display:flex;position:fixed;overflow:auto}.fO_A5q_node{min-width:0;position:relative}.fO_A5q_menu>.fO_A5q_node{margin-left:-2px}.fO_A5q_row{box-sizing:border-box;width:100%;min-height:44px;color:var(--qilin-alias-label-primary);text-align:left;cursor:pointer;background:0 0;border:0;border-radius:7px;outline:none;align-items:flex-start;gap:6px;padding:6px 7px 6px 9px;font-size:12px;line-height:17px;display:flex;position:relative}.fO_A5q_row:hover>.fO_A5q_clickarea,.fO_A5q_row:focus-visible>.fO_A5q_clickarea{background:var(--qilin-alias-interactive-bg-hover)}.fO_A5q_clickarea{box-sizing:border-box;border-radius:7px;flex:1;align-self:stretch;align-items:flex-start;gap:6px;min-width:0;margin:-6px -7px;padding:6px 7px;display:flex}.fO_A5q_rowActivitySlot{flex:none;justify-content:center;align-items:center;width:14px;height:17px;display:inline-flex}.fO_A5q_disclosure,.fO_A5q_disclosureSpace{flex:none;width:14px;height:17px}.fO_A5q_disclosure{color:var(--qilin-alias-label-tertiary);cursor:pointer;background:0 0;border:0;justify-content:center;align-items:center;padding:0;transition:transform .12s;display:inline-flex}.fO_A5q_disclosure svg{width:12px;height:12px}.fO_A5q_disclosure:hover{color:var(--qilin-alias-label-primary)}.fO_A5q_disclosureOpen{transform:rotate(90deg)}.fO_A5q_content{flex-direction:column;flex:1;min-width:0;display:flex}.fO_A5q_label,.fO_A5q_summary{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.fO_A5q_label{color:inherit;font-weight:400}.fO_A5q_currentLabel{font-weight:600}.fO_A5q_summary,.fO_A5q_metrics{color:var(--qilin-alias-label-tertiary);font-size:10px;line-height:15px}.fO_A5q_metrics{font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap;flex:none;grid-template-rows:17px 15px;display:grid}.fO_A5q_metricToken{grid-row:1;line-height:17px}.fO_A5q_metricDuration{grid-row:2}.fO_A5q_sidebarButton{corner-shape:round;width:28px;height:28px;color:var(--qilin-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:50%;flex:none;justify-content:center;align-items:center;margin:4px 0;padding:6px;display:inline-flex}.fO_A5q_sidebarButton:hover,.fO_A5q_sidebarButton:focus-visible{background:var(--qilin-alias-interactive-bg-hover);color:var(--qilin-alias-label-primary)}.fO_A5q_children{margin-left:16px;padding-left:3px;position:relative}.fO_A5q_children:before,.fO_A5q_children>.fO_A5q_node:before{content:\"\";border-left:.5px solid var(--qilin-alias-border-l2);position:absolute;left:0}.fO_A5q_children:before{height:23px;top:-23px}.fO_A5q_children[aria-busy=true]:before{content:none}.fO_A5q_children>.fO_A5q_node:before{top:0;bottom:0;left:-3px}.fO_A5q_children>.fO_A5q_node:last-child:before{height:15px;bottom:auto}.fO_A5q_children>.fO_A5q_node>.fO_A5q_row:before{content:\"\";border-top:.5px solid var(--qilin-alias-border-l2);width:12px;position:absolute;top:14px;left:-3px}.fO_A5q_notice,.fO_A5q_error{color:var(--qilin-alias-label-tertiary);padding:8px 10px;font-size:11px;line-height:16px}.fO_A5q_error{color:var(--qilin-alias-state-error-primary);justify-content:space-between;align-items:center;gap:10px;display:flex}.fO_A5q_refresh{color:inherit;cursor:pointer;background:0 0;border:0;border-radius:5px;flex:none;align-items:center;gap:3px;padding:3px 5px;display:inline-flex}.fO_A5q_refresh svg{width:12px;height:12px}.fO_A5q_refresh:hover{background:var(--qilin-alias-interactive-bg-hover)}";
|
|
13
|
+
const tagId$2 = "@qilin-agent/client-ui-subagent/SubagentHeaderLineage.module.css";
|
|
14
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
15
|
+
const tag = document.createElement("style");
|
|
16
|
+
tag.dataset.plugin = "@qilin-agent/client-ui-subagent";
|
|
17
|
+
tag.dataset.pluginCss = tagId$2;
|
|
18
|
+
tag.textContent = css$2;
|
|
19
|
+
document.head.appendChild(tag);
|
|
20
|
+
}
|
|
21
|
+
var SubagentHeaderLineage_module_css_default = {
|
|
22
|
+
"activitySlot": "fO_A5q_activitySlot",
|
|
23
|
+
"ancestorSwitcherTrigger": "fO_A5q_ancestorSwitcherTrigger",
|
|
24
|
+
"children": "fO_A5q_children",
|
|
25
|
+
"clickarea": "fO_A5q_clickarea",
|
|
26
|
+
"content": "fO_A5q_content",
|
|
27
|
+
"currentLabel": "fO_A5q_currentLabel",
|
|
28
|
+
"disclosure": "fO_A5q_disclosure",
|
|
29
|
+
"disclosureOpen": "fO_A5q_disclosureOpen",
|
|
30
|
+
"disclosureSpace": "fO_A5q_disclosureSpace",
|
|
31
|
+
"error": "fO_A5q_error",
|
|
32
|
+
"label": "fO_A5q_label",
|
|
33
|
+
"menu": "fO_A5q_menu",
|
|
34
|
+
"metricDuration": "fO_A5q_metricDuration",
|
|
35
|
+
"metricToken": "fO_A5q_metricToken",
|
|
36
|
+
"metrics": "fO_A5q_metrics",
|
|
37
|
+
"node": "fO_A5q_node",
|
|
38
|
+
"notice": "fO_A5q_notice",
|
|
39
|
+
"refresh": "fO_A5q_refresh",
|
|
40
|
+
"root": "fO_A5q_root",
|
|
41
|
+
"row": "fO_A5q_row",
|
|
42
|
+
"rowActivitySlot": "fO_A5q_rowActivitySlot",
|
|
43
|
+
"sidebarButton": "fO_A5q_sidebarButton",
|
|
44
|
+
"summary": "fO_A5q_summary",
|
|
45
|
+
"switcherRoot": "fO_A5q_switcherRoot",
|
|
46
|
+
"switcherTitle": "fO_A5q_switcherTitle",
|
|
47
|
+
"switcherTrigger": "fO_A5q_switcherTrigger",
|
|
48
|
+
"trigger": "fO_A5q_trigger",
|
|
49
|
+
"triggerOpen": "fO_A5q_triggerOpen"
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region lib/types/client/SubagentHeaderLineage.js
|
|
53
|
+
function treeItems(root) {
|
|
54
|
+
return root === null ? [] : Array.from(root.querySelectorAll("[role=\"treeitem\"]:not([aria-disabled=\"true\"])"));
|
|
55
|
+
}
|
|
56
|
+
/** Compact token count shared in shape with the conversation stats strip. */
|
|
57
|
+
function formatTokens(value, t) {
|
|
58
|
+
const scaled = (next) => next >= 100 ? String(Math.round(next)) : String(Math.round(next * 10) / 10);
|
|
59
|
+
if (value < 1e3) return String(value);
|
|
60
|
+
if (value < 1e6) return t("tokens.thousand", { value: scaled(value / 1e3) });
|
|
61
|
+
return t("tokens.million", { value: scaled(value / 1e6) });
|
|
62
|
+
}
|
|
63
|
+
/** Sum the four disjoint durable provider-usage buckets. */
|
|
64
|
+
function tokenTotal(usage) {
|
|
65
|
+
return usage === void 0 ? void 0 : usage.uncachedInputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
|
|
66
|
+
}
|
|
67
|
+
/** Exact whole-second active-turn duration for one catalog row. */
|
|
68
|
+
function activityDuration(summary, activity, now) {
|
|
69
|
+
if (summary === void 0) return void 0;
|
|
70
|
+
const timing = summary.projectionValues?.subagentTiming;
|
|
71
|
+
if (timing === void 0) return void 0;
|
|
72
|
+
if (timing.active === void 0) return timing.settledMs;
|
|
73
|
+
const end = activity === "running" ? now : timing.active.through;
|
|
74
|
+
return timing.settledMs + Math.max(0, end - timing.active.since);
|
|
75
|
+
}
|
|
76
|
+
function splitDuration(ms) {
|
|
77
|
+
const totalSeconds = Math.floor(Math.max(0, ms) / 1e3);
|
|
78
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
79
|
+
const totalHours = Math.floor(totalMinutes / 60);
|
|
80
|
+
return {
|
|
81
|
+
seconds: totalSeconds % 60,
|
|
82
|
+
minutes: totalMinutes % 60,
|
|
83
|
+
hours: totalHours % 24,
|
|
84
|
+
days: Math.floor(totalHours / 24),
|
|
85
|
+
totalMinutes,
|
|
86
|
+
totalHours
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** Format a duration with decreasing visual precision at larger scales. */
|
|
90
|
+
function formatDuration(ms, t) {
|
|
91
|
+
const { seconds, minutes, hours, days, totalMinutes, totalHours } = splitDuration(ms);
|
|
92
|
+
if (days >= 365) {
|
|
93
|
+
const years = Math.floor(days / 365);
|
|
94
|
+
const months = Math.floor(days % 365 / 30);
|
|
95
|
+
return months === 0 ? t("duration.years", { years }) : t("duration.yearsMonths", {
|
|
96
|
+
years,
|
|
97
|
+
months
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (days >= 30) {
|
|
101
|
+
const months = Math.floor(days / 30);
|
|
102
|
+
const remainingDays = days % 30;
|
|
103
|
+
return remainingDays === 0 ? t("duration.months", { months }) : t("duration.monthsDays", {
|
|
104
|
+
months,
|
|
105
|
+
days: remainingDays
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (days > 0) return hours === 0 ? t("duration.days", { days }) : t("duration.daysHours", {
|
|
109
|
+
days,
|
|
110
|
+
hours
|
|
111
|
+
});
|
|
112
|
+
if (totalHours > 0) return t("duration.hours", {
|
|
113
|
+
hours: totalHours,
|
|
114
|
+
minutes: String(minutes).padStart(2, "0"),
|
|
115
|
+
seconds: String(seconds).padStart(2, "0")
|
|
116
|
+
});
|
|
117
|
+
if (totalMinutes > 0) return t("duration.minutes", {
|
|
118
|
+
minutes: totalMinutes,
|
|
119
|
+
seconds: String(seconds).padStart(2, "0")
|
|
120
|
+
});
|
|
121
|
+
return t("duration.seconds", { seconds });
|
|
122
|
+
}
|
|
123
|
+
/** Preserve exact whole seconds for hover and accessible naming. */
|
|
124
|
+
function formatExactDuration(ms, t) {
|
|
125
|
+
const { seconds, minutes, hours, days } = splitDuration(ms);
|
|
126
|
+
return days === 0 ? formatDuration(ms, t) : t("duration.exactDays", {
|
|
127
|
+
days,
|
|
128
|
+
hours: String(hours).padStart(2, "0"),
|
|
129
|
+
minutes: String(minutes).padStart(2, "0"),
|
|
130
|
+
seconds: String(seconds).padStart(2, "0")
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function SubagentSwitcherIcon() {
|
|
134
|
+
return (0, react_jsx_runtime.jsxs)("svg", {
|
|
135
|
+
width: "16",
|
|
136
|
+
height: "16",
|
|
137
|
+
viewBox: "0 0 20 20",
|
|
138
|
+
fill: "none",
|
|
139
|
+
"aria-hidden": "true",
|
|
140
|
+
children: [(0, react_jsx_runtime.jsx)("path", {
|
|
141
|
+
d: "M5.99951 12.7L8.95546 14.9478C9.40011 15.2859 9.62244 15.455 9.87526 15.488C9.95774 15.4988 10.0413 15.4988 10.1238 15.488C10.3766 15.455 10.5989 15.2859 11.0436 14.9478L13.9995 12.7",
|
|
142
|
+
stroke: "currentColor",
|
|
143
|
+
strokeWidth: "1.5"
|
|
144
|
+
}), (0, react_jsx_runtime.jsx)("path", {
|
|
145
|
+
d: "M13.9995 7.7417L11.0436 5.49387C10.5989 5.15574 10.3766 4.98668 10.1238 4.95362C10.0413 4.94283 9.95775 4.94283 9.87527 4.95362C9.62245 4.98668 9.40012 5.15574 8.95547 5.49387L5.99952 7.7417",
|
|
146
|
+
stroke: "currentColor",
|
|
147
|
+
strokeWidth: "1.5"
|
|
148
|
+
})]
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/** Render catalog loading without inventing child membership. */
|
|
152
|
+
function CatalogLoadingRows({ t }) {
|
|
153
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
154
|
+
className: SubagentHeaderLineage_module_css_default.notice,
|
|
155
|
+
children: t("loading.label")
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/** A child becomes a known leaf only after its own authoritative catalog loads empty. */
|
|
159
|
+
function isKnownLeaf(catalog) {
|
|
160
|
+
return catalog?.state === "ready" && catalog.entries.length === 0;
|
|
161
|
+
}
|
|
162
|
+
/** Render one catalog level and recurse only through explicitly expanded rows. */
|
|
163
|
+
function CatalogRows({ parentSessionId, currentSessionId, catalog, catalogs, summaries, expanded, level, openChild, openChildAside, refreshProjection, toggleBranch, closeCatalog, t }) {
|
|
164
|
+
const [now, setNow] = (0, react.useState)(() => Date.now());
|
|
165
|
+
const running = catalog.entries.some((entry) => entry.activity === "running");
|
|
166
|
+
(0, react.useEffect)(() => {
|
|
167
|
+
if (!running) return;
|
|
168
|
+
const timer = setInterval(() => {
|
|
169
|
+
setNow(Date.now());
|
|
170
|
+
}, 1e3);
|
|
171
|
+
return () => {
|
|
172
|
+
clearInterval(timer);
|
|
173
|
+
};
|
|
174
|
+
}, [running]);
|
|
175
|
+
const emptyLoading = catalog.state === "loading" && catalog.entries.length === 0;
|
|
176
|
+
const reserveDisclosure = catalog.entries.some((entry) => !isKnownLeaf(catalogs[entry.id]));
|
|
177
|
+
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
178
|
+
emptyLoading && (0, react_jsx_runtime.jsx)(CatalogLoadingRows, { t }),
|
|
179
|
+
catalog.state === "error" && (0, react_jsx_runtime.jsxs)("div", {
|
|
180
|
+
className: SubagentHeaderLineage_module_css_default.error,
|
|
181
|
+
children: [(0, react_jsx_runtime.jsx)("span", { children: catalog.error?.message ?? t("load.error") }), (0, react_jsx_runtime.jsxs)("button", {
|
|
182
|
+
type: "button",
|
|
183
|
+
className: SubagentHeaderLineage_module_css_default.refresh,
|
|
184
|
+
onClick: () => {
|
|
185
|
+
refreshProjection(parentSessionId);
|
|
186
|
+
},
|
|
187
|
+
children: [(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconRefreshOutline14, { size: 14 }), t("retry")]
|
|
188
|
+
})]
|
|
189
|
+
}),
|
|
190
|
+
catalog.entries.map((entry) => {
|
|
191
|
+
const childCatalog = catalogs[entry.id];
|
|
192
|
+
const isCurrent = entry.id === currentSessionId;
|
|
193
|
+
const isExpanded = expanded.has(entry.id);
|
|
194
|
+
const knownLeaf = isKnownLeaf(childCatalog);
|
|
195
|
+
const childLoading = childCatalog === void 0 || childCatalog.state === "loading" && childCatalog.entries.length === 0;
|
|
196
|
+
const summary = summaries[entry.id];
|
|
197
|
+
const label = entry.label ?? entry.id;
|
|
198
|
+
const mode = entry.mode === "unknown" ? t("mode.unknown") : entry.mode === "one-shot" ? t("mode.oneShot") : t("mode.continuable");
|
|
199
|
+
const completed = entry.activity === "inactive" && summary?.projectionValues?.subagentTiming?.lastTurnCompleted === true;
|
|
200
|
+
const activity = entry.activity === "running" ? t("activity.running") : completed ? t("activity.completed") : t("activity.inactive");
|
|
201
|
+
const secondary = [
|
|
202
|
+
summary?.title,
|
|
203
|
+
mode,
|
|
204
|
+
activity
|
|
205
|
+
].filter((value) => value !== void 0).join(" · ");
|
|
206
|
+
const totalTokens = tokenTotal(summary?.projectionValues?.tokenUsage);
|
|
207
|
+
const durationMs = activityDuration(summary, entry.activity, now);
|
|
208
|
+
const tokenMetric = totalTokens === void 0 ? void 0 : t("tokens.total", { value: formatTokens(totalTokens, t) });
|
|
209
|
+
const durationMetric = durationMs === void 0 ? void 0 : {
|
|
210
|
+
compact: formatDuration(durationMs, t),
|
|
211
|
+
exact: formatExactDuration(durationMs, t)
|
|
212
|
+
};
|
|
213
|
+
const metrics = [tokenMetric, durationMetric?.exact].filter((value) => value !== void 0).join(" · ");
|
|
214
|
+
const open = () => {
|
|
215
|
+
openChild({
|
|
216
|
+
parentSessionId,
|
|
217
|
+
childSessionId: entry.id,
|
|
218
|
+
mode: entry.mode
|
|
219
|
+
});
|
|
220
|
+
closeCatalog();
|
|
221
|
+
};
|
|
222
|
+
const openAside = (event) => {
|
|
223
|
+
event.preventDefault();
|
|
224
|
+
event.stopPropagation();
|
|
225
|
+
openChildAside({
|
|
226
|
+
parentSessionId,
|
|
227
|
+
childSessionId: entry.id,
|
|
228
|
+
mode: entry.mode
|
|
229
|
+
});
|
|
230
|
+
closeCatalog();
|
|
231
|
+
};
|
|
232
|
+
const handleKey = (event) => {
|
|
233
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
234
|
+
event.preventDefault();
|
|
235
|
+
event.stopPropagation();
|
|
236
|
+
open();
|
|
237
|
+
} else if (event.key === "ArrowRight" && !knownLeaf && !isExpanded || event.key === "ArrowLeft" && isExpanded) {
|
|
238
|
+
event.preventDefault();
|
|
239
|
+
event.stopPropagation();
|
|
240
|
+
toggleBranch(entry.id);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
const toggle = (event) => {
|
|
244
|
+
event.preventDefault();
|
|
245
|
+
event.stopPropagation();
|
|
246
|
+
toggleBranch(entry.id);
|
|
247
|
+
};
|
|
248
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
249
|
+
className: SubagentHeaderLineage_module_css_default.node,
|
|
250
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
251
|
+
role: "treeitem",
|
|
252
|
+
tabIndex: 0,
|
|
253
|
+
"aria-level": level,
|
|
254
|
+
"aria-current": isCurrent || void 0,
|
|
255
|
+
"aria-label": [
|
|
256
|
+
label,
|
|
257
|
+
secondary,
|
|
258
|
+
metrics
|
|
259
|
+
].filter((value) => value !== "").join(" "),
|
|
260
|
+
...knownLeaf ? {} : { "aria-expanded": isExpanded },
|
|
261
|
+
className: SubagentHeaderLineage_module_css_default.row,
|
|
262
|
+
onClick: open,
|
|
263
|
+
onKeyDown: handleKey,
|
|
264
|
+
children: [knownLeaf ? reserveDisclosure && (0, react_jsx_runtime.jsx)("span", { className: SubagentHeaderLineage_module_css_default.disclosureSpace }) : (0, react_jsx_runtime.jsx)("button", {
|
|
265
|
+
type: "button",
|
|
266
|
+
tabIndex: -1,
|
|
267
|
+
className: `${SubagentHeaderLineage_module_css_default.disclosure} ${isExpanded ? SubagentHeaderLineage_module_css_default.disclosureOpen : ""}`,
|
|
268
|
+
"aria-label": t(isExpanded ? "branch.collapse" : "branch.expand", { label }),
|
|
269
|
+
onClick: toggle,
|
|
270
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconChevronRightOutline14, {})
|
|
271
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
272
|
+
className: SubagentHeaderLineage_module_css_default.clickarea,
|
|
273
|
+
children: [
|
|
274
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
275
|
+
className: SubagentHeaderLineage_module_css_default.rowActivitySlot,
|
|
276
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.StateDot, { state: entry.activity === "running" ? "ongoing" : completed ? "done" : "idle" })
|
|
277
|
+
}),
|
|
278
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
279
|
+
className: SubagentHeaderLineage_module_css_default.content,
|
|
280
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
281
|
+
className: `${SubagentHeaderLineage_module_css_default.label} ${isCurrent ? SubagentHeaderLineage_module_css_default.currentLabel : ""}`,
|
|
282
|
+
children: label
|
|
283
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
284
|
+
className: SubagentHeaderLineage_module_css_default.summary,
|
|
285
|
+
children: secondary
|
|
286
|
+
})]
|
|
287
|
+
}),
|
|
288
|
+
metrics !== "" && (0, react_jsx_runtime.jsxs)("span", {
|
|
289
|
+
className: SubagentHeaderLineage_module_css_default.metrics,
|
|
290
|
+
children: [tokenMetric !== void 0 && (0, react_jsx_runtime.jsx)("span", {
|
|
291
|
+
className: SubagentHeaderLineage_module_css_default.metricToken,
|
|
292
|
+
children: tokenMetric
|
|
293
|
+
}), durationMetric !== void 0 && (0, react_jsx_runtime.jsx)("span", {
|
|
294
|
+
className: SubagentHeaderLineage_module_css_default.metricDuration,
|
|
295
|
+
title: t("duration.exactTitle", { duration: durationMetric.exact }),
|
|
296
|
+
children: durationMetric.compact
|
|
297
|
+
})]
|
|
298
|
+
}),
|
|
299
|
+
!isCurrent && (0, react_jsx_runtime.jsx)("button", {
|
|
300
|
+
type: "button",
|
|
301
|
+
className: SubagentHeaderLineage_module_css_default.sidebarButton,
|
|
302
|
+
"aria-label": t("open.sidebar", { label }),
|
|
303
|
+
title: t("open.sidebar", { label }),
|
|
304
|
+
onClick: openAside,
|
|
305
|
+
onKeyDown: (event) => {
|
|
306
|
+
event.stopPropagation();
|
|
307
|
+
},
|
|
308
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconChevronRightOutline14, {})
|
|
309
|
+
})
|
|
310
|
+
]
|
|
311
|
+
})]
|
|
312
|
+
}), isExpanded && !knownLeaf && (0, react_jsx_runtime.jsx)("div", {
|
|
313
|
+
role: "group",
|
|
314
|
+
className: SubagentHeaderLineage_module_css_default.children,
|
|
315
|
+
"aria-busy": childLoading || void 0,
|
|
316
|
+
children: childCatalog === void 0 ? (0, react_jsx_runtime.jsx)(CatalogLoadingRows, { t }) : (0, react_jsx_runtime.jsx)(CatalogRows, {
|
|
317
|
+
parentSessionId: entry.id,
|
|
318
|
+
currentSessionId,
|
|
319
|
+
catalog: childCatalog,
|
|
320
|
+
catalogs,
|
|
321
|
+
summaries,
|
|
322
|
+
expanded,
|
|
323
|
+
level: level + 1,
|
|
324
|
+
openChild,
|
|
325
|
+
openChildAside,
|
|
326
|
+
refreshProjection,
|
|
327
|
+
toggleBranch,
|
|
328
|
+
closeCatalog,
|
|
329
|
+
t
|
|
330
|
+
})
|
|
331
|
+
})]
|
|
332
|
+
}, entry.id);
|
|
333
|
+
})
|
|
334
|
+
] });
|
|
335
|
+
}
|
|
336
|
+
const MENU_VIEWPORT_MARGIN = 16;
|
|
337
|
+
/** Place a portaled catalog below its trigger without crossing the viewport edge. */
|
|
338
|
+
function catalogMenuPosition(trigger) {
|
|
339
|
+
const rect = trigger.getBoundingClientRect();
|
|
340
|
+
const width = Math.min(336, window.innerWidth - MENU_VIEWPORT_MARGIN * 2);
|
|
341
|
+
return {
|
|
342
|
+
top: rect.bottom + 5,
|
|
343
|
+
left: Math.min(Math.max(MENU_VIEWPORT_MARGIN, rect.left), window.innerWidth - width - MENU_VIEWPORT_MARGIN)
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/** One trigger-plus-tree dropdown over the catalog rooted at `rootSessionId`. */
|
|
347
|
+
function CatalogDropdown({ rootSessionId, currentSessionId, displayTitle, openTitle, variant, useSessions, useSessionStatus, openChild, openChildAside, refreshProjection, t }) {
|
|
348
|
+
const ancestorSwitcher = variant === "switcher" && openTitle !== void 0;
|
|
349
|
+
const projections = useSessions((state) => state.projectionsBySession);
|
|
350
|
+
const summaries = useSessions((state) => state.byId);
|
|
351
|
+
const statuses = useSessionStatus((value) => value);
|
|
352
|
+
const catalogs = (0, react.useMemo)(() => Object.fromEntries(Object.entries(projections).map(([id, snapshot]) => [id, {
|
|
353
|
+
state: snapshot.state === "idle" ? snapshot.values.subagentCatalog === void 0 ? "loading" : "ready" : snapshot.state,
|
|
354
|
+
error: snapshot.error,
|
|
355
|
+
entries: (snapshot.values.subagentCatalog ?? []).map((entry) => ({
|
|
356
|
+
...entry,
|
|
357
|
+
activity: (statuses.get(entry.id)?.running ?? summaries[entry.id]?.running) === true ? "running" : "inactive"
|
|
358
|
+
}))
|
|
359
|
+
}])), [
|
|
360
|
+
projections,
|
|
361
|
+
summaries,
|
|
362
|
+
statuses
|
|
363
|
+
]);
|
|
364
|
+
const catalog = catalogs[rootSessionId];
|
|
365
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
366
|
+
const [menuPosition, setMenuPosition] = (0, react.useState)();
|
|
367
|
+
const [expanded, setExpanded] = (0, react.useState)(() => /* @__PURE__ */ new Set());
|
|
368
|
+
const rootRef = (0, react.useRef)(null);
|
|
369
|
+
const triggerRef = (0, react.useRef)(null);
|
|
370
|
+
const menuRef = (0, react.useRef)(null);
|
|
371
|
+
const hoverOpenTimer = (0, react.useRef)(void 0);
|
|
372
|
+
const hoverCloseTimer = (0, react.useRef)(void 0);
|
|
373
|
+
const currentEntry = currentSessionId === void 0 ? void 0 : catalog?.entries.find((entry) => entry.id === currentSessionId);
|
|
374
|
+
const switcherDisplayTitle = currentEntry !== void 0 ? currentEntry.label ?? currentEntry.id : displayTitle;
|
|
375
|
+
const directChildren = catalog?.entries ?? [];
|
|
376
|
+
const directCount = directChildren.length;
|
|
377
|
+
const runningCount = directChildren.filter((entry) => entry.activity === "running").length;
|
|
378
|
+
const totalCountKey = directCount === 1 ? "count.total.one" : "count.total.other";
|
|
379
|
+
const runningCountKey = runningCount === 1 ? "count.running.one" : "count.running.other";
|
|
380
|
+
const presentedCatalog = catalog ?? (variant === "switcher" ? {
|
|
381
|
+
entries: [],
|
|
382
|
+
state: "loading",
|
|
383
|
+
error: null
|
|
384
|
+
} : void 0);
|
|
385
|
+
const cancelHoverClose = () => {
|
|
386
|
+
if (hoverCloseTimer.current === void 0) return;
|
|
387
|
+
clearTimeout(hoverCloseTimer.current);
|
|
388
|
+
hoverCloseTimer.current = void 0;
|
|
389
|
+
};
|
|
390
|
+
const cancelHoverOpen = () => {
|
|
391
|
+
if (hoverOpenTimer.current === void 0) return;
|
|
392
|
+
clearTimeout(hoverOpenTimer.current);
|
|
393
|
+
hoverOpenTimer.current = void 0;
|
|
394
|
+
};
|
|
395
|
+
const changeOpen = (next, restoreFocus = false) => {
|
|
396
|
+
cancelHoverOpen();
|
|
397
|
+
cancelHoverClose();
|
|
398
|
+
if (next) {
|
|
399
|
+
const trigger = triggerRef.current;
|
|
400
|
+
/* v8 ignore next -- a queued callback can outlive the trigger */
|
|
401
|
+
if (trigger === null) return;
|
|
402
|
+
setOpen(true);
|
|
403
|
+
setMenuPosition(catalogMenuPosition(trigger));
|
|
404
|
+
} else {
|
|
405
|
+
setOpen(false);
|
|
406
|
+
setMenuPosition(void 0);
|
|
407
|
+
setExpanded(/* @__PURE__ */ new Set());
|
|
408
|
+
}
|
|
409
|
+
if (restoreFocus) queueMicrotask(() => {
|
|
410
|
+
triggerRef.current?.focus();
|
|
411
|
+
});
|
|
412
|
+
};
|
|
413
|
+
const scheduleHoverOpen = () => {
|
|
414
|
+
cancelHoverOpen();
|
|
415
|
+
cancelHoverClose();
|
|
416
|
+
if (open) return;
|
|
417
|
+
hoverOpenTimer.current = setTimeout(() => {
|
|
418
|
+
hoverOpenTimer.current = void 0;
|
|
419
|
+
changeOpen(true);
|
|
420
|
+
}, 150);
|
|
421
|
+
};
|
|
422
|
+
const scheduleHoverClose = () => {
|
|
423
|
+
cancelHoverOpen();
|
|
424
|
+
cancelHoverClose();
|
|
425
|
+
hoverCloseTimer.current = setTimeout(() => {
|
|
426
|
+
hoverCloseTimer.current = void 0;
|
|
427
|
+
changeOpen(false);
|
|
428
|
+
}, 120);
|
|
429
|
+
};
|
|
430
|
+
const closeBranch = (root) => {
|
|
431
|
+
const closing = /* @__PURE__ */ new Set();
|
|
432
|
+
const visit = (parentSessionId) => {
|
|
433
|
+
if (closing.has(parentSessionId) || !expanded.has(parentSessionId)) return;
|
|
434
|
+
closing.add(parentSessionId);
|
|
435
|
+
const branch = catalogs[parentSessionId];
|
|
436
|
+
for (const entry of branch?.entries ?? []) visit(entry.id);
|
|
437
|
+
};
|
|
438
|
+
visit(root);
|
|
439
|
+
setExpanded((current) => new Set([...current].filter((id) => !closing.has(id))));
|
|
440
|
+
};
|
|
441
|
+
const toggleBranch = (childSessionId) => {
|
|
442
|
+
if (expanded.has(childSessionId)) {
|
|
443
|
+
closeBranch(childSessionId);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
setExpanded((current) => new Set(current).add(childSessionId));
|
|
447
|
+
refreshProjection(childSessionId);
|
|
448
|
+
};
|
|
449
|
+
(0, react.useEffect)(() => {
|
|
450
|
+
if (!open) return;
|
|
451
|
+
const closeOutside = (event) => {
|
|
452
|
+
if (event.target instanceof Node && !rootRef.current?.contains(event.target) && !menuRef.current?.contains(event.target)) changeOpen(false);
|
|
453
|
+
};
|
|
454
|
+
document.addEventListener("pointerdown", closeOutside);
|
|
455
|
+
return () => {
|
|
456
|
+
document.removeEventListener("pointerdown", closeOutside);
|
|
457
|
+
};
|
|
458
|
+
}, [open]);
|
|
459
|
+
(0, react.useEffect)(() => {
|
|
460
|
+
if (!open) return;
|
|
461
|
+
const placeMenu = () => {
|
|
462
|
+
const trigger = triggerRef.current;
|
|
463
|
+
/* v8 ignore next -- native resize or scroll can outlive the trigger */
|
|
464
|
+
if (trigger === null) return;
|
|
465
|
+
setMenuPosition(catalogMenuPosition(trigger));
|
|
466
|
+
};
|
|
467
|
+
window.addEventListener("resize", placeMenu);
|
|
468
|
+
document.addEventListener("scroll", placeMenu, true);
|
|
469
|
+
return () => {
|
|
470
|
+
window.removeEventListener("resize", placeMenu);
|
|
471
|
+
document.removeEventListener("scroll", placeMenu, true);
|
|
472
|
+
};
|
|
473
|
+
}, [open]);
|
|
474
|
+
(0, react.useEffect)(() => () => {
|
|
475
|
+
cancelHoverOpen();
|
|
476
|
+
cancelHoverClose();
|
|
477
|
+
}, []);
|
|
478
|
+
const visible = presentedCatalog !== void 0 && (variant === "switcher" || presentedCatalog.state === "error" || presentedCatalog.entries.length > 0);
|
|
479
|
+
(0, react.useEffect)(() => {
|
|
480
|
+
if (visible) return;
|
|
481
|
+
cancelHoverOpen();
|
|
482
|
+
cancelHoverClose();
|
|
483
|
+
if (!open) return;
|
|
484
|
+
setOpen(false);
|
|
485
|
+
setExpanded(/* @__PURE__ */ new Set());
|
|
486
|
+
}, [visible, open]);
|
|
487
|
+
if (!visible) return null;
|
|
488
|
+
const focusAt = (index) => {
|
|
489
|
+
const items = treeItems(menuRef.current);
|
|
490
|
+
if (items.length === 0) return;
|
|
491
|
+
items[(index + items.length) % items.length]?.focus();
|
|
492
|
+
};
|
|
493
|
+
const navigate = (event) => {
|
|
494
|
+
const items = treeItems(menuRef.current);
|
|
495
|
+
const index = items.indexOf(document.activeElement);
|
|
496
|
+
if (event.key === "Escape") {
|
|
497
|
+
event.preventDefault();
|
|
498
|
+
changeOpen(false, true);
|
|
499
|
+
} else if (event.key === "Home") {
|
|
500
|
+
event.preventDefault();
|
|
501
|
+
focusAt(0);
|
|
502
|
+
} else if (event.key === "End") {
|
|
503
|
+
event.preventDefault();
|
|
504
|
+
focusAt(items.length - 1);
|
|
505
|
+
} else if (event.key === "ArrowDown") {
|
|
506
|
+
event.preventDefault();
|
|
507
|
+
focusAt(index + 1);
|
|
508
|
+
} else if (event.key === "ArrowUp") {
|
|
509
|
+
event.preventDefault();
|
|
510
|
+
focusAt(index < 0 ? items.length - 1 : index - 1);
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
514
|
+
className: `${SubagentHeaderLineage_module_css_default.root} ${variant === "switcher" ? SubagentHeaderLineage_module_css_default.switcherRoot : ""}`,
|
|
515
|
+
ref: rootRef,
|
|
516
|
+
onKeyDown: navigate,
|
|
517
|
+
onMouseEnter: scheduleHoverOpen,
|
|
518
|
+
onMouseLeave: scheduleHoverClose,
|
|
519
|
+
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
520
|
+
ref: triggerRef,
|
|
521
|
+
type: "button",
|
|
522
|
+
className: variant === "switcher" ? `${SubagentHeaderLineage_module_css_default.switcherTrigger} ${ancestorSwitcher ? SubagentHeaderLineage_module_css_default.ancestorSwitcherTrigger : ""}` : SubagentHeaderLineage_module_css_default.trigger,
|
|
523
|
+
"aria-haspopup": "tree",
|
|
524
|
+
"aria-expanded": open,
|
|
525
|
+
"aria-label": variant === "switcher" ? t("switcher.aria", { title: switcherDisplayTitle }) : t(runningCount > 0 ? runningCountKey : totalCountKey, { count: runningCount > 0 ? runningCount : directCount }),
|
|
526
|
+
onClick: openTitle === void 0 ? void 0 : () => {
|
|
527
|
+
cancelHoverOpen();
|
|
528
|
+
if (open) changeOpen(false);
|
|
529
|
+
openTitle();
|
|
530
|
+
},
|
|
531
|
+
onKeyDown: (event) => {
|
|
532
|
+
if (event.key !== "ArrowDown") return;
|
|
533
|
+
event.preventDefault();
|
|
534
|
+
if (!open) changeOpen(true);
|
|
535
|
+
queueMicrotask(() => {
|
|
536
|
+
focusAt(0);
|
|
537
|
+
});
|
|
538
|
+
},
|
|
539
|
+
children: [variant === "switcher" ? (0, react_jsx_runtime.jsx)("span", {
|
|
540
|
+
className: SubagentHeaderLineage_module_css_default.switcherTitle,
|
|
541
|
+
children: switcherDisplayTitle
|
|
542
|
+
}) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [runningCount > 0 && (0, react_jsx_runtime.jsx)("span", {
|
|
543
|
+
className: SubagentHeaderLineage_module_css_default.activitySlot,
|
|
544
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.StateDot, { state: "ongoing" })
|
|
545
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
546
|
+
className: SubagentHeaderLineage_module_css_default.count,
|
|
547
|
+
children: t(totalCountKey, { count: directCount })
|
|
548
|
+
})] }), variant === "switcher" ? (0, react_jsx_runtime.jsx)(SubagentSwitcherIcon, {}) : (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconChevronDownOutline14, { className: open ? SubagentHeaderLineage_module_css_default.triggerOpen : void 0 })]
|
|
549
|
+
}), open && (0, react_dom.createPortal)((0, react_jsx_runtime.jsx)("div", {
|
|
550
|
+
ref: menuRef,
|
|
551
|
+
className: SubagentHeaderLineage_module_css_default.menu,
|
|
552
|
+
style: menuPosition,
|
|
553
|
+
role: "tree",
|
|
554
|
+
"aria-label": t("tree.aria"),
|
|
555
|
+
onMouseEnter: cancelHoverClose,
|
|
556
|
+
onMouseLeave: scheduleHoverClose,
|
|
557
|
+
children: (0, react_jsx_runtime.jsx)(CatalogRows, {
|
|
558
|
+
parentSessionId: rootSessionId,
|
|
559
|
+
currentSessionId,
|
|
560
|
+
catalog: presentedCatalog,
|
|
561
|
+
catalogs,
|
|
562
|
+
summaries,
|
|
563
|
+
expanded,
|
|
564
|
+
level: 1,
|
|
565
|
+
openChild,
|
|
566
|
+
openChildAside,
|
|
567
|
+
refreshProjection,
|
|
568
|
+
toggleBranch,
|
|
569
|
+
closeCatalog: () => {
|
|
570
|
+
changeOpen(false);
|
|
571
|
+
},
|
|
572
|
+
t
|
|
573
|
+
})
|
|
574
|
+
}), document.body)]
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Session-header catalog action for root sessions: the descendant count and
|
|
579
|
+
* its dropdown, ordered after the task list. Child sessions render nothing
|
|
580
|
+
* here — their breadcrumb switcher in the lineage slot owns the same
|
|
581
|
+
* navigation.
|
|
582
|
+
* @param props - Session standard props plus the catalog actions and translator.
|
|
583
|
+
* @returns The count dropdown, or null on a child session.
|
|
584
|
+
*/
|
|
585
|
+
function SubagentCatalogAction({ sessionId, useSessions, useSessionStatus, openChild, openChildAside, refreshProjection, t }) {
|
|
586
|
+
if (useSessions((state) => state.byId[sessionId]?.origin === "subagent")) return null;
|
|
587
|
+
return (0, react_jsx_runtime.jsx)(CatalogDropdown, {
|
|
588
|
+
rootSessionId: sessionId,
|
|
589
|
+
variant: "count",
|
|
590
|
+
useSessions,
|
|
591
|
+
useSessionStatus,
|
|
592
|
+
openChild,
|
|
593
|
+
openChildAside,
|
|
594
|
+
refreshProjection,
|
|
595
|
+
t
|
|
596
|
+
}, sessionId);
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Render one breadcrumb title together with its subagent navigation.
|
|
600
|
+
* @param props - Breadcrumb title, session standard props, and catalog actions.
|
|
601
|
+
* @returns A title-and-chevron sibling switcher, or nothing on a root session.
|
|
602
|
+
*/
|
|
603
|
+
function SubagentHeaderLineage({ lineageSessionId, displayTitle, openTitle, useSessions, useSession, useSessionStatus, openChild, openChildAside, refreshProjection, t }) {
|
|
604
|
+
const address = useSession((session) => session.subagent?.address);
|
|
605
|
+
const parentId = useSessions((state) => {
|
|
606
|
+
if (address?.childSessionId === lineageSessionId) return address.parentSessionId;
|
|
607
|
+
for (const [parentId, snapshot] of Object.entries(state.projectionsBySession)) if (snapshot.values.subagentCatalog?.some((entry) => entry.id === lineageSessionId)) return parentId;
|
|
608
|
+
});
|
|
609
|
+
const shared = {
|
|
610
|
+
useSessions,
|
|
611
|
+
useSessionStatus,
|
|
612
|
+
openChild,
|
|
613
|
+
openChildAside,
|
|
614
|
+
refreshProjection,
|
|
615
|
+
t
|
|
616
|
+
};
|
|
617
|
+
if (parentId === void 0) return null;
|
|
618
|
+
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(CatalogDropdown, {
|
|
619
|
+
rootSessionId: parentId,
|
|
620
|
+
currentSessionId: lineageSessionId,
|
|
621
|
+
variant: "switcher",
|
|
622
|
+
displayTitle,
|
|
623
|
+
...openTitle === void 0 ? {} : { openTitle },
|
|
624
|
+
...shared
|
|
625
|
+
}, lineageSessionId), openTitle === void 0 && (0, react_jsx_runtime.jsx)(CatalogDropdown, {
|
|
626
|
+
rootSessionId: lineageSessionId,
|
|
627
|
+
variant: "count",
|
|
628
|
+
...shared
|
|
629
|
+
}, lineageSessionId)] });
|
|
630
|
+
}
|
|
631
|
+
//#endregion
|
|
632
|
+
//#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-subagent/src/client/SubagentReadOnlyComposer.module.css.mjs
|
|
633
|
+
const css$1 = "._84UIXW_frame{border:.5px solid var(--qilin-alias-border-l4);background:var(--qilin-alias-bg-layer-1);min-height:54px;color:var(--qilin-alias-label-tertiary);border-radius:14px;justify-content:center;align-items:center;gap:8px;margin:0 24px 20px;padding:10px 16px;font-size:13px;line-height:20px;display:flex}._84UIXW_frame strong{color:var(--qilin-alias-label-primary);font-weight:510}";
|
|
634
|
+
const tagId$1 = "@qilin-agent/client-ui-subagent/SubagentReadOnlyComposer.module.css";
|
|
635
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
636
|
+
const tag = document.createElement("style");
|
|
637
|
+
tag.dataset.plugin = "@qilin-agent/client-ui-subagent";
|
|
638
|
+
tag.dataset.pluginCss = tagId$1;
|
|
639
|
+
tag.textContent = css$1;
|
|
640
|
+
document.head.appendChild(tag);
|
|
641
|
+
}
|
|
642
|
+
var SubagentReadOnlyComposer_module_css_default = { "frame": "_84UIXW_frame" };
|
|
643
|
+
//#endregion
|
|
644
|
+
//#region lib/types/client/SubagentReadOnlyComposer.js
|
|
645
|
+
/**
|
|
646
|
+
* Explain why the normal composer is unavailable for an addressed child.
|
|
647
|
+
* @param props - selector-owned read-only reason plus standard slot props.
|
|
648
|
+
* @returns A read-only composer replacement.
|
|
649
|
+
*/
|
|
650
|
+
function SubagentReadOnlyComposer({ matched, t }) {
|
|
651
|
+
const oneShot = matched.reason === "one-shot";
|
|
652
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
653
|
+
className: SubagentReadOnlyComposer_module_css_default.frame,
|
|
654
|
+
role: "status",
|
|
655
|
+
children: [(0, react_jsx_runtime.jsx)("strong", { children: t(oneShot ? "readonly.oneShot.title" : "readonly.title") }), (0, react_jsx_runtime.jsx)("span", { children: t(matched.reason === "unknown" ? "readonly.unknown.body" : oneShot ? "readonly.oneShot.body" : "readonly.body") })]
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
//#endregion
|
|
659
|
+
//#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-subagent/src/client/sidebar-chat/SidebarChat.module.css.mjs
|
|
660
|
+
const css = ".JbVn8q_root{width:100%;min-width:0;height:100%;min-height:0;display:flex}";
|
|
661
|
+
const tagId = "@qilin-agent/client-ui-subagent/SidebarChat.module.css";
|
|
662
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
663
|
+
const tag = document.createElement("style");
|
|
664
|
+
tag.dataset.plugin = "@qilin-agent/client-ui-subagent";
|
|
665
|
+
tag.dataset.pluginCss = tagId;
|
|
666
|
+
tag.textContent = css;
|
|
667
|
+
document.head.appendChild(tag);
|
|
668
|
+
}
|
|
669
|
+
var SidebarChat_module_css_default = { "root": "JbVn8q_root" };
|
|
670
|
+
//#endregion
|
|
671
|
+
//#region lib/types/client/sidebar-chat/index.js
|
|
672
|
+
/** Stable implementation identity for the Sidebar tab body. */
|
|
673
|
+
const SUBAGENT_CHAT_ID = "@qilin-agent/client-ui-subagent";
|
|
674
|
+
/** Resource-address prefix for an embedded Session chat. */
|
|
675
|
+
const SUBAGENT_CHAT_ADDRESS = "qilin-resource://subagentchat/session/";
|
|
676
|
+
/**
|
|
677
|
+
* Address one subagent Session together with the routing facts needed to restore it.
|
|
678
|
+
* @param address - durable direct-parent subagent address.
|
|
679
|
+
* @returns canonical Sidebar resource address.
|
|
680
|
+
*/
|
|
681
|
+
function subagentChatAddress(address) {
|
|
682
|
+
const query = new URLSearchParams({
|
|
683
|
+
parent: address.parentSessionId,
|
|
684
|
+
mode: address.mode
|
|
685
|
+
});
|
|
686
|
+
return `${SUBAGENT_CHAT_ADDRESS}${encodeURIComponent(address.childSessionId)}?${query}`;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Parse one canonical Sidebar chat resource address.
|
|
690
|
+
* @param value - possible chat resource address.
|
|
691
|
+
* @returns the encoded direct-parent address, or undefined for another or malformed resource.
|
|
692
|
+
*/
|
|
693
|
+
function parseSubagentChatAddress(value) {
|
|
694
|
+
let url;
|
|
695
|
+
try {
|
|
696
|
+
url = new URL(value);
|
|
697
|
+
} catch (_invalidUrl) {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
if (url.protocol !== "qilin-resource:" || url.hostname.toLowerCase() !== "subagentchat") return void 0;
|
|
701
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
702
|
+
if (parts.length !== 2 || parts[0] !== "session") return void 0;
|
|
703
|
+
const parentSessionId = url.searchParams.get("parent");
|
|
704
|
+
const mode = url.searchParams.get("mode");
|
|
705
|
+
if (parentSessionId === null || parentSessionId === "" || mode !== "one-shot" && mode !== "continuable" && mode !== "unknown") return;
|
|
706
|
+
try {
|
|
707
|
+
return {
|
|
708
|
+
parentSessionId,
|
|
709
|
+
childSessionId: decodeURIComponent(parts[1]),
|
|
710
|
+
mode
|
|
711
|
+
};
|
|
712
|
+
} catch (_invalidEncoding) {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function waitForAbort(signal) {
|
|
717
|
+
if (signal.aborted) return Promise.resolve();
|
|
718
|
+
return new Promise((resolve) => {
|
|
719
|
+
signal.addEventListener("abort", () => {
|
|
720
|
+
resolve();
|
|
721
|
+
}, { once: true });
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
function isAbortRequested(signal) {
|
|
725
|
+
return signal.aborted;
|
|
726
|
+
}
|
|
727
|
+
function subagentChatResourceProvider(sessions) {
|
|
728
|
+
return {
|
|
729
|
+
protocol: "subagentchat",
|
|
730
|
+
async *open(resourceAddress, { signal }) {
|
|
731
|
+
const address = parseSubagentChatAddress(resourceAddress);
|
|
732
|
+
if (address === void 0) throw new Error(`ui-subagent: invalid chat resource address "${resourceAddress}"`);
|
|
733
|
+
if (isAbortRequested(signal)) return;
|
|
734
|
+
const reference = sessions.retain(address, {
|
|
735
|
+
source: "sidebarChat",
|
|
736
|
+
signal
|
|
737
|
+
});
|
|
738
|
+
try {
|
|
739
|
+
yield {
|
|
740
|
+
ok: true,
|
|
741
|
+
value: {
|
|
742
|
+
address,
|
|
743
|
+
reference
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
await waitForAbort(signal);
|
|
747
|
+
} finally {
|
|
748
|
+
reference.release();
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
/** Fixed Chat selection used by an embedded Conversation occurrence. */
|
|
754
|
+
function FixedChatConversationView(props) {
|
|
755
|
+
return (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: props.renderSlot("conversation.session", { view: "chat" }) });
|
|
756
|
+
}
|
|
757
|
+
/** Render the shared Conversation content for one explicitly provided child Session. */
|
|
758
|
+
function ConversationSlotPanel({ sessionId, useSession, useConversation, useSessions, renderFactorySlot }) {
|
|
759
|
+
const session = useSession((value) => value);
|
|
760
|
+
const shellPhase = useConversation((value) => value).activeTargets.size > 0 || !session.blank && !session.awaitingFirstTurn || session.running ? "active" : session.promptAttempted ? "engaging" : "blank";
|
|
761
|
+
const summaryBlank = useSessions((state) => state.byId[sessionId]?.blank);
|
|
762
|
+
const parentAvailabilityPending = session.subagent?.address.mode === "continuable" && session.subagent.parentAvailable === void 0;
|
|
763
|
+
const settling = shellPhase === "blank" && session.openState === "loading" && summaryBlank !== true || parentAvailabilityPending;
|
|
764
|
+
const hero = shellPhase === "blank" && (session.openState === "open" || summaryBlank === true);
|
|
765
|
+
return renderFactorySlot("conversation.content", {
|
|
766
|
+
variant: "embedded",
|
|
767
|
+
phase: settling ? "settling" : hero ? "hero" : "active",
|
|
768
|
+
hero
|
|
769
|
+
}, { slots: { views: FixedChatConversationView } });
|
|
770
|
+
}
|
|
771
|
+
/** Bind a chat resource's child reference around its Conversation slot. */
|
|
772
|
+
function SidebarChatTab({ useResource, useTabInfo, SessionProvider, renderSlot }) {
|
|
773
|
+
const { tab } = useTabInfo();
|
|
774
|
+
const resource = useResource(tab.contentId);
|
|
775
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
776
|
+
className: SidebarChat_module_css_default.root,
|
|
777
|
+
"data-sidebar-chat": "",
|
|
778
|
+
children: resource.value === void 0 ? null : (0, react_jsx_runtime.jsx)(SessionProvider, {
|
|
779
|
+
session: resource.value.reference,
|
|
780
|
+
children: renderSlot("sidebar.chat.conversation", {})
|
|
781
|
+
})
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Register the chat resource owner and its right-Sidebar presentation.
|
|
786
|
+
* @param ctx - Client root carrying Sessions, resources, Slots, and Sidebar registries.
|
|
787
|
+
* @param t - Chat namespace translator used for fallback tab titles.
|
|
788
|
+
*/
|
|
789
|
+
function registerSidebarChat(ctx, t) {
|
|
790
|
+
ctx.effect(() => ctx.resources.register(subagentChatResourceProvider(ctx.sessions)), "ui-subagent: Sidebar chat resources");
|
|
791
|
+
ctx.effect(() => ctx.sidebarRightTabs.register({
|
|
792
|
+
id: SUBAGENT_CHAT_ID,
|
|
793
|
+
kind: "subagentchat",
|
|
794
|
+
patterns: [`${SUBAGENT_CHAT_ADDRESS}**`],
|
|
795
|
+
priority: "builtin",
|
|
796
|
+
canOpen: (address) => parseSubagentChatAddress(address) !== void 0,
|
|
797
|
+
title: (address) => {
|
|
798
|
+
const child = parseSubagentChatAddress(address)?.childSessionId;
|
|
799
|
+
return child === void 0 ? t("sidebar.chat") : ctx.sessions.list.getSnapshot().byId[child]?.projectionValues?.subagent?.label ?? child;
|
|
800
|
+
}
|
|
801
|
+
}), "ui-subagent: Sidebar chat type");
|
|
802
|
+
ctx.effect(() => ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register({
|
|
803
|
+
name: "sidebar.right.pane.tab",
|
|
804
|
+
key: SUBAGENT_CHAT_ID,
|
|
805
|
+
children: { "sidebar.chat.conversation": {
|
|
806
|
+
kind: "single",
|
|
807
|
+
scope: "session"
|
|
808
|
+
} }
|
|
809
|
+
}, SidebarChatTab)), "ui-subagent: Sidebar chat body");
|
|
810
|
+
ctx.effect(() => ctx.slots.inject("sidebar.chat.conversation", () => ctx.slots.register({ name: "sidebar.chat.conversation" }, ConversationSlotPanel)), "ui-subagent: Sidebar Conversation");
|
|
811
|
+
}
|
|
812
|
+
//#endregion
|
|
813
|
+
//#region lib/types/client/locales.js
|
|
814
|
+
/** `subagent` namespace dictionaries. */
|
|
815
|
+
/** Dictionary namespace owned by this plugin. */
|
|
816
|
+
const NS = "subagent";
|
|
817
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
818
|
+
const zh = {
|
|
819
|
+
"duration.seconds": "{seconds}秒",
|
|
820
|
+
"duration.minutes": "{minutes}分{seconds}秒",
|
|
821
|
+
"duration.hours": "{hours}小时{minutes}分{seconds}秒",
|
|
822
|
+
"duration.days": "{days}天",
|
|
823
|
+
"duration.daysHours": "{days}天{hours}小时",
|
|
824
|
+
"duration.months": "约{months}个月",
|
|
825
|
+
"duration.monthsDays": "约{months}个月{days}天",
|
|
826
|
+
"duration.years": "约{years}年",
|
|
827
|
+
"duration.yearsMonths": "约{years}年{months}个月",
|
|
828
|
+
"duration.exactDays": "{days}天{hours}小时{minutes}分{seconds}秒",
|
|
829
|
+
"duration.exactTitle": "总活跃耗时:{duration}",
|
|
830
|
+
"tokens.thousand": "{value}K",
|
|
831
|
+
"tokens.million": "{value}M",
|
|
832
|
+
"tokens.total": "{value} tok",
|
|
833
|
+
"loading.label": "正在加载子代理…",
|
|
834
|
+
"load.error": "无法加载子代理",
|
|
835
|
+
"retry": "重试",
|
|
836
|
+
"mode.oneShot": "一次性",
|
|
837
|
+
"mode.continuable": "可继续",
|
|
838
|
+
"mode.unknown": "模式未知",
|
|
839
|
+
"readonly.unknown.body": "读取子会话后才能确定是否可继续。",
|
|
840
|
+
"activity.running": "正在运行",
|
|
841
|
+
"activity.completed": "已完成",
|
|
842
|
+
"activity.inactive": "当前未运行",
|
|
843
|
+
"branch.collapse": "收起 {label} 的下级子代理",
|
|
844
|
+
"branch.expand": "展开 {label} 的下级子代理",
|
|
845
|
+
"count.total.one": "{count} 个子代理",
|
|
846
|
+
"count.total.other": "{count} 个子代理",
|
|
847
|
+
"count.running.one": "{count} 个子代理,正在运行",
|
|
848
|
+
"count.running.other": "{count} 个子代理,正在运行",
|
|
849
|
+
"switcher.aria": "切换子代理:{title}",
|
|
850
|
+
"tree.aria": "子代理会话",
|
|
851
|
+
"open.sidebar": "在侧边栏打开 {label}",
|
|
852
|
+
"sidebar.chat": "聊天",
|
|
853
|
+
"readonly.oneShot.title": "一次性子代理记录",
|
|
854
|
+
"readonly.title": "此子代理暂时只读",
|
|
855
|
+
"readonly.oneShot.body": "一次性任务不支持后续消息,可在这里查看完整执行记录。",
|
|
856
|
+
"readonly.body": "父会话当前不在线,重新打开父会话后即可继续发送消息。"
|
|
857
|
+
};
|
|
858
|
+
/** English dictionary, key-identical to the Chinese source of truth. */
|
|
859
|
+
const en = {
|
|
860
|
+
"duration.seconds": "{seconds}s",
|
|
861
|
+
"duration.minutes": "{minutes}m {seconds}s",
|
|
862
|
+
"duration.hours": "{hours}h {minutes}m {seconds}s",
|
|
863
|
+
"duration.days": "{days}d",
|
|
864
|
+
"duration.daysHours": "{days}d {hours}h",
|
|
865
|
+
"duration.months": "~{months}mo",
|
|
866
|
+
"duration.monthsDays": "~{months}mo {days}d",
|
|
867
|
+
"duration.years": "~{years}y",
|
|
868
|
+
"duration.yearsMonths": "~{years}y {months}mo",
|
|
869
|
+
"duration.exactDays": "{days}d {hours}h {minutes}m {seconds}s",
|
|
870
|
+
"duration.exactTitle": "Total active duration: {duration}",
|
|
871
|
+
"tokens.thousand": "{value}K",
|
|
872
|
+
"tokens.million": "{value}M",
|
|
873
|
+
"tokens.total": "{value} tok",
|
|
874
|
+
"loading.label": "Loading subagents…",
|
|
875
|
+
"load.error": "Unable to load subagents",
|
|
876
|
+
"retry": "Retry",
|
|
877
|
+
"mode.oneShot": "one-shot",
|
|
878
|
+
"mode.continuable": "continuable",
|
|
879
|
+
"mode.unknown": "unknown mode",
|
|
880
|
+
"readonly.unknown.body": "Read the child session to determine whether it can be continued.",
|
|
881
|
+
"activity.running": "running",
|
|
882
|
+
"activity.completed": "completed",
|
|
883
|
+
"activity.inactive": "not running",
|
|
884
|
+
"branch.collapse": "Collapse {label} descendants",
|
|
885
|
+
"branch.expand": "Expand {label} descendants",
|
|
886
|
+
"count.total.one": "{count} subagent",
|
|
887
|
+
"count.total.other": "{count} subagents",
|
|
888
|
+
"count.running.one": "{count} subagent running",
|
|
889
|
+
"count.running.other": "{count} subagents running",
|
|
890
|
+
"switcher.aria": "Switch subagent: {title}",
|
|
891
|
+
"tree.aria": "Subagent sessions",
|
|
892
|
+
"open.sidebar": "Open {label} in sidebar",
|
|
893
|
+
"sidebar.chat": "Chat",
|
|
894
|
+
"readonly.oneShot.title": "One-shot subagent record",
|
|
895
|
+
"readonly.title": "This subagent is read-only for now",
|
|
896
|
+
"readonly.oneShot.body": "One-shot tasks do not accept follow-ups; review the full execution record here.",
|
|
897
|
+
"readonly.body": "The parent session is offline; reopen it to continue sending messages."
|
|
898
|
+
};
|
|
899
|
+
//#endregion
|
|
900
|
+
//#region lib/types/client/index.js
|
|
901
|
+
/** Required services for subagent presentation and navigation. */
|
|
902
|
+
const inject = [
|
|
903
|
+
"sessions",
|
|
904
|
+
"uiWorkspace",
|
|
905
|
+
"slots",
|
|
906
|
+
"locale",
|
|
907
|
+
"sidebarRight"
|
|
908
|
+
];
|
|
909
|
+
/** Claim the composer for one-shot history or an unavailable continuation owner. */
|
|
910
|
+
function selectReadOnlySubagent(owner) {
|
|
911
|
+
const subagent = owner.session?.subagent;
|
|
912
|
+
if (subagent === void 0 || subagent === null) return null;
|
|
913
|
+
if (subagent.address.mode === "unknown") return { reason: "unknown" };
|
|
914
|
+
if (subagent.address.mode === "one-shot") return { reason: "one-shot" };
|
|
915
|
+
if (subagent.parentAvailable !== false) return null;
|
|
916
|
+
return owner.session?.running === true ? null : { reason: "parent-unavailable" };
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Client plugin body: register the subagent catalog and read-only composer seats.
|
|
920
|
+
* @param ctx - client root context.
|
|
921
|
+
*/
|
|
922
|
+
function apply(ctx) {
|
|
923
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
924
|
+
zh,
|
|
925
|
+
en
|
|
926
|
+
}), "ui-subagent: dictionaries");
|
|
927
|
+
ctx.inject(["resources", "sidebarRightTabs"], (scope) => {
|
|
928
|
+
registerSidebarChat(scope, ctx.locale.bind(NS));
|
|
929
|
+
});
|
|
930
|
+
const catalogActions = (_parentSessionId) => ({
|
|
931
|
+
openChild(address) {
|
|
932
|
+
ctx.uiWorkspace.openSession(address);
|
|
933
|
+
},
|
|
934
|
+
openChildAside(address) {
|
|
935
|
+
ctx.sidebarRight.openResource(subagentChatAddress(address), {
|
|
936
|
+
kind: "subagentchat",
|
|
937
|
+
preferNewPane: true
|
|
938
|
+
});
|
|
939
|
+
},
|
|
940
|
+
refreshProjection(parentSessionId) {
|
|
941
|
+
ctx.sessions.refreshProjections(parentSessionId);
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
ctx.slots.inject("conversation.session.header.lineage", () => ctx.slots.register({
|
|
945
|
+
name: "conversation.session.header.lineage",
|
|
946
|
+
locale: NS,
|
|
947
|
+
inject: catalogActions
|
|
948
|
+
}, SubagentHeaderLineage));
|
|
949
|
+
ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
|
|
950
|
+
name: "conversation.session.header.actions",
|
|
951
|
+
id: "subagent-catalog",
|
|
952
|
+
order: 30,
|
|
953
|
+
locale: NS,
|
|
954
|
+
inject: catalogActions
|
|
955
|
+
}, SubagentCatalogAction));
|
|
956
|
+
ctx.slots.inject("conversation.composer", () => ctx.slots.register({
|
|
957
|
+
name: "conversation.composer",
|
|
958
|
+
priority: -10,
|
|
959
|
+
locale: NS,
|
|
960
|
+
select: selectReadOnlySubagent
|
|
961
|
+
}, SubagentReadOnlyComposer));
|
|
962
|
+
}
|
|
963
|
+
//#endregion
|
|
964
|
+
exports.apply = apply;
|
|
965
|
+
exports.inject = inject;
|
|
966
|
+
return module.exports;
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
//# sourceMappingURL=client.js.map
|