@qilin-agent/client-ui-workspace 3.1.4 → 3.1.6
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/README.i18n.yaml +5 -2
- package/README.md +10 -2
- package/README.zh.md +10 -2
- package/lib/client.js +392 -101
- package/lib/types/client/contract/slots.d.ts +26 -0
- package/lib/types/client/contract/toast.d.ts +55 -0
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/locales.d.ts +12 -0
- package/lib/types/client/navigation.d.ts +12 -3
- package/lib/types/client/rows/Rows.d.ts +7 -3
- package/lib/types/client/rows/WorkspaceBrowser.d.ts +1 -1
- package/lib/types/client/toast/RowActionToast.d.ts +9 -0
- package/lib/types/client/toast/Toast.d.ts +15 -0
- package/lib/types/client/toast/register.d.ts +8 -0
- package/package.json +23 -23
package/lib/client.js
CHANGED
|
@@ -4,11 +4,11 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let _qilin_agent_client_ui_primitives = require("@qilin-agent/client-ui-primitives");
|
|
7
8
|
let _qilin_agent_client_store = require("@qilin-agent/client-store");
|
|
8
9
|
let _qilin_agent_kylin = require("@qilin-agent/kylin");
|
|
9
10
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
11
|
let react = require("react");
|
|
11
|
-
let _qilin_agent_client_ui_primitives = require("@qilin-agent/client-ui-primitives");
|
|
12
12
|
//#region lib/types/client/shortcuts.js
|
|
13
13
|
/**
|
|
14
14
|
* Create the private browser request source shared by commands and controls.
|
|
@@ -688,6 +688,7 @@ window.__ModuleLoader__.load({
|
|
|
688
688
|
workspaces;
|
|
689
689
|
sessions;
|
|
690
690
|
view;
|
|
691
|
+
notify;
|
|
691
692
|
connecting = /* @__PURE__ */ new Map();
|
|
692
693
|
lifetime = new AbortController();
|
|
693
694
|
/**
|
|
@@ -703,13 +704,15 @@ window.__ModuleLoader__.load({
|
|
|
703
704
|
* @param workspaces - pure Workspace Controller.
|
|
704
705
|
* @param sessions - pure Session Controller.
|
|
705
706
|
* @param view - the browser's viewing-store write set, which owns saved Session order.
|
|
707
|
+
* @param notify - show one notice through the Workspace notice channel.
|
|
706
708
|
*/
|
|
707
|
-
constructor(ctx, directoryPicker, workspaces, sessions, view) {
|
|
709
|
+
constructor(ctx, directoryPicker, workspaces, sessions, view, notify) {
|
|
708
710
|
super(ctx, "uiWorkspace");
|
|
709
711
|
this.directoryPicker = directoryPicker;
|
|
710
712
|
this.workspaces = workspaces;
|
|
711
713
|
this.sessions = sessions;
|
|
712
714
|
this.view = view;
|
|
715
|
+
this.notify = notify;
|
|
713
716
|
ctx.effect(() => {
|
|
714
717
|
const stop = this.watchNavigation();
|
|
715
718
|
return () => {
|
|
@@ -755,7 +758,9 @@ window.__ModuleLoader__.load({
|
|
|
755
758
|
});
|
|
756
759
|
if (!navigation.aborted) this.replaceMain(childId, navigation);
|
|
757
760
|
}
|
|
758
|
-
startSession(workspaceId) {
|
|
761
|
+
startSession(workspaceId, options) {
|
|
762
|
+
const draftOptions = options === void 0 ? void 0 : structuredClone(options);
|
|
763
|
+
const initializeDraft = draftOptions !== void 0 && (draftOptions.prompt !== void 0 || draftOptions.clearPreviousDraft === true);
|
|
759
764
|
const workspace = this.workspaces.list.getSnapshot();
|
|
760
765
|
const sessions = this.sessions.list.getSnapshot();
|
|
761
766
|
const current = this.mainReference?.sessionId;
|
|
@@ -763,13 +768,37 @@ window.__ModuleLoader__.load({
|
|
|
763
768
|
const recent = workspace.phase === "ready" && sessions.phase === "ready" ? recentWorkspace(workspace.items, sessions.byId) : void 0;
|
|
764
769
|
const target = workspaceId ?? currentWorkspaceId ?? recent;
|
|
765
770
|
if (target === void 0) {
|
|
771
|
+
if (initializeDraft) {
|
|
772
|
+
this.notify({
|
|
773
|
+
kind: "createFailed",
|
|
774
|
+
message: this.ctx.locale.bind("workspace")("draft.workspaceRequired")
|
|
775
|
+
});
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
766
778
|
this.clearMain();
|
|
767
779
|
return;
|
|
768
780
|
}
|
|
769
|
-
this.openWorkspace(target
|
|
781
|
+
this.openWorkspace(target, initializeDraft ? (id) => {
|
|
782
|
+
const binding = this.sessions.binding(id);
|
|
783
|
+
if (binding === void 0) this.draftPreparationFailed();
|
|
784
|
+
this.prepareDraft(binding, draftOptions);
|
|
785
|
+
} : void 0).catch((reason) => {
|
|
770
786
|
console.warn("new session failed:", reason);
|
|
771
787
|
});
|
|
772
788
|
}
|
|
789
|
+
prepareDraft(binding, options) {
|
|
790
|
+
const conversation = this.ctx.get("conversation");
|
|
791
|
+
if (conversation === void 0) this.draftPreparationFailed();
|
|
792
|
+
if (conversation.input.requestDraftInitialization(binding, options) === "blocked") this.draftPreparationFailed();
|
|
793
|
+
}
|
|
794
|
+
draftPreparationFailed() {
|
|
795
|
+
const message = this.ctx.locale.bind("workspace")("draft.initializationFailed");
|
|
796
|
+
this.notify({
|
|
797
|
+
kind: "createFailed",
|
|
798
|
+
message
|
|
799
|
+
});
|
|
800
|
+
throw new Error(message);
|
|
801
|
+
}
|
|
773
802
|
async archiveSession(sessionId) {
|
|
774
803
|
await this.workspaces.archiveSession(sessionId);
|
|
775
804
|
if (this.mainReference?.sessionId === sessionId) this.clearMain();
|
|
@@ -927,7 +956,7 @@ window.__ModuleLoader__.load({
|
|
|
927
956
|
}
|
|
928
957
|
//#endregion
|
|
929
958
|
//#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-workspace/src/client/rows/Rows.module.css.mjs
|
|
930
|
-
const css$2 = ".RrwzeW_projectRow,.RrwzeW_sessionRow{padding:0 8px;cursor:pointer;user-select:none;color:var(--qilin-alias-label-primary);border-radius:8px;align-items:center;gap:6px;padding-inline-start:calc(8px + var(--dsh-workspace-indent,0px));display:flex}.RrwzeW_projectRow:hover,.RrwzeW_sessionRow:hover,.RrwzeW_sessionRow.RrwzeW_selected{background:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_searchResultRow{box-sizing:border-box;cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--qilin-alias-label-primary);background:0 0;border:none;border-radius:8px;flex-direction:column;align-items:stretch;padding:4px 8px;display:flex}.RrwzeW_searchResultRow:hover,.RrwzeW_searchResultRow.RrwzeW_selected{background:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_searchResultHeading{align-items:center;min-width:0;display:flex}.RrwzeW_searchResultTitle{text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;margin-left:4px;font-size:14px;line-height:20px;overflow:hidden}.RrwzeW_searchResultMeta{align-items:center;gap:6px;min-width:0;margin-left:20px;display:flex}.RrwzeW_searchResultWorkspace,.RrwzeW_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px;overflow:hidden}.RrwzeW_searchResultWorkspace{max-width:40%;color:var(--qilin-alias-label-tertiary);flex:none}.RrwzeW_searchResultSnippet{min-width:0;color:var(--qilin-alias-label-secondary);flex:1}.RrwzeW_projectRow{box-sizing:border-box;align-items:center;height:34px}.RrwzeW_projectRow .RrwzeW_rowActions{height:20px}.RrwzeW_sessionRow{height:32px;animation:RrwzeW_row-in .15s var(--ds-ease-in-out);gap:0}.RrwzeW_sessionRow .RrwzeW_title{margin:0 6px 0 4px}@keyframes RrwzeW_row-in{0%{opacity:0}}.RrwzeW_slot{width:16px;height:20px;color:var(--qilin-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.RrwzeW_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.RrwzeW_folderActive{color:var(--qilin-alias-state-business-primary)}.RrwzeW_projectRow .RrwzeW_chevron{display:none}.RrwzeW_projectRow:hover .RrwzeW_chevron{display:inline-flex}.RrwzeW_projectRow:hover .RrwzeW_folder{display:none}.RrwzeW_arrow{transition:transform .15s var(--ds-ease-in-out)}.RrwzeW_arrowOpen{transform:rotate(90deg)}.RrwzeW_projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.RrwzeW_title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}.RrwzeW_renameInput{border:.5px solid var(--qilin-alias-border-l4);background:var(--qilin-alias-button-elevated-fill);min-width:0;color:inherit;border-radius:4px;outline:none;padding:0 2px;font-size:14px;line-height:20px}.RrwzeW_sessionRow .RrwzeW_title{
|
|
959
|
+
const css$2 = ".RrwzeW_projectRow,.RrwzeW_sessionRow{padding:0 8px;cursor:pointer;user-select:none;color:var(--qilin-alias-label-primary);border-radius:8px;align-items:center;gap:6px;padding-inline-start:calc(8px + var(--dsh-workspace-indent,0px));display:flex}.RrwzeW_projectRow:hover,.RrwzeW_sessionRow:hover,.RrwzeW_sessionRow.RrwzeW_selected{background:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_searchResultRow{box-sizing:border-box;cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--qilin-alias-label-primary);background:0 0;border:none;border-radius:8px;flex-direction:column;align-items:stretch;padding:4px 8px;display:flex}.RrwzeW_searchResultRow:hover,.RrwzeW_searchResultRow.RrwzeW_selected{background:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_searchResultHeading{align-items:center;min-width:0;display:flex}.RrwzeW_searchResultTitle{text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;margin-left:4px;font-size:14px;line-height:20px;overflow:hidden}.RrwzeW_searchResultMeta{align-items:center;gap:6px;min-width:0;margin-left:20px;display:flex}.RrwzeW_searchResultWorkspace,.RrwzeW_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px;overflow:hidden}.RrwzeW_searchResultWorkspace{max-width:40%;color:var(--qilin-alias-label-tertiary);flex:none}.RrwzeW_searchResultSnippet{min-width:0;color:var(--qilin-alias-label-secondary);flex:1}.RrwzeW_projectRow{box-sizing:border-box;align-items:center;height:34px}.RrwzeW_projectRow .RrwzeW_rowActions{height:20px}.RrwzeW_sessionRow{height:32px;animation:RrwzeW_row-in .15s var(--ds-ease-in-out);gap:0}.RrwzeW_sessionRow .RrwzeW_title{margin:0 6px 0 4px}@keyframes RrwzeW_row-in{0%{opacity:0}}.RrwzeW_slot{width:16px;height:20px;color:var(--qilin-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.RrwzeW_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.RrwzeW_folderActive{color:var(--qilin-alias-state-business-primary)}.RrwzeW_projectRow .RrwzeW_chevron{display:none}.RrwzeW_projectRow:hover .RrwzeW_chevron{display:inline-flex}.RrwzeW_projectRow:hover .RrwzeW_folder{display:none}.RrwzeW_arrow{transition:transform .15s var(--ds-ease-in-out)}.RrwzeW_arrowOpen{transform:rotate(90deg)}.RrwzeW_projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.RrwzeW_title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}.RrwzeW_renameInput{border:.5px solid var(--qilin-alias-border-l4);background:var(--qilin-alias-button-elevated-fill);min-width:0;color:inherit;border-radius:4px;outline:none;padding:0 2px;font-size:14px;line-height:20px}.RrwzeW_sessionRow .RrwzeW_title{flex:1}.RrwzeW_sessionRow .RrwzeW_title[data-scrolled]{mask-image:linear-gradient(90deg,#0000,#000 12px)}.RrwzeW_sessionRow .RrwzeW_title[data-clipped]{mask-image:linear-gradient(270deg,#0000,#000 12px)}.RrwzeW_sessionRow .RrwzeW_title[data-scrolled][data-clipped]{mask-image:linear-gradient(90deg,#0000,#000 12px calc(100% - 12px),#0000)}@media (hover:hover){.RrwzeW_sessionRow:hover .RrwzeW_title,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_title{text-overflow:clip}}.RrwzeW_meta{text-overflow:ellipsis;white-space:nowrap;color:var(--qilin-alias-label-tertiary);font-size:12px;line-height:20px;overflow:hidden}.RrwzeW_time{color:var(--qilin-alias-label-tertiary);flex:none;font-size:12px;line-height:20px}.RrwzeW_dot{flex:none}.RrwzeW_pinIndicator{width:16px;height:20px;color:var(--qilin-alias-label-caption);flex:none;justify-content:center;align-items:center;margin-left:6px;display:inline-flex}.RrwzeW_sessionRow:hover .RrwzeW_pinIndicator,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_pinIndicator{display:none}.RrwzeW_rowActions{flex:none;align-items:center;gap:15px;display:none}.RrwzeW_projectRow:hover .RrwzeW_rowActions,.RrwzeW_sessionRow:hover .RrwzeW_rowActions,.RrwzeW_projectRow.RrwzeW_menuOpen .RrwzeW_rowActions,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_rowActions{display:inline-flex}.RrwzeW_sessionRow:hover .RrwzeW_time,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_time{display:none}.RrwzeW_projectRow.RrwzeW_menuOpen,.RrwzeW_sessionRow.RrwzeW_menuOpen{background:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_sessionRow.RrwzeW_dropBefore,.RrwzeW_sessionRow.RrwzeW_dropAfter{position:relative}.RrwzeW_sessionRow.RrwzeW_dropBefore:before,.RrwzeW_sessionRow.RrwzeW_dropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--qilin-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--qilin-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--qilin-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:4px}.RrwzeW_sessionRow.RrwzeW_dropBefore:before{top:-7px}.RrwzeW_sessionRow.RrwzeW_dropAfter:after{bottom:-7px}.RrwzeW_hoverContent{flex-direction:column;gap:8px;display:flex}.RrwzeW_hoverTitle{color:#fff;overflow-wrap:break-word;font-size:14px;line-height:20px}.RrwzeW_hoverPath{color:#cfd3d6;word-break:break-all;font-size:12px;line-height:16px}.RrwzeW_hoverTime{color:#cfd3d6;font-size:12px;line-height:16px}.RrwzeW_hoverStatus{color:#adb2b8;align-items:center;gap:8px;font-size:12px;line-height:20px;display:flex}.RrwzeW_iconButton{cursor:pointer;width:16px;height:16px;color:var(--qilin-alias-label-tertiary);background:0 0;border:none;border-radius:4px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.RrwzeW_iconButton:hover{color:var(--qilin-alias-label-primary)}.RrwzeW_chevron{color:var(--qilin-alias-label-caption)}@media (prefers-reduced-motion:reduce){.RrwzeW_sessionRow,.RrwzeW_arrow{transition:none;animation:none}}";
|
|
931
960
|
const tagId$2 = "@qilin-agent/client-ui-workspace/Rows.module.css";
|
|
932
961
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
933
962
|
const tag = document.createElement("style");
|
|
@@ -978,7 +1007,8 @@ window.__ModuleLoader__.load({
|
|
|
978
1007
|
* Workspace browser tree row components (figma Cell set 14:3080): pure presentational —
|
|
979
1008
|
* all data and callbacks arrive via props. Hover swaps (folder->chevron,
|
|
980
1009
|
* time->ellipsis, action buttons) are CSS-only, and a session row's clipped
|
|
981
|
-
* title
|
|
1010
|
+
* title marquees programmatically while the row is hovered, fading both
|
|
1011
|
+
* clipping edges from the depth of its travel. Row ... menus are
|
|
982
1012
|
* visual-only except workspace Rename/Delete and session Rename/Fork/Archive; the
|
|
983
1013
|
* session and workspace hover cards are suppressed while a menu is open.
|
|
984
1014
|
*/
|
|
@@ -986,29 +1016,91 @@ window.__ModuleLoader__.load({
|
|
|
986
1016
|
function displayTitle(node, t) {
|
|
987
1017
|
return node.blank ? t("session.new") : node.title || t("session.untitled");
|
|
988
1018
|
}
|
|
1019
|
+
const MIN_TITLE_REVEAL_PX = 8;
|
|
1020
|
+
const TITLE_MARQUEE_PX_PER_MS = .03;
|
|
1021
|
+
/** Whether the platform asked for reduced motion; jsdom implements no matchMedia. */
|
|
1022
|
+
function prefersReducedMotion() {
|
|
1023
|
+
return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1024
|
+
}
|
|
989
1025
|
/**
|
|
990
|
-
*
|
|
991
|
-
*
|
|
992
|
-
*
|
|
993
|
-
* to the start in one step, because the resting ellipsis and the narrowed cell
|
|
994
|
-
* would otherwise meet the text while it travelled back. A title that fits has
|
|
995
|
-
* no scroll range to move, and the stylesheet decides whether either move
|
|
996
|
-
* glides or jumps.
|
|
1026
|
+
* Place the title's scroll position and publish the stylesheet's fade-mask
|
|
1027
|
+
* hooks: `data-scrolled` while the title has left its start (left fade) and
|
|
1028
|
+
* `data-clipped` while text remains beyond the right edge (right fade).
|
|
997
1029
|
* @param title - the row's clipping title element.
|
|
998
|
-
* @param
|
|
1030
|
+
* @param left - scroll offset in CSS pixels.
|
|
1031
|
+
* @param range - the title's maximum scroll offset in CSS pixels.
|
|
999
1032
|
*/
|
|
1000
|
-
function
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1033
|
+
function placeTitle(title, left, range) {
|
|
1034
|
+
if (typeof title.scrollTo === "function") title.scrollTo({
|
|
1035
|
+
left,
|
|
1036
|
+
behavior: "instant"
|
|
1037
|
+
});
|
|
1038
|
+
else title.scrollLeft = left;
|
|
1039
|
+
if (left > 0) title.dataset.scrolled = "";
|
|
1040
|
+
else delete title.dataset.scrolled;
|
|
1041
|
+
if (left < range) title.dataset.clipped = "";
|
|
1042
|
+
else delete title.dataset.clipped;
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Return the title to its resting state: scrolled to the start with both fade
|
|
1046
|
+
* masks off, so the resting ellipsis renders at full strength.
|
|
1047
|
+
* @param title - the row's clipping title element.
|
|
1048
|
+
*/
|
|
1049
|
+
function restTitle(title) {
|
|
1007
1050
|
if (typeof title.scrollTo === "function") title.scrollTo({
|
|
1008
1051
|
left: 0,
|
|
1009
1052
|
behavior: "instant"
|
|
1010
1053
|
});
|
|
1011
1054
|
else title.scrollLeft = 0;
|
|
1055
|
+
delete title.dataset.scrolled;
|
|
1056
|
+
delete title.dataset.clipped;
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Marquee a title wider than its one-line cell while its row is hovered: the
|
|
1060
|
+
* title clips its own text, so entering crawls it at a constant speed until the
|
|
1061
|
+
* far edge (a fork's incremented title, for example) is in view, then rests
|
|
1062
|
+
* there under the pointer. Overflow of at most {@link MIN_TITLE_REVEAL_PX}
|
|
1063
|
+
* stays put — a barely-clipped title moving a few pixels reads as jitter, not a
|
|
1064
|
+
* reveal. Leaving returns the title to the start in one step, because the
|
|
1065
|
+
* resting ellipsis and the narrowed cell would otherwise meet the text while it
|
|
1066
|
+
* travelled back. Reduced motion jumps to the far edge instead of crawling.
|
|
1067
|
+
* @param title - ref to the row's clipping title element.
|
|
1068
|
+
* @returns stable pointer enter/leave handlers for the row.
|
|
1069
|
+
*/
|
|
1070
|
+
function useTitleMarquee(title) {
|
|
1071
|
+
const frame = (0, react.useRef)(0);
|
|
1072
|
+
(0, react.useEffect)(() => () => {
|
|
1073
|
+
cancelAnimationFrame(frame.current);
|
|
1074
|
+
}, []);
|
|
1075
|
+
return (0, react.useMemo)(() => ({
|
|
1076
|
+
enter: () => {
|
|
1077
|
+
/* v8 ignore next -- defensive: the title span renders unconditionally. */
|
|
1078
|
+
if (title.current === null) return;
|
|
1079
|
+
const element = title.current;
|
|
1080
|
+
const range = element.scrollWidth - element.clientWidth;
|
|
1081
|
+
if (range <= MIN_TITLE_REVEAL_PX) return;
|
|
1082
|
+
if (prefersReducedMotion()) {
|
|
1083
|
+
placeTitle(element, range, range);
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
cancelAnimationFrame(frame.current);
|
|
1087
|
+
let previous;
|
|
1088
|
+
let position = 0;
|
|
1089
|
+
const step = (now) => {
|
|
1090
|
+
position += previous === void 0 ? 0 : (now - previous) * TITLE_MARQUEE_PX_PER_MS;
|
|
1091
|
+
previous = now;
|
|
1092
|
+
placeTitle(element, Math.min(position, range), range);
|
|
1093
|
+
if (position < range) frame.current = requestAnimationFrame(step);
|
|
1094
|
+
};
|
|
1095
|
+
frame.current = requestAnimationFrame(step);
|
|
1096
|
+
},
|
|
1097
|
+
leave: () => {
|
|
1098
|
+
cancelAnimationFrame(frame.current);
|
|
1099
|
+
/* v8 ignore next -- defensive: the title span renders unconditionally. */
|
|
1100
|
+
if (title.current === null) return;
|
|
1101
|
+
restTitle(title.current);
|
|
1102
|
+
}
|
|
1103
|
+
}), [title]);
|
|
1012
1104
|
}
|
|
1013
1105
|
/** Localized compact relative time ("刚刚"/"5分钟" in zh, "now"/"5min" in en). */
|
|
1014
1106
|
function timeLabel(updatedAt, now, t) {
|
|
@@ -1314,8 +1406,9 @@ window.__ModuleLoader__.load({
|
|
|
1314
1406
|
});
|
|
1315
1407
|
}
|
|
1316
1408
|
/**
|
|
1317
|
-
* One top-level
|
|
1318
|
-
* own or descendant activity), title
|
|
1409
|
+
* One top-level 32px session row: status dot (pending user interaction outranks
|
|
1410
|
+
* own or descendant activity), a title that marquees while the row is hovered,
|
|
1411
|
+
* relative time, and the hover action strip (menu, archive, pin).
|
|
1319
1412
|
* @param props.node - derived session node.
|
|
1320
1413
|
* @param props.currentId - selected session id (row highlight).
|
|
1321
1414
|
* @param props.now - epoch ms for relative-time formatting.
|
|
@@ -1323,6 +1416,7 @@ window.__ModuleLoader__.load({
|
|
|
1323
1416
|
* @param props.onRename - open the session rename dialog (id + current title).
|
|
1324
1417
|
* @param props.onFork - fork a session at its last completed turn.
|
|
1325
1418
|
* @param props.onArchive - archive a session by id.
|
|
1419
|
+
* @param props.onCopyId - copy a session's complete id without opening it.
|
|
1326
1420
|
* @param props.onPin - pin a session by id (leading its group and the flat list).
|
|
1327
1421
|
* @param props.onUnpin - drop a session's pin.
|
|
1328
1422
|
* @param props.onReveal - scroll this row into view after search navigation, then acknowledge it.
|
|
@@ -1330,7 +1424,7 @@ window.__ModuleLoader__.load({
|
|
|
1330
1424
|
* @param props.t - the browser root's locale seat.
|
|
1331
1425
|
* @returns the session row.
|
|
1332
1426
|
*/
|
|
1333
|
-
function SessionNodeItem({ node, currentId, now, onOpen, onRename, onFork, onArchive, onPin, onUnpin, onReveal, drag, renderSlot, t }) {
|
|
1427
|
+
function SessionNodeItem({ node, currentId, now, onOpen, onRename, onFork, onArchive, onCopyId, onPin, onUnpin, onReveal, drag, renderSlot, t }) {
|
|
1334
1428
|
const row = node;
|
|
1335
1429
|
const title = displayTitle(node, t);
|
|
1336
1430
|
const selected = node.id === currentId;
|
|
@@ -1340,6 +1434,7 @@ window.__ModuleLoader__.load({
|
|
|
1340
1434
|
const [menuOpen, setMenuOpen] = (0, react.useState)(false);
|
|
1341
1435
|
const rowRef = (0, react.useRef)(null);
|
|
1342
1436
|
const titleRef = (0, react.useRef)(null);
|
|
1437
|
+
const marquee = useTitleMarquee(titleRef);
|
|
1343
1438
|
(0, react.useEffect)(() => {
|
|
1344
1439
|
if (onReveal === void 0) return;
|
|
1345
1440
|
rowRef.current?.scrollIntoView({ block: "nearest" });
|
|
@@ -1365,6 +1460,11 @@ window.__ModuleLoader__.load({
|
|
|
1365
1460
|
id: "archive",
|
|
1366
1461
|
label: t("menu.archiveSession"),
|
|
1367
1462
|
icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconArchiveOutline20, { size: 16 })
|
|
1463
|
+
},
|
|
1464
|
+
{
|
|
1465
|
+
id: "copyId",
|
|
1466
|
+
label: t("menu.sessionId"),
|
|
1467
|
+
icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconCopyOutlineRegular, {})
|
|
1368
1468
|
}
|
|
1369
1469
|
];
|
|
1370
1470
|
return (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.HoverCard, {
|
|
@@ -1376,12 +1476,8 @@ window.__ModuleLoader__.load({
|
|
|
1376
1476
|
onClick: () => {
|
|
1377
1477
|
onOpen(node.id);
|
|
1378
1478
|
},
|
|
1379
|
-
onPointerEnter:
|
|
1380
|
-
|
|
1381
|
-
},
|
|
1382
|
-
onPointerLeave: () => {
|
|
1383
|
-
revealClippedTitle(titleRef.current, false);
|
|
1384
|
-
},
|
|
1479
|
+
onPointerEnter: marquee.enter,
|
|
1480
|
+
onPointerLeave: marquee.leave,
|
|
1385
1481
|
draggable,
|
|
1386
1482
|
onDragStart: drag === void 0 || row.blank ? void 0 : (e) => {
|
|
1387
1483
|
e.dataTransfer.effectAllowed = "move";
|
|
@@ -1417,41 +1513,67 @@ window.__ModuleLoader__.load({
|
|
|
1417
1513
|
row.pinned && !row.blank && (0, react_jsx_runtime.jsx)(PinnedIndicator, { t }),
|
|
1418
1514
|
!row.blank && (0, react_jsx_runtime.jsxs)("span", {
|
|
1419
1515
|
className: Rows_module_css_default.rowActions,
|
|
1420
|
-
children: [
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
items: sessionMenuItems,
|
|
1435
|
-
onSelect: (id) => {
|
|
1436
|
-
setMenuOpen(false);
|
|
1437
|
-
if (id === "pin") (row.pinned ? onUnpin : onPin)(node.id);
|
|
1438
|
-
if (id === "rename") onRename(node.id, row.title);
|
|
1439
|
-
if (id === "fork") onFork(node.id);
|
|
1440
|
-
if (id === "archive") onArchive(node.id);
|
|
1441
|
-
},
|
|
1442
|
-
portal: true,
|
|
1443
|
-
closeOnPointerLeave: true,
|
|
1444
|
-
anchor: (0, react_jsx_runtime.jsx)("button", {
|
|
1445
|
-
type: "button",
|
|
1446
|
-
className: Rows_module_css_default.iconButton,
|
|
1447
|
-
"aria-label": t("actions.session.aria", { name: title }),
|
|
1448
|
-
onClick: (e) => {
|
|
1449
|
-
e.stopPropagation();
|
|
1450
|
-
setMenuOpen((v) => !v);
|
|
1516
|
+
children: [
|
|
1517
|
+
(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Menu, {
|
|
1518
|
+
open: menuOpen,
|
|
1519
|
+
onClose: () => {
|
|
1520
|
+
setMenuOpen(false);
|
|
1521
|
+
},
|
|
1522
|
+
items: sessionMenuItems,
|
|
1523
|
+
onSelect: (id) => {
|
|
1524
|
+
setMenuOpen(false);
|
|
1525
|
+
if (id === "pin") (row.pinned ? onUnpin : onPin)(node.id);
|
|
1526
|
+
if (id === "rename") onRename(node.id, row.title);
|
|
1527
|
+
if (id === "fork") onFork(node.id);
|
|
1528
|
+
if (id === "archive") onArchive(node.id);
|
|
1529
|
+
if (id === "copyId") onCopyId(node.id);
|
|
1451
1530
|
},
|
|
1452
|
-
|
|
1531
|
+
portal: true,
|
|
1532
|
+
closeOnPointerLeave: true,
|
|
1533
|
+
anchor: (0, react_jsx_runtime.jsx)("button", {
|
|
1534
|
+
type: "button",
|
|
1535
|
+
className: Rows_module_css_default.iconButton,
|
|
1536
|
+
"aria-label": t("actions.session.aria", { name: title }),
|
|
1537
|
+
onClick: (e) => {
|
|
1538
|
+
e.stopPropagation();
|
|
1539
|
+
setMenuOpen((v) => !v);
|
|
1540
|
+
},
|
|
1541
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconEllipsisOutline16, {})
|
|
1542
|
+
})
|
|
1543
|
+
}),
|
|
1544
|
+
(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Tooltip, {
|
|
1545
|
+
label: t("actions.archive"),
|
|
1546
|
+
side: "bottom",
|
|
1547
|
+
delayMs: 500,
|
|
1548
|
+
portal: true,
|
|
1549
|
+
children: (0, react_jsx_runtime.jsx)("button", {
|
|
1550
|
+
type: "button",
|
|
1551
|
+
className: Rows_module_css_default.iconButton,
|
|
1552
|
+
"aria-label": t("menu.archiveSession"),
|
|
1553
|
+
onClick: (e) => {
|
|
1554
|
+
e.stopPropagation();
|
|
1555
|
+
onArchive(node.id);
|
|
1556
|
+
},
|
|
1557
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconArchiveOutline20, { size: 16 })
|
|
1558
|
+
})
|
|
1559
|
+
}),
|
|
1560
|
+
(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Tooltip, {
|
|
1561
|
+
label: t(row.pinned ? "actions.unpin" : "actions.pin"),
|
|
1562
|
+
side: "bottom",
|
|
1563
|
+
delayMs: 500,
|
|
1564
|
+
portal: true,
|
|
1565
|
+
children: (0, react_jsx_runtime.jsx)("button", {
|
|
1566
|
+
type: "button",
|
|
1567
|
+
className: Rows_module_css_default.iconButton,
|
|
1568
|
+
"aria-label": t(row.pinned ? "actions.unpin" : "actions.pin"),
|
|
1569
|
+
onClick: (e) => {
|
|
1570
|
+
e.stopPropagation();
|
|
1571
|
+
(row.pinned ? onUnpin : onPin)(node.id);
|
|
1572
|
+
},
|
|
1573
|
+
children: row.pinned ? (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPinFillRegular, { size: 14 }) : (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPinOutlineRegular, { size: 14 })
|
|
1574
|
+
})
|
|
1453
1575
|
})
|
|
1454
|
-
|
|
1576
|
+
]
|
|
1455
1577
|
})
|
|
1456
1578
|
]
|
|
1457
1579
|
}),
|
|
@@ -1710,22 +1832,46 @@ window.__ModuleLoader__.load({
|
|
|
1710
1832
|
const SEARCH_DEBOUNCE_MS = 250;
|
|
1711
1833
|
/** `session.search` wire bound, measured in JavaScript UTF-16 code units. */
|
|
1712
1834
|
const SEARCH_QUERY_MAX_CODE_UNITS = 500;
|
|
1713
|
-
/** Session rows visible per Workspace
|
|
1714
|
-
const
|
|
1715
|
-
/**
|
|
1716
|
-
|
|
1717
|
-
|
|
1835
|
+
/** Non-blank Session rows visible per Workspace while no Session raises its watermark. */
|
|
1836
|
+
const SESSION_WATERMARK_BASE = 5;
|
|
1837
|
+
/** Highest watermark that attention-bearing Sessions raise automatically; Show more may exceed it. */
|
|
1838
|
+
const SESSION_WATERMARK_AUTO_MAX = 50;
|
|
1839
|
+
/** Show the blank row plus the first `watermark` non-blank rows, in list order. */
|
|
1840
|
+
function watermarkRows(sessions, watermark) {
|
|
1841
|
+
let count = 0;
|
|
1718
1842
|
const rows = sessions.filter((session) => {
|
|
1719
1843
|
if (session.blank) return true;
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
return true;
|
|
1844
|
+
count += 1;
|
|
1845
|
+
return count <= watermark;
|
|
1723
1846
|
});
|
|
1724
1847
|
return {
|
|
1725
1848
|
rows,
|
|
1726
1849
|
hiddenCount: sessions.length - rows.length
|
|
1727
1850
|
};
|
|
1728
1851
|
}
|
|
1852
|
+
/**
|
|
1853
|
+
* Smallest watermark that covers every non-blank row within the automatic maximum that is running, has a running
|
|
1854
|
+
* child, awaits the user, carries an unread completion, or is current, and the reveal target at any position.
|
|
1855
|
+
*/
|
|
1856
|
+
function requiredWatermark(sessions, current, revealSessionId) {
|
|
1857
|
+
let index = 0;
|
|
1858
|
+
let required = 0;
|
|
1859
|
+
for (const session of sessions) {
|
|
1860
|
+
if (session.blank) continue;
|
|
1861
|
+
index += 1;
|
|
1862
|
+
if (index <= SESSION_WATERMARK_AUTO_MAX && (session.running || session.runningSubagentCount > 0 || session.pendingInteraction !== void 0 || session.completed || session.id === current) || session.id === revealSessionId) required = index;
|
|
1863
|
+
}
|
|
1864
|
+
return required;
|
|
1865
|
+
}
|
|
1866
|
+
/** Raise each listed group's watermark to its requirement; returns `marks` itself when nothing rises. */
|
|
1867
|
+
function raiseWatermarks(marks, required) {
|
|
1868
|
+
let raised = marks;
|
|
1869
|
+
for (const [key, watermark] of required) if (watermark > (raised[key] ?? SESSION_WATERMARK_BASE)) raised = {
|
|
1870
|
+
...raised,
|
|
1871
|
+
[key]: watermark
|
|
1872
|
+
};
|
|
1873
|
+
return raised;
|
|
1874
|
+
}
|
|
1729
1875
|
/** Keep controlled input and RPC payload inside the session.search wire contract. */
|
|
1730
1876
|
function sanitizeSearchQuery(value) {
|
|
1731
1877
|
const withoutNul = value.replaceAll("\0", "");
|
|
@@ -1736,10 +1882,6 @@ window.__ModuleLoader__.load({
|
|
|
1736
1882
|
if (last >= 55296 && last <= 56319 && next >= 56320 && next <= 57343) end--;
|
|
1737
1883
|
return withoutNul.slice(0, end);
|
|
1738
1884
|
}
|
|
1739
|
-
/** Immutable membership toggle for the local expand-all array. */
|
|
1740
|
-
function toggled(list, key) {
|
|
1741
|
-
return list.includes(key) ? list.filter((k) => k !== key) : [...list, key];
|
|
1742
|
-
}
|
|
1743
1885
|
/**
|
|
1744
1886
|
* Accept the native drag at document level while a row drag is active: row
|
|
1745
1887
|
* hover still owns the insertion marker, and releasing outside the list must
|
|
@@ -1838,12 +1980,12 @@ window.__ModuleLoader__.load({
|
|
|
1838
1980
|
return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
|
|
1839
1981
|
}
|
|
1840
1982
|
/** The scrolling session tree; unmounting drops the sessions subscription and expand-all state. */
|
|
1841
|
-
function SessionTree({ list, useSessionStatus, startSession, open, forkSession, workspaces, ungroupedSessionIds, archivedSessionIds, pinnedSessionIds, onSessionPin, onSessionUnpin, workspaceReady, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore, nestWorkspaces, groupExpansion, setGroupExpanded, setSessionOrder, home, t, renderSlot, revealSessionId, onSessionRevealed }) {
|
|
1983
|
+
function SessionTree({ list, useSessionStatus, startSession, open, forkSession, copySessionId, workspaces, ungroupedSessionIds, archivedSessionIds, pinnedSessionIds, onSessionPin, onSessionUnpin, workspaceReady, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore, nestWorkspaces, groupExpansion, setGroupExpanded, setSessionOrder, home, t, renderSlot, revealSessionId, onSessionRevealed }) {
|
|
1842
1984
|
const panelActive = usePanelInfo((info) => info.activePanelId !== null);
|
|
1843
1985
|
const statuses = useSessionStatus((s) => s);
|
|
1844
1986
|
const current = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
|
|
1845
1987
|
const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
|
|
1846
|
-
const [
|
|
1988
|
+
const [storedWatermarks, setStoredWatermarks] = (0, react.useState)({});
|
|
1847
1989
|
const [drag, setDrag] = (0, react.useState)(null);
|
|
1848
1990
|
const sessionDropCommitted = (0, react.useRef)(false);
|
|
1849
1991
|
const [workspaceDrag, setWorkspaceDrag] = (0, react.useState)(null);
|
|
@@ -1904,17 +2046,23 @@ window.__ModuleLoader__.load({
|
|
|
1904
2046
|
revealGroup,
|
|
1905
2047
|
setGroupExpanded
|
|
1906
2048
|
]);
|
|
1907
|
-
(
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
2049
|
+
const baselines = new Map(groups.map((group) => [group.key, Math.max(SESSION_WATERMARK_BASE, requiredWatermark(group.sessions, current, group.key === revealGroup ? revealSessionId : void 0))]));
|
|
2050
|
+
const foldedKeys = new Set(groups.filter((group) => !group.expanded).map((group) => group.key));
|
|
2051
|
+
const underFoldedAncestor = (key) => {
|
|
2052
|
+
for (let owner = parents.get(key); owner !== void 0; owner = parents.get(owner)) if (foldedKeys.has(owner)) return true;
|
|
2053
|
+
return false;
|
|
2054
|
+
};
|
|
2055
|
+
const requiredWatermarks = new Map([...baselines].filter(([key]) => !underFoldedAncestor(key)));
|
|
2056
|
+
const sessionWatermarks = raiseWatermarks(storedWatermarks, requiredWatermarks);
|
|
2057
|
+
if (sessionWatermarks !== storedWatermarks) setStoredWatermarks((marks) => raiseWatermarks(marks, requiredWatermarks));
|
|
2058
|
+
const dropWatermarks = (drop) => {
|
|
2059
|
+
if (!Object.keys(storedWatermarks).some(drop)) return;
|
|
2060
|
+
setStoredWatermarks((marks) => Object.fromEntries(Object.entries(marks).filter(([key]) => !drop(key))));
|
|
2061
|
+
};
|
|
2062
|
+
const withinGroup = (key, root) => {
|
|
2063
|
+
for (let owner = key; owner !== void 0; owner = parents.get(owner)) if (owner === root) return true;
|
|
2064
|
+
return false;
|
|
2065
|
+
};
|
|
1918
2066
|
const now = Date.now();
|
|
1919
2067
|
const commitSessionDrag = (activeDrag, over) => {
|
|
1920
2068
|
if (sessionDropCommitted.current) return;
|
|
@@ -1922,8 +2070,10 @@ window.__ModuleLoader__.load({
|
|
|
1922
2070
|
setDrag(null);
|
|
1923
2071
|
const group = groups.find((candidate) => candidate.key === activeDrag.accountKey);
|
|
1924
2072
|
if (group === void 0) return;
|
|
1925
|
-
const
|
|
1926
|
-
const
|
|
2073
|
+
const watermark = sessionWatermarks[group.key] ?? SESSION_WATERMARK_BASE;
|
|
2074
|
+
const visible = watermarkRows(group.sessions, watermark);
|
|
2075
|
+
const sessionsExpanded = visible.hiddenCount === 0;
|
|
2076
|
+
const renderedSessions = visible.rows;
|
|
1927
2077
|
const targetIndex = renderedSessions.findIndex((session) => session.id === over.id);
|
|
1928
2078
|
if (targetIndex === -1) return;
|
|
1929
2079
|
const sourceIndex = renderedSessions.findIndex((session) => session.id === activeDrag.sessionId);
|
|
@@ -1951,10 +2101,10 @@ window.__ModuleLoader__.load({
|
|
|
1951
2101
|
nextOrder.splice(insertAt === -1 ? nextOrder.length : insertAt, 0, activeDrag.sessionId);
|
|
1952
2102
|
if (!sessionsExpanded && sourceIndex !== -1) {
|
|
1953
2103
|
const nodes = new Map(group.sessions.map((node) => [node.id, node]));
|
|
1954
|
-
if (!
|
|
2104
|
+
if (!watermarkRows(nextOrder.flatMap((id) => {
|
|
1955
2105
|
const node = nodes.get(id);
|
|
1956
2106
|
return node === void 0 ? [] : [node];
|
|
1957
|
-
})).rows.some((node) => node.id === activeDrag.sessionId)) return;
|
|
2107
|
+
}), watermark).rows.some((node) => node.id === activeDrag.sessionId)) return;
|
|
1958
2108
|
}
|
|
1959
2109
|
const currentBlank = group.sessions.find((node) => node.blank)?.id;
|
|
1960
2110
|
setSessionOrder(activeDrag.accountKey, pinCurrentBlank(nextOrder, currentBlank));
|
|
@@ -1992,8 +2142,10 @@ window.__ModuleLoader__.load({
|
|
|
1992
2142
|
const workspaceId = group.workspaceId;
|
|
1993
2143
|
const children = childrenByParent.get(group.key) ?? [];
|
|
1994
2144
|
const compatibleDrag = workspaceDrag !== null && parents.get(workspaceDrag.workspaceId) === parents.get(group.key);
|
|
1995
|
-
const
|
|
1996
|
-
const
|
|
2145
|
+
const watermark = sessionWatermarks[group.key] ?? SESSION_WATERMARK_BASE;
|
|
2146
|
+
const collapsed = watermarkRows(group.sessions, baselines.get(group.key) ?? SESSION_WATERMARK_BASE);
|
|
2147
|
+
const visible = watermarkRows(group.sessions, watermark);
|
|
2148
|
+
const sessionsExpanded = visible.hiddenCount === 0;
|
|
1997
2149
|
const workspaceMarker = workspaceId !== void 0 && workspaceDrag?.over?.id === workspaceId ? workspaceDrag.over.half : null;
|
|
1998
2150
|
const workspaceDragProps = workspaceId === void 0 ? void 0 : {
|
|
1999
2151
|
start: () => {
|
|
@@ -2058,7 +2210,7 @@ window.__ModuleLoader__.load({
|
|
|
2058
2210
|
home,
|
|
2059
2211
|
t,
|
|
2060
2212
|
onToggle: () => {
|
|
2061
|
-
if (group.expanded)
|
|
2213
|
+
if (group.expanded) dropWatermarks((key) => withinGroup(key, group.key));
|
|
2062
2214
|
setGroupExpanded(group.key, !group.expanded);
|
|
2063
2215
|
},
|
|
2064
2216
|
onCreate: () => {
|
|
@@ -2083,7 +2235,7 @@ window.__ModuleLoader__.load({
|
|
|
2083
2235
|
role: "group",
|
|
2084
2236
|
children: children.map((child) => renderGroup(child, depth + 1))
|
|
2085
2237
|
}),
|
|
2086
|
-
|
|
2238
|
+
visible.rows.map((node) => {
|
|
2087
2239
|
const sameGroupDrag = drag !== null && drag.accountKey === group.key;
|
|
2088
2240
|
const normalizeHalf = (half) => node.blank ? "after" : half;
|
|
2089
2241
|
const dragProps = {
|
|
@@ -2129,6 +2281,9 @@ window.__ModuleLoader__.load({
|
|
|
2129
2281
|
onRename: onSessionRename,
|
|
2130
2282
|
onFork: forkSession,
|
|
2131
2283
|
onArchive: onSessionArchive,
|
|
2284
|
+
onCopyId: (sessionId) => {
|
|
2285
|
+
copySessionId(sessionId);
|
|
2286
|
+
},
|
|
2132
2287
|
onPin: onSessionPin,
|
|
2133
2288
|
onUnpin: onSessionUnpin,
|
|
2134
2289
|
onReveal: node.id === revealSessionId && group.key === revealGroup ? () => {
|
|
@@ -2144,9 +2299,16 @@ window.__ModuleLoader__.load({
|
|
|
2144
2299
|
className: WorkspaceBrowser_module_css_default.sessionOverflowButton,
|
|
2145
2300
|
"aria-expanded": sessionsExpanded,
|
|
2146
2301
|
onClick: () => {
|
|
2147
|
-
|
|
2302
|
+
if (sessionsExpanded) {
|
|
2303
|
+
dropWatermarks((key) => key === group.key);
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
2306
|
+
setStoredWatermarks((marks) => ({
|
|
2307
|
+
...marks,
|
|
2308
|
+
[group.key]: visible.hiddenCount <= SESSION_WATERMARK_BASE ? Infinity : watermark + SESSION_WATERMARK_BASE
|
|
2309
|
+
}));
|
|
2148
2310
|
},
|
|
2149
|
-
children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n:
|
|
2311
|
+
children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n: visible.hiddenCount })
|
|
2150
2312
|
})
|
|
2151
2313
|
]
|
|
2152
2314
|
}, group.key);
|
|
@@ -2172,7 +2334,7 @@ window.__ModuleLoader__.load({
|
|
|
2172
2334
|
});
|
|
2173
2335
|
}
|
|
2174
2336
|
/** The flat "In one list" body: every session is one draggable top-level row. */
|
|
2175
|
-
function FlatList({ list, sessionIds, pinnedSessionIds, useSessionStatus, open, forkSession, onSessionRename, onSessionArchive, onSessionPin, onSessionUnpin, usePanelInfo, setSessionOrder, revealSessionId, onSessionRevealed, t, renderSlot }) {
|
|
2337
|
+
function FlatList({ list, sessionIds, pinnedSessionIds, useSessionStatus, open, forkSession, copySessionId, onSessionRename, onSessionArchive, onSessionPin, onSessionUnpin, usePanelInfo, setSessionOrder, revealSessionId, onSessionRevealed, t, renderSlot }) {
|
|
2176
2338
|
const panelActive = usePanelInfo((info) => info.activePanelId !== null);
|
|
2177
2339
|
const statuses = useSessionStatus((s) => s);
|
|
2178
2340
|
const rows = (0, react.useMemo)(() => deriveFlat(list, sessionIds, pinnedSessionIds, statuses), [
|
|
@@ -2223,6 +2385,9 @@ window.__ModuleLoader__.load({
|
|
|
2223
2385
|
onRename: onSessionRename,
|
|
2224
2386
|
onFork: forkSession,
|
|
2225
2387
|
onArchive: onSessionArchive,
|
|
2388
|
+
onCopyId: (sessionId) => {
|
|
2389
|
+
copySessionId(sessionId);
|
|
2390
|
+
},
|
|
2226
2391
|
onPin: onSessionPin,
|
|
2227
2392
|
onUnpin: onSessionUnpin,
|
|
2228
2393
|
onReveal: node.id === revealSessionId ? () => {
|
|
@@ -2333,7 +2498,7 @@ window.__ModuleLoader__.load({
|
|
|
2333
2498
|
* @param props - composed slot props (shell owner share + store + injected actions).
|
|
2334
2499
|
* @returns the region element tree.
|
|
2335
2500
|
*/
|
|
2336
|
-
function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, pinSession, unpinSession, createWorkspace, searchSessions, searchResultLimit, requestSearch, requestAddWorkspace, closeAddWorkspace, closeRenameRequest, setDirectoryBusy, useDirectoryFlow, useHostInfo, useWorkspaceShortcuts, useShortcuts, renderSlot, t }) {
|
|
2501
|
+
function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, copySessionId, pinSession, unpinSession, createWorkspace, searchSessions, searchResultLimit, requestSearch, requestAddWorkspace, closeAddWorkspace, closeRenameRequest, setDirectoryBusy, useDirectoryFlow, useHostInfo, useWorkspaceShortcuts, useShortcuts, renderSlot, t }) {
|
|
2337
2502
|
const home = useHostInfo((info) => info.home);
|
|
2338
2503
|
const list = useSessions((state) => state);
|
|
2339
2504
|
const workspaces = useWorkspaces((state) => state.items);
|
|
@@ -2839,6 +3004,7 @@ window.__ModuleLoader__.load({
|
|
|
2839
3004
|
useSessionStatus,
|
|
2840
3005
|
open,
|
|
2841
3006
|
forkSession,
|
|
3007
|
+
copySessionId,
|
|
2842
3008
|
onSessionRename,
|
|
2843
3009
|
onSessionArchive,
|
|
2844
3010
|
onSessionPin,
|
|
@@ -2853,6 +3019,7 @@ window.__ModuleLoader__.load({
|
|
|
2853
3019
|
usePanelInfo,
|
|
2854
3020
|
list,
|
|
2855
3021
|
useSessionStatus,
|
|
3022
|
+
copySessionId,
|
|
2856
3023
|
renderSlot,
|
|
2857
3024
|
onSessionRename,
|
|
2858
3025
|
onSessionArchive,
|
|
@@ -3026,6 +3193,91 @@ window.__ModuleLoader__.load({
|
|
|
3026
3193
|
});
|
|
3027
3194
|
}
|
|
3028
3195
|
//#endregion
|
|
3196
|
+
//#region lib/types/client/toast/RowActionToast.js
|
|
3197
|
+
/**
|
|
3198
|
+
* Dispatch the current notice without interpreting its business payload.
|
|
3199
|
+
* @param props - Current notice, dismissal, and the declared keyed renderer.
|
|
3200
|
+
* @returns The selected notice presentation, or nothing.
|
|
3201
|
+
*/
|
|
3202
|
+
function RowActionToast({ useToast, dismissToast, renderSlot }) {
|
|
3203
|
+
const notice = useToast((current) => current);
|
|
3204
|
+
if (notice === null) return null;
|
|
3205
|
+
return renderSlot("workspace.toast", {
|
|
3206
|
+
...notice,
|
|
3207
|
+
dismissToast
|
|
3208
|
+
}, { entryKey: notice.kind });
|
|
3209
|
+
}
|
|
3210
|
+
//#endregion
|
|
3211
|
+
//#region lib/types/client/toast/Toast.js
|
|
3212
|
+
/** One presentation Factory and the adapters for keyed Workspace notices. */
|
|
3213
|
+
/**
|
|
3214
|
+
* Render one notice using the shared Factory.
|
|
3215
|
+
* @param props - Keyed payload, injected display mapping, locale, and Factory renderer.
|
|
3216
|
+
* @returns The shared Toast occurrence.
|
|
3217
|
+
*/
|
|
3218
|
+
function ToastEntry(props) {
|
|
3219
|
+
const { dismissToast, display, t, renderFactorySlot, seq } = props;
|
|
3220
|
+
return renderFactorySlot("workspace.toast.content", {
|
|
3221
|
+
...display(props, t, dismissToast),
|
|
3222
|
+
seq,
|
|
3223
|
+
onDone: dismissToast
|
|
3224
|
+
});
|
|
3225
|
+
}
|
|
3226
|
+
/**
|
|
3227
|
+
* Present plain display data with the shared primitive's timing and portal.
|
|
3228
|
+
* @param props - Localized display fields, display identity, and dismissal.
|
|
3229
|
+
* @returns One Toast, remounted only for a new display identity.
|
|
3230
|
+
*/
|
|
3231
|
+
function WorkspaceToast({ seq, text, tone, onDone }) {
|
|
3232
|
+
return (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Toast, {
|
|
3233
|
+
text,
|
|
3234
|
+
onDone,
|
|
3235
|
+
...tone === "success" ? { tone: "success" } : {},
|
|
3236
|
+
...tone === "warning" ? { icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconWarningOutlineRegular, {}) } : {}
|
|
3237
|
+
}, seq);
|
|
3238
|
+
}
|
|
3239
|
+
//#endregion
|
|
3240
|
+
//#region lib/types/client/toast/register.js
|
|
3241
|
+
/**
|
|
3242
|
+
* Register notice presentations with their business-owned display fields.
|
|
3243
|
+
* @param ctx - Workspace plugin context.
|
|
3244
|
+
*/
|
|
3245
|
+
function registerToasts(ctx) {
|
|
3246
|
+
ctx.slots.registerFactory({
|
|
3247
|
+
name: "workspace.toast.content",
|
|
3248
|
+
scope: "root"
|
|
3249
|
+
}, WorkspaceToast);
|
|
3250
|
+
ctx.slots.inject("workspace.toast", function* () {
|
|
3251
|
+
yield ctx.slots.register({
|
|
3252
|
+
name: "workspace.toast",
|
|
3253
|
+
key: "sessionIdCopied",
|
|
3254
|
+
locale: "workspace",
|
|
3255
|
+
inject: () => ({ display: (_notice, t) => ({
|
|
3256
|
+
text: t("toast.sessionIdCopied"),
|
|
3257
|
+
tone: "success"
|
|
3258
|
+
}) })
|
|
3259
|
+
}, ToastEntry);
|
|
3260
|
+
yield ctx.slots.register({
|
|
3261
|
+
name: "workspace.toast",
|
|
3262
|
+
key: "createFailed",
|
|
3263
|
+
locale: "workspace",
|
|
3264
|
+
inject: () => ({ display: (notice) => ({
|
|
3265
|
+
text: notice.message,
|
|
3266
|
+
tone: "warning"
|
|
3267
|
+
}) })
|
|
3268
|
+
}, ToastEntry);
|
|
3269
|
+
yield ctx.slots.register({
|
|
3270
|
+
name: "workspace.toast",
|
|
3271
|
+
key: "sessionIdCopyFailed",
|
|
3272
|
+
locale: "workspace",
|
|
3273
|
+
inject: () => ({ display: (_notice, t) => ({
|
|
3274
|
+
text: t("toast.sessionIdCopyFailed"),
|
|
3275
|
+
tone: "warning"
|
|
3276
|
+
}) })
|
|
3277
|
+
}, ToastEntry);
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
3280
|
+
//#endregion
|
|
3029
3281
|
//#region lib/types/client/locales.js
|
|
3030
3282
|
/**
|
|
3031
3283
|
* `workspace` namespace dictionaries: the browsing region (section header,
|
|
@@ -3034,6 +3286,8 @@ window.__ModuleLoader__.load({
|
|
|
3034
3286
|
*/
|
|
3035
3287
|
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
3036
3288
|
const zh = {
|
|
3289
|
+
"draft.workspaceRequired": "请先选择工作区",
|
|
3290
|
+
"draft.initializationFailed": "暂时无法填入草稿,请稍后重试",
|
|
3037
3291
|
"group.ungrouped": "未分组",
|
|
3038
3292
|
"session.new": "新会话",
|
|
3039
3293
|
"session.untitled": "未命名",
|
|
@@ -3081,12 +3335,16 @@ window.__ModuleLoader__.load({
|
|
|
3081
3335
|
"menu.unpinSession": "取消置顶",
|
|
3082
3336
|
"menu.fork": "分叉会话",
|
|
3083
3337
|
"menu.archiveSession": "归档会话",
|
|
3338
|
+
"menu.sessionId": "复制 Session ID",
|
|
3339
|
+
"toast.sessionIdCopied": "Session ID 已复制",
|
|
3340
|
+
"toast.sessionIdCopyFailed": "复制失败,请重试",
|
|
3084
3341
|
"row.pinned": "已置顶",
|
|
3085
3342
|
"sessions.count.one": "{n} 个会话",
|
|
3086
3343
|
"sessions.count.other": "{n} 个会话",
|
|
3087
3344
|
"actions.workspace.aria": "工作区“{name}”的操作",
|
|
3088
3345
|
"actions.session.aria": "会话“{name}”的操作",
|
|
3089
3346
|
"actions.newSession.aria": "在“{name}”中新建会话",
|
|
3347
|
+
"actions.archive": "归档会话",
|
|
3090
3348
|
"actions.pin": "置顶会话",
|
|
3091
3349
|
"actions.unpin": "取消置顶",
|
|
3092
3350
|
"status.running": "进行中",
|
|
@@ -3110,6 +3368,8 @@ window.__ModuleLoader__.load({
|
|
|
3110
3368
|
};
|
|
3111
3369
|
/** English dictionary, checked complete against the zh key set. */
|
|
3112
3370
|
const en = {
|
|
3371
|
+
"draft.workspaceRequired": "Choose a workspace first.",
|
|
3372
|
+
"draft.initializationFailed": "Could not fill the draft. Please try again shortly.",
|
|
3113
3373
|
"group.ungrouped": "Ungrouped",
|
|
3114
3374
|
"session.new": "New Session",
|
|
3115
3375
|
"session.untitled": "Untitled",
|
|
@@ -3157,12 +3417,16 @@ window.__ModuleLoader__.load({
|
|
|
3157
3417
|
"menu.unpinSession": "Unpin session",
|
|
3158
3418
|
"menu.fork": "Fork session",
|
|
3159
3419
|
"menu.archiveSession": "Archive session",
|
|
3420
|
+
"menu.sessionId": "Copy Session ID",
|
|
3421
|
+
"toast.sessionIdCopied": "Session ID copied",
|
|
3422
|
+
"toast.sessionIdCopyFailed": "Copy failed. Try again.",
|
|
3160
3423
|
"row.pinned": "Pinned",
|
|
3161
3424
|
"sessions.count.one": "{n} session",
|
|
3162
3425
|
"sessions.count.other": "{n} sessions",
|
|
3163
3426
|
"actions.workspace.aria": "Workspace actions for {name}",
|
|
3164
3427
|
"actions.session.aria": "Session actions for {name}",
|
|
3165
3428
|
"actions.newSession.aria": "New session in {name}",
|
|
3429
|
+
"actions.archive": "Archive",
|
|
3166
3430
|
"actions.pin": "Pin",
|
|
3167
3431
|
"actions.unpin": "Unpin",
|
|
3168
3432
|
"status.running": "Running",
|
|
@@ -3221,7 +3485,15 @@ window.__ModuleLoader__.load({
|
|
|
3221
3485
|
...viewHandle,
|
|
3222
3486
|
create: () => viewInstance
|
|
3223
3487
|
};
|
|
3224
|
-
const
|
|
3488
|
+
const rowToast = (0, _qilin_agent_client_store.createSnapshotStore)(null);
|
|
3489
|
+
let toastSeq = 0;
|
|
3490
|
+
const notify = (toast) => {
|
|
3491
|
+
rowToast.set({
|
|
3492
|
+
...toast,
|
|
3493
|
+
seq: ++toastSeq
|
|
3494
|
+
});
|
|
3495
|
+
};
|
|
3496
|
+
const uiWorkspace = new UiWorkspaceService(ctx, ctx.remote.directoryPicker, workspaces, sessions, viewInstance.actions, notify);
|
|
3225
3497
|
ctx.slots.provideRoot({ hooks: { workspaces: workspaces.list } });
|
|
3226
3498
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
3227
3499
|
zh,
|
|
@@ -3280,6 +3552,9 @@ window.__ModuleLoader__.load({
|
|
|
3280
3552
|
closeAddWorkspace: shortcutControls.closeAdd,
|
|
3281
3553
|
closeRenameRequest: shortcutControls.closeRename,
|
|
3282
3554
|
setDirectoryBusy: shortcutControls.directoryBusy,
|
|
3555
|
+
copySessionId: async (sessionId) => {
|
|
3556
|
+
notify({ kind: await (0, _qilin_agent_client_ui_primitives.writeClipboard)(sessionId) ? "sessionIdCopied" : "sessionIdCopyFailed" });
|
|
3557
|
+
},
|
|
3283
3558
|
hooks: {
|
|
3284
3559
|
directoryFlow: browserFlowSource,
|
|
3285
3560
|
hostInfo,
|
|
@@ -3290,6 +3565,13 @@ window.__ModuleLoader__.load({
|
|
|
3290
3565
|
installWorkspaceShortcuts(ctx, uiWorkspace, shortcutControls, (sessionId) => {
|
|
3291
3566
|
uiWorkspace.archiveSession(sessionId);
|
|
3292
3567
|
});
|
|
3568
|
+
registerToasts(ctx);
|
|
3569
|
+
const rowToastInjected = () => ({
|
|
3570
|
+
hooks: { toast: rowToast },
|
|
3571
|
+
dismissToast: () => {
|
|
3572
|
+
rowToast.set(null);
|
|
3573
|
+
}
|
|
3574
|
+
});
|
|
3293
3575
|
const pickerInjected = () => ({
|
|
3294
3576
|
createWorkspace: (input) => workspaces.create(input),
|
|
3295
3577
|
hooks: { directoryFlow: pickerFlowSource }
|
|
@@ -3314,6 +3596,15 @@ window.__ModuleLoader__.load({
|
|
|
3314
3596
|
inject: browserInjected,
|
|
3315
3597
|
locale: NS
|
|
3316
3598
|
}, WorkspaceBrowser));
|
|
3599
|
+
ctx.slots.inject("shell.overlay", () => ctx.slots.register({
|
|
3600
|
+
name: "shell.overlay",
|
|
3601
|
+
id: "workspace.row-toast",
|
|
3602
|
+
inject: rowToastInjected,
|
|
3603
|
+
children: { "workspace.toast": {
|
|
3604
|
+
kind: "keyed",
|
|
3605
|
+
scope: "root"
|
|
3606
|
+
} }
|
|
3607
|
+
}, RowActionToast));
|
|
3317
3608
|
ctx.slots.inject("conversation.hero.workspace", () => ctx.slots.register({
|
|
3318
3609
|
name: "conversation.hero.workspace",
|
|
3319
3610
|
children: { "conversation.hero.workspace.directoryFlow": {
|