@researai/deepscientist 1.5.15 → 1.5.17
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.md +385 -104
- package/bin/ds.js +1241 -110
- package/docs/en/00_QUICK_START.md +100 -19
- package/docs/en/01_SETTINGS_REFERENCE.md +34 -1
- package/docs/en/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/en/05_TUI_GUIDE.md +6 -0
- package/docs/en/06_RUNTIME_AND_CANVAS.md +4 -3
- package/docs/en/09_DOCTOR.md +25 -8
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +37 -11
- package/docs/en/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/en/19_LOCAL_BROWSER_AUTH.md +70 -0
- package/docs/en/20_WORKSPACE_MODES_GUIDE.md +250 -0
- package/docs/en/21_LOCAL_MODEL_BACKENDS_GUIDE.md +283 -0
- package/docs/en/91_DEVELOPMENT.md +237 -0
- package/docs/en/README.md +24 -2
- package/docs/zh/00_QUICK_START.md +89 -19
- package/docs/zh/01_SETTINGS_REFERENCE.md +34 -1
- package/docs/zh/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/zh/05_TUI_GUIDE.md +6 -0
- package/docs/zh/09_DOCTOR.md +26 -9
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +37 -11
- package/docs/zh/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/zh/19_LOCAL_BROWSER_AUTH.md +68 -0
- package/docs/zh/20_WORKSPACE_MODES_GUIDE.md +251 -0
- package/docs/zh/21_LOCAL_MODEL_BACKENDS_GUIDE.md +281 -0
- package/docs/zh/README.md +24 -2
- package/install.sh +46 -4
- package/package.json +2 -1
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/acp/envelope.py +6 -0
- package/src/deepscientist/artifact/service.py +647 -22
- package/src/deepscientist/bash_exec/service.py +234 -9
- package/src/deepscientist/bridges/connectors.py +8 -2
- package/src/deepscientist/cli.py +115 -19
- package/src/deepscientist/codex_cli_compat.py +367 -22
- package/src/deepscientist/config/models.py +2 -1
- package/src/deepscientist/config/service.py +183 -13
- package/src/deepscientist/daemon/api/handlers.py +255 -31
- package/src/deepscientist/daemon/api/router.py +9 -0
- package/src/deepscientist/daemon/app.py +1146 -105
- package/src/deepscientist/diagnostics/__init__.py +6 -0
- package/src/deepscientist/diagnostics/runner_failures.py +130 -0
- package/src/deepscientist/doctor.py +207 -3
- package/src/deepscientist/gitops/__init__.py +10 -1
- package/src/deepscientist/gitops/diff.py +129 -0
- package/src/deepscientist/gitops/service.py +4 -1
- package/src/deepscientist/mcp/server.py +39 -0
- package/src/deepscientist/prompts/builder.py +275 -34
- package/src/deepscientist/quest/layout.py +15 -2
- package/src/deepscientist/quest/service.py +707 -55
- package/src/deepscientist/quest/stage_views.py +6 -1
- package/src/deepscientist/runners/codex.py +143 -43
- package/src/deepscientist/shared.py +19 -0
- package/src/deepscientist/skills/__init__.py +2 -2
- package/src/deepscientist/skills/installer.py +196 -5
- package/src/deepscientist/skills/registry.py +66 -0
- package/src/prompts/connectors/qq.md +18 -8
- package/src/prompts/connectors/weixin.md +16 -6
- package/src/prompts/contracts/shared_interaction.md +14 -2
- package/src/prompts/system.md +23 -5
- package/src/prompts/system_copilot.md +56 -0
- package/src/skills/analysis-campaign/SKILL.md +1 -0
- package/src/skills/baseline/SKILL.md +8 -0
- package/src/skills/decision/SKILL.md +8 -0
- package/src/skills/experiment/SKILL.md +8 -0
- package/src/skills/figure-polish/SKILL.md +1 -0
- package/src/skills/finalize/SKILL.md +1 -0
- package/src/skills/idea/SKILL.md +1 -0
- package/src/skills/intake-audit/SKILL.md +8 -0
- package/src/skills/mentor/SKILL.md +217 -0
- package/src/skills/mentor/references/correction-rules.md +210 -0
- package/src/skills/mentor/references/knowledge-profile.md +91 -0
- package/src/skills/mentor/references/persona-profile.md +138 -0
- package/src/skills/mentor/references/taste-profile.md +128 -0
- package/src/skills/mentor/references/thought-style-profile.md +138 -0
- package/src/skills/mentor/references/work-profile.md +289 -0
- package/src/skills/mentor/references/workflow-profile.md +240 -0
- package/src/skills/optimize/SKILL.md +1 -0
- package/src/skills/rebuttal/SKILL.md +1 -0
- package/src/skills/review/SKILL.md +1 -0
- package/src/skills/scout/SKILL.md +8 -0
- package/src/skills/write/SKILL.md +1 -0
- package/src/tui/dist/app/AppContainer.js +19 -11
- package/src/tui/dist/index.js +4 -1
- package/src/tui/dist/lib/api.js +33 -3
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/AiManusChatView-Bv-Z8YpU.js +204 -0
- package/src/ui/dist/assets/AnalysisPlugin-BCKAfjba.js +1 -0
- package/src/ui/dist/assets/CliPlugin-BCKcpc35.js +109 -0
- package/src/ui/dist/assets/CodeEditorPlugin-DbOfSJ8K.js +2 -0
- package/src/ui/dist/assets/CodeViewerPlugin-CbaFRrUU.js +270 -0
- package/src/ui/dist/assets/DocViewerPlugin-DAjLVeQD.js +7 -0
- package/src/ui/dist/assets/GitCommitViewerPlugin-CIUqbUDO.js +1 -0
- package/src/ui/dist/assets/GitDiffViewerPlugin-CQACjoAA.js +6 -0
- package/src/ui/dist/assets/GitSnapshotViewer-0r4nLPke.js +30 -0
- package/src/ui/dist/assets/ImageViewerPlugin-nBOmI2v_.js +26 -0
- package/src/ui/dist/assets/LabCopilotPanel-BHxOxF4z.js +14 -0
- package/src/ui/dist/assets/LabPlugin-BKoZGs95.js +22 -0
- package/src/ui/dist/assets/LatexPlugin-ZwtV8pIp.js +25 -0
- package/src/ui/dist/assets/MarkdownViewerPlugin-DKqVfKyW.js +128 -0
- package/src/ui/dist/assets/MarketplacePlugin-BwxStZ9D.js +13 -0
- package/src/ui/dist/assets/NotebookEditor-BEQhaQbt.js +81 -0
- package/src/ui/dist/assets/{NotebookEditor-CccQYZjX.css → NotebookEditor-BHH8rdGj.css} +1 -1
- package/src/ui/dist/assets/NotebookEditor-BOr3x3Ej.css +1 -0
- package/src/ui/dist/assets/NotebookEditor-DB9N_T9q.js +361 -0
- package/src/ui/dist/assets/PdfLoader-Cy5jtWrr.css +1 -0
- package/src/ui/dist/assets/PdfLoader-eWBONbQP.js +16 -0
- package/src/ui/dist/assets/PdfMarkdownPlugin-D22YOZL3.js +1 -0
- package/src/ui/dist/assets/PdfViewerPlugin-c-RK9DLM.js +17 -0
- package/src/ui/dist/assets/PdfViewerPlugin-nwwE-fjJ.css +1 -0
- package/src/ui/dist/assets/SearchPlugin-CxF9ytAx.js +16 -0
- package/src/ui/dist/assets/SearchPlugin-DA4en4hK.css +1 -0
- package/src/ui/dist/assets/TextViewerPlugin-C5xqeeUH.js +54 -0
- package/src/ui/dist/assets/VNCViewer-BoLGLnHz.js +11 -0
- package/src/ui/dist/assets/bot-DREQOxzP.js +6 -0
- package/src/ui/dist/assets/browser-CTB2jwNe.js +8 -0
- package/src/ui/dist/assets/chevron-up-C9Qpx4DE.js +6 -0
- package/src/ui/dist/assets/code-WlFHE7z_.js +6 -0
- package/src/ui/dist/assets/file-content-BZMz3RYp.js +1 -0
- package/src/ui/dist/assets/file-diff-panel-CQhw0jS2.js +1 -0
- package/src/ui/dist/assets/file-jump-queue-DA-SdG__.js +1 -0
- package/src/ui/dist/assets/file-socket-CfQPKQKj.js +1 -0
- package/src/ui/dist/assets/git-commit-horizontal-DxZ8DCZh.js +6 -0
- package/src/ui/dist/assets/image-Bgl4VIyx.js +6 -0
- package/src/ui/dist/assets/index-BpV6lusQ.css +33 -0
- package/src/ui/dist/assets/index-CBNVuWcP.js +2496 -0
- package/src/ui/dist/assets/index-CwNu1aH4.js +11 -0
- package/src/ui/dist/assets/index-DrUnlf6K.js +1 -0
- package/src/ui/dist/assets/index-NW-h8VzN.js +1 -0
- package/src/ui/dist/assets/monaco-CiHMMNH_.js +1 -0
- package/src/ui/dist/assets/pdf-effect-queue-J8OnM0jE.js +6 -0
- package/src/ui/dist/assets/plugin-monaco-C8UgLomw.js +19 -0
- package/src/ui/dist/assets/plugin-notebook-HbW2K-1c.js +169 -0
- package/src/ui/dist/assets/plugin-pdf-CR8hgQBV.js +357 -0
- package/src/ui/dist/assets/plugin-terminal-MXFIPun8.js +227 -0
- package/src/ui/dist/assets/popover-CLc0pPP8.js +1 -0
- package/src/ui/dist/assets/project-sync-C9IdzdZW.js +1 -0
- package/src/ui/dist/assets/select-Cs2PmzwL.js +11 -0
- package/src/ui/dist/assets/sigma-ClKcHAXm.js +6 -0
- package/src/ui/dist/assets/trash-DwpbFr3w.js +11 -0
- package/src/ui/dist/assets/useCliAccess-NQ8m0Let.js +1 -0
- package/src/ui/dist/assets/useFileDiffOverlay-FuhcnKiw.js +1 -0
- package/src/ui/dist/assets/wrap-text-BC-Hltpd.js +11 -0
- package/src/ui/dist/assets/zoom-out-E_gaeAxL.js +11 -0
- package/src/ui/dist/index.html +5 -2
- package/src/ui/dist/assets/AiManusChatView-DDjbFnbt.js +0 -26597
- package/src/ui/dist/assets/AnalysisPlugin-Yb5IdmaU.js +0 -123
- package/src/ui/dist/assets/CliPlugin-e64sreyu.js +0 -31037
- package/src/ui/dist/assets/CodeEditorPlugin-C4D2TIkU.js +0 -427
- package/src/ui/dist/assets/CodeViewerPlugin-BVoNZIvC.js +0 -905
- package/src/ui/dist/assets/DocViewerPlugin-CLChbllo.js +0 -278
- package/src/ui/dist/assets/GitDiffViewerPlugin-C4xeFyFQ.js +0 -2661
- package/src/ui/dist/assets/ImageViewerPlugin-OiMUAcLi.js +0 -500
- package/src/ui/dist/assets/LabCopilotPanel-BjD2ThQF.js +0 -4104
- package/src/ui/dist/assets/LabPlugin-DQPg-NrB.js +0 -2677
- package/src/ui/dist/assets/LatexPlugin-CI05XAV9.js +0 -1792
- package/src/ui/dist/assets/MarkdownViewerPlugin-DpeBLYZf.js +0 -308
- package/src/ui/dist/assets/MarketplacePlugin-DolE58Q2.js +0 -413
- package/src/ui/dist/assets/NotebookEditor-7Qm2rSWD.js +0 -4214
- package/src/ui/dist/assets/NotebookEditor-C1kWaxKi.js +0 -84873
- package/src/ui/dist/assets/NotebookEditor-C3VQ7ylN.css +0 -1405
- package/src/ui/dist/assets/PdfLoader-BfOHw8Zw.js +0 -25468
- package/src/ui/dist/assets/PdfLoader-C-Y707R3.css +0 -49
- package/src/ui/dist/assets/PdfMarkdownPlugin-BulDREv1.js +0 -409
- package/src/ui/dist/assets/PdfViewerPlugin-C-daaOaL.js +0 -3095
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +0 -3627
- package/src/ui/dist/assets/SearchPlugin-CjpaiJ3A.js +0 -741
- package/src/ui/dist/assets/SearchPlugin-DDMrGDkh.css +0 -379
- package/src/ui/dist/assets/TextViewerPlugin-BxIyqPQC.js +0 -472
- package/src/ui/dist/assets/VNCViewer-HAg9mF7M.js +0 -18821
- package/src/ui/dist/assets/awareness-C0NPR2Dj.js +0 -292
- package/src/ui/dist/assets/bot-0DYntytV.js +0 -21
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +0 -2895
- package/src/ui/dist/assets/code-B20Slj_w.js +0 -17
- package/src/ui/dist/assets/file-content-DT24KFma.js +0 -377
- package/src/ui/dist/assets/file-diff-panel-DK13YPql.js +0 -92
- package/src/ui/dist/assets/file-jump-queue-r5XKgJEV.js +0 -16
- package/src/ui/dist/assets/file-socket-B4T2o4nR.js +0 -58
- package/src/ui/dist/assets/function-B5QZkkHC.js +0 -1895
- package/src/ui/dist/assets/image-DSeR_sDS.js +0 -18
- package/src/ui/dist/assets/index-BrFje2Uk.js +0 -120
- package/src/ui/dist/assets/index-BwRJaoTl.js +0 -25
- package/src/ui/dist/assets/index-D_E4281X.js +0 -221322
- package/src/ui/dist/assets/index-DnYB3xb1.js +0 -159
- package/src/ui/dist/assets/index-G7AcWcMu.css +0 -12594
- package/src/ui/dist/assets/monaco-LExaAN3Y.js +0 -623
- package/src/ui/dist/assets/pdf-effect-queue-BJk5okWJ.js +0 -47
- package/src/ui/dist/assets/pdf_viewer-e0g1is2C.js +0 -8206
- package/src/ui/dist/assets/popover-D3Gg_FoV.js +0 -476
- package/src/ui/dist/assets/project-sync-C_ygLlVU.js +0 -297
- package/src/ui/dist/assets/select-CpAK6uWm.js +0 -1690
- package/src/ui/dist/assets/sigma-DEccaSgk.js +0 -22
- package/src/ui/dist/assets/square-check-big-uUfyVsbD.js +0 -17
- package/src/ui/dist/assets/trash-CXvwwSe8.js +0 -32
- package/src/ui/dist/assets/useCliAccess-Bnop4mgR.js +0 -957
- package/src/ui/dist/assets/useFileDiffOverlay-B8eUAX0I.js +0 -53
- package/src/ui/dist/assets/wrap-text-9vbOBpkW.js +0 -35
- package/src/ui/dist/assets/yjs-DncrqiZ8.js +0 -11243
- package/src/ui/dist/assets/zoom-out-BgVMmOW4.js +0 -34
|
@@ -1,4104 +0,0 @@
|
|
|
1
|
-
import { w as createLucideIcon, r as reactExports, aR as reactDomExports, aS as useReducedMotion, j as jsxRuntimeExports, aT as useQueryClient, o as useToast, u as useI18n, aA as useLabCopilotStore, aU as useLabGraphSelectionStore, aV as useChatSessionStore, aW as buildAvatarColorMap, aX as isLabWorkingStatus, aY as getLabAgentDirectSession, aZ as buildAgentDescriptor, a_ as resolveAgentLogo, aF as resolveAgentDisplayName, aG as resolveAgentMentionLabel, a$ as AnimatePresence, b0 as motion, aO as Button, b1 as pickAvatarFrameColor, aH as resolveQuestLabel, d as Check, b as cn, z as useAuthStore, b2 as useCopilotDockHeaderPortal, b3 as assignLabAgent, b4 as ScrollArea, L as LoaderCircle, b5 as likeLabMoment, b6 as unlikeLabMoment, b7 as commentLabMoment, b8 as Ellipsis, b9 as Plus, l as Search, ba as Dialog, bb as DialogContent, bc as DialogHeader, bd as DialogTitle, be as DialogFooter, bf as getLabGroupSession, bg as getLabFriendsSession } from './index-D_E4281X.js';
|
|
2
|
-
import { P as Popover, a as PopoverTrigger, b as PopoverContent } from './popover-D3Gg_FoV.js';
|
|
3
|
-
import { S as Select, a as SelectTrigger, b as SelectValue, c as SelectContent, d as SelectItem } from './select-CpAK6uWm.js';
|
|
4
|
-
import { A as AiManusChatView, D as DEFAULT_AGENT_ID, C as ChatBox, a as applyChatEvent, u as useSSESession, b as ChatMessage, r as renderMarkdown } from './AiManusChatView-DDjbFnbt.js';
|
|
5
|
-
import './index-BwRJaoTl.js';
|
|
6
|
-
import './file-content-DT24KFma.js';
|
|
7
|
-
import './file-jump-queue-r5XKgJEV.js';
|
|
8
|
-
import './pdf-effect-queue-BJk5okWJ.js';
|
|
9
|
-
import './file-diff-panel-DK13YPql.js';
|
|
10
|
-
import './bot-0DYntytV.js';
|
|
11
|
-
import './NotebookEditor-C1kWaxKi.js';
|
|
12
|
-
import './trash-CXvwwSe8.js';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @license lucide-react v0.511.0 - ISC
|
|
16
|
-
*
|
|
17
|
-
* This source code is licensed under the ISC license.
|
|
18
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const __iconNode$1 = [
|
|
23
|
-
["path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22Z", key: "vv11sd" }]
|
|
24
|
-
];
|
|
25
|
-
const MessageCircle = createLucideIcon("message-circle", __iconNode$1);
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @license lucide-react v0.511.0 - ISC
|
|
29
|
-
*
|
|
30
|
-
* This source code is licensed under the ISC license.
|
|
31
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const __iconNode = [
|
|
36
|
-
["path", { d: "M7 10v12", key: "1qc93n" }],
|
|
37
|
-
[
|
|
38
|
-
"path",
|
|
39
|
-
{
|
|
40
|
-
d: "M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z",
|
|
41
|
-
key: "emmmcr"
|
|
42
|
-
}
|
|
43
|
-
]
|
|
44
|
-
];
|
|
45
|
-
const ThumbsUp = createLucideIcon("thumbs-up", __iconNode);
|
|
46
|
-
|
|
47
|
-
function memo(getDeps, fn, opts) {
|
|
48
|
-
let deps = opts.initialDeps ?? [];
|
|
49
|
-
let result;
|
|
50
|
-
let isInitial = true;
|
|
51
|
-
function memoizedFunction() {
|
|
52
|
-
var _a, _b, _c;
|
|
53
|
-
let depTime;
|
|
54
|
-
if (opts.key && ((_a = opts.debug) == null ? void 0 : _a.call(opts))) depTime = Date.now();
|
|
55
|
-
const newDeps = getDeps();
|
|
56
|
-
const depsChanged = newDeps.length !== deps.length || newDeps.some((dep, index) => deps[index] !== dep);
|
|
57
|
-
if (!depsChanged) {
|
|
58
|
-
return result;
|
|
59
|
-
}
|
|
60
|
-
deps = newDeps;
|
|
61
|
-
let resultTime;
|
|
62
|
-
if (opts.key && ((_b = opts.debug) == null ? void 0 : _b.call(opts))) resultTime = Date.now();
|
|
63
|
-
result = fn(...newDeps);
|
|
64
|
-
if (opts.key && ((_c = opts.debug) == null ? void 0 : _c.call(opts))) {
|
|
65
|
-
const depEndTime = Math.round((Date.now() - depTime) * 100) / 100;
|
|
66
|
-
const resultEndTime = Math.round((Date.now() - resultTime) * 100) / 100;
|
|
67
|
-
const resultFpsPercentage = resultEndTime / 16;
|
|
68
|
-
const pad = (str, num) => {
|
|
69
|
-
str = String(str);
|
|
70
|
-
while (str.length < num) {
|
|
71
|
-
str = " " + str;
|
|
72
|
-
}
|
|
73
|
-
return str;
|
|
74
|
-
};
|
|
75
|
-
console.info(
|
|
76
|
-
`%c⏱ ${pad(resultEndTime, 5)} /${pad(depEndTime, 5)} ms`,
|
|
77
|
-
`
|
|
78
|
-
font-size: .6rem;
|
|
79
|
-
font-weight: bold;
|
|
80
|
-
color: hsl(${Math.max(
|
|
81
|
-
0,
|
|
82
|
-
Math.min(120 - 120 * resultFpsPercentage, 120)
|
|
83
|
-
)}deg 100% 31%);`,
|
|
84
|
-
opts == null ? void 0 : opts.key
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
if ((opts == null ? void 0 : opts.onChange) && !(isInitial && opts.skipInitialOnChange)) {
|
|
88
|
-
opts.onChange(result);
|
|
89
|
-
}
|
|
90
|
-
isInitial = false;
|
|
91
|
-
return result;
|
|
92
|
-
}
|
|
93
|
-
memoizedFunction.updateDeps = (newDeps) => {
|
|
94
|
-
deps = newDeps;
|
|
95
|
-
};
|
|
96
|
-
return memoizedFunction;
|
|
97
|
-
}
|
|
98
|
-
function notUndefined(value, msg) {
|
|
99
|
-
if (value === void 0) {
|
|
100
|
-
throw new Error(`Unexpected undefined${""}`);
|
|
101
|
-
} else {
|
|
102
|
-
return value;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
const approxEqual = (a, b) => Math.abs(a - b) < 1.01;
|
|
106
|
-
const debounce = (targetWindow, fn, ms) => {
|
|
107
|
-
let timeoutId;
|
|
108
|
-
return function(...args) {
|
|
109
|
-
targetWindow.clearTimeout(timeoutId);
|
|
110
|
-
timeoutId = targetWindow.setTimeout(() => fn.apply(this, args), ms);
|
|
111
|
-
};
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const getRect = (element) => {
|
|
115
|
-
const { offsetWidth, offsetHeight } = element;
|
|
116
|
-
return { width: offsetWidth, height: offsetHeight };
|
|
117
|
-
};
|
|
118
|
-
const defaultKeyExtractor = (index) => index;
|
|
119
|
-
const defaultRangeExtractor = (range) => {
|
|
120
|
-
const start = Math.max(range.startIndex - range.overscan, 0);
|
|
121
|
-
const end = Math.min(range.endIndex + range.overscan, range.count - 1);
|
|
122
|
-
const arr = [];
|
|
123
|
-
for (let i = start; i <= end; i++) {
|
|
124
|
-
arr.push(i);
|
|
125
|
-
}
|
|
126
|
-
return arr;
|
|
127
|
-
};
|
|
128
|
-
const observeElementRect = (instance, cb) => {
|
|
129
|
-
const element = instance.scrollElement;
|
|
130
|
-
if (!element) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const targetWindow = instance.targetWindow;
|
|
134
|
-
if (!targetWindow) {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
const handler = (rect) => {
|
|
138
|
-
const { width, height } = rect;
|
|
139
|
-
cb({ width: Math.round(width), height: Math.round(height) });
|
|
140
|
-
};
|
|
141
|
-
handler(getRect(element));
|
|
142
|
-
if (!targetWindow.ResizeObserver) {
|
|
143
|
-
return () => {
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
const observer = new targetWindow.ResizeObserver((entries) => {
|
|
147
|
-
const run = () => {
|
|
148
|
-
const entry = entries[0];
|
|
149
|
-
if (entry == null ? void 0 : entry.borderBoxSize) {
|
|
150
|
-
const box = entry.borderBoxSize[0];
|
|
151
|
-
if (box) {
|
|
152
|
-
handler({ width: box.inlineSize, height: box.blockSize });
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
handler(getRect(element));
|
|
157
|
-
};
|
|
158
|
-
instance.options.useAnimationFrameWithResizeObserver ? requestAnimationFrame(run) : run();
|
|
159
|
-
});
|
|
160
|
-
observer.observe(element, { box: "border-box" });
|
|
161
|
-
return () => {
|
|
162
|
-
observer.unobserve(element);
|
|
163
|
-
};
|
|
164
|
-
};
|
|
165
|
-
const addEventListenerOptions = {
|
|
166
|
-
passive: true
|
|
167
|
-
};
|
|
168
|
-
const supportsScrollend = typeof window == "undefined" ? true : "onscrollend" in window;
|
|
169
|
-
const observeElementOffset = (instance, cb) => {
|
|
170
|
-
const element = instance.scrollElement;
|
|
171
|
-
if (!element) {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
const targetWindow = instance.targetWindow;
|
|
175
|
-
if (!targetWindow) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
let offset = 0;
|
|
179
|
-
const fallback = instance.options.useScrollendEvent && supportsScrollend ? () => void 0 : debounce(
|
|
180
|
-
targetWindow,
|
|
181
|
-
() => {
|
|
182
|
-
cb(offset, false);
|
|
183
|
-
},
|
|
184
|
-
instance.options.isScrollingResetDelay
|
|
185
|
-
);
|
|
186
|
-
const createHandler = (isScrolling) => () => {
|
|
187
|
-
const { horizontal, isRtl } = instance.options;
|
|
188
|
-
offset = horizontal ? element["scrollLeft"] * (isRtl && -1 || 1) : element["scrollTop"];
|
|
189
|
-
fallback();
|
|
190
|
-
cb(offset, isScrolling);
|
|
191
|
-
};
|
|
192
|
-
const handler = createHandler(true);
|
|
193
|
-
const endHandler = createHandler(false);
|
|
194
|
-
element.addEventListener("scroll", handler, addEventListenerOptions);
|
|
195
|
-
const registerScrollendEvent = instance.options.useScrollendEvent && supportsScrollend;
|
|
196
|
-
if (registerScrollendEvent) {
|
|
197
|
-
element.addEventListener("scrollend", endHandler, addEventListenerOptions);
|
|
198
|
-
}
|
|
199
|
-
return () => {
|
|
200
|
-
element.removeEventListener("scroll", handler);
|
|
201
|
-
if (registerScrollendEvent) {
|
|
202
|
-
element.removeEventListener("scrollend", endHandler);
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
};
|
|
206
|
-
const measureElement = (element, entry, instance) => {
|
|
207
|
-
if (entry == null ? void 0 : entry.borderBoxSize) {
|
|
208
|
-
const box = entry.borderBoxSize[0];
|
|
209
|
-
if (box) {
|
|
210
|
-
const size = Math.round(
|
|
211
|
-
box[instance.options.horizontal ? "inlineSize" : "blockSize"]
|
|
212
|
-
);
|
|
213
|
-
return size;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return element[instance.options.horizontal ? "offsetWidth" : "offsetHeight"];
|
|
217
|
-
};
|
|
218
|
-
const elementScroll = (offset, {
|
|
219
|
-
adjustments = 0,
|
|
220
|
-
behavior
|
|
221
|
-
}, instance) => {
|
|
222
|
-
var _a, _b;
|
|
223
|
-
const toOffset = offset + adjustments;
|
|
224
|
-
(_b = (_a = instance.scrollElement) == null ? void 0 : _a.scrollTo) == null ? void 0 : _b.call(_a, {
|
|
225
|
-
[instance.options.horizontal ? "left" : "top"]: toOffset,
|
|
226
|
-
behavior
|
|
227
|
-
});
|
|
228
|
-
};
|
|
229
|
-
class Virtualizer {
|
|
230
|
-
constructor(opts) {
|
|
231
|
-
this.unsubs = [];
|
|
232
|
-
this.scrollElement = null;
|
|
233
|
-
this.targetWindow = null;
|
|
234
|
-
this.isScrolling = false;
|
|
235
|
-
this.scrollState = null;
|
|
236
|
-
this.measurementsCache = [];
|
|
237
|
-
this.itemSizeCache = /* @__PURE__ */ new Map();
|
|
238
|
-
this.laneAssignments = /* @__PURE__ */ new Map();
|
|
239
|
-
this.pendingMeasuredCacheIndexes = [];
|
|
240
|
-
this.prevLanes = void 0;
|
|
241
|
-
this.lanesChangedFlag = false;
|
|
242
|
-
this.lanesSettling = false;
|
|
243
|
-
this.scrollRect = null;
|
|
244
|
-
this.scrollOffset = null;
|
|
245
|
-
this.scrollDirection = null;
|
|
246
|
-
this.scrollAdjustments = 0;
|
|
247
|
-
this.elementsCache = /* @__PURE__ */ new Map();
|
|
248
|
-
this.now = () => {
|
|
249
|
-
var _a, _b, _c;
|
|
250
|
-
return ((_c = (_b = (_a = this.targetWindow) == null ? void 0 : _a.performance) == null ? void 0 : _b.now) == null ? void 0 : _c.call(_b)) ?? Date.now();
|
|
251
|
-
};
|
|
252
|
-
this.observer = /* @__PURE__ */ (() => {
|
|
253
|
-
let _ro = null;
|
|
254
|
-
const get = () => {
|
|
255
|
-
if (_ro) {
|
|
256
|
-
return _ro;
|
|
257
|
-
}
|
|
258
|
-
if (!this.targetWindow || !this.targetWindow.ResizeObserver) {
|
|
259
|
-
return null;
|
|
260
|
-
}
|
|
261
|
-
return _ro = new this.targetWindow.ResizeObserver((entries) => {
|
|
262
|
-
entries.forEach((entry) => {
|
|
263
|
-
const run = () => {
|
|
264
|
-
this._measureElement(entry.target, entry);
|
|
265
|
-
};
|
|
266
|
-
this.options.useAnimationFrameWithResizeObserver ? requestAnimationFrame(run) : run();
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
};
|
|
270
|
-
return {
|
|
271
|
-
disconnect: () => {
|
|
272
|
-
var _a;
|
|
273
|
-
(_a = get()) == null ? void 0 : _a.disconnect();
|
|
274
|
-
_ro = null;
|
|
275
|
-
},
|
|
276
|
-
observe: (target) => {
|
|
277
|
-
var _a;
|
|
278
|
-
return (_a = get()) == null ? void 0 : _a.observe(target, { box: "border-box" });
|
|
279
|
-
},
|
|
280
|
-
unobserve: (target) => {
|
|
281
|
-
var _a;
|
|
282
|
-
return (_a = get()) == null ? void 0 : _a.unobserve(target);
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
})();
|
|
286
|
-
this.range = null;
|
|
287
|
-
this.setOptions = (opts2) => {
|
|
288
|
-
Object.entries(opts2).forEach(([key, value]) => {
|
|
289
|
-
if (typeof value === "undefined") delete opts2[key];
|
|
290
|
-
});
|
|
291
|
-
this.options = {
|
|
292
|
-
debug: false,
|
|
293
|
-
initialOffset: 0,
|
|
294
|
-
overscan: 1,
|
|
295
|
-
paddingStart: 0,
|
|
296
|
-
paddingEnd: 0,
|
|
297
|
-
scrollPaddingStart: 0,
|
|
298
|
-
scrollPaddingEnd: 0,
|
|
299
|
-
horizontal: false,
|
|
300
|
-
getItemKey: defaultKeyExtractor,
|
|
301
|
-
rangeExtractor: defaultRangeExtractor,
|
|
302
|
-
onChange: () => {
|
|
303
|
-
},
|
|
304
|
-
measureElement,
|
|
305
|
-
initialRect: { width: 0, height: 0 },
|
|
306
|
-
scrollMargin: 0,
|
|
307
|
-
gap: 0,
|
|
308
|
-
indexAttribute: "data-index",
|
|
309
|
-
initialMeasurementsCache: [],
|
|
310
|
-
lanes: 1,
|
|
311
|
-
isScrollingResetDelay: 150,
|
|
312
|
-
enabled: true,
|
|
313
|
-
isRtl: false,
|
|
314
|
-
useScrollendEvent: false,
|
|
315
|
-
useAnimationFrameWithResizeObserver: false,
|
|
316
|
-
...opts2
|
|
317
|
-
};
|
|
318
|
-
};
|
|
319
|
-
this.notify = (sync) => {
|
|
320
|
-
var _a, _b;
|
|
321
|
-
(_b = (_a = this.options).onChange) == null ? void 0 : _b.call(_a, this, sync);
|
|
322
|
-
};
|
|
323
|
-
this.maybeNotify = memo(
|
|
324
|
-
() => {
|
|
325
|
-
this.calculateRange();
|
|
326
|
-
return [
|
|
327
|
-
this.isScrolling,
|
|
328
|
-
this.range ? this.range.startIndex : null,
|
|
329
|
-
this.range ? this.range.endIndex : null
|
|
330
|
-
];
|
|
331
|
-
},
|
|
332
|
-
(isScrolling) => {
|
|
333
|
-
this.notify(isScrolling);
|
|
334
|
-
},
|
|
335
|
-
{
|
|
336
|
-
key: false,
|
|
337
|
-
debug: () => this.options.debug,
|
|
338
|
-
initialDeps: [
|
|
339
|
-
this.isScrolling,
|
|
340
|
-
this.range ? this.range.startIndex : null,
|
|
341
|
-
this.range ? this.range.endIndex : null
|
|
342
|
-
]
|
|
343
|
-
}
|
|
344
|
-
);
|
|
345
|
-
this.cleanup = () => {
|
|
346
|
-
this.unsubs.filter(Boolean).forEach((d) => d());
|
|
347
|
-
this.unsubs = [];
|
|
348
|
-
this.observer.disconnect();
|
|
349
|
-
if (this.rafId != null && this.targetWindow) {
|
|
350
|
-
this.targetWindow.cancelAnimationFrame(this.rafId);
|
|
351
|
-
this.rafId = null;
|
|
352
|
-
}
|
|
353
|
-
this.scrollState = null;
|
|
354
|
-
this.scrollElement = null;
|
|
355
|
-
this.targetWindow = null;
|
|
356
|
-
};
|
|
357
|
-
this._didMount = () => {
|
|
358
|
-
return () => {
|
|
359
|
-
this.cleanup();
|
|
360
|
-
};
|
|
361
|
-
};
|
|
362
|
-
this._willUpdate = () => {
|
|
363
|
-
var _a;
|
|
364
|
-
const scrollElement = this.options.enabled ? this.options.getScrollElement() : null;
|
|
365
|
-
if (this.scrollElement !== scrollElement) {
|
|
366
|
-
this.cleanup();
|
|
367
|
-
if (!scrollElement) {
|
|
368
|
-
this.maybeNotify();
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
this.scrollElement = scrollElement;
|
|
372
|
-
if (this.scrollElement && "ownerDocument" in this.scrollElement) {
|
|
373
|
-
this.targetWindow = this.scrollElement.ownerDocument.defaultView;
|
|
374
|
-
} else {
|
|
375
|
-
this.targetWindow = ((_a = this.scrollElement) == null ? void 0 : _a.window) ?? null;
|
|
376
|
-
}
|
|
377
|
-
this.elementsCache.forEach((cached) => {
|
|
378
|
-
this.observer.observe(cached);
|
|
379
|
-
});
|
|
380
|
-
this.unsubs.push(
|
|
381
|
-
this.options.observeElementRect(this, (rect) => {
|
|
382
|
-
this.scrollRect = rect;
|
|
383
|
-
this.maybeNotify();
|
|
384
|
-
})
|
|
385
|
-
);
|
|
386
|
-
this.unsubs.push(
|
|
387
|
-
this.options.observeElementOffset(this, (offset, isScrolling) => {
|
|
388
|
-
this.scrollAdjustments = 0;
|
|
389
|
-
this.scrollDirection = isScrolling ? this.getScrollOffset() < offset ? "forward" : "backward" : null;
|
|
390
|
-
this.scrollOffset = offset;
|
|
391
|
-
this.isScrolling = isScrolling;
|
|
392
|
-
if (this.scrollState) {
|
|
393
|
-
this.scheduleScrollReconcile();
|
|
394
|
-
}
|
|
395
|
-
this.maybeNotify();
|
|
396
|
-
})
|
|
397
|
-
);
|
|
398
|
-
this._scrollToOffset(this.getScrollOffset(), {
|
|
399
|
-
adjustments: void 0,
|
|
400
|
-
behavior: void 0
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
};
|
|
404
|
-
this.rafId = null;
|
|
405
|
-
this.getSize = () => {
|
|
406
|
-
if (!this.options.enabled) {
|
|
407
|
-
this.scrollRect = null;
|
|
408
|
-
return 0;
|
|
409
|
-
}
|
|
410
|
-
this.scrollRect = this.scrollRect ?? this.options.initialRect;
|
|
411
|
-
return this.scrollRect[this.options.horizontal ? "width" : "height"];
|
|
412
|
-
};
|
|
413
|
-
this.getScrollOffset = () => {
|
|
414
|
-
if (!this.options.enabled) {
|
|
415
|
-
this.scrollOffset = null;
|
|
416
|
-
return 0;
|
|
417
|
-
}
|
|
418
|
-
this.scrollOffset = this.scrollOffset ?? (typeof this.options.initialOffset === "function" ? this.options.initialOffset() : this.options.initialOffset);
|
|
419
|
-
return this.scrollOffset;
|
|
420
|
-
};
|
|
421
|
-
this.getFurthestMeasurement = (measurements, index) => {
|
|
422
|
-
const furthestMeasurementsFound = /* @__PURE__ */ new Map();
|
|
423
|
-
const furthestMeasurements = /* @__PURE__ */ new Map();
|
|
424
|
-
for (let m = index - 1; m >= 0; m--) {
|
|
425
|
-
const measurement = measurements[m];
|
|
426
|
-
if (furthestMeasurementsFound.has(measurement.lane)) {
|
|
427
|
-
continue;
|
|
428
|
-
}
|
|
429
|
-
const previousFurthestMeasurement = furthestMeasurements.get(
|
|
430
|
-
measurement.lane
|
|
431
|
-
);
|
|
432
|
-
if (previousFurthestMeasurement == null || measurement.end > previousFurthestMeasurement.end) {
|
|
433
|
-
furthestMeasurements.set(measurement.lane, measurement);
|
|
434
|
-
} else if (measurement.end < previousFurthestMeasurement.end) {
|
|
435
|
-
furthestMeasurementsFound.set(measurement.lane, true);
|
|
436
|
-
}
|
|
437
|
-
if (furthestMeasurementsFound.size === this.options.lanes) {
|
|
438
|
-
break;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
return furthestMeasurements.size === this.options.lanes ? Array.from(furthestMeasurements.values()).sort((a, b) => {
|
|
442
|
-
if (a.end === b.end) {
|
|
443
|
-
return a.index - b.index;
|
|
444
|
-
}
|
|
445
|
-
return a.end - b.end;
|
|
446
|
-
})[0] : void 0;
|
|
447
|
-
};
|
|
448
|
-
this.getMeasurementOptions = memo(
|
|
449
|
-
() => [
|
|
450
|
-
this.options.count,
|
|
451
|
-
this.options.paddingStart,
|
|
452
|
-
this.options.scrollMargin,
|
|
453
|
-
this.options.getItemKey,
|
|
454
|
-
this.options.enabled,
|
|
455
|
-
this.options.lanes
|
|
456
|
-
],
|
|
457
|
-
(count, paddingStart, scrollMargin, getItemKey, enabled, lanes) => {
|
|
458
|
-
const lanesChanged = this.prevLanes !== void 0 && this.prevLanes !== lanes;
|
|
459
|
-
if (lanesChanged) {
|
|
460
|
-
this.lanesChangedFlag = true;
|
|
461
|
-
}
|
|
462
|
-
this.prevLanes = lanes;
|
|
463
|
-
this.pendingMeasuredCacheIndexes = [];
|
|
464
|
-
return {
|
|
465
|
-
count,
|
|
466
|
-
paddingStart,
|
|
467
|
-
scrollMargin,
|
|
468
|
-
getItemKey,
|
|
469
|
-
enabled,
|
|
470
|
-
lanes
|
|
471
|
-
};
|
|
472
|
-
},
|
|
473
|
-
{
|
|
474
|
-
key: false
|
|
475
|
-
}
|
|
476
|
-
);
|
|
477
|
-
this.getMeasurements = memo(
|
|
478
|
-
() => [this.getMeasurementOptions(), this.itemSizeCache],
|
|
479
|
-
({ count, paddingStart, scrollMargin, getItemKey, enabled, lanes }, itemSizeCache) => {
|
|
480
|
-
if (!enabled) {
|
|
481
|
-
this.measurementsCache = [];
|
|
482
|
-
this.itemSizeCache.clear();
|
|
483
|
-
this.laneAssignments.clear();
|
|
484
|
-
return [];
|
|
485
|
-
}
|
|
486
|
-
if (this.laneAssignments.size > count) {
|
|
487
|
-
for (const index of this.laneAssignments.keys()) {
|
|
488
|
-
if (index >= count) {
|
|
489
|
-
this.laneAssignments.delete(index);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
if (this.lanesChangedFlag) {
|
|
494
|
-
this.lanesChangedFlag = false;
|
|
495
|
-
this.lanesSettling = true;
|
|
496
|
-
this.measurementsCache = [];
|
|
497
|
-
this.itemSizeCache.clear();
|
|
498
|
-
this.laneAssignments.clear();
|
|
499
|
-
this.pendingMeasuredCacheIndexes = [];
|
|
500
|
-
}
|
|
501
|
-
if (this.measurementsCache.length === 0 && !this.lanesSettling) {
|
|
502
|
-
this.measurementsCache = this.options.initialMeasurementsCache;
|
|
503
|
-
this.measurementsCache.forEach((item) => {
|
|
504
|
-
this.itemSizeCache.set(item.key, item.size);
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
const min = this.lanesSettling ? 0 : this.pendingMeasuredCacheIndexes.length > 0 ? Math.min(...this.pendingMeasuredCacheIndexes) : 0;
|
|
508
|
-
this.pendingMeasuredCacheIndexes = [];
|
|
509
|
-
if (this.lanesSettling && this.measurementsCache.length === count) {
|
|
510
|
-
this.lanesSettling = false;
|
|
511
|
-
}
|
|
512
|
-
const measurements = this.measurementsCache.slice(0, min);
|
|
513
|
-
const laneLastIndex = new Array(lanes).fill(
|
|
514
|
-
void 0
|
|
515
|
-
);
|
|
516
|
-
for (let m = 0; m < min; m++) {
|
|
517
|
-
const item = measurements[m];
|
|
518
|
-
if (item) {
|
|
519
|
-
laneLastIndex[item.lane] = m;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
for (let i = min; i < count; i++) {
|
|
523
|
-
const key = getItemKey(i);
|
|
524
|
-
const cachedLane = this.laneAssignments.get(i);
|
|
525
|
-
let lane;
|
|
526
|
-
let start;
|
|
527
|
-
if (cachedLane !== void 0 && this.options.lanes > 1) {
|
|
528
|
-
lane = cachedLane;
|
|
529
|
-
const prevIndex = laneLastIndex[lane];
|
|
530
|
-
const prevInLane = prevIndex !== void 0 ? measurements[prevIndex] : void 0;
|
|
531
|
-
start = prevInLane ? prevInLane.end + this.options.gap : paddingStart + scrollMargin;
|
|
532
|
-
} else {
|
|
533
|
-
const furthestMeasurement = this.options.lanes === 1 ? measurements[i - 1] : this.getFurthestMeasurement(measurements, i);
|
|
534
|
-
start = furthestMeasurement ? furthestMeasurement.end + this.options.gap : paddingStart + scrollMargin;
|
|
535
|
-
lane = furthestMeasurement ? furthestMeasurement.lane : i % this.options.lanes;
|
|
536
|
-
if (this.options.lanes > 1) {
|
|
537
|
-
this.laneAssignments.set(i, lane);
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
const measuredSize = itemSizeCache.get(key);
|
|
541
|
-
const size = typeof measuredSize === "number" ? measuredSize : this.options.estimateSize(i);
|
|
542
|
-
const end = start + size;
|
|
543
|
-
measurements[i] = {
|
|
544
|
-
index: i,
|
|
545
|
-
start,
|
|
546
|
-
size,
|
|
547
|
-
end,
|
|
548
|
-
key,
|
|
549
|
-
lane
|
|
550
|
-
};
|
|
551
|
-
laneLastIndex[lane] = i;
|
|
552
|
-
}
|
|
553
|
-
this.measurementsCache = measurements;
|
|
554
|
-
return measurements;
|
|
555
|
-
},
|
|
556
|
-
{
|
|
557
|
-
key: false,
|
|
558
|
-
debug: () => this.options.debug
|
|
559
|
-
}
|
|
560
|
-
);
|
|
561
|
-
this.calculateRange = memo(
|
|
562
|
-
() => [
|
|
563
|
-
this.getMeasurements(),
|
|
564
|
-
this.getSize(),
|
|
565
|
-
this.getScrollOffset(),
|
|
566
|
-
this.options.lanes
|
|
567
|
-
],
|
|
568
|
-
(measurements, outerSize, scrollOffset, lanes) => {
|
|
569
|
-
return this.range = measurements.length > 0 && outerSize > 0 ? calculateRange({
|
|
570
|
-
measurements,
|
|
571
|
-
outerSize,
|
|
572
|
-
scrollOffset,
|
|
573
|
-
lanes
|
|
574
|
-
}) : null;
|
|
575
|
-
},
|
|
576
|
-
{
|
|
577
|
-
key: false,
|
|
578
|
-
debug: () => this.options.debug
|
|
579
|
-
}
|
|
580
|
-
);
|
|
581
|
-
this.getVirtualIndexes = memo(
|
|
582
|
-
() => {
|
|
583
|
-
let startIndex = null;
|
|
584
|
-
let endIndex = null;
|
|
585
|
-
const range = this.calculateRange();
|
|
586
|
-
if (range) {
|
|
587
|
-
startIndex = range.startIndex;
|
|
588
|
-
endIndex = range.endIndex;
|
|
589
|
-
}
|
|
590
|
-
this.maybeNotify.updateDeps([this.isScrolling, startIndex, endIndex]);
|
|
591
|
-
return [
|
|
592
|
-
this.options.rangeExtractor,
|
|
593
|
-
this.options.overscan,
|
|
594
|
-
this.options.count,
|
|
595
|
-
startIndex,
|
|
596
|
-
endIndex
|
|
597
|
-
];
|
|
598
|
-
},
|
|
599
|
-
(rangeExtractor, overscan, count, startIndex, endIndex) => {
|
|
600
|
-
return startIndex === null || endIndex === null ? [] : rangeExtractor({
|
|
601
|
-
startIndex,
|
|
602
|
-
endIndex,
|
|
603
|
-
overscan,
|
|
604
|
-
count
|
|
605
|
-
});
|
|
606
|
-
},
|
|
607
|
-
{
|
|
608
|
-
key: false,
|
|
609
|
-
debug: () => this.options.debug
|
|
610
|
-
}
|
|
611
|
-
);
|
|
612
|
-
this.indexFromElement = (node) => {
|
|
613
|
-
const attributeName = this.options.indexAttribute;
|
|
614
|
-
const indexStr = node.getAttribute(attributeName);
|
|
615
|
-
if (!indexStr) {
|
|
616
|
-
console.warn(
|
|
617
|
-
`Missing attribute name '${attributeName}={index}' on measured element.`
|
|
618
|
-
);
|
|
619
|
-
return -1;
|
|
620
|
-
}
|
|
621
|
-
return parseInt(indexStr, 10);
|
|
622
|
-
};
|
|
623
|
-
this.shouldMeasureDuringScroll = (index) => {
|
|
624
|
-
var _a;
|
|
625
|
-
if (!this.scrollState || this.scrollState.behavior !== "smooth") {
|
|
626
|
-
return true;
|
|
627
|
-
}
|
|
628
|
-
const scrollIndex = this.scrollState.index ?? ((_a = this.getVirtualItemForOffset(this.scrollState.lastTargetOffset)) == null ? void 0 : _a.index);
|
|
629
|
-
if (scrollIndex !== void 0 && this.range) {
|
|
630
|
-
const bufferSize = Math.max(
|
|
631
|
-
this.options.overscan,
|
|
632
|
-
Math.ceil((this.range.endIndex - this.range.startIndex) / 2)
|
|
633
|
-
);
|
|
634
|
-
const minIndex = Math.max(0, scrollIndex - bufferSize);
|
|
635
|
-
const maxIndex = Math.min(
|
|
636
|
-
this.options.count - 1,
|
|
637
|
-
scrollIndex + bufferSize
|
|
638
|
-
);
|
|
639
|
-
return index >= minIndex && index <= maxIndex;
|
|
640
|
-
}
|
|
641
|
-
return true;
|
|
642
|
-
};
|
|
643
|
-
this._measureElement = (node, entry) => {
|
|
644
|
-
if (!node.isConnected) {
|
|
645
|
-
this.observer.unobserve(node);
|
|
646
|
-
return;
|
|
647
|
-
}
|
|
648
|
-
const index = this.indexFromElement(node);
|
|
649
|
-
const item = this.measurementsCache[index];
|
|
650
|
-
if (!item) {
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
const key = item.key;
|
|
654
|
-
const prevNode = this.elementsCache.get(key);
|
|
655
|
-
if (prevNode !== node) {
|
|
656
|
-
if (prevNode) {
|
|
657
|
-
this.observer.unobserve(prevNode);
|
|
658
|
-
}
|
|
659
|
-
this.observer.observe(node);
|
|
660
|
-
this.elementsCache.set(key, node);
|
|
661
|
-
}
|
|
662
|
-
if (this.shouldMeasureDuringScroll(index)) {
|
|
663
|
-
this.resizeItem(index, this.options.measureElement(node, entry, this));
|
|
664
|
-
}
|
|
665
|
-
};
|
|
666
|
-
this.resizeItem = (index, size) => {
|
|
667
|
-
var _a;
|
|
668
|
-
const item = this.measurementsCache[index];
|
|
669
|
-
if (!item) {
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
const itemSize = this.itemSizeCache.get(item.key) ?? item.size;
|
|
673
|
-
const delta = size - itemSize;
|
|
674
|
-
if (delta !== 0) {
|
|
675
|
-
if (((_a = this.scrollState) == null ? void 0 : _a.behavior) !== "smooth" && (this.shouldAdjustScrollPositionOnItemSizeChange !== void 0 ? this.shouldAdjustScrollPositionOnItemSizeChange(item, delta, this) : item.start < this.getScrollOffset() + this.scrollAdjustments)) {
|
|
676
|
-
this._scrollToOffset(this.getScrollOffset(), {
|
|
677
|
-
adjustments: this.scrollAdjustments += delta,
|
|
678
|
-
behavior: void 0
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
this.pendingMeasuredCacheIndexes.push(item.index);
|
|
682
|
-
this.itemSizeCache = new Map(this.itemSizeCache.set(item.key, size));
|
|
683
|
-
this.notify(false);
|
|
684
|
-
}
|
|
685
|
-
};
|
|
686
|
-
this.measureElement = (node) => {
|
|
687
|
-
if (!node) {
|
|
688
|
-
this.elementsCache.forEach((cached, key) => {
|
|
689
|
-
if (!cached.isConnected) {
|
|
690
|
-
this.observer.unobserve(cached);
|
|
691
|
-
this.elementsCache.delete(key);
|
|
692
|
-
}
|
|
693
|
-
});
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
this._measureElement(node, void 0);
|
|
697
|
-
};
|
|
698
|
-
this.getVirtualItems = memo(
|
|
699
|
-
() => [this.getVirtualIndexes(), this.getMeasurements()],
|
|
700
|
-
(indexes, measurements) => {
|
|
701
|
-
const virtualItems = [];
|
|
702
|
-
for (let k = 0, len = indexes.length; k < len; k++) {
|
|
703
|
-
const i = indexes[k];
|
|
704
|
-
const measurement = measurements[i];
|
|
705
|
-
virtualItems.push(measurement);
|
|
706
|
-
}
|
|
707
|
-
return virtualItems;
|
|
708
|
-
},
|
|
709
|
-
{
|
|
710
|
-
key: false,
|
|
711
|
-
debug: () => this.options.debug
|
|
712
|
-
}
|
|
713
|
-
);
|
|
714
|
-
this.getVirtualItemForOffset = (offset) => {
|
|
715
|
-
const measurements = this.getMeasurements();
|
|
716
|
-
if (measurements.length === 0) {
|
|
717
|
-
return void 0;
|
|
718
|
-
}
|
|
719
|
-
return notUndefined(
|
|
720
|
-
measurements[findNearestBinarySearch(
|
|
721
|
-
0,
|
|
722
|
-
measurements.length - 1,
|
|
723
|
-
(index) => notUndefined(measurements[index]).start,
|
|
724
|
-
offset
|
|
725
|
-
)]
|
|
726
|
-
);
|
|
727
|
-
};
|
|
728
|
-
this.getMaxScrollOffset = () => {
|
|
729
|
-
if (!this.scrollElement) return 0;
|
|
730
|
-
if ("scrollHeight" in this.scrollElement) {
|
|
731
|
-
return this.options.horizontal ? this.scrollElement.scrollWidth - this.scrollElement.clientWidth : this.scrollElement.scrollHeight - this.scrollElement.clientHeight;
|
|
732
|
-
} else {
|
|
733
|
-
const doc = this.scrollElement.document.documentElement;
|
|
734
|
-
return this.options.horizontal ? doc.scrollWidth - this.scrollElement.innerWidth : doc.scrollHeight - this.scrollElement.innerHeight;
|
|
735
|
-
}
|
|
736
|
-
};
|
|
737
|
-
this.getOffsetForAlignment = (toOffset, align, itemSize = 0) => {
|
|
738
|
-
if (!this.scrollElement) return 0;
|
|
739
|
-
const size = this.getSize();
|
|
740
|
-
const scrollOffset = this.getScrollOffset();
|
|
741
|
-
if (align === "auto") {
|
|
742
|
-
align = toOffset >= scrollOffset + size ? "end" : "start";
|
|
743
|
-
}
|
|
744
|
-
if (align === "center") {
|
|
745
|
-
toOffset += (itemSize - size) / 2;
|
|
746
|
-
} else if (align === "end") {
|
|
747
|
-
toOffset -= size;
|
|
748
|
-
}
|
|
749
|
-
const maxOffset = this.getMaxScrollOffset();
|
|
750
|
-
return Math.max(Math.min(maxOffset, toOffset), 0);
|
|
751
|
-
};
|
|
752
|
-
this.getOffsetForIndex = (index, align = "auto") => {
|
|
753
|
-
index = Math.max(0, Math.min(index, this.options.count - 1));
|
|
754
|
-
const size = this.getSize();
|
|
755
|
-
const scrollOffset = this.getScrollOffset();
|
|
756
|
-
const item = this.measurementsCache[index];
|
|
757
|
-
if (!item) return;
|
|
758
|
-
if (align === "auto") {
|
|
759
|
-
if (item.end >= scrollOffset + size - this.options.scrollPaddingEnd) {
|
|
760
|
-
align = "end";
|
|
761
|
-
} else if (item.start <= scrollOffset + this.options.scrollPaddingStart) {
|
|
762
|
-
align = "start";
|
|
763
|
-
} else {
|
|
764
|
-
return [scrollOffset, align];
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
if (align === "end" && index === this.options.count - 1) {
|
|
768
|
-
return [this.getMaxScrollOffset(), align];
|
|
769
|
-
}
|
|
770
|
-
const toOffset = align === "end" ? item.end + this.options.scrollPaddingEnd : item.start - this.options.scrollPaddingStart;
|
|
771
|
-
return [
|
|
772
|
-
this.getOffsetForAlignment(toOffset, align, item.size),
|
|
773
|
-
align
|
|
774
|
-
];
|
|
775
|
-
};
|
|
776
|
-
this.scrollToOffset = (toOffset, { align = "start", behavior = "auto" } = {}) => {
|
|
777
|
-
const offset = this.getOffsetForAlignment(toOffset, align);
|
|
778
|
-
const now = this.now();
|
|
779
|
-
this.scrollState = {
|
|
780
|
-
index: null,
|
|
781
|
-
align,
|
|
782
|
-
behavior,
|
|
783
|
-
startedAt: now,
|
|
784
|
-
lastTargetOffset: offset,
|
|
785
|
-
stableFrames: 0
|
|
786
|
-
};
|
|
787
|
-
this._scrollToOffset(offset, { adjustments: void 0, behavior });
|
|
788
|
-
this.scheduleScrollReconcile();
|
|
789
|
-
};
|
|
790
|
-
this.scrollToIndex = (index, {
|
|
791
|
-
align: initialAlign = "auto",
|
|
792
|
-
behavior = "auto"
|
|
793
|
-
} = {}) => {
|
|
794
|
-
index = Math.max(0, Math.min(index, this.options.count - 1));
|
|
795
|
-
const offsetInfo = this.getOffsetForIndex(index, initialAlign);
|
|
796
|
-
if (!offsetInfo) {
|
|
797
|
-
return;
|
|
798
|
-
}
|
|
799
|
-
const [offset, align] = offsetInfo;
|
|
800
|
-
const now = this.now();
|
|
801
|
-
this.scrollState = {
|
|
802
|
-
index,
|
|
803
|
-
align,
|
|
804
|
-
behavior,
|
|
805
|
-
startedAt: now,
|
|
806
|
-
lastTargetOffset: offset,
|
|
807
|
-
stableFrames: 0
|
|
808
|
-
};
|
|
809
|
-
this._scrollToOffset(offset, { adjustments: void 0, behavior });
|
|
810
|
-
this.scheduleScrollReconcile();
|
|
811
|
-
};
|
|
812
|
-
this.scrollBy = (delta, { behavior = "auto" } = {}) => {
|
|
813
|
-
const offset = this.getScrollOffset() + delta;
|
|
814
|
-
const now = this.now();
|
|
815
|
-
this.scrollState = {
|
|
816
|
-
index: null,
|
|
817
|
-
align: "start",
|
|
818
|
-
behavior,
|
|
819
|
-
startedAt: now,
|
|
820
|
-
lastTargetOffset: offset,
|
|
821
|
-
stableFrames: 0
|
|
822
|
-
};
|
|
823
|
-
this._scrollToOffset(offset, { adjustments: void 0, behavior });
|
|
824
|
-
this.scheduleScrollReconcile();
|
|
825
|
-
};
|
|
826
|
-
this.getTotalSize = () => {
|
|
827
|
-
var _a;
|
|
828
|
-
const measurements = this.getMeasurements();
|
|
829
|
-
let end;
|
|
830
|
-
if (measurements.length === 0) {
|
|
831
|
-
end = this.options.paddingStart;
|
|
832
|
-
} else if (this.options.lanes === 1) {
|
|
833
|
-
end = ((_a = measurements[measurements.length - 1]) == null ? void 0 : _a.end) ?? 0;
|
|
834
|
-
} else {
|
|
835
|
-
const endByLane = Array(this.options.lanes).fill(null);
|
|
836
|
-
let endIndex = measurements.length - 1;
|
|
837
|
-
while (endIndex >= 0 && endByLane.some((val) => val === null)) {
|
|
838
|
-
const item = measurements[endIndex];
|
|
839
|
-
if (endByLane[item.lane] === null) {
|
|
840
|
-
endByLane[item.lane] = item.end;
|
|
841
|
-
}
|
|
842
|
-
endIndex--;
|
|
843
|
-
}
|
|
844
|
-
end = Math.max(...endByLane.filter((val) => val !== null));
|
|
845
|
-
}
|
|
846
|
-
return Math.max(
|
|
847
|
-
end - this.options.scrollMargin + this.options.paddingEnd,
|
|
848
|
-
0
|
|
849
|
-
);
|
|
850
|
-
};
|
|
851
|
-
this._scrollToOffset = (offset, {
|
|
852
|
-
adjustments,
|
|
853
|
-
behavior
|
|
854
|
-
}) => {
|
|
855
|
-
this.options.scrollToFn(offset, { behavior, adjustments }, this);
|
|
856
|
-
};
|
|
857
|
-
this.measure = () => {
|
|
858
|
-
this.itemSizeCache = /* @__PURE__ */ new Map();
|
|
859
|
-
this.laneAssignments = /* @__PURE__ */ new Map();
|
|
860
|
-
this.notify(false);
|
|
861
|
-
};
|
|
862
|
-
this.setOptions(opts);
|
|
863
|
-
}
|
|
864
|
-
scheduleScrollReconcile() {
|
|
865
|
-
if (!this.targetWindow) {
|
|
866
|
-
this.scrollState = null;
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
if (this.rafId != null) return;
|
|
870
|
-
this.rafId = this.targetWindow.requestAnimationFrame(() => {
|
|
871
|
-
this.rafId = null;
|
|
872
|
-
this.reconcileScroll();
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
reconcileScroll() {
|
|
876
|
-
if (!this.scrollState) return;
|
|
877
|
-
const el = this.scrollElement;
|
|
878
|
-
if (!el) return;
|
|
879
|
-
const MAX_RECONCILE_MS = 5e3;
|
|
880
|
-
if (this.now() - this.scrollState.startedAt > MAX_RECONCILE_MS) {
|
|
881
|
-
this.scrollState = null;
|
|
882
|
-
return;
|
|
883
|
-
}
|
|
884
|
-
const offsetInfo = this.scrollState.index != null ? this.getOffsetForIndex(this.scrollState.index, this.scrollState.align) : void 0;
|
|
885
|
-
const targetOffset = offsetInfo ? offsetInfo[0] : this.scrollState.lastTargetOffset;
|
|
886
|
-
const STABLE_FRAMES = 1;
|
|
887
|
-
const targetChanged = targetOffset !== this.scrollState.lastTargetOffset;
|
|
888
|
-
if (!targetChanged && approxEqual(targetOffset, this.getScrollOffset())) {
|
|
889
|
-
this.scrollState.stableFrames++;
|
|
890
|
-
if (this.scrollState.stableFrames >= STABLE_FRAMES) {
|
|
891
|
-
this.scrollState = null;
|
|
892
|
-
return;
|
|
893
|
-
}
|
|
894
|
-
} else {
|
|
895
|
-
this.scrollState.stableFrames = 0;
|
|
896
|
-
if (targetChanged) {
|
|
897
|
-
this.scrollState.lastTargetOffset = targetOffset;
|
|
898
|
-
this.scrollState.behavior = "auto";
|
|
899
|
-
this._scrollToOffset(targetOffset, {
|
|
900
|
-
adjustments: void 0,
|
|
901
|
-
behavior: "auto"
|
|
902
|
-
});
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
this.scheduleScrollReconcile();
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
const findNearestBinarySearch = (low, high, getCurrentValue, value) => {
|
|
909
|
-
while (low <= high) {
|
|
910
|
-
const middle = (low + high) / 2 | 0;
|
|
911
|
-
const currentValue = getCurrentValue(middle);
|
|
912
|
-
if (currentValue < value) {
|
|
913
|
-
low = middle + 1;
|
|
914
|
-
} else if (currentValue > value) {
|
|
915
|
-
high = middle - 1;
|
|
916
|
-
} else {
|
|
917
|
-
return middle;
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
if (low > 0) {
|
|
921
|
-
return low - 1;
|
|
922
|
-
} else {
|
|
923
|
-
return 0;
|
|
924
|
-
}
|
|
925
|
-
};
|
|
926
|
-
function calculateRange({
|
|
927
|
-
measurements,
|
|
928
|
-
outerSize,
|
|
929
|
-
scrollOffset,
|
|
930
|
-
lanes
|
|
931
|
-
}) {
|
|
932
|
-
const lastIndex = measurements.length - 1;
|
|
933
|
-
const getOffset = (index) => measurements[index].start;
|
|
934
|
-
if (measurements.length <= lanes) {
|
|
935
|
-
return {
|
|
936
|
-
startIndex: 0,
|
|
937
|
-
endIndex: lastIndex
|
|
938
|
-
};
|
|
939
|
-
}
|
|
940
|
-
let startIndex = findNearestBinarySearch(
|
|
941
|
-
0,
|
|
942
|
-
lastIndex,
|
|
943
|
-
getOffset,
|
|
944
|
-
scrollOffset
|
|
945
|
-
);
|
|
946
|
-
let endIndex = startIndex;
|
|
947
|
-
if (lanes === 1) {
|
|
948
|
-
while (endIndex < lastIndex && measurements[endIndex].end < scrollOffset + outerSize) {
|
|
949
|
-
endIndex++;
|
|
950
|
-
}
|
|
951
|
-
} else if (lanes > 1) {
|
|
952
|
-
const endPerLane = Array(lanes).fill(0);
|
|
953
|
-
while (endIndex < lastIndex && endPerLane.some((pos) => pos < scrollOffset + outerSize)) {
|
|
954
|
-
const item = measurements[endIndex];
|
|
955
|
-
endPerLane[item.lane] = item.end;
|
|
956
|
-
endIndex++;
|
|
957
|
-
}
|
|
958
|
-
const startPerLane = Array(lanes).fill(scrollOffset + outerSize);
|
|
959
|
-
while (startIndex >= 0 && startPerLane.some((pos) => pos >= scrollOffset)) {
|
|
960
|
-
const item = measurements[startIndex];
|
|
961
|
-
startPerLane[item.lane] = item.start;
|
|
962
|
-
startIndex--;
|
|
963
|
-
}
|
|
964
|
-
startIndex = Math.max(0, startIndex - startIndex % lanes);
|
|
965
|
-
endIndex = Math.min(lastIndex, endIndex + (lanes - 1 - endIndex % lanes));
|
|
966
|
-
}
|
|
967
|
-
return { startIndex, endIndex };
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
const useIsomorphicLayoutEffect = typeof document !== "undefined" ? reactExports.useLayoutEffect : reactExports.useEffect;
|
|
971
|
-
function useVirtualizerBase({
|
|
972
|
-
useFlushSync = true,
|
|
973
|
-
...options
|
|
974
|
-
}) {
|
|
975
|
-
const rerender = reactExports.useReducer(() => ({}), {})[1];
|
|
976
|
-
const resolvedOptions = {
|
|
977
|
-
...options,
|
|
978
|
-
onChange: (instance2, sync) => {
|
|
979
|
-
var _a;
|
|
980
|
-
if (useFlushSync && sync) {
|
|
981
|
-
reactDomExports.flushSync(rerender);
|
|
982
|
-
} else {
|
|
983
|
-
rerender();
|
|
984
|
-
}
|
|
985
|
-
(_a = options.onChange) == null ? void 0 : _a.call(options, instance2, sync);
|
|
986
|
-
}
|
|
987
|
-
};
|
|
988
|
-
const [instance] = reactExports.useState(
|
|
989
|
-
() => new Virtualizer(resolvedOptions)
|
|
990
|
-
);
|
|
991
|
-
instance.setOptions(resolvedOptions);
|
|
992
|
-
useIsomorphicLayoutEffect(() => {
|
|
993
|
-
return instance._didMount();
|
|
994
|
-
}, []);
|
|
995
|
-
useIsomorphicLayoutEffect(() => {
|
|
996
|
-
return instance._willUpdate();
|
|
997
|
-
});
|
|
998
|
-
return instance;
|
|
999
|
-
}
|
|
1000
|
-
function useVirtualizer(options) {
|
|
1001
|
-
return useVirtualizerBase({
|
|
1002
|
-
observeElementRect,
|
|
1003
|
-
observeElementOffset,
|
|
1004
|
-
scrollToFn: elementScroll,
|
|
1005
|
-
...options
|
|
1006
|
-
});
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
const STORAGE_KEY = "lab-animation-level";
|
|
1010
|
-
function useLabAnimationLevel() {
|
|
1011
|
-
const prefersReducedMotion = useReducedMotion();
|
|
1012
|
-
const [level, setLevel] = reactExports.useState("full");
|
|
1013
|
-
reactExports.useEffect(() => {
|
|
1014
|
-
if (typeof window === "undefined") return;
|
|
1015
|
-
const stored = window.localStorage.getItem(STORAGE_KEY);
|
|
1016
|
-
if (stored) {
|
|
1017
|
-
setLevel(stored);
|
|
1018
|
-
return;
|
|
1019
|
-
}
|
|
1020
|
-
if (prefersReducedMotion) {
|
|
1021
|
-
setLevel("off");
|
|
1022
|
-
return;
|
|
1023
|
-
}
|
|
1024
|
-
const lowEnd = typeof navigator !== "undefined" && navigator.hardwareConcurrency <= 4;
|
|
1025
|
-
setLevel(lowEnd ? "simple" : "full");
|
|
1026
|
-
}, [prefersReducedMotion]);
|
|
1027
|
-
const update = (next) => {
|
|
1028
|
-
setLevel(next);
|
|
1029
|
-
if (typeof window !== "undefined") {
|
|
1030
|
-
window.localStorage.setItem(STORAGE_KEY, next);
|
|
1031
|
-
}
|
|
1032
|
-
};
|
|
1033
|
-
return { level, setLevel: update };
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
function LabDirectChatView({
|
|
1037
|
-
projectId,
|
|
1038
|
-
readOnly,
|
|
1039
|
-
visible,
|
|
1040
|
-
prefill,
|
|
1041
|
-
leadMessage,
|
|
1042
|
-
hideCopilotGreeting,
|
|
1043
|
-
mentionablesOverride,
|
|
1044
|
-
defaultAgentOverride,
|
|
1045
|
-
mentionsEnabledOverride,
|
|
1046
|
-
enforcedMentionPrefix,
|
|
1047
|
-
lockedMentionPrefix,
|
|
1048
|
-
messageMetadata,
|
|
1049
|
-
composerMode,
|
|
1050
|
-
composerFooter,
|
|
1051
|
-
busyOverride,
|
|
1052
|
-
onActionsChange,
|
|
1053
|
-
onMetaChange,
|
|
1054
|
-
onUserSubmit
|
|
1055
|
-
}) {
|
|
1056
|
-
const normalizedPrefill = reactExports.useMemo(() => {
|
|
1057
|
-
if (!prefill?.text) return prefill ?? null;
|
|
1058
|
-
const raw = prefill.text;
|
|
1059
|
-
if (!raw.startsWith("@")) return prefill;
|
|
1060
|
-
const match = raw.match(/^@[^\s]+/);
|
|
1061
|
-
if (!match) return prefill;
|
|
1062
|
-
const end = match[0].length;
|
|
1063
|
-
if (raw[end] === " ") return prefill;
|
|
1064
|
-
return { ...prefill, text: `${raw.slice(0, end)} ${raw.slice(end)}` };
|
|
1065
|
-
}, [prefill]);
|
|
1066
|
-
const [historyOpenOverride, setHistoryOpenOverride] = reactExports.useState(false);
|
|
1067
|
-
const historyPanelId = reactExports.useId();
|
|
1068
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1069
|
-
AiManusChatView,
|
|
1070
|
-
{
|
|
1071
|
-
mode: "lab-direct",
|
|
1072
|
-
uiMode: "copilot",
|
|
1073
|
-
projectId,
|
|
1074
|
-
readOnly,
|
|
1075
|
-
visible,
|
|
1076
|
-
prefill: normalizedPrefill,
|
|
1077
|
-
embedded: true,
|
|
1078
|
-
historyMode: "overlay",
|
|
1079
|
-
layoutPadding: "flush",
|
|
1080
|
-
sessionListEnabled: false,
|
|
1081
|
-
historyPanelId,
|
|
1082
|
-
historyOpenOverride,
|
|
1083
|
-
onHistoryOpenChange: setHistoryOpenOverride,
|
|
1084
|
-
onActionsChange,
|
|
1085
|
-
onMetaChange,
|
|
1086
|
-
mentionablesOverride,
|
|
1087
|
-
defaultAgentOverride,
|
|
1088
|
-
mentionsEnabledOverride,
|
|
1089
|
-
enforcedMentionPrefix,
|
|
1090
|
-
lockedMentionPrefix,
|
|
1091
|
-
lockLeadingMentionSpace: true,
|
|
1092
|
-
messageMetadata,
|
|
1093
|
-
hideCopilotGreeting,
|
|
1094
|
-
composerMode,
|
|
1095
|
-
composerFooter,
|
|
1096
|
-
leadMessage,
|
|
1097
|
-
busyOverride,
|
|
1098
|
-
onUserSubmit
|
|
1099
|
-
}
|
|
1100
|
-
);
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
const GROUP_FOLLOW_THRESHOLD_PX = 10;
|
|
1104
|
-
const LAB_SESSION_EVENT_LIMIT = 300;
|
|
1105
|
-
const buildTextDeltaId = (eventId, seq) => {
|
|
1106
|
-
const base = eventId.trim() ? eventId.trim() : "text";
|
|
1107
|
-
return `text-${base}-${seq}`;
|
|
1108
|
-
};
|
|
1109
|
-
const isScrolledToBottom = (container, threshold = GROUP_FOLLOW_THRESHOLD_PX) => {
|
|
1110
|
-
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
1111
|
-
return scrollHeight - scrollTop - clientHeight <= threshold;
|
|
1112
|
-
};
|
|
1113
|
-
const isScrolledToTop = (container, threshold = GROUP_FOLLOW_THRESHOLD_PX) => {
|
|
1114
|
-
return container.scrollTop <= threshold;
|
|
1115
|
-
};
|
|
1116
|
-
const useLabSurfaceSession = ({
|
|
1117
|
-
projectId,
|
|
1118
|
-
questId,
|
|
1119
|
-
surface,
|
|
1120
|
-
enabled
|
|
1121
|
-
}) => {
|
|
1122
|
-
const [sessionId, setSessionId] = reactExports.useState(null);
|
|
1123
|
-
const [messages, setMessages] = reactExports.useState([]);
|
|
1124
|
-
const [historyTruncated, setHistoryTruncated] = reactExports.useState(false);
|
|
1125
|
-
const [historyLimit, setHistoryLimit] = reactExports.useState(null);
|
|
1126
|
-
const [historyLoadingFull, setHistoryLoadingFull] = reactExports.useState(false);
|
|
1127
|
-
const [historyLoading, setHistoryLoading] = reactExports.useState(false);
|
|
1128
|
-
const [hasLoadedOnce, setHasLoadedOnce] = reactExports.useState(false);
|
|
1129
|
-
const [restoreToken, setRestoreToken] = reactExports.useState(0);
|
|
1130
|
-
const sessionIdRef = reactExports.useRef(null);
|
|
1131
|
-
const previousSessionIdRef = reactExports.useRef(null);
|
|
1132
|
-
const messagesRef = reactExports.useRef([]);
|
|
1133
|
-
const assistantMessageIndexRef = reactExports.useRef(/* @__PURE__ */ new Map());
|
|
1134
|
-
const attachmentsSeenRef = reactExports.useRef(/* @__PURE__ */ new Set());
|
|
1135
|
-
const lastAssistantSegmentIdRef = reactExports.useRef(null);
|
|
1136
|
-
const timelineSeqRef = reactExports.useRef(0);
|
|
1137
|
-
const fullHistoryRef = reactExports.useRef(false);
|
|
1138
|
-
const fullHistoryRequestRef = reactExports.useRef(false);
|
|
1139
|
-
const setSessionIdForSurface = useChatSessionStore((state) => state.setSessionIdForSurface);
|
|
1140
|
-
const surfaceKey = `lab-${surface}`;
|
|
1141
|
-
const resolveTimelineSeq = reactExports.useCallback((candidate) => {
|
|
1142
|
-
if (typeof candidate === "number" && Number.isFinite(candidate)) {
|
|
1143
|
-
if (candidate > timelineSeqRef.current) {
|
|
1144
|
-
timelineSeqRef.current = candidate;
|
|
1145
|
-
}
|
|
1146
|
-
return candidate;
|
|
1147
|
-
}
|
|
1148
|
-
timelineSeqRef.current += 1;
|
|
1149
|
-
return timelineSeqRef.current;
|
|
1150
|
-
}, []);
|
|
1151
|
-
const updateMessages = reactExports.useCallback((next) => {
|
|
1152
|
-
messagesRef.current = next;
|
|
1153
|
-
setMessages(next);
|
|
1154
|
-
}, []);
|
|
1155
|
-
const appendMessage = reactExports.useCallback(
|
|
1156
|
-
(message) => {
|
|
1157
|
-
updateMessages([...messagesRef.current, message]);
|
|
1158
|
-
},
|
|
1159
|
-
[updateMessages]
|
|
1160
|
-
);
|
|
1161
|
-
const resetState = reactExports.useCallback(() => {
|
|
1162
|
-
messagesRef.current = [];
|
|
1163
|
-
setMessages([]);
|
|
1164
|
-
assistantMessageIndexRef.current = /* @__PURE__ */ new Map();
|
|
1165
|
-
attachmentsSeenRef.current = /* @__PURE__ */ new Set();
|
|
1166
|
-
lastAssistantSegmentIdRef.current = null;
|
|
1167
|
-
timelineSeqRef.current = 0;
|
|
1168
|
-
}, []);
|
|
1169
|
-
const resetHistoryState = reactExports.useCallback(() => {
|
|
1170
|
-
setHistoryTruncated(false);
|
|
1171
|
-
setHistoryLimit(null);
|
|
1172
|
-
setHistoryLoadingFull(false);
|
|
1173
|
-
setHistoryLoading(false);
|
|
1174
|
-
setHasLoadedOnce(false);
|
|
1175
|
-
fullHistoryRef.current = false;
|
|
1176
|
-
fullHistoryRequestRef.current = false;
|
|
1177
|
-
setRestoreToken(0);
|
|
1178
|
-
}, []);
|
|
1179
|
-
const handleEvent = reactExports.useCallback(
|
|
1180
|
-
(event) => {
|
|
1181
|
-
if (!sessionId) return;
|
|
1182
|
-
applyChatEvent(event, {
|
|
1183
|
-
sessionId,
|
|
1184
|
-
messagesRef,
|
|
1185
|
-
assistantMessageIndexRef,
|
|
1186
|
-
lastAssistantSegmentIdRef,
|
|
1187
|
-
attachmentsSeenRef,
|
|
1188
|
-
resolveTimelineSeq,
|
|
1189
|
-
buildTextDeltaId,
|
|
1190
|
-
appendMessage,
|
|
1191
|
-
updateMessages
|
|
1192
|
-
});
|
|
1193
|
-
},
|
|
1194
|
-
[appendMessage, resolveTimelineSeq, sessionId, updateMessages]
|
|
1195
|
-
);
|
|
1196
|
-
const { sendMessage, restoreSession, stop, connection } = useSSESession({
|
|
1197
|
-
sessionId,
|
|
1198
|
-
projectId,
|
|
1199
|
-
onEvent: handleEvent
|
|
1200
|
-
});
|
|
1201
|
-
const pingTimerRef = reactExports.useRef(null);
|
|
1202
|
-
const pingInFlightRef = reactExports.useRef(false);
|
|
1203
|
-
reactExports.useEffect(() => {
|
|
1204
|
-
if (!enabled || !projectId || !questId) {
|
|
1205
|
-
setSessionId(null);
|
|
1206
|
-
resetState();
|
|
1207
|
-
resetHistoryState();
|
|
1208
|
-
return;
|
|
1209
|
-
}
|
|
1210
|
-
if (sessionIdRef.current) {
|
|
1211
|
-
setSessionId(null);
|
|
1212
|
-
}
|
|
1213
|
-
let active = true;
|
|
1214
|
-
const fetchSession = async () => {
|
|
1215
|
-
resetState();
|
|
1216
|
-
resetHistoryState();
|
|
1217
|
-
setHistoryLoading(true);
|
|
1218
|
-
try {
|
|
1219
|
-
const response = surface === "group" ? await getLabGroupSession(projectId, questId) : await getLabFriendsSession(projectId, questId);
|
|
1220
|
-
if (!active) return;
|
|
1221
|
-
const nextSessionId = response?.session_id?.trim?.() ?? response?.session_id;
|
|
1222
|
-
if (!nextSessionId) {
|
|
1223
|
-
setSessionId(null);
|
|
1224
|
-
setHistoryLoading(false);
|
|
1225
|
-
setHasLoadedOnce(true);
|
|
1226
|
-
return;
|
|
1227
|
-
}
|
|
1228
|
-
setSessionId(nextSessionId);
|
|
1229
|
-
setSessionIdForSurface(projectId, `lab-${surface}`, nextSessionId);
|
|
1230
|
-
} catch {
|
|
1231
|
-
if (active) {
|
|
1232
|
-
setHistoryLoading(false);
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
};
|
|
1236
|
-
void fetchSession();
|
|
1237
|
-
return () => {
|
|
1238
|
-
active = false;
|
|
1239
|
-
};
|
|
1240
|
-
}, [enabled, projectId, questId, resetHistoryState, resetState, setSessionIdForSurface, surface]);
|
|
1241
|
-
reactExports.useEffect(() => {
|
|
1242
|
-
if (!enabled || !sessionId) return;
|
|
1243
|
-
let active = true;
|
|
1244
|
-
const restore = async () => {
|
|
1245
|
-
const requestFull = fullHistoryRequestRef.current;
|
|
1246
|
-
const wantsFull = fullHistoryRef.current || requestFull;
|
|
1247
|
-
if (requestFull) {
|
|
1248
|
-
fullHistoryRequestRef.current = false;
|
|
1249
|
-
}
|
|
1250
|
-
resetState();
|
|
1251
|
-
setHistoryLoading(true);
|
|
1252
|
-
try {
|
|
1253
|
-
const session = await restoreSession(
|
|
1254
|
-
wantsFull ? { full: true } : { limit: LAB_SESSION_EVENT_LIMIT }
|
|
1255
|
-
);
|
|
1256
|
-
if (!active) return;
|
|
1257
|
-
const events = Array.isArray(session?.events) ? session.events : [];
|
|
1258
|
-
let lastEventId = null;
|
|
1259
|
-
events.forEach((record) => {
|
|
1260
|
-
if (!record || !record.event || !record.data) return;
|
|
1261
|
-
if (record.data?.event_id) {
|
|
1262
|
-
lastEventId = record.data.event_id;
|
|
1263
|
-
}
|
|
1264
|
-
applyChatEvent(
|
|
1265
|
-
{ event: record.event, data: record.data },
|
|
1266
|
-
{
|
|
1267
|
-
sessionId,
|
|
1268
|
-
messagesRef,
|
|
1269
|
-
assistantMessageIndexRef,
|
|
1270
|
-
lastAssistantSegmentIdRef,
|
|
1271
|
-
attachmentsSeenRef,
|
|
1272
|
-
resolveTimelineSeq,
|
|
1273
|
-
buildTextDeltaId,
|
|
1274
|
-
appendMessage,
|
|
1275
|
-
updateMessages
|
|
1276
|
-
}
|
|
1277
|
-
);
|
|
1278
|
-
});
|
|
1279
|
-
const eventsTruncated = Boolean(session?.events_truncated);
|
|
1280
|
-
const eventLimit = typeof session?.event_limit === "number" ? session.event_limit : null;
|
|
1281
|
-
setHistoryTruncated(eventsTruncated);
|
|
1282
|
-
setHistoryLimit(eventLimit);
|
|
1283
|
-
if (wantsFull) {
|
|
1284
|
-
fullHistoryRef.current = !eventsTruncated;
|
|
1285
|
-
} else {
|
|
1286
|
-
fullHistoryRef.current = false;
|
|
1287
|
-
}
|
|
1288
|
-
if (lastEventId) {
|
|
1289
|
-
useChatSessionStore.getState().setLastEventId(sessionId, lastEventId);
|
|
1290
|
-
}
|
|
1291
|
-
if (!active) return;
|
|
1292
|
-
try {
|
|
1293
|
-
await sendMessage({
|
|
1294
|
-
sessionId,
|
|
1295
|
-
message: "",
|
|
1296
|
-
surface: `lab-${surface}`,
|
|
1297
|
-
replayFromLastEvent: true
|
|
1298
|
-
});
|
|
1299
|
-
} catch {
|
|
1300
|
-
}
|
|
1301
|
-
} finally {
|
|
1302
|
-
if (requestFull && active) {
|
|
1303
|
-
setHistoryLoadingFull(false);
|
|
1304
|
-
}
|
|
1305
|
-
if (active) {
|
|
1306
|
-
setHistoryLoading(false);
|
|
1307
|
-
setHasLoadedOnce(true);
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
};
|
|
1311
|
-
void restore();
|
|
1312
|
-
return () => {
|
|
1313
|
-
active = false;
|
|
1314
|
-
};
|
|
1315
|
-
}, [
|
|
1316
|
-
appendMessage,
|
|
1317
|
-
enabled,
|
|
1318
|
-
resolveTimelineSeq,
|
|
1319
|
-
resetState,
|
|
1320
|
-
restoreSession,
|
|
1321
|
-
restoreToken,
|
|
1322
|
-
sendMessage,
|
|
1323
|
-
sessionId,
|
|
1324
|
-
surface,
|
|
1325
|
-
updateMessages
|
|
1326
|
-
]);
|
|
1327
|
-
reactExports.useEffect(() => {
|
|
1328
|
-
if (!enabled || !sessionId) return;
|
|
1329
|
-
let isMounted = true;
|
|
1330
|
-
const schedulePing = () => {
|
|
1331
|
-
const delay = 2e4 + Math.floor(Math.random() * 1e4);
|
|
1332
|
-
pingTimerRef.current = window.setTimeout(async () => {
|
|
1333
|
-
if (!isMounted || !sessionId || pingInFlightRef.current) {
|
|
1334
|
-
schedulePing();
|
|
1335
|
-
return;
|
|
1336
|
-
}
|
|
1337
|
-
pingInFlightRef.current = true;
|
|
1338
|
-
try {
|
|
1339
|
-
await sendMessage({
|
|
1340
|
-
sessionId,
|
|
1341
|
-
message: "",
|
|
1342
|
-
surface: surfaceKey,
|
|
1343
|
-
replayFromLastEvent: true
|
|
1344
|
-
});
|
|
1345
|
-
} catch {
|
|
1346
|
-
} finally {
|
|
1347
|
-
pingInFlightRef.current = false;
|
|
1348
|
-
}
|
|
1349
|
-
schedulePing();
|
|
1350
|
-
}, delay);
|
|
1351
|
-
};
|
|
1352
|
-
schedulePing();
|
|
1353
|
-
return () => {
|
|
1354
|
-
isMounted = false;
|
|
1355
|
-
if (pingTimerRef.current) {
|
|
1356
|
-
window.clearTimeout(pingTimerRef.current);
|
|
1357
|
-
pingTimerRef.current = null;
|
|
1358
|
-
}
|
|
1359
|
-
};
|
|
1360
|
-
}, [enabled, sendMessage, sessionId, surfaceKey]);
|
|
1361
|
-
reactExports.useEffect(() => {
|
|
1362
|
-
if (!enabled || !sessionId) return;
|
|
1363
|
-
if (connection.status === "closed" || connection.status === "error") {
|
|
1364
|
-
if (pingInFlightRef.current) return;
|
|
1365
|
-
pingInFlightRef.current = true;
|
|
1366
|
-
void sendMessage({
|
|
1367
|
-
sessionId,
|
|
1368
|
-
message: "",
|
|
1369
|
-
surface: surfaceKey,
|
|
1370
|
-
replayFromLastEvent: true
|
|
1371
|
-
}).finally(() => {
|
|
1372
|
-
pingInFlightRef.current = false;
|
|
1373
|
-
});
|
|
1374
|
-
}
|
|
1375
|
-
}, [connection.status, enabled, sendMessage, sessionId, surfaceKey]);
|
|
1376
|
-
reactExports.useEffect(() => {
|
|
1377
|
-
sessionIdRef.current = sessionId;
|
|
1378
|
-
}, [sessionId]);
|
|
1379
|
-
reactExports.useEffect(() => {
|
|
1380
|
-
if (previousSessionIdRef.current && previousSessionIdRef.current !== sessionId) {
|
|
1381
|
-
stop(previousSessionIdRef.current);
|
|
1382
|
-
}
|
|
1383
|
-
previousSessionIdRef.current = sessionId;
|
|
1384
|
-
}, [sessionId, stop]);
|
|
1385
|
-
reactExports.useEffect(() => {
|
|
1386
|
-
return () => {
|
|
1387
|
-
if (sessionIdRef.current) {
|
|
1388
|
-
stop(sessionIdRef.current);
|
|
1389
|
-
}
|
|
1390
|
-
};
|
|
1391
|
-
}, [stop]);
|
|
1392
|
-
const loadFullHistory = reactExports.useCallback(() => {
|
|
1393
|
-
if (!sessionId || !enabled) return;
|
|
1394
|
-
if (historyLoadingFull || fullHistoryRef.current) return;
|
|
1395
|
-
fullHistoryRequestRef.current = true;
|
|
1396
|
-
setHistoryLoadingFull(true);
|
|
1397
|
-
setRestoreToken((value) => value + 1);
|
|
1398
|
-
}, [enabled, historyLoadingFull, sessionId]);
|
|
1399
|
-
return {
|
|
1400
|
-
sessionId,
|
|
1401
|
-
messages,
|
|
1402
|
-
connection,
|
|
1403
|
-
sendMessage,
|
|
1404
|
-
historyTruncated,
|
|
1405
|
-
historyLimit,
|
|
1406
|
-
historyLoadingFull,
|
|
1407
|
-
historyLoading,
|
|
1408
|
-
hasLoadedOnce,
|
|
1409
|
-
loadFullHistory
|
|
1410
|
-
};
|
|
1411
|
-
};
|
|
1412
|
-
const getMessageText = (message) => {
|
|
1413
|
-
const content = message.content;
|
|
1414
|
-
return typeof content?.content === "string" ? content.content : "";
|
|
1415
|
-
};
|
|
1416
|
-
const getMessageMetadata = (message) => {
|
|
1417
|
-
const content = message.content;
|
|
1418
|
-
return content?.metadata ?? null;
|
|
1419
|
-
};
|
|
1420
|
-
const toSelectionMetadata = (selection) => {
|
|
1421
|
-
if (!selection) return void 0;
|
|
1422
|
-
return {
|
|
1423
|
-
selection_type: selection.selection_type,
|
|
1424
|
-
selection_ref: selection.selection_ref,
|
|
1425
|
-
quest_id: selection.quest_id,
|
|
1426
|
-
branch_name: selection.branch_name ?? void 0,
|
|
1427
|
-
edge_id: selection.edge_id ?? void 0,
|
|
1428
|
-
agent_instance_id: selection.agent_instance_id ?? void 0,
|
|
1429
|
-
worktree_rel_path: selection.worktree_rel_path ?? void 0
|
|
1430
|
-
};
|
|
1431
|
-
};
|
|
1432
|
-
const resolveSelectionLabel = (selection) => {
|
|
1433
|
-
if (!selection) return "";
|
|
1434
|
-
if (selection.label?.trim()) return selection.label.trim();
|
|
1435
|
-
return selection.branch_name?.trim() || selection.edge_id?.trim() || selection.agent_instance_id?.trim() || selection.selection_ref;
|
|
1436
|
-
};
|
|
1437
|
-
const resolveReplyStateLabel = (t, replyState) => {
|
|
1438
|
-
const normalized = typeof replyState === "string" ? replyState.trim().toLowerCase() : "";
|
|
1439
|
-
if (!normalized) return null;
|
|
1440
|
-
if (normalized === "queued") {
|
|
1441
|
-
return t("copilot_group_reply_state_queued", void 0, "Queued");
|
|
1442
|
-
}
|
|
1443
|
-
if (normalized === "acked") {
|
|
1444
|
-
return t("copilot_group_reply_state_acked", void 0, "Acknowledged");
|
|
1445
|
-
}
|
|
1446
|
-
if (normalized === "final") {
|
|
1447
|
-
return t("copilot_group_reply_state_final", void 0, "Final");
|
|
1448
|
-
}
|
|
1449
|
-
return normalized;
|
|
1450
|
-
};
|
|
1451
|
-
function LabControlReferenceChips({
|
|
1452
|
-
selection,
|
|
1453
|
-
proposal,
|
|
1454
|
-
onClearSelection,
|
|
1455
|
-
onClearProposal,
|
|
1456
|
-
selectionPrefix,
|
|
1457
|
-
proposalPrefix
|
|
1458
|
-
}) {
|
|
1459
|
-
if (!selection && !proposal) return null;
|
|
1460
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-[var(--lab-border)] bg-[var(--lab-background)] px-4 py-3", children: [
|
|
1461
|
-
selection ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1462
|
-
"button",
|
|
1463
|
-
{
|
|
1464
|
-
type: "button",
|
|
1465
|
-
onClick: onClearSelection,
|
|
1466
|
-
className: "inline-flex items-center gap-2 rounded-full border border-[var(--lab-border)] bg-[var(--lab-surface)] px-3 py-1 text-[11px] font-medium text-[var(--lab-text-primary)]",
|
|
1467
|
-
children: [
|
|
1468
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[var(--lab-text-secondary)]", children: selectionPrefix }),
|
|
1469
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: resolveSelectionLabel(selection) }),
|
|
1470
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[var(--lab-text-muted)]", children: "×" })
|
|
1471
|
-
]
|
|
1472
|
-
}
|
|
1473
|
-
) : null,
|
|
1474
|
-
proposal ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1475
|
-
"button",
|
|
1476
|
-
{
|
|
1477
|
-
type: "button",
|
|
1478
|
-
onClick: onClearProposal,
|
|
1479
|
-
className: "inline-flex items-center gap-2 rounded-full border border-[rgba(83,176,174,0.28)] bg-[rgba(83,176,174,0.12)] px-3 py-1 text-[11px] font-medium text-[var(--lab-text-primary)]",
|
|
1480
|
-
children: [
|
|
1481
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[var(--lab-text-secondary)]", children: proposalPrefix }),
|
|
1482
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: proposal.action_type }),
|
|
1483
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[var(--lab-text-muted)]", children: [
|
|
1484
|
-
"· ",
|
|
1485
|
-
proposal.status
|
|
1486
|
-
] }),
|
|
1487
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[var(--lab-text-muted)]", children: "×" })
|
|
1488
|
-
]
|
|
1489
|
-
}
|
|
1490
|
-
) : null
|
|
1491
|
-
] });
|
|
1492
|
-
}
|
|
1493
|
-
const getMessageRole = (message) => {
|
|
1494
|
-
if (message.type !== "text_delta") return null;
|
|
1495
|
-
const content = message.content;
|
|
1496
|
-
return content.role;
|
|
1497
|
-
};
|
|
1498
|
-
const formatAbsoluteTimestamp = (value) => {
|
|
1499
|
-
if (value === null || value === void 0) return "";
|
|
1500
|
-
const date = typeof value === "number" ? new Date(value < 1e12 ? value * 1e3 : value) : new Date(value);
|
|
1501
|
-
if (Number.isNaN(date.getTime())) return "";
|
|
1502
|
-
const pad = (item) => String(item).padStart(2, "0");
|
|
1503
|
-
const year = pad(date.getFullYear() % 100);
|
|
1504
|
-
const month = pad(date.getMonth() + 1);
|
|
1505
|
-
const day = pad(date.getDate());
|
|
1506
|
-
const hour = pad(date.getHours());
|
|
1507
|
-
const minute = pad(date.getMinutes());
|
|
1508
|
-
const second = pad(date.getSeconds());
|
|
1509
|
-
return `${year}-${month}-${day} ${hour}-${minute}-${second}`;
|
|
1510
|
-
};
|
|
1511
|
-
const resolveMessageTitle = (message) => {
|
|
1512
|
-
const role = getMessageRole(message);
|
|
1513
|
-
if (role === "user") return "You";
|
|
1514
|
-
const metadata = getMessageMetadata(message);
|
|
1515
|
-
if (metadata?.sender_name) return metadata.sender_name;
|
|
1516
|
-
if (metadata?.agent_display_name) return metadata.agent_display_name;
|
|
1517
|
-
if (metadata?.sender_label) return metadata.sender_label;
|
|
1518
|
-
if (metadata?.agent_label) return metadata.agent_label;
|
|
1519
|
-
if (metadata?.agent_id) return `@${metadata.agent_id}`;
|
|
1520
|
-
return "Agent";
|
|
1521
|
-
};
|
|
1522
|
-
const resolveMomentMedia = (raw) => {
|
|
1523
|
-
if (!raw) return [];
|
|
1524
|
-
const normalizeEntry = (entry) => {
|
|
1525
|
-
if (typeof entry === "string") {
|
|
1526
|
-
const trimmed = entry.trim();
|
|
1527
|
-
return trimmed ? { url: trimmed } : null;
|
|
1528
|
-
}
|
|
1529
|
-
if (!entry || typeof entry !== "object") return null;
|
|
1530
|
-
const record = entry;
|
|
1531
|
-
const urlCandidate = typeof record.url === "string" && record.url || typeof record.src === "string" && record.src || typeof record.file_url === "string" && record.file_url || typeof record.preview_url === "string" && record.preview_url || typeof record.path === "string" && record.path || "";
|
|
1532
|
-
if (!urlCandidate) return null;
|
|
1533
|
-
const label = typeof record.label === "string" ? record.label : void 0;
|
|
1534
|
-
return { url: urlCandidate, label };
|
|
1535
|
-
};
|
|
1536
|
-
const rawList = Array.isArray(raw) ? raw : typeof raw === "object" ? raw.items || raw.images || raw.files || [] : [];
|
|
1537
|
-
if (!Array.isArray(rawList)) return [];
|
|
1538
|
-
return rawList.map((entry) => normalizeEntry(entry)).filter((entry) => Boolean(entry)).slice(0, 9);
|
|
1539
|
-
};
|
|
1540
|
-
const resolveMomentLikes = (raw) => {
|
|
1541
|
-
if (!raw) return [];
|
|
1542
|
-
const normalizeEntry = (entry) => {
|
|
1543
|
-
if (typeof entry === "string") {
|
|
1544
|
-
const trimmed2 = entry.trim();
|
|
1545
|
-
return trimmed2 ? { name: trimmed2 } : null;
|
|
1546
|
-
}
|
|
1547
|
-
if (!entry || typeof entry !== "object") return null;
|
|
1548
|
-
const record = entry;
|
|
1549
|
-
const nameCandidate = typeof record.name === "string" && record.name || typeof record.user_name === "string" && record.user_name || typeof record.username === "string" && record.username || typeof record.display_name === "string" && record.display_name || typeof record.label === "string" && record.label || typeof record.user_id === "string" && record.user_id || "";
|
|
1550
|
-
const trimmed = nameCandidate.trim();
|
|
1551
|
-
if (!trimmed) return null;
|
|
1552
|
-
return { name: trimmed };
|
|
1553
|
-
};
|
|
1554
|
-
const rawListValue = Array.isArray(raw) ? raw : typeof raw === "object" ? raw.items || raw.users || raw.likes || [] : [];
|
|
1555
|
-
const rawList = Array.isArray(rawListValue) ? rawListValue : [];
|
|
1556
|
-
return rawList.map((entry) => normalizeEntry(entry)).filter((entry) => Boolean(entry));
|
|
1557
|
-
};
|
|
1558
|
-
const resolveMomentComments = (raw) => {
|
|
1559
|
-
if (!raw) return [];
|
|
1560
|
-
const normalizeEntry = (entry) => {
|
|
1561
|
-
if (typeof entry === "string") {
|
|
1562
|
-
const trimmed = entry.trim();
|
|
1563
|
-
return trimmed ? { name: "Unknown", content: trimmed } : null;
|
|
1564
|
-
}
|
|
1565
|
-
if (!entry || typeof entry !== "object") return null;
|
|
1566
|
-
const record = entry;
|
|
1567
|
-
const nameCandidate = typeof record.name === "string" && record.name || typeof record.user_name === "string" && record.user_name || typeof record.username === "string" && record.username || typeof record.display_name === "string" && record.display_name || typeof record.label === "string" && record.label || typeof record.user_id === "string" && record.user_id || "";
|
|
1568
|
-
const contentCandidate = typeof record.content === "string" && record.content || typeof record.text === "string" && record.text || typeof record.message === "string" && record.message || typeof record.body === "string" && record.body || "";
|
|
1569
|
-
const name = nameCandidate.trim() || "Unknown";
|
|
1570
|
-
const content = contentCandidate.trim();
|
|
1571
|
-
if (!content) return null;
|
|
1572
|
-
return { name, content };
|
|
1573
|
-
};
|
|
1574
|
-
const rawListValue = Array.isArray(raw) ? raw : typeof raw === "object" ? raw.items || raw.comments || raw.entries || [] : [];
|
|
1575
|
-
const rawList = Array.isArray(rawListValue) ? rawListValue : [];
|
|
1576
|
-
return rawList.map((entry) => normalizeEntry(entry)).filter((entry) => Boolean(entry));
|
|
1577
|
-
};
|
|
1578
|
-
const mergeMomentLikes = (base, extra) => {
|
|
1579
|
-
const seen = /* @__PURE__ */ new Map();
|
|
1580
|
-
base.forEach((item) => {
|
|
1581
|
-
const key = item.name.trim().toLowerCase();
|
|
1582
|
-
if (!key) return;
|
|
1583
|
-
if (!seen.has(key)) seen.set(key, item.name);
|
|
1584
|
-
});
|
|
1585
|
-
extra.forEach((item) => {
|
|
1586
|
-
const key = item.name.trim().toLowerCase();
|
|
1587
|
-
if (!key) return;
|
|
1588
|
-
if (!seen.has(key)) seen.set(key, item.name);
|
|
1589
|
-
});
|
|
1590
|
-
return Array.from(seen.values());
|
|
1591
|
-
};
|
|
1592
|
-
const mergeMomentComments = (base, extra) => {
|
|
1593
|
-
const seen = /* @__PURE__ */ new Map();
|
|
1594
|
-
base.forEach((item) => {
|
|
1595
|
-
const key = `${item.name.trim().toLowerCase()}::${item.content.trim().toLowerCase()}`;
|
|
1596
|
-
if (seen.has(key)) return;
|
|
1597
|
-
seen.set(key, item);
|
|
1598
|
-
});
|
|
1599
|
-
extra.forEach((item) => {
|
|
1600
|
-
const key = `${item.name.trim().toLowerCase()}::${item.content.trim().toLowerCase()}`;
|
|
1601
|
-
if (seen.has(key)) return;
|
|
1602
|
-
seen.set(key, item);
|
|
1603
|
-
});
|
|
1604
|
-
return Array.from(seen.values());
|
|
1605
|
-
};
|
|
1606
|
-
const resolveMomentTimestamp = (message) => {
|
|
1607
|
-
const content = message.content;
|
|
1608
|
-
if (typeof content?.timestamp === "number") {
|
|
1609
|
-
return new Date(content.timestamp * 1e3).toISOString();
|
|
1610
|
-
}
|
|
1611
|
-
const metadata = content?.metadata;
|
|
1612
|
-
if (typeof metadata?.source_ts === "string") {
|
|
1613
|
-
return metadata.source_ts;
|
|
1614
|
-
}
|
|
1615
|
-
return null;
|
|
1616
|
-
};
|
|
1617
|
-
function LabCopilotHeader({
|
|
1618
|
-
disabled,
|
|
1619
|
-
agents,
|
|
1620
|
-
templates,
|
|
1621
|
-
quests,
|
|
1622
|
-
onClearChat,
|
|
1623
|
-
clearChatDisabled
|
|
1624
|
-
}) {
|
|
1625
|
-
const mode = useLabCopilotStore((state) => state.mode);
|
|
1626
|
-
const setMode = useLabCopilotStore((state) => state.setMode);
|
|
1627
|
-
const activeAgentId = useLabCopilotStore((state) => state.activeAgentId);
|
|
1628
|
-
const activeQuestId = useLabCopilotStore((state) => state.activeQuestId);
|
|
1629
|
-
const followEffects = useLabCopilotStore((state) => state.followEffects);
|
|
1630
|
-
const setFollowEffects = useLabCopilotStore((state) => state.setFollowEffects);
|
|
1631
|
-
const agentStatusOverrides = useLabCopilotStore((state) => state.agentStatusOverrides);
|
|
1632
|
-
const piOnboardingActive = useLabCopilotStore((state) => state.piOnboardingActive);
|
|
1633
|
-
const endPiOnboarding = useLabCopilotStore((state) => state.endPiOnboarding);
|
|
1634
|
-
const templatesById = reactExports.useMemo(() => {
|
|
1635
|
-
return new Map(templates.map((template) => [template.template_id, template]));
|
|
1636
|
-
}, [templates]);
|
|
1637
|
-
const avatarColors = reactExports.useMemo(() => buildAvatarColorMap(agents), [agents]);
|
|
1638
|
-
const activeAgent = reactExports.useMemo(() => {
|
|
1639
|
-
if (!activeAgentId) return null;
|
|
1640
|
-
return agents.find((agent) => agent.instance_id === activeAgentId) ?? null;
|
|
1641
|
-
}, [activeAgentId, agents]);
|
|
1642
|
-
const activeTemplate = activeAgent?.template_id ? templatesById.get(activeAgent.template_id) ?? null : null;
|
|
1643
|
-
const resolvedQuestId = activeAgent?.active_quest_id ?? activeQuestId ?? null;
|
|
1644
|
-
const activeQuest = resolvedQuestId ? quests.find((quest) => quest.quest_id === resolvedQuestId) ?? null : null;
|
|
1645
|
-
const questLabel = activeQuest ? resolveQuestLabel(activeQuest) : "Not joined yet";
|
|
1646
|
-
const agentDisplayName = activeAgent ? resolveAgentDisplayName(activeAgent) : "";
|
|
1647
|
-
const agentLogo = activeAgent ? resolveAgentLogo(activeAgent, activeTemplate) : "";
|
|
1648
|
-
const agentAvatarColor = activeAgent ? avatarColors.get(activeAgent.instance_id) || pickAvatarFrameColor(activeAgent.instance_id) : null;
|
|
1649
|
-
const overrideStatus = activeAgent ? agentStatusOverrides[activeAgent.instance_id] ?? null : null;
|
|
1650
|
-
const rawAgentStatus = overrideStatus ?? activeAgent?.status ?? "";
|
|
1651
|
-
const activeAgentStatus = typeof rawAgentStatus === "string" ? rawAgentStatus.trim() : "";
|
|
1652
|
-
const showRunningStatus = mode === "direct" && activeAgent && Boolean(activeAgentStatus) && isLabWorkingStatus(activeAgentStatus) && activeAgentStatus.toLowerCase() !== "waiting";
|
|
1653
|
-
const [contextMenu, setContextMenu] = reactExports.useState(null);
|
|
1654
|
-
const clearChatBlocked = Boolean(clearChatDisabled || !onClearChat);
|
|
1655
|
-
const items = [
|
|
1656
|
-
{ value: "direct", label: "Direct" },
|
|
1657
|
-
{ value: "group", label: "Group" },
|
|
1658
|
-
{ value: "friends", label: "Friends" }
|
|
1659
|
-
];
|
|
1660
|
-
const handleAvatarContextMenu = reactExports.useCallback(
|
|
1661
|
-
(event) => {
|
|
1662
|
-
if (!onClearChat) return;
|
|
1663
|
-
event.preventDefault();
|
|
1664
|
-
event.stopPropagation();
|
|
1665
|
-
setContextMenu({ x: event.clientX, y: event.clientY });
|
|
1666
|
-
},
|
|
1667
|
-
[onClearChat]
|
|
1668
|
-
);
|
|
1669
|
-
const handleClearChat = reactExports.useCallback(() => {
|
|
1670
|
-
if (clearChatBlocked || !onClearChat) return;
|
|
1671
|
-
onClearChat();
|
|
1672
|
-
setContextMenu(null);
|
|
1673
|
-
}, [clearChatBlocked, onClearChat]);
|
|
1674
|
-
const handleToggleFollow = reactExports.useCallback(() => {
|
|
1675
|
-
setFollowEffects(!followEffects);
|
|
1676
|
-
setContextMenu(null);
|
|
1677
|
-
}, [followEffects, setFollowEffects]);
|
|
1678
|
-
reactExports.useEffect(() => {
|
|
1679
|
-
if (!contextMenu) return;
|
|
1680
|
-
const handleDismiss = () => setContextMenu(null);
|
|
1681
|
-
const handleKey = (event) => {
|
|
1682
|
-
if (event.key === "Escape") {
|
|
1683
|
-
setContextMenu(null);
|
|
1684
|
-
}
|
|
1685
|
-
};
|
|
1686
|
-
window.addEventListener("click", handleDismiss);
|
|
1687
|
-
window.addEventListener("contextmenu", handleDismiss);
|
|
1688
|
-
window.addEventListener("keydown", handleKey);
|
|
1689
|
-
window.addEventListener("scroll", handleDismiss, true);
|
|
1690
|
-
return () => {
|
|
1691
|
-
window.removeEventListener("click", handleDismiss);
|
|
1692
|
-
window.removeEventListener("contextmenu", handleDismiss);
|
|
1693
|
-
window.removeEventListener("keydown", handleKey);
|
|
1694
|
-
window.removeEventListener("scroll", handleDismiss, true);
|
|
1695
|
-
};
|
|
1696
|
-
}, [contextMenu]);
|
|
1697
|
-
const contextMenuPortal = contextMenu && typeof document !== "undefined" ? reactDomExports.createPortal(
|
|
1698
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-copilot-context-menu", style: { left: contextMenu.x, top: contextMenu.y }, children: [
|
|
1699
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-copilot-context-item", children: [
|
|
1700
|
-
"Quest: ",
|
|
1701
|
-
questLabel
|
|
1702
|
-
] }),
|
|
1703
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: handleClearChat, disabled: clearChatBlocked, children: "Clear chat" }),
|
|
1704
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: handleToggleFollow, children: [
|
|
1705
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1706
|
-
Check,
|
|
1707
|
-
{
|
|
1708
|
-
size: 14,
|
|
1709
|
-
className: cn("shrink-0", followEffects ? "opacity-100" : "opacity-0")
|
|
1710
|
-
}
|
|
1711
|
-
),
|
|
1712
|
-
"Follow effects"
|
|
1713
|
-
] })
|
|
1714
|
-
] }),
|
|
1715
|
-
document.body
|
|
1716
|
-
) : null;
|
|
1717
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
1718
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-copilot-header", children: [
|
|
1719
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-section-tabs lab-copilot-tabs", role: "tablist", "aria-label": "Copilot modes", children: items.map((item) => {
|
|
1720
|
-
const isActive = mode === item.value;
|
|
1721
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
1722
|
-
"button",
|
|
1723
|
-
{
|
|
1724
|
-
type: "button",
|
|
1725
|
-
role: "tab",
|
|
1726
|
-
"aria-selected": isActive,
|
|
1727
|
-
disabled,
|
|
1728
|
-
onClick: () => setMode(item.value),
|
|
1729
|
-
className: cn(
|
|
1730
|
-
"lab-section-tab",
|
|
1731
|
-
isActive && "lab-section-tab-active",
|
|
1732
|
-
disabled && "cursor-not-allowed opacity-60"
|
|
1733
|
-
),
|
|
1734
|
-
children: [
|
|
1735
|
-
item.label,
|
|
1736
|
-
isActive ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-section-tab-indicator" }) : null
|
|
1737
|
-
]
|
|
1738
|
-
},
|
|
1739
|
-
item.value
|
|
1740
|
-
);
|
|
1741
|
-
}) }),
|
|
1742
|
-
mode === "direct" && activeAgent ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-copilot-agent", onContextMenu: handleAvatarContextMenu, children: [
|
|
1743
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-avatar lab-avatar-sm", children: [
|
|
1744
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
1745
|
-
"span",
|
|
1746
|
-
{
|
|
1747
|
-
className: "lab-avatar-ring",
|
|
1748
|
-
style: { borderColor: agentAvatarColor || pickAvatarFrameColor(activeAgent.instance_id) }
|
|
1749
|
-
}
|
|
1750
|
-
),
|
|
1751
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: agentLogo, alt: agentDisplayName || "Agent" })
|
|
1752
|
-
] }),
|
|
1753
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-copilot-agent-meta", children: [
|
|
1754
|
-
agentDisplayName ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-copilot-agent-name", children: agentDisplayName }) : null,
|
|
1755
|
-
showRunningStatus ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-copilot-agent-status", children: [
|
|
1756
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-status-dot lab-status-dot-running" }),
|
|
1757
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Working" })
|
|
1758
|
-
] }) : null
|
|
1759
|
-
] }),
|
|
1760
|
-
piOnboardingActive ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "lab-onboarding-exit", onClick: endPiOnboarding, children: "Exit onboarding" }) : null
|
|
1761
|
-
] }) : null
|
|
1762
|
-
] }),
|
|
1763
|
-
contextMenuPortal
|
|
1764
|
-
] });
|
|
1765
|
-
}
|
|
1766
|
-
function LabCopilotPanel({
|
|
1767
|
-
projectId,
|
|
1768
|
-
readOnly,
|
|
1769
|
-
cliStatus,
|
|
1770
|
-
templates,
|
|
1771
|
-
agents,
|
|
1772
|
-
quests,
|
|
1773
|
-
prefill: externalPrefill,
|
|
1774
|
-
onActionsChange
|
|
1775
|
-
}) {
|
|
1776
|
-
const queryClient = useQueryClient();
|
|
1777
|
-
const { addToast } = useToast();
|
|
1778
|
-
const { t } = useI18n("lab");
|
|
1779
|
-
const mode = useLabCopilotStore((state) => state.mode);
|
|
1780
|
-
const activeAgentId = useLabCopilotStore((state) => state.activeAgentId);
|
|
1781
|
-
const activeQuestId = useLabCopilotStore((state) => state.activeQuestId);
|
|
1782
|
-
const directPrefill = useLabCopilotStore((state) => state.directPrefill);
|
|
1783
|
-
const directComposeRequest = useLabCopilotStore((state) => state.directComposeRequest);
|
|
1784
|
-
const setActiveQuest = useLabCopilotStore((state) => state.setActiveQuest);
|
|
1785
|
-
const setActiveAgent = useLabCopilotStore((state) => state.setActiveAgent);
|
|
1786
|
-
const setDirectPrefill = useLabCopilotStore((state) => state.setDirectPrefill);
|
|
1787
|
-
const clearDirectComposeRequest = useLabCopilotStore((state) => state.clearDirectComposeRequest);
|
|
1788
|
-
const agentStatusOverrides = useLabCopilotStore((state) => state.agentStatusOverrides);
|
|
1789
|
-
const setAgentStatusOverride = useLabCopilotStore((state) => state.setAgentStatusOverride);
|
|
1790
|
-
const piOnboardingActive = useLabCopilotStore((state) => state.piOnboardingActive);
|
|
1791
|
-
const piOnboardingQuestId = useLabCopilotStore((state) => state.piOnboardingQuestId);
|
|
1792
|
-
const piOnboardingKind = useLabCopilotStore((state) => state.piOnboardingKind);
|
|
1793
|
-
const endPiOnboarding = useLabCopilotStore((state) => state.endPiOnboarding);
|
|
1794
|
-
const selection = useLabGraphSelectionStore((state) => state.selection);
|
|
1795
|
-
const activeProposal = useLabGraphSelectionStore((state) => state.activeProposal);
|
|
1796
|
-
const setSelection = useLabGraphSelectionStore((state) => state.setSelection);
|
|
1797
|
-
const setActiveProposal = useLabGraphSelectionStore((state) => state.setActiveProposal);
|
|
1798
|
-
const setSessionIdForSurface = useChatSessionStore((state) => state.setSessionIdForSurface);
|
|
1799
|
-
const clearSessionIdForSurface = useChatSessionStore((state) => state.clearSessionIdForSurface);
|
|
1800
|
-
const currentSessionId = useChatSessionStore(
|
|
1801
|
-
(state) => projectId ? state.sessionIdsByProjectSurface[projectId]?.["lab-direct"] ?? null : null
|
|
1802
|
-
);
|
|
1803
|
-
const cliServerId = useChatSessionStore(
|
|
1804
|
-
(state) => projectId ? state.cliServerIdsByProject[projectId] ?? null : null
|
|
1805
|
-
);
|
|
1806
|
-
const [agentPrefill, setAgentPrefill] = reactExports.useState(null);
|
|
1807
|
-
const [copilotActions, setCopilotActions] = reactExports.useState(null);
|
|
1808
|
-
const [copilotMeta, setCopilotMeta] = reactExports.useState(null);
|
|
1809
|
-
const autoSubmittedComposeTokenRef = reactExports.useRef(null);
|
|
1810
|
-
const resetKeyRef = reactExports.useRef("init");
|
|
1811
|
-
const prevAgentIdRef = reactExports.useRef(null);
|
|
1812
|
-
const prevRespondingRef = reactExports.useRef(false);
|
|
1813
|
-
const { level: animationLevel } = useLabAnimationLevel();
|
|
1814
|
-
const prefersReducedMotion = useReducedMotion();
|
|
1815
|
-
const allowMotion = animationLevel === "full" && !prefersReducedMotion;
|
|
1816
|
-
const isCopilotMetaEqual = reactExports.useCallback(
|
|
1817
|
-
(prev, next) => {
|
|
1818
|
-
if (!prev) return false;
|
|
1819
|
-
return prev.threadId === next.threadId && prev.historyOpen === next.historyOpen && prev.isResponding === next.isResponding && prev.ready === next.ready && prev.isRestoring === next.isRestoring && prev.restoreAttempted === next.restoreAttempted && prev.hasHistory === next.hasHistory && prev.error === next.error && prev.title === next.title && prev.statusText === next.statusText && prev.statusPrevText === next.statusPrevText && prev.statusKey === next.statusKey && prev.toolPanelVisible === next.toolPanelVisible && prev.toolToggleVisible === next.toolToggleVisible && prev.attachmentsDrawerOpen === next.attachmentsDrawerOpen && prev.fixWithAiRunning === next.fixWithAiRunning;
|
|
1820
|
-
},
|
|
1821
|
-
[]
|
|
1822
|
-
);
|
|
1823
|
-
reactExports.useEffect(() => {
|
|
1824
|
-
if (mode !== "direct") {
|
|
1825
|
-
resetKeyRef.current = "init";
|
|
1826
|
-
}
|
|
1827
|
-
}, [mode]);
|
|
1828
|
-
const templatesById = reactExports.useMemo(() => {
|
|
1829
|
-
return new Map(templates.map((template) => [template.template_id, template]));
|
|
1830
|
-
}, [templates]);
|
|
1831
|
-
const agentsById = reactExports.useMemo(() => {
|
|
1832
|
-
return new Map(agents.map((agent) => [agent.instance_id, agent]));
|
|
1833
|
-
}, [agents]);
|
|
1834
|
-
const avatarColors = reactExports.useMemo(() => buildAvatarColorMap(agents), [agents]);
|
|
1835
|
-
const resolvedAgentId = activeAgentId ?? null;
|
|
1836
|
-
const activeAgent = resolvedAgentId ? agentsById.get(resolvedAgentId) ?? null : null;
|
|
1837
|
-
const isResponding = Boolean(copilotMeta?.isResponding);
|
|
1838
|
-
const rawActiveAgentStatus = activeAgent && agentStatusOverrides[activeAgent.instance_id] ? agentStatusOverrides[activeAgent.instance_id] : activeAgent?.status ?? "";
|
|
1839
|
-
const activeAgentStatusKey = typeof rawActiveAgentStatus === "string" ? rawActiveAgentStatus.toLowerCase() : "";
|
|
1840
|
-
const activeAgentWorking = Boolean(activeAgent) && Boolean(activeAgentStatusKey) && isLabWorkingStatus(activeAgentStatusKey) && activeAgentStatusKey !== "waiting";
|
|
1841
|
-
const activeAgentStatusLabel = typeof activeAgent?.status === "string" ? activeAgent.status.toLowerCase() : "";
|
|
1842
|
-
const shouldOverrideWorking = mode === "direct" && Boolean(activeAgent) && isResponding && activeAgentStatusLabel !== "waiting";
|
|
1843
|
-
const desiredStatusOverride = shouldOverrideWorking ? "working" : null;
|
|
1844
|
-
const currentStatusOverride = activeAgent ? agentStatusOverrides[activeAgent.instance_id] ?? null : null;
|
|
1845
|
-
reactExports.useEffect(() => {
|
|
1846
|
-
const prev = prevAgentIdRef.current;
|
|
1847
|
-
const next = activeAgent?.instance_id ?? null;
|
|
1848
|
-
if (prev && prev !== next) {
|
|
1849
|
-
setAgentStatusOverride(prev, null);
|
|
1850
|
-
}
|
|
1851
|
-
prevAgentIdRef.current = next;
|
|
1852
|
-
}, [activeAgent?.instance_id, setAgentStatusOverride]);
|
|
1853
|
-
reactExports.useEffect(() => {
|
|
1854
|
-
if (!activeAgent?.instance_id) return;
|
|
1855
|
-
if (currentStatusOverride === desiredStatusOverride) return;
|
|
1856
|
-
setAgentStatusOverride(activeAgent.instance_id, desiredStatusOverride);
|
|
1857
|
-
}, [activeAgent?.instance_id, currentStatusOverride, desiredStatusOverride, setAgentStatusOverride]);
|
|
1858
|
-
reactExports.useEffect(() => {
|
|
1859
|
-
if (!projectId || mode !== "direct" || !activeAgent?.instance_id) {
|
|
1860
|
-
prevRespondingRef.current = false;
|
|
1861
|
-
return;
|
|
1862
|
-
}
|
|
1863
|
-
const wasResponding = prevRespondingRef.current;
|
|
1864
|
-
prevRespondingRef.current = isResponding;
|
|
1865
|
-
if (wasResponding && !isResponding) {
|
|
1866
|
-
queryClient.invalidateQueries({ queryKey: ["lab-agents", projectId] });
|
|
1867
|
-
}
|
|
1868
|
-
}, [activeAgent?.instance_id, isResponding, mode, projectId, queryClient]);
|
|
1869
|
-
const activeTemplate = activeAgent?.template_id ? templatesById.get(activeAgent.template_id) ?? null : null;
|
|
1870
|
-
const initQuestion = activeTemplate?.init_question?.trim() || "";
|
|
1871
|
-
const hasInitQuestion = Boolean(initQuestion);
|
|
1872
|
-
const activeQuest = (activeAgent?.active_quest_id ? quests.find((quest) => quest.quest_id === activeAgent.active_quest_id) : quests.find((quest) => quest.quest_id === activeQuestId)) ?? null;
|
|
1873
|
-
const initTemplateEligible = mode === "direct" && hasInitQuestion;
|
|
1874
|
-
const initTemplateReady = Boolean(
|
|
1875
|
-
copilotMeta?.ready && copilotMeta?.restoreAttempted && !copilotMeta?.isRestoring
|
|
1876
|
-
);
|
|
1877
|
-
const shouldShowInitTemplate = initTemplateEligible && initTemplateReady && !copilotMeta?.hasHistory;
|
|
1878
|
-
const shouldHoldInitPrefill = initTemplateEligible && !initTemplateReady;
|
|
1879
|
-
const [directSessionId, setDirectSessionId] = reactExports.useState(null);
|
|
1880
|
-
const [directSessionRetryToken, setDirectSessionRetryToken] = reactExports.useState(0);
|
|
1881
|
-
const directSessionRetryRef = reactExports.useRef(null);
|
|
1882
|
-
const directSessionRetryCountRef = reactExports.useRef(0);
|
|
1883
|
-
const resetDirectSessionRetry = reactExports.useCallback(() => {
|
|
1884
|
-
if (directSessionRetryRef.current) {
|
|
1885
|
-
window.clearTimeout(directSessionRetryRef.current);
|
|
1886
|
-
}
|
|
1887
|
-
directSessionRetryRef.current = null;
|
|
1888
|
-
directSessionRetryCountRef.current = 0;
|
|
1889
|
-
}, []);
|
|
1890
|
-
const scheduleDirectSessionRetry = reactExports.useCallback(() => {
|
|
1891
|
-
if (directSessionRetryRef.current) return;
|
|
1892
|
-
const attempt = Math.min(directSessionRetryCountRef.current, 4);
|
|
1893
|
-
const delayMs = Math.min(15e3, 1500 * 2 ** attempt);
|
|
1894
|
-
directSessionRetryCountRef.current = attempt + 1;
|
|
1895
|
-
directSessionRetryRef.current = window.setTimeout(() => {
|
|
1896
|
-
directSessionRetryRef.current = null;
|
|
1897
|
-
setDirectSessionRetryToken((value) => value + 1);
|
|
1898
|
-
}, delayMs);
|
|
1899
|
-
}, []);
|
|
1900
|
-
reactExports.useEffect(() => {
|
|
1901
|
-
return () => resetDirectSessionRetry();
|
|
1902
|
-
}, [resetDirectSessionRetry]);
|
|
1903
|
-
reactExports.useEffect(() => {
|
|
1904
|
-
if (activeQuestId || quests.length !== 1) return;
|
|
1905
|
-
setActiveQuest(quests[0].quest_id);
|
|
1906
|
-
}, [activeQuestId, quests, setActiveQuest]);
|
|
1907
|
-
reactExports.useEffect(() => {
|
|
1908
|
-
if (!projectId || mode !== "direct") return;
|
|
1909
|
-
if (!activeAgent?.instance_id) {
|
|
1910
|
-
setDirectSessionId(null);
|
|
1911
|
-
resetDirectSessionRetry();
|
|
1912
|
-
clearSessionIdForSurface(projectId, "lab-direct");
|
|
1913
|
-
return;
|
|
1914
|
-
}
|
|
1915
|
-
const fallbackSessionId = activeAgent.direct_session_id ?? null;
|
|
1916
|
-
if (fallbackSessionId) {
|
|
1917
|
-
setDirectSessionId((prev) => prev === fallbackSessionId ? prev : fallbackSessionId);
|
|
1918
|
-
if (currentSessionId !== fallbackSessionId) {
|
|
1919
|
-
setSessionIdForSurface(projectId, "lab-direct", fallbackSessionId);
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
let cancelled = false;
|
|
1923
|
-
getLabAgentDirectSession(projectId, activeAgent.instance_id).then((response) => {
|
|
1924
|
-
if (cancelled) return;
|
|
1925
|
-
resetDirectSessionRetry();
|
|
1926
|
-
setDirectSessionId(response.session_id);
|
|
1927
|
-
if (currentSessionId !== response.session_id) {
|
|
1928
|
-
setSessionIdForSurface(projectId, "lab-direct", response.session_id);
|
|
1929
|
-
}
|
|
1930
|
-
}).catch((error) => {
|
|
1931
|
-
if (cancelled) return;
|
|
1932
|
-
const detail = typeof error?.response?.data?.detail === "string" ? error.response.data.detail : null;
|
|
1933
|
-
if (detail && ["cli_server_required", "cli_offline", "cli_server_not_bound"].includes(detail)) {
|
|
1934
|
-
scheduleDirectSessionRetry();
|
|
1935
|
-
queryClient.invalidateQueries({ queryKey: ["lab-agents", projectId] });
|
|
1936
|
-
return;
|
|
1937
|
-
}
|
|
1938
|
-
if (!fallbackSessionId) {
|
|
1939
|
-
setDirectSessionId(null);
|
|
1940
|
-
}
|
|
1941
|
-
});
|
|
1942
|
-
return () => {
|
|
1943
|
-
cancelled = true;
|
|
1944
|
-
};
|
|
1945
|
-
}, [
|
|
1946
|
-
activeAgent?.direct_session_id,
|
|
1947
|
-
activeAgent?.instance_id,
|
|
1948
|
-
activeAgent?.cli_server_id,
|
|
1949
|
-
activeAgent?.active_quest_id,
|
|
1950
|
-
activeAgent?.active_quest_node_id,
|
|
1951
|
-
clearSessionIdForSurface,
|
|
1952
|
-
currentSessionId,
|
|
1953
|
-
directSessionRetryToken,
|
|
1954
|
-
mode,
|
|
1955
|
-
projectId,
|
|
1956
|
-
queryClient,
|
|
1957
|
-
resetDirectSessionRetry,
|
|
1958
|
-
scheduleDirectSessionRetry,
|
|
1959
|
-
setSessionIdForSurface
|
|
1960
|
-
]);
|
|
1961
|
-
reactExports.useEffect(() => {
|
|
1962
|
-
if (!piOnboardingActive) return;
|
|
1963
|
-
if (!activeAgent) {
|
|
1964
|
-
endPiOnboarding();
|
|
1965
|
-
return;
|
|
1966
|
-
}
|
|
1967
|
-
if (activeTemplate?.template_key !== "pi" && activeAgent.agent_id !== "pi") {
|
|
1968
|
-
endPiOnboarding();
|
|
1969
|
-
}
|
|
1970
|
-
}, [activeAgent, activeTemplate?.template_key, endPiOnboarding, piOnboardingActive]);
|
|
1971
|
-
reactExports.useEffect(() => {
|
|
1972
|
-
if (!piOnboardingActive) return;
|
|
1973
|
-
if (copilotMeta?.hasHistory) {
|
|
1974
|
-
endPiOnboarding();
|
|
1975
|
-
}
|
|
1976
|
-
}, [copilotMeta?.hasHistory, endPiOnboarding, piOnboardingActive]);
|
|
1977
|
-
const cliReadOnly = cliStatus !== "online";
|
|
1978
|
-
const labReadOnly = readOnly || cliReadOnly;
|
|
1979
|
-
const mentionsEnabled = !labReadOnly && !(mode === "direct" && shouldShowInitTemplate);
|
|
1980
|
-
const mentionablesOverride = reactExports.useMemo(() => {
|
|
1981
|
-
return agents.map((agent) => {
|
|
1982
|
-
const template = agent.template_id ? templatesById.get(agent.template_id) ?? null : null;
|
|
1983
|
-
return buildAgentDescriptor(agent, template);
|
|
1984
|
-
});
|
|
1985
|
-
}, [agents, templatesById]);
|
|
1986
|
-
const defaultAgentOverride = reactExports.useMemo(() => {
|
|
1987
|
-
if (!activeAgent) return void 0;
|
|
1988
|
-
const template = activeAgent.template_id ? templatesById.get(activeAgent.template_id) ?? null : null;
|
|
1989
|
-
return buildAgentDescriptor(activeAgent, template);
|
|
1990
|
-
}, [activeAgent, templatesById]);
|
|
1991
|
-
reactExports.useEffect(() => {
|
|
1992
|
-
if (!activeAgent) {
|
|
1993
|
-
setAgentPrefill(null);
|
|
1994
|
-
return;
|
|
1995
|
-
}
|
|
1996
|
-
if (shouldShowInitTemplate) {
|
|
1997
|
-
setAgentPrefill(null);
|
|
1998
|
-
return;
|
|
1999
|
-
}
|
|
2000
|
-
if (shouldHoldInitPrefill) {
|
|
2001
|
-
setAgentPrefill(null);
|
|
2002
|
-
return;
|
|
2003
|
-
}
|
|
2004
|
-
const rawLabel = activeAgent.mention_label?.trim() || activeAgent.agent_id;
|
|
2005
|
-
const label = rawLabel.startsWith("@") ? rawLabel : `@${rawLabel}`;
|
|
2006
|
-
setAgentPrefill({ text: `${label} `, focus: false, token: Date.now() });
|
|
2007
|
-
}, [activeAgent, shouldHoldInitPrefill, shouldShowInitTemplate]);
|
|
2008
|
-
const enforcedMentionLabel = reactExports.useMemo(() => {
|
|
2009
|
-
if (activeAgent) {
|
|
2010
|
-
const rawLabel = activeAgent.mention_label?.trim() || activeAgent.agent_id;
|
|
2011
|
-
return rawLabel.startsWith("@") ? rawLabel : `@${rawLabel}`;
|
|
2012
|
-
}
|
|
2013
|
-
return `@${DEFAULT_AGENT_ID}`;
|
|
2014
|
-
}, [activeAgent]);
|
|
2015
|
-
const useStarterPrompt = shouldShowInitTemplate;
|
|
2016
|
-
const enforcedMentionPrefix = mode === "direct" ? enforcedMentionLabel : void 0;
|
|
2017
|
-
const lockedMentionPrefix = activeAgent && mode === "direct" ? enforcedMentionLabel : void 0;
|
|
2018
|
-
const handleOpenRecruit = reactExports.useCallback(() => {
|
|
2019
|
-
if (labReadOnly) {
|
|
2020
|
-
addToast({
|
|
2021
|
-
type: "warning",
|
|
2022
|
-
title: "Recruitment unavailable",
|
|
2023
|
-
description: cliStatus === "online" ? "Recruitment is disabled in read-only mode." : "Bind an execution server to recruit agents."
|
|
2024
|
-
});
|
|
2025
|
-
return;
|
|
2026
|
-
}
|
|
2027
|
-
if (typeof window !== "undefined") {
|
|
2028
|
-
window.dispatchEvent(new CustomEvent("lab:open-recruit"));
|
|
2029
|
-
}
|
|
2030
|
-
}, [addToast, cliStatus, labReadOnly]);
|
|
2031
|
-
const handleSelectAgent = reactExports.useCallback(
|
|
2032
|
-
(agentId) => {
|
|
2033
|
-
setActiveAgent(agentId);
|
|
2034
|
-
},
|
|
2035
|
-
[setActiveAgent]
|
|
2036
|
-
);
|
|
2037
|
-
const starterMetadata = reactExports.useMemo(() => {
|
|
2038
|
-
if (!activeAgent) {
|
|
2039
|
-
return { agent_label: enforcedMentionLabel };
|
|
2040
|
-
}
|
|
2041
|
-
const logo = resolveAgentLogo(activeAgent, activeTemplate);
|
|
2042
|
-
return {
|
|
2043
|
-
agent_label: enforcedMentionLabel,
|
|
2044
|
-
agent_id: activeAgent.agent_id,
|
|
2045
|
-
agent_instance_id: activeAgent.instance_id,
|
|
2046
|
-
agent_display_name: resolveAgentDisplayName(activeAgent),
|
|
2047
|
-
agent_logo: logo || void 0
|
|
2048
|
-
};
|
|
2049
|
-
}, [activeAgent, activeTemplate, enforcedMentionLabel]);
|
|
2050
|
-
const starterMessage = reactExports.useMemo(() => {
|
|
2051
|
-
if (!useStarterPrompt) return null;
|
|
2052
|
-
const timestamp = Math.floor(Date.now() / 1e3);
|
|
2053
|
-
return {
|
|
2054
|
-
id: `lab-starter-${resolvedAgentId ?? "unknown"}`,
|
|
2055
|
-
type: "text_delta",
|
|
2056
|
-
seq: 0,
|
|
2057
|
-
ts: timestamp,
|
|
2058
|
-
content: {
|
|
2059
|
-
content: initQuestion,
|
|
2060
|
-
role: "assistant",
|
|
2061
|
-
status: "completed",
|
|
2062
|
-
timestamp,
|
|
2063
|
-
metadata: starterMetadata
|
|
2064
|
-
}
|
|
2065
|
-
};
|
|
2066
|
-
}, [initQuestion, resolvedAgentId, starterMetadata, useStarterPrompt]);
|
|
2067
|
-
const handleActionsChange = reactExports.useCallback(
|
|
2068
|
-
(actions) => {
|
|
2069
|
-
setCopilotActions(actions);
|
|
2070
|
-
onActionsChange?.(actions);
|
|
2071
|
-
},
|
|
2072
|
-
[onActionsChange]
|
|
2073
|
-
);
|
|
2074
|
-
const handleMetaChange = reactExports.useCallback(
|
|
2075
|
-
(meta) => {
|
|
2076
|
-
setCopilotMeta((prev) => isCopilotMetaEqual(prev, meta) ? prev : meta);
|
|
2077
|
-
},
|
|
2078
|
-
[isCopilotMetaEqual]
|
|
2079
|
-
);
|
|
2080
|
-
const handleDirectSubmit = reactExports.useCallback(
|
|
2081
|
-
(_message) => {
|
|
2082
|
-
if (directComposeRequest) {
|
|
2083
|
-
clearDirectComposeRequest();
|
|
2084
|
-
} else if (directPrefill) {
|
|
2085
|
-
setDirectPrefill(null);
|
|
2086
|
-
}
|
|
2087
|
-
if (piOnboardingActive) {
|
|
2088
|
-
endPiOnboarding();
|
|
2089
|
-
}
|
|
2090
|
-
},
|
|
2091
|
-
[
|
|
2092
|
-
clearDirectComposeRequest,
|
|
2093
|
-
directComposeRequest,
|
|
2094
|
-
directPrefill,
|
|
2095
|
-
endPiOnboarding,
|
|
2096
|
-
piOnboardingActive,
|
|
2097
|
-
setDirectPrefill
|
|
2098
|
-
]
|
|
2099
|
-
);
|
|
2100
|
-
const resolvedDirectPrefill = reactExports.useMemo(() => {
|
|
2101
|
-
if (externalPrefill) return externalPrefill;
|
|
2102
|
-
if (directComposeRequest?.text?.trim()) {
|
|
2103
|
-
return {
|
|
2104
|
-
text: directComposeRequest.text,
|
|
2105
|
-
focus: true,
|
|
2106
|
-
token: directComposeRequest.token
|
|
2107
|
-
};
|
|
2108
|
-
}
|
|
2109
|
-
if (directPrefill && directPrefill.trim()) {
|
|
2110
|
-
return {
|
|
2111
|
-
text: directPrefill,
|
|
2112
|
-
focus: true,
|
|
2113
|
-
token: Date.now()
|
|
2114
|
-
};
|
|
2115
|
-
}
|
|
2116
|
-
return agentPrefill;
|
|
2117
|
-
}, [agentPrefill, directComposeRequest, directPrefill, externalPrefill]);
|
|
2118
|
-
reactExports.useEffect(() => {
|
|
2119
|
-
const request = directComposeRequest;
|
|
2120
|
-
if (!request || request.submitMode !== "auto") return;
|
|
2121
|
-
if (mode !== "direct") return;
|
|
2122
|
-
if (!copilotActions?.setComposerValue || !copilotActions.submitComposer) return;
|
|
2123
|
-
if (copilotMeta?.isResponding) return;
|
|
2124
|
-
if (autoSubmittedComposeTokenRef.current === request.token) return;
|
|
2125
|
-
autoSubmittedComposeTokenRef.current = request.token;
|
|
2126
|
-
copilotActions.setComposerValue(request.text, true);
|
|
2127
|
-
const timer = window.setTimeout(() => {
|
|
2128
|
-
copilotActions.submitComposer?.();
|
|
2129
|
-
}, 30);
|
|
2130
|
-
return () => {
|
|
2131
|
-
window.clearTimeout(timer);
|
|
2132
|
-
};
|
|
2133
|
-
}, [copilotActions, copilotMeta?.isResponding, directComposeRequest, mode]);
|
|
2134
|
-
reactExports.useEffect(() => {
|
|
2135
|
-
if (mode !== "direct") return;
|
|
2136
|
-
if (!copilotActions) return;
|
|
2137
|
-
const key = activeAgent?.instance_id ?? "none";
|
|
2138
|
-
const sessionId = directSessionId ?? activeAgent?.direct_session_id ?? currentSessionId ?? null;
|
|
2139
|
-
if (sessionId) {
|
|
2140
|
-
if (currentSessionId !== sessionId) {
|
|
2141
|
-
copilotActions.clearThread?.();
|
|
2142
|
-
copilotActions.setThreadId(sessionId);
|
|
2143
|
-
}
|
|
2144
|
-
resetKeyRef.current = key;
|
|
2145
|
-
return;
|
|
2146
|
-
}
|
|
2147
|
-
if (resetKeyRef.current === key) return;
|
|
2148
|
-
resetKeyRef.current = key;
|
|
2149
|
-
copilotActions.setThreadId(null);
|
|
2150
|
-
}, [
|
|
2151
|
-
activeAgent?.direct_session_id,
|
|
2152
|
-
activeAgent?.instance_id,
|
|
2153
|
-
copilotActions,
|
|
2154
|
-
currentSessionId,
|
|
2155
|
-
directSessionId,
|
|
2156
|
-
labReadOnly,
|
|
2157
|
-
mode
|
|
2158
|
-
]);
|
|
2159
|
-
const directMetadata = reactExports.useMemo(() => {
|
|
2160
|
-
if (!activeAgent) return null;
|
|
2161
|
-
const questOverride = piOnboardingActive && piOnboardingQuestId ? piOnboardingQuestId : null;
|
|
2162
|
-
const logo = resolveAgentLogo(activeAgent, activeTemplate);
|
|
2163
|
-
const selectionContext = toSelectionMetadata(selection);
|
|
2164
|
-
return {
|
|
2165
|
-
lab_mode: "direct",
|
|
2166
|
-
quest_id: questOverride ?? activeAgent.active_quest_id ?? void 0,
|
|
2167
|
-
quest_node_id: activeAgent.active_quest_node_id ?? void 0,
|
|
2168
|
-
agent_id: activeAgent.agent_id,
|
|
2169
|
-
agent_label: resolveAgentMentionLabel(activeAgent),
|
|
2170
|
-
agent_display_name: resolveAgentDisplayName(activeAgent),
|
|
2171
|
-
agent_logo: logo || null,
|
|
2172
|
-
agent_avatar_color: activeAgent.avatar_frame_color ?? void 0,
|
|
2173
|
-
agent_instance_id: activeAgent.instance_id,
|
|
2174
|
-
cli_server_id: activeAgent.cli_server_id ?? cliServerId ?? void 0,
|
|
2175
|
-
pi_onboarding_kind: piOnboardingActive ? piOnboardingKind ?? void 0 : void 0,
|
|
2176
|
-
selection_context: selectionContext,
|
|
2177
|
-
proposal_id: activeProposal?.proposal_id ?? void 0,
|
|
2178
|
-
target_label: resolveAgentMentionLabel(activeAgent),
|
|
2179
|
-
message_kind: selectionContext || activeProposal ? "user_control" : piOnboardingActive ? "pi_onboarding" : "text"
|
|
2180
|
-
};
|
|
2181
|
-
}, [
|
|
2182
|
-
activeAgent,
|
|
2183
|
-
activeTemplate,
|
|
2184
|
-
activeProposal,
|
|
2185
|
-
cliServerId,
|
|
2186
|
-
piOnboardingActive,
|
|
2187
|
-
piOnboardingKind,
|
|
2188
|
-
piOnboardingQuestId,
|
|
2189
|
-
selection
|
|
2190
|
-
]);
|
|
2191
|
-
const controlReferenceFooter = reactExports.useMemo(
|
|
2192
|
-
() => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2193
|
-
LabControlReferenceChips,
|
|
2194
|
-
{
|
|
2195
|
-
selection,
|
|
2196
|
-
proposal: activeProposal ? {
|
|
2197
|
-
proposal_id: activeProposal.proposal_id,
|
|
2198
|
-
action_type: activeProposal.action_type,
|
|
2199
|
-
status: activeProposal.status
|
|
2200
|
-
} : null,
|
|
2201
|
-
onClearSelection: () => setSelection(null),
|
|
2202
|
-
onClearProposal: () => setActiveProposal(null),
|
|
2203
|
-
selectionPrefix: t("copilot_selection_chip", void 0, "Ref"),
|
|
2204
|
-
proposalPrefix: t("copilot_proposal_chip", void 0, "Proposal")
|
|
2205
|
-
}
|
|
2206
|
-
),
|
|
2207
|
-
[activeProposal, selection, setActiveProposal, setSelection, t]
|
|
2208
|
-
);
|
|
2209
|
-
const EASE_OUT = [0, 0, 0.2, 1];
|
|
2210
|
-
const modeMotion = allowMotion ? {
|
|
2211
|
-
initial: { opacity: 0, y: 12 },
|
|
2212
|
-
animate: { opacity: 1, y: 0 },
|
|
2213
|
-
exit: { opacity: 0, y: -12 },
|
|
2214
|
-
transition: { duration: 0.2, ease: EASE_OUT }
|
|
2215
|
-
} : {
|
|
2216
|
-
initial: false,
|
|
2217
|
-
animate: { opacity: 1 },
|
|
2218
|
-
exit: { opacity: 1 },
|
|
2219
|
-
transition: { duration: 0 }
|
|
2220
|
-
};
|
|
2221
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex h-full min-h-0 flex-col", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
2222
|
-
cliReadOnly ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-b border-[var(--lab-border)] px-5 py-2 text-xs text-[var(--lab-text-secondary)]", children: cliStatus === "unbound" ? "Bind an execution server to activate Lab Copilot." : "Your execution server is offline. Messages will be sent once it reconnects." }) : null,
|
|
2223
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(AnimatePresence, { mode: "wait", initial: false, children: [
|
|
2224
|
-
mode === "direct" ? /* @__PURE__ */ jsxRuntimeExports.jsx(motion.div, { className: "flex flex-1 min-h-0 flex-col overflow-hidden", ...modeMotion, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 overflow-hidden", children: [
|
|
2225
|
-
piOnboardingActive ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-pi-onboarding-banner", children: [
|
|
2226
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
2227
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-pi-onboarding-title", children: "Chat with PI" }),
|
|
2228
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-pi-onboarding-subtitle", children: "Send your message to start the conversation." })
|
|
2229
|
-
] }),
|
|
2230
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: endPiOnboarding, children: "Exit" })
|
|
2231
|
-
] }) : null,
|
|
2232
|
-
!activeAgent ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex h-full flex-col items-center justify-center px-6 py-8", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full max-w-3xl", children: [
|
|
2233
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
|
|
2234
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
2235
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-base font-semibold text-[var(--lab-text-primary)]", children: "Choose an agent" }),
|
|
2236
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-[var(--lab-text-secondary)]", children: "Select an agent to start a direct chat." })
|
|
2237
|
-
] }),
|
|
2238
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2239
|
-
Button,
|
|
2240
|
-
{
|
|
2241
|
-
type: "button",
|
|
2242
|
-
size: "sm",
|
|
2243
|
-
className: "h-8 px-3 text-xs",
|
|
2244
|
-
onClick: handleOpenRecruit,
|
|
2245
|
-
disabled: labReadOnly,
|
|
2246
|
-
children: "Recruit Agent"
|
|
2247
|
-
}
|
|
2248
|
-
)
|
|
2249
|
-
] }),
|
|
2250
|
-
agents.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-4 rounded-xl border border-[var(--lab-border)] bg-[var(--lab-surface)] p-4 text-xs text-[var(--lab-text-secondary)]", children: "No agents yet. Recruit an agent to begin a direct chat." }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2", children: agents.map((agent, index) => {
|
|
2251
|
-
const template = agent.template_id ? templatesById.get(agent.template_id) ?? null : null;
|
|
2252
|
-
const displayName = resolveAgentDisplayName(agent);
|
|
2253
|
-
const mentionLabel = resolveAgentMentionLabel(agent);
|
|
2254
|
-
const avatarColor = avatarColors.get(agent.instance_id) ?? pickAvatarFrameColor(agent.instance_id, index);
|
|
2255
|
-
const logoPath = resolveAgentLogo(agent, template);
|
|
2256
|
-
const statusLabel = typeof agent.status === "string" ? agent.status.toLowerCase() : "idle";
|
|
2257
|
-
const isWorking = statusLabel !== "waiting" && isLabWorkingStatus(statusLabel);
|
|
2258
|
-
const statusClass = isWorking ? "lab-status-dot-running" : statusLabel === "waiting" ? "lab-status-dot-busy" : "lab-status-dot-idle";
|
|
2259
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2260
|
-
"button",
|
|
2261
|
-
{
|
|
2262
|
-
type: "button",
|
|
2263
|
-
className: "text-left",
|
|
2264
|
-
onClick: () => handleSelectAgent(agent.instance_id),
|
|
2265
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-card lab-card-hover flex items-center gap-3 rounded-xl border border-[var(--lab-border)] bg-[var(--lab-surface)] p-3", children: [
|
|
2266
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-avatar lab-avatar-sm", children: [
|
|
2267
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-avatar-ring", style: { borderColor: avatarColor } }),
|
|
2268
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: logoPath, alt: displayName })
|
|
2269
|
-
] }),
|
|
2270
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
2271
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-semibold text-[var(--lab-text-primary)] truncate", children: displayName }),
|
|
2272
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-[var(--lab-text-secondary)] truncate", children: mentionLabel })
|
|
2273
|
-
] }),
|
|
2274
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-[10px] uppercase text-[var(--lab-text-muted)]", children: [
|
|
2275
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `lab-status-dot ${statusClass}` }),
|
|
2276
|
-
isWorking ? "Working" : statusLabel === "waiting" ? "Waiting" : "Idle"
|
|
2277
|
-
] })
|
|
2278
|
-
] })
|
|
2279
|
-
},
|
|
2280
|
-
agent.instance_id
|
|
2281
|
-
);
|
|
2282
|
-
}) })
|
|
2283
|
-
] }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2284
|
-
LabDirectChatView,
|
|
2285
|
-
{
|
|
2286
|
-
projectId,
|
|
2287
|
-
readOnly: labReadOnly,
|
|
2288
|
-
prefill: resolvedDirectPrefill,
|
|
2289
|
-
leadMessage: useStarterPrompt ? starterMessage : null,
|
|
2290
|
-
mentionablesOverride,
|
|
2291
|
-
defaultAgentOverride,
|
|
2292
|
-
mentionsEnabledOverride: mentionsEnabled,
|
|
2293
|
-
enforcedMentionPrefix,
|
|
2294
|
-
lockedMentionPrefix,
|
|
2295
|
-
messageMetadata: directMetadata ?? void 0,
|
|
2296
|
-
composerFooter: controlReferenceFooter,
|
|
2297
|
-
hideCopilotGreeting: useStarterPrompt,
|
|
2298
|
-
busyOverride: activeAgentWorking,
|
|
2299
|
-
onActionsChange: handleActionsChange,
|
|
2300
|
-
onMetaChange: handleMetaChange,
|
|
2301
|
-
onUserSubmit: handleDirectSubmit
|
|
2302
|
-
}
|
|
2303
|
-
)
|
|
2304
|
-
] }) }, "direct") : null,
|
|
2305
|
-
mode === "group" ? /* @__PURE__ */ jsxRuntimeExports.jsx(motion.div, { className: "flex flex-1 min-h-0 flex-col overflow-hidden", ...modeMotion, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2306
|
-
LabGroupChatView,
|
|
2307
|
-
{
|
|
2308
|
-
projectId,
|
|
2309
|
-
quest: activeQuest,
|
|
2310
|
-
quests,
|
|
2311
|
-
agents,
|
|
2312
|
-
templatesById,
|
|
2313
|
-
readOnly: labReadOnly,
|
|
2314
|
-
onQuestChange: setActiveQuest
|
|
2315
|
-
}
|
|
2316
|
-
) }, "group") : null,
|
|
2317
|
-
mode === "friends" ? /* @__PURE__ */ jsxRuntimeExports.jsx(motion.div, { className: "flex flex-1 min-h-0 flex-col overflow-hidden", ...modeMotion, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2318
|
-
LabFriendsFeed,
|
|
2319
|
-
{
|
|
2320
|
-
projectId,
|
|
2321
|
-
agents,
|
|
2322
|
-
templatesById,
|
|
2323
|
-
readOnly: labReadOnly,
|
|
2324
|
-
quest: activeQuest,
|
|
2325
|
-
quests,
|
|
2326
|
-
onQuestChange: setActiveQuest
|
|
2327
|
-
}
|
|
2328
|
-
) }, "friends") : null
|
|
2329
|
-
] })
|
|
2330
|
-
] }) });
|
|
2331
|
-
}
|
|
2332
|
-
const buildSearchSnippet = (content, query, maxLength = 90) => {
|
|
2333
|
-
const trimmed = content.trim();
|
|
2334
|
-
if (!trimmed) return "";
|
|
2335
|
-
const normalizedQuery = query.trim().toLowerCase();
|
|
2336
|
-
if (!normalizedQuery) return trimmed.slice(0, maxLength);
|
|
2337
|
-
const lower = trimmed.toLowerCase();
|
|
2338
|
-
const matchIndex = lower.indexOf(normalizedQuery);
|
|
2339
|
-
if (matchIndex === -1) return trimmed.slice(0, maxLength);
|
|
2340
|
-
const half = Math.floor(maxLength / 2);
|
|
2341
|
-
const start = Math.max(0, matchIndex - half);
|
|
2342
|
-
const end = Math.min(trimmed.length, matchIndex + normalizedQuery.length + half);
|
|
2343
|
-
const prefix = start > 0 ? "..." : "";
|
|
2344
|
-
const suffix = end < trimmed.length ? "..." : "";
|
|
2345
|
-
return `${prefix}${trimmed.slice(start, end)}${suffix}`;
|
|
2346
|
-
};
|
|
2347
|
-
function LabCopilotOverflowMenu({
|
|
2348
|
-
label,
|
|
2349
|
-
entities,
|
|
2350
|
-
emptyEntitiesLabel,
|
|
2351
|
-
searchValue,
|
|
2352
|
-
onSearchChange,
|
|
2353
|
-
searchResults,
|
|
2354
|
-
onSearchSelect,
|
|
2355
|
-
searchPlaceholder,
|
|
2356
|
-
questId,
|
|
2357
|
-
quests,
|
|
2358
|
-
onQuestChange,
|
|
2359
|
-
canManageRoster,
|
|
2360
|
-
rosterBusy,
|
|
2361
|
-
availableAgents,
|
|
2362
|
-
onAddAgents,
|
|
2363
|
-
onRemoveAgent
|
|
2364
|
-
}) {
|
|
2365
|
-
const hasSearch = searchValue.trim().length > 0;
|
|
2366
|
-
const manageEnabled = Boolean(canManageRoster && !rosterBusy);
|
|
2367
|
-
const availableList = availableAgents ?? [];
|
|
2368
|
-
const [addOpen, setAddOpen] = reactExports.useState(false);
|
|
2369
|
-
const [selectedAgentIds, setSelectedAgentIds] = reactExports.useState(/* @__PURE__ */ new Set());
|
|
2370
|
-
const [isAdding, setIsAdding] = reactExports.useState(false);
|
|
2371
|
-
const [contextMenu, setContextMenu] = reactExports.useState(null);
|
|
2372
|
-
reactExports.useEffect(() => {
|
|
2373
|
-
if (addOpen) return;
|
|
2374
|
-
setSelectedAgentIds(/* @__PURE__ */ new Set());
|
|
2375
|
-
}, [addOpen]);
|
|
2376
|
-
reactExports.useEffect(() => {
|
|
2377
|
-
setSelectedAgentIds(/* @__PURE__ */ new Set());
|
|
2378
|
-
}, [questId]);
|
|
2379
|
-
reactExports.useEffect(() => {
|
|
2380
|
-
if (!contextMenu) return;
|
|
2381
|
-
const handleDismiss = () => setContextMenu(null);
|
|
2382
|
-
const handleKey = (event) => {
|
|
2383
|
-
if (event.key === "Escape") {
|
|
2384
|
-
setContextMenu(null);
|
|
2385
|
-
}
|
|
2386
|
-
};
|
|
2387
|
-
window.addEventListener("click", handleDismiss);
|
|
2388
|
-
window.addEventListener("contextmenu", handleDismiss);
|
|
2389
|
-
window.addEventListener("keydown", handleKey);
|
|
2390
|
-
window.addEventListener("scroll", handleDismiss, true);
|
|
2391
|
-
return () => {
|
|
2392
|
-
window.removeEventListener("click", handleDismiss);
|
|
2393
|
-
window.removeEventListener("contextmenu", handleDismiss);
|
|
2394
|
-
window.removeEventListener("keydown", handleKey);
|
|
2395
|
-
window.removeEventListener("scroll", handleDismiss, true);
|
|
2396
|
-
};
|
|
2397
|
-
}, [contextMenu]);
|
|
2398
|
-
const toggleAgentSelection = reactExports.useCallback((agentId) => {
|
|
2399
|
-
setSelectedAgentIds((prev) => {
|
|
2400
|
-
const next = new Set(prev);
|
|
2401
|
-
if (next.has(agentId)) {
|
|
2402
|
-
next.delete(agentId);
|
|
2403
|
-
} else {
|
|
2404
|
-
next.add(agentId);
|
|
2405
|
-
}
|
|
2406
|
-
return next;
|
|
2407
|
-
});
|
|
2408
|
-
}, []);
|
|
2409
|
-
const handleAddConfirm = reactExports.useCallback(async () => {
|
|
2410
|
-
if (!manageEnabled || !onAddAgents) return;
|
|
2411
|
-
const agentIds = Array.from(selectedAgentIds);
|
|
2412
|
-
if (agentIds.length === 0) return;
|
|
2413
|
-
setIsAdding(true);
|
|
2414
|
-
try {
|
|
2415
|
-
await onAddAgents(agentIds);
|
|
2416
|
-
setAddOpen(false);
|
|
2417
|
-
setSelectedAgentIds(/* @__PURE__ */ new Set());
|
|
2418
|
-
} finally {
|
|
2419
|
-
setIsAdding(false);
|
|
2420
|
-
}
|
|
2421
|
-
}, [manageEnabled, onAddAgents, selectedAgentIds]);
|
|
2422
|
-
const handleContextMenu = reactExports.useCallback(
|
|
2423
|
-
(event, agentId) => {
|
|
2424
|
-
if (!manageEnabled || !onRemoveAgent) return;
|
|
2425
|
-
event.preventDefault();
|
|
2426
|
-
event.stopPropagation();
|
|
2427
|
-
setContextMenu({ agentId, x: event.clientX, y: event.clientY });
|
|
2428
|
-
},
|
|
2429
|
-
[manageEnabled, onRemoveAgent]
|
|
2430
|
-
);
|
|
2431
|
-
const handleRemove = reactExports.useCallback(() => {
|
|
2432
|
-
if (!contextMenu || !onRemoveAgent || !manageEnabled) return;
|
|
2433
|
-
onRemoveAgent(contextMenu.agentId);
|
|
2434
|
-
setContextMenu(null);
|
|
2435
|
-
}, [contextMenu, manageEnabled, onRemoveAgent]);
|
|
2436
|
-
const contextMenuPortal = contextMenu && typeof document !== "undefined" ? reactDomExports.createPortal(
|
|
2437
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-copilot-context-menu", style: { left: contextMenu.x, top: contextMenu.y }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: handleRemove, disabled: !manageEnabled, children: "Remove from quest" }) }),
|
|
2438
|
-
document.body
|
|
2439
|
-
) : null;
|
|
2440
|
-
const canShowAdd = Boolean(onAddAgents);
|
|
2441
|
-
const addDisabled = !manageEnabled || availableList.length === 0;
|
|
2442
|
-
const addHint = !questId ? "Select a quest to add agents." : availableList.length === 0 ? "All available agents are already in this quest." : "Add agents to this quest.";
|
|
2443
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Popover, { modal: false, children: [
|
|
2444
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2445
|
-
"button",
|
|
2446
|
-
{
|
|
2447
|
-
type: "button",
|
|
2448
|
-
className: "ds-copilot-icon-btn lab-copilot-overflow-trigger",
|
|
2449
|
-
"aria-label": `${label} options`,
|
|
2450
|
-
"data-tooltip": `${label} options`,
|
|
2451
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Ellipsis, { size: 16 })
|
|
2452
|
-
}
|
|
2453
|
-
) }),
|
|
2454
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(PopoverContent, { align: "end", sideOffset: 12, className: "lab-copilot-menu", children: [
|
|
2455
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-copilot-menu-section", children: [
|
|
2456
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-copilot-menu-title", children: [
|
|
2457
|
-
label,
|
|
2458
|
-
" roster"
|
|
2459
|
-
] }),
|
|
2460
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-copilot-entity-list", children: [
|
|
2461
|
-
entities.map((entity) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
2462
|
-
"div",
|
|
2463
|
-
{
|
|
2464
|
-
className: cn("lab-copilot-entity", manageEnabled && "is-manageable"),
|
|
2465
|
-
onContextMenu: (event) => handleContextMenu(event, entity.id),
|
|
2466
|
-
children: [
|
|
2467
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-avatar lab-avatar-sm", children: /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: entity.avatar, alt: entity.name }) }),
|
|
2468
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-copilot-entity-name", title: entity.name, children: entity.name })
|
|
2469
|
-
]
|
|
2470
|
-
},
|
|
2471
|
-
entity.id
|
|
2472
|
-
)),
|
|
2473
|
-
canShowAdd ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2474
|
-
"button",
|
|
2475
|
-
{
|
|
2476
|
-
type: "button",
|
|
2477
|
-
className: "lab-copilot-entity lab-copilot-entity-add",
|
|
2478
|
-
onClick: () => setAddOpen(true),
|
|
2479
|
-
disabled: addDisabled,
|
|
2480
|
-
title: addHint,
|
|
2481
|
-
"aria-label": "Add agents",
|
|
2482
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 14 })
|
|
2483
|
-
}
|
|
2484
|
-
) : null
|
|
2485
|
-
] }),
|
|
2486
|
-
!entities.length ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-copilot-menu-muted", children: emptyEntitiesLabel }) : null
|
|
2487
|
-
] }),
|
|
2488
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-copilot-menu-section", children: [
|
|
2489
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-copilot-menu-title", children: "Search chat" }),
|
|
2490
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-copilot-search", children: [
|
|
2491
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Search, { size: 14 }),
|
|
2492
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2493
|
-
"input",
|
|
2494
|
-
{
|
|
2495
|
-
type: "text",
|
|
2496
|
-
value: searchValue,
|
|
2497
|
-
onChange: (event) => onSearchChange(event.target.value),
|
|
2498
|
-
placeholder: searchPlaceholder,
|
|
2499
|
-
className: "lab-copilot-search-input"
|
|
2500
|
-
}
|
|
2501
|
-
)
|
|
2502
|
-
] }),
|
|
2503
|
-
hasSearch ? searchResults.length ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-copilot-search-results", children: searchResults.map((result) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
2504
|
-
"button",
|
|
2505
|
-
{
|
|
2506
|
-
type: "button",
|
|
2507
|
-
className: "lab-copilot-search-result",
|
|
2508
|
-
onClick: () => onSearchSelect(result),
|
|
2509
|
-
children: [
|
|
2510
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-copilot-search-title", children: result.title }),
|
|
2511
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-copilot-search-excerpt", children: result.excerpt })
|
|
2512
|
-
]
|
|
2513
|
-
},
|
|
2514
|
-
result.id
|
|
2515
|
-
)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-copilot-menu-muted", children: "No matches found." }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-copilot-menu-muted", children: "Type to search this chat." })
|
|
2516
|
-
] }),
|
|
2517
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-copilot-menu-section", children: [
|
|
2518
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-copilot-menu-title", children: "Switch quest" }),
|
|
2519
|
-
quests.length ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { value: questId ?? "", onValueChange: (value) => onQuestChange(value || null), children: [
|
|
2520
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { className: "lab-copilot-menu-select", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: "Select quest" }) }),
|
|
2521
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(SelectContent, { children: quests.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: item.quest_id, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block max-w-[var(--radix-select-trigger-width)] line-clamp-2", children: resolveQuestLabel(item) }) }, item.quest_id)) })
|
|
2522
|
-
] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-copilot-menu-muted", children: "No quests available." })
|
|
2523
|
-
] })
|
|
2524
|
-
] }),
|
|
2525
|
-
contextMenuPortal,
|
|
2526
|
-
canShowAdd ? /* @__PURE__ */ jsxRuntimeExports.jsx(Dialog, { open: addOpen, onOpenChange: setAddOpen, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { className: "lab-copilot-add-dialog", showCloseButton: true, children: [
|
|
2527
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogHeader, { children: [
|
|
2528
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle, { className: "lab-copilot-add-title", children: "Add agents" }),
|
|
2529
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-copilot-add-subtitle", children: questId ? "Select agents to join this quest." : "Select a quest first to add agents." })
|
|
2530
|
-
] }),
|
|
2531
|
-
availableList.length ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-copilot-add-list", children: availableList.map((agent) => {
|
|
2532
|
-
const selected = selectedAgentIds.has(agent.id);
|
|
2533
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
2534
|
-
"button",
|
|
2535
|
-
{
|
|
2536
|
-
type: "button",
|
|
2537
|
-
className: cn("lab-copilot-add-row", selected && "is-selected"),
|
|
2538
|
-
onClick: () => toggleAgentSelection(agent.id),
|
|
2539
|
-
children: [
|
|
2540
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-copilot-add-check", children: selected ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 12 }) : null }),
|
|
2541
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-avatar lab-avatar-sm", children: /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: agent.avatar, alt: agent.name }) }),
|
|
2542
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-copilot-add-name", title: agent.name, children: agent.name })
|
|
2543
|
-
]
|
|
2544
|
-
},
|
|
2545
|
-
agent.id
|
|
2546
|
-
);
|
|
2547
|
-
}) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-copilot-add-empty", children: questId ? "No available agents to add right now." : "Select a quest to see available agents." }),
|
|
2548
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogFooter, { className: "lab-copilot-add-footer", children: [
|
|
2549
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2550
|
-
Button,
|
|
2551
|
-
{
|
|
2552
|
-
variant: "outline",
|
|
2553
|
-
size: "sm",
|
|
2554
|
-
className: "min-h-[40px] px-4 text-xs",
|
|
2555
|
-
onClick: () => setAddOpen(false),
|
|
2556
|
-
children: "Cancel"
|
|
2557
|
-
}
|
|
2558
|
-
),
|
|
2559
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
2560
|
-
Button,
|
|
2561
|
-
{
|
|
2562
|
-
size: "sm",
|
|
2563
|
-
className: "min-h-[40px] px-4 text-xs",
|
|
2564
|
-
onClick: handleAddConfirm,
|
|
2565
|
-
disabled: addDisabled || selectedAgentIds.size === 0 || isAdding,
|
|
2566
|
-
children: isAdding ? "Adding..." : "Add to quest"
|
|
2567
|
-
}
|
|
2568
|
-
)
|
|
2569
|
-
] })
|
|
2570
|
-
] }) }) : null
|
|
2571
|
-
] });
|
|
2572
|
-
}
|
|
2573
|
-
function LabGroupChatView({
|
|
2574
|
-
projectId,
|
|
2575
|
-
quest,
|
|
2576
|
-
quests,
|
|
2577
|
-
agents,
|
|
2578
|
-
templatesById,
|
|
2579
|
-
readOnly,
|
|
2580
|
-
onQuestChange
|
|
2581
|
-
}) {
|
|
2582
|
-
const { t } = useI18n("lab");
|
|
2583
|
-
const { addToast } = useToast();
|
|
2584
|
-
const queryClient = useQueryClient();
|
|
2585
|
-
const [input, setInput] = reactExports.useState("");
|
|
2586
|
-
const [attachments, setAttachments] = reactExports.useState([]);
|
|
2587
|
-
const [rosterBusy, setRosterBusy] = reactExports.useState(false);
|
|
2588
|
-
const [highlightedMessageId, setHighlightedMessageId] = reactExports.useState(null);
|
|
2589
|
-
const [messageContextMenu, setMessageContextMenu] = reactExports.useState(null);
|
|
2590
|
-
const lastSeenMessageIdRef = reactExports.useRef(null);
|
|
2591
|
-
const scrollRef = reactExports.useRef(null);
|
|
2592
|
-
const [follow, setFollow] = reactExports.useState(true);
|
|
2593
|
-
const groupPrefill = useLabCopilotStore((state) => state.groupPrefill);
|
|
2594
|
-
const setGroupPrefill = useLabCopilotStore((state) => state.setGroupPrefill);
|
|
2595
|
-
const selection = useLabGraphSelectionStore((state) => state.selection);
|
|
2596
|
-
const activeProposal = useLabGraphSelectionStore((state) => state.activeProposal);
|
|
2597
|
-
const setSelection = useLabGraphSelectionStore((state) => state.setSelection);
|
|
2598
|
-
const setActiveProposal = useLabGraphSelectionStore((state) => state.setActiveProposal);
|
|
2599
|
-
const setMode = useLabCopilotStore((state) => state.setMode);
|
|
2600
|
-
const setActiveAgent = useLabCopilotStore((state) => state.setActiveAgent);
|
|
2601
|
-
const setSessionIdForSurface = useChatSessionStore((state) => state.setSessionIdForSurface);
|
|
2602
|
-
const user = useAuthStore((state) => state.user);
|
|
2603
|
-
const headerPortalTarget = useCopilotDockHeaderPortal();
|
|
2604
|
-
const [searchQuery, setSearchQuery] = reactExports.useState("");
|
|
2605
|
-
const questId = quest?.quest_id ?? null;
|
|
2606
|
-
const {
|
|
2607
|
-
sessionId,
|
|
2608
|
-
messages,
|
|
2609
|
-
connection,
|
|
2610
|
-
sendMessage,
|
|
2611
|
-
historyTruncated,
|
|
2612
|
-
historyLimit,
|
|
2613
|
-
historyLoadingFull,
|
|
2614
|
-
historyLoading,
|
|
2615
|
-
hasLoadedOnce,
|
|
2616
|
-
loadFullHistory
|
|
2617
|
-
} = useLabSurfaceSession({
|
|
2618
|
-
projectId,
|
|
2619
|
-
questId,
|
|
2620
|
-
surface: "group",
|
|
2621
|
-
enabled: Boolean(questId)
|
|
2622
|
-
});
|
|
2623
|
-
const agentsById = reactExports.useMemo(() => new Map(agents.map((agent) => [agent.instance_id, agent])), [agents]);
|
|
2624
|
-
const questAgents = reactExports.useMemo(() => {
|
|
2625
|
-
if (!questId) return [];
|
|
2626
|
-
return agents.filter((agent) => agent.active_quest_id === questId);
|
|
2627
|
-
}, [agents, questId]);
|
|
2628
|
-
const questAgentIds = reactExports.useMemo(
|
|
2629
|
-
() => questAgents.map((agent) => agent.instance_id),
|
|
2630
|
-
[questAgents]
|
|
2631
|
-
);
|
|
2632
|
-
const mentionLookup = reactExports.useMemo(() => {
|
|
2633
|
-
const map = /* @__PURE__ */ new Map();
|
|
2634
|
-
questAgents.forEach((agent) => {
|
|
2635
|
-
const mentionLabel = resolveAgentMentionLabel(agent);
|
|
2636
|
-
const candidates = /* @__PURE__ */ new Set();
|
|
2637
|
-
if (mentionLabel) {
|
|
2638
|
-
candidates.add(mentionLabel);
|
|
2639
|
-
candidates.add(mentionLabel.replace(/^@/, ""));
|
|
2640
|
-
}
|
|
2641
|
-
if (agent.agent_id) {
|
|
2642
|
-
candidates.add(agent.agent_id);
|
|
2643
|
-
}
|
|
2644
|
-
if (agent.display_name) {
|
|
2645
|
-
candidates.add(agent.display_name);
|
|
2646
|
-
}
|
|
2647
|
-
candidates.forEach((candidate) => {
|
|
2648
|
-
const normalized = candidate.trim().toLowerCase();
|
|
2649
|
-
if (!normalized) return;
|
|
2650
|
-
if (!map.has(normalized)) {
|
|
2651
|
-
map.set(normalized, agent.instance_id);
|
|
2652
|
-
}
|
|
2653
|
-
});
|
|
2654
|
-
});
|
|
2655
|
-
return map;
|
|
2656
|
-
}, [questAgents]);
|
|
2657
|
-
const mentionables = reactExports.useMemo(() => {
|
|
2658
|
-
if (!questId) return [];
|
|
2659
|
-
const allDescriptor = {
|
|
2660
|
-
id: "all",
|
|
2661
|
-
label: "@ALL",
|
|
2662
|
-
description: "Notify all agents in this quest",
|
|
2663
|
-
role: "broadcast",
|
|
2664
|
-
source: "lab"
|
|
2665
|
-
};
|
|
2666
|
-
const entries = questAgents.map((agent) => {
|
|
2667
|
-
const template = agent.template_id ? templatesById.get(agent.template_id) ?? null : null;
|
|
2668
|
-
return buildAgentDescriptor(agent, template);
|
|
2669
|
-
});
|
|
2670
|
-
return [allDescriptor, ...entries];
|
|
2671
|
-
}, [questAgents, questId, templatesById]);
|
|
2672
|
-
const menuEntities = reactExports.useMemo(() => {
|
|
2673
|
-
return questAgents.map((agent) => {
|
|
2674
|
-
const template = agent.template_id ? templatesById.get(agent.template_id) ?? null : null;
|
|
2675
|
-
return {
|
|
2676
|
-
id: agent.instance_id,
|
|
2677
|
-
name: resolveAgentDisplayName(agent),
|
|
2678
|
-
avatar: resolveAgentLogo(agent, template)
|
|
2679
|
-
};
|
|
2680
|
-
});
|
|
2681
|
-
}, [questAgents, templatesById]);
|
|
2682
|
-
const availableAgents = reactExports.useMemo(() => {
|
|
2683
|
-
if (!questId) return [];
|
|
2684
|
-
return agents.filter((agent) => agent.active_quest_id !== questId).map((agent) => {
|
|
2685
|
-
const template = agent.template_id ? templatesById.get(agent.template_id) ?? null : null;
|
|
2686
|
-
return {
|
|
2687
|
-
id: agent.instance_id,
|
|
2688
|
-
name: resolveAgentDisplayName(agent),
|
|
2689
|
-
avatar: resolveAgentLogo(agent, template)
|
|
2690
|
-
};
|
|
2691
|
-
});
|
|
2692
|
-
}, [agents, questId, templatesById]);
|
|
2693
|
-
const emptyStateLabel = reactExports.useMemo(() => {
|
|
2694
|
-
if (quests.length === 0) return "Create a quest to get started.";
|
|
2695
|
-
return "Select a quest to start collaborating.";
|
|
2696
|
-
}, [quests.length]);
|
|
2697
|
-
const emptyRosterLabel = questId ? "No agents assigned to this quest yet." : "Select a quest to see its agents.";
|
|
2698
|
-
const connectionStatus = reactExports.useMemo(() => {
|
|
2699
|
-
if (connection.status === "rate_limited") return "Rate limited. Retrying...";
|
|
2700
|
-
if (connection.status === "reconnecting") return "Reconnecting...";
|
|
2701
|
-
if (connection.status === "error") return connection.error || "Connection error";
|
|
2702
|
-
return null;
|
|
2703
|
-
}, [connection.error, connection.status]);
|
|
2704
|
-
const showLoadFullHistory = historyTruncated && Boolean(sessionId);
|
|
2705
|
-
const showHistoryLoadingOverlay = Boolean(questId) && historyLoading && messages.length === 0 && !hasLoadedOnce;
|
|
2706
|
-
const historyLabel = typeof historyLimit === "number" && historyLimit > 0 ? `Showing latest ${historyLimit} messages.` : "Showing recent messages.";
|
|
2707
|
-
reactExports.useMemo(() => {
|
|
2708
|
-
const raw = user?.username || user?.email || user?.id || "You";
|
|
2709
|
-
const trimmed = raw?.trim?.() ?? "";
|
|
2710
|
-
return trimmed || "You";
|
|
2711
|
-
}, [user?.email, user?.id, user?.username]);
|
|
2712
|
-
reactExports.useEffect(() => {
|
|
2713
|
-
setSearchQuery("");
|
|
2714
|
-
setHighlightedMessageId(null);
|
|
2715
|
-
lastSeenMessageIdRef.current = null;
|
|
2716
|
-
setFollow(true);
|
|
2717
|
-
setInput("");
|
|
2718
|
-
}, [questId]);
|
|
2719
|
-
reactExports.useEffect(() => {
|
|
2720
|
-
if (!messageContextMenu) return;
|
|
2721
|
-
const handleDismiss = () => setMessageContextMenu(null);
|
|
2722
|
-
const handleKey = (event) => {
|
|
2723
|
-
if (event.key === "Escape") {
|
|
2724
|
-
setMessageContextMenu(null);
|
|
2725
|
-
}
|
|
2726
|
-
};
|
|
2727
|
-
window.addEventListener("click", handleDismiss);
|
|
2728
|
-
window.addEventListener("contextmenu", handleDismiss);
|
|
2729
|
-
window.addEventListener("keydown", handleKey);
|
|
2730
|
-
window.addEventListener("scroll", handleDismiss, true);
|
|
2731
|
-
return () => {
|
|
2732
|
-
window.removeEventListener("click", handleDismiss);
|
|
2733
|
-
window.removeEventListener("contextmenu", handleDismiss);
|
|
2734
|
-
window.removeEventListener("keydown", handleKey);
|
|
2735
|
-
window.removeEventListener("scroll", handleDismiss, true);
|
|
2736
|
-
};
|
|
2737
|
-
}, [messageContextMenu]);
|
|
2738
|
-
reactExports.useEffect(() => {
|
|
2739
|
-
if (!groupPrefill) return;
|
|
2740
|
-
if (!input.trim()) {
|
|
2741
|
-
setInput(groupPrefill.trim() ? `${groupPrefill} ` : "");
|
|
2742
|
-
}
|
|
2743
|
-
setGroupPrefill(null);
|
|
2744
|
-
}, [groupPrefill, input, setGroupPrefill]);
|
|
2745
|
-
const stripLeadingMentions = reactExports.useCallback((raw) => {
|
|
2746
|
-
const trimmed = raw.trim();
|
|
2747
|
-
if (!trimmed) return "";
|
|
2748
|
-
const parts = trimmed.split(/\s+/);
|
|
2749
|
-
let index = 0;
|
|
2750
|
-
while (index < parts.length) {
|
|
2751
|
-
const token = parts[index];
|
|
2752
|
-
if (!token || !token.startsWith("@") || token.length === 1) break;
|
|
2753
|
-
index += 1;
|
|
2754
|
-
}
|
|
2755
|
-
return parts.slice(index).join(" ").trim();
|
|
2756
|
-
}, []);
|
|
2757
|
-
const parseGroupInput = reactExports.useCallback(
|
|
2758
|
-
(raw) => {
|
|
2759
|
-
const text = raw.trim();
|
|
2760
|
-
const mentionPattern = /@([A-Za-z0-9_.-]+)/g;
|
|
2761
|
-
const targets = /* @__PURE__ */ new Set();
|
|
2762
|
-
const missing = /* @__PURE__ */ new Set();
|
|
2763
|
-
let hasAll = false;
|
|
2764
|
-
let match;
|
|
2765
|
-
while ((match = mentionPattern.exec(text)) !== null) {
|
|
2766
|
-
const token = match[1]?.trim();
|
|
2767
|
-
if (!token) continue;
|
|
2768
|
-
const lowered = token.toLowerCase();
|
|
2769
|
-
if (lowered === "all") {
|
|
2770
|
-
hasAll = true;
|
|
2771
|
-
continue;
|
|
2772
|
-
}
|
|
2773
|
-
const directKey = lowered.startsWith("@") ? lowered : `@${lowered}`;
|
|
2774
|
-
const targetId = mentionLookup.get(directKey) ?? mentionLookup.get(lowered);
|
|
2775
|
-
if (targetId) {
|
|
2776
|
-
targets.add(targetId);
|
|
2777
|
-
} else {
|
|
2778
|
-
missing.add(`@${token}`);
|
|
2779
|
-
}
|
|
2780
|
-
}
|
|
2781
|
-
if (hasAll) {
|
|
2782
|
-
questAgentIds.forEach((agentId) => targets.add(agentId));
|
|
2783
|
-
missing.clear();
|
|
2784
|
-
}
|
|
2785
|
-
const stripped = stripLeadingMentions(text);
|
|
2786
|
-
const content = stripped || (targets.size > 0 || hasAll ? "" : text);
|
|
2787
|
-
return {
|
|
2788
|
-
content,
|
|
2789
|
-
targets: Array.from(targets),
|
|
2790
|
-
missing: Array.from(missing)
|
|
2791
|
-
};
|
|
2792
|
-
},
|
|
2793
|
-
[mentionLookup, questAgentIds, stripLeadingMentions]
|
|
2794
|
-
);
|
|
2795
|
-
const resolveMentionLabel = reactExports.useCallback(
|
|
2796
|
-
(agentInstanceId) => {
|
|
2797
|
-
const agent = agentsById.get(agentInstanceId);
|
|
2798
|
-
if (!agent) return "";
|
|
2799
|
-
return resolveAgentMentionLabel(agent);
|
|
2800
|
-
},
|
|
2801
|
-
[agentsById]
|
|
2802
|
-
);
|
|
2803
|
-
const handleSend = async () => {
|
|
2804
|
-
if (!questId || !input.trim() || readOnly || !sessionId) return;
|
|
2805
|
-
try {
|
|
2806
|
-
const parsed = parseGroupInput(input);
|
|
2807
|
-
if (parsed.missing.length > 0) {
|
|
2808
|
-
addToast({
|
|
2809
|
-
type: "error",
|
|
2810
|
-
title: "Unknown agent",
|
|
2811
|
-
description: `Unknown mention: ${parsed.missing.join(", ")}`
|
|
2812
|
-
});
|
|
2813
|
-
return;
|
|
2814
|
-
}
|
|
2815
|
-
if (parsed.targets.length === 0) {
|
|
2816
|
-
addToast({
|
|
2817
|
-
type: "error",
|
|
2818
|
-
title: "Mention required",
|
|
2819
|
-
description: "Mention a quest agent to start a group request."
|
|
2820
|
-
});
|
|
2821
|
-
return;
|
|
2822
|
-
}
|
|
2823
|
-
setFollow(true);
|
|
2824
|
-
await sendMessage({
|
|
2825
|
-
sessionId,
|
|
2826
|
-
message: parsed.content,
|
|
2827
|
-
surface: "lab-group",
|
|
2828
|
-
mentionTargets: parsed.targets,
|
|
2829
|
-
metadata: {
|
|
2830
|
-
selection_context: toSelectionMetadata(selection),
|
|
2831
|
-
proposal_id: activeProposal?.proposal_id ?? void 0,
|
|
2832
|
-
message_kind: activeProposal || selection ? "user_control" : parsed.targets.length > 0 ? "group_request" : "text",
|
|
2833
|
-
target_label: parsed.targets.length === 1 ? resolveMentionLabel(parsed.targets[0]) || void 0 : void 0
|
|
2834
|
-
}
|
|
2835
|
-
});
|
|
2836
|
-
setInput("");
|
|
2837
|
-
setAttachments([]);
|
|
2838
|
-
} catch {
|
|
2839
|
-
addToast({
|
|
2840
|
-
type: "error",
|
|
2841
|
-
title: "Message failed",
|
|
2842
|
-
description: "Message couldn't be sent. Please check your connection and try again."
|
|
2843
|
-
});
|
|
2844
|
-
}
|
|
2845
|
-
};
|
|
2846
|
-
reactExports.useEffect(() => {
|
|
2847
|
-
const latest = messages[messages.length - 1];
|
|
2848
|
-
if (!latest?.id) return;
|
|
2849
|
-
if (!lastSeenMessageIdRef.current) {
|
|
2850
|
-
lastSeenMessageIdRef.current = latest.id;
|
|
2851
|
-
return;
|
|
2852
|
-
}
|
|
2853
|
-
if (latest.id !== lastSeenMessageIdRef.current) {
|
|
2854
|
-
lastSeenMessageIdRef.current = latest.id;
|
|
2855
|
-
setHighlightedMessageId(latest.id);
|
|
2856
|
-
const timer = window.setTimeout(() => setHighlightedMessageId(null), 1200);
|
|
2857
|
-
return () => window.clearTimeout(timer);
|
|
2858
|
-
}
|
|
2859
|
-
}, [messages]);
|
|
2860
|
-
const resolveGroupMentionPrefix = reactExports.useCallback(
|
|
2861
|
-
(message) => {
|
|
2862
|
-
const metadata = getMessageMetadata(message);
|
|
2863
|
-
const targets = Array.isArray(metadata?.mention_targets) ? metadata?.mention_targets : [];
|
|
2864
|
-
if (!targets.length) return "";
|
|
2865
|
-
const labels = targets.map((agentId) => resolveMentionLabel(agentId)).filter((label) => Boolean(label));
|
|
2866
|
-
if (!labels.length) return "";
|
|
2867
|
-
return Array.from(new Set(labels)).join(" ");
|
|
2868
|
-
},
|
|
2869
|
-
[resolveMentionLabel]
|
|
2870
|
-
);
|
|
2871
|
-
const resolveGroupDisplayText = reactExports.useCallback(
|
|
2872
|
-
(message) => {
|
|
2873
|
-
const base = getMessageText(message);
|
|
2874
|
-
if (getMessageRole(message) !== "user") return base;
|
|
2875
|
-
const prefix = resolveGroupMentionPrefix(message);
|
|
2876
|
-
if (!prefix) return base;
|
|
2877
|
-
return base ? `${prefix} ${base}` : prefix;
|
|
2878
|
-
},
|
|
2879
|
-
[resolveGroupMentionPrefix]
|
|
2880
|
-
);
|
|
2881
|
-
const resolveGroupDisplayMessage = reactExports.useCallback(
|
|
2882
|
-
(message) => {
|
|
2883
|
-
if (getMessageRole(message) !== "user") return message;
|
|
2884
|
-
const base = getMessageText(message);
|
|
2885
|
-
const nextText = resolveGroupDisplayText(message);
|
|
2886
|
-
if (!nextText || nextText === base) return message;
|
|
2887
|
-
const content = message.content;
|
|
2888
|
-
if (!content || typeof content !== "object" || typeof content.content !== "string") {
|
|
2889
|
-
return message;
|
|
2890
|
-
}
|
|
2891
|
-
return {
|
|
2892
|
-
...message,
|
|
2893
|
-
content: {
|
|
2894
|
-
...content,
|
|
2895
|
-
content: nextText
|
|
2896
|
-
}
|
|
2897
|
-
};
|
|
2898
|
-
},
|
|
2899
|
-
[resolveGroupDisplayText]
|
|
2900
|
-
);
|
|
2901
|
-
const messageContentById = reactExports.useMemo(() => {
|
|
2902
|
-
const map = /* @__PURE__ */ new Map();
|
|
2903
|
-
messages.forEach((message) => {
|
|
2904
|
-
const content = resolveGroupDisplayText(message);
|
|
2905
|
-
if (content) {
|
|
2906
|
-
map.set(message.id, content);
|
|
2907
|
-
}
|
|
2908
|
-
});
|
|
2909
|
-
return map;
|
|
2910
|
-
}, [messages, resolveGroupDisplayText]);
|
|
2911
|
-
const messagesById = reactExports.useMemo(() => {
|
|
2912
|
-
return new Map(messages.map((message) => [message.id, message]));
|
|
2913
|
-
}, [messages]);
|
|
2914
|
-
const messageContentByGroupId = reactExports.useMemo(() => {
|
|
2915
|
-
const map = /* @__PURE__ */ new Map();
|
|
2916
|
-
messages.forEach((message) => {
|
|
2917
|
-
const metadata = getMessageMetadata(message);
|
|
2918
|
-
const groupId = typeof metadata?.group_message_id === "string" ? metadata.group_message_id : "";
|
|
2919
|
-
if (!groupId) return;
|
|
2920
|
-
const content = resolveGroupDisplayText(message);
|
|
2921
|
-
if (content) {
|
|
2922
|
-
map.set(groupId, content);
|
|
2923
|
-
}
|
|
2924
|
-
});
|
|
2925
|
-
return map;
|
|
2926
|
-
}, [messages, resolveGroupDisplayText]);
|
|
2927
|
-
const messagesByGroupId = reactExports.useMemo(() => {
|
|
2928
|
-
const map = /* @__PURE__ */ new Map();
|
|
2929
|
-
messages.forEach((message) => {
|
|
2930
|
-
const metadata = getMessageMetadata(message);
|
|
2931
|
-
const groupId = typeof metadata?.group_message_id === "string" ? metadata.group_message_id : "";
|
|
2932
|
-
if (groupId) {
|
|
2933
|
-
map.set(groupId, message);
|
|
2934
|
-
}
|
|
2935
|
-
});
|
|
2936
|
-
return map;
|
|
2937
|
-
}, [messages]);
|
|
2938
|
-
const resolveGroupQuote = reactExports.useCallback(
|
|
2939
|
-
(message) => {
|
|
2940
|
-
const metadata = getMessageMetadata(message);
|
|
2941
|
-
if (!metadata) return null;
|
|
2942
|
-
const metaRecord = metadata;
|
|
2943
|
-
const context = metaRecord.context && typeof metaRecord.context === "object" ? metaRecord.context : null;
|
|
2944
|
-
const replyToMessageId = typeof metaRecord.reply_to_message_id === "string" && metaRecord.reply_to_message_id || typeof metaRecord.quote_message_id === "string" && metaRecord.quote_message_id || typeof context?.reply_to_message_id === "string" && context.reply_to_message_id || typeof context?.quote_message_id === "string" && context.quote_message_id || "";
|
|
2945
|
-
const rawSnapshot = metaRecord.quote_snapshot && typeof metaRecord.quote_snapshot === "object" ? metaRecord.quote_snapshot : context?.quote_snapshot && typeof context.quote_snapshot === "object" ? context.quote_snapshot : null;
|
|
2946
|
-
if (!replyToMessageId && !rawSnapshot) return null;
|
|
2947
|
-
const snapshotMessageId = rawSnapshot && typeof rawSnapshot.group_message_id === "string" ? rawSnapshot.group_message_id : "";
|
|
2948
|
-
const lookupMessageId = replyToMessageId || snapshotMessageId;
|
|
2949
|
-
const quotedMessage = lookupMessageId ? messagesByGroupId.get(lookupMessageId) ?? null : null;
|
|
2950
|
-
const snapshotContent = rawSnapshot && typeof rawSnapshot.content === "string" ? rawSnapshot.content : "";
|
|
2951
|
-
const fallbackContent = lookupMessageId ? messageContentByGroupId.get(lookupMessageId) ?? "" : "";
|
|
2952
|
-
const quoteContent = (snapshotContent || fallbackContent).replace(/\s+/g, " ").trim();
|
|
2953
|
-
if (!quoteContent) return null;
|
|
2954
|
-
let sender = "";
|
|
2955
|
-
if (quotedMessage) {
|
|
2956
|
-
sender = resolveMessageTitle(quotedMessage);
|
|
2957
|
-
} else if (rawSnapshot) {
|
|
2958
|
-
sender = typeof rawSnapshot.sender_name === "string" && rawSnapshot.sender_name || typeof rawSnapshot.agent_display_name === "string" && rawSnapshot.agent_display_name || typeof rawSnapshot.sender_label === "string" && rawSnapshot.sender_label || typeof rawSnapshot.agent_label === "string" && rawSnapshot.agent_label || "";
|
|
2959
|
-
}
|
|
2960
|
-
if (!sender) sender = "User";
|
|
2961
|
-
return { sender, content: quoteContent };
|
|
2962
|
-
},
|
|
2963
|
-
[messageContentByGroupId, messagesByGroupId]
|
|
2964
|
-
);
|
|
2965
|
-
const handleAvatarContextMenu = reactExports.useCallback(
|
|
2966
|
-
(event, message) => {
|
|
2967
|
-
const metadata = getMessageMetadata(message);
|
|
2968
|
-
const agentId = metadata?.agent_instance_id ?? metadata?.sender_instance_id;
|
|
2969
|
-
const sessionId2 = metadata?.session_id;
|
|
2970
|
-
if (!agentId && !sessionId2) return;
|
|
2971
|
-
setMessageContextMenu({ messageId: message.id, x: event.clientX, y: event.clientY });
|
|
2972
|
-
},
|
|
2973
|
-
[]
|
|
2974
|
-
);
|
|
2975
|
-
const contextTarget = messageContextMenu ? messagesById.get(messageContextMenu.messageId) ?? null : null;
|
|
2976
|
-
const contextMetadata = contextTarget ? getMessageMetadata(contextTarget) : null;
|
|
2977
|
-
const contextAgentId = contextMetadata?.agent_instance_id ?? null;
|
|
2978
|
-
const contextSessionId = typeof contextMetadata?.session_id === "string" ? contextMetadata.session_id : null;
|
|
2979
|
-
const canOpenDirect = Boolean(contextAgentId);
|
|
2980
|
-
const handleOpenDirect = reactExports.useCallback(() => {
|
|
2981
|
-
if (!contextAgentId) return;
|
|
2982
|
-
setActiveAgent(contextAgentId);
|
|
2983
|
-
if (contextSessionId) {
|
|
2984
|
-
setSessionIdForSurface(projectId, "lab-direct", contextSessionId);
|
|
2985
|
-
}
|
|
2986
|
-
setMode("direct");
|
|
2987
|
-
setMessageContextMenu(null);
|
|
2988
|
-
}, [contextAgentId, contextSessionId, projectId, setActiveAgent, setMode, setSessionIdForSurface]);
|
|
2989
|
-
const scrollToBottom = reactExports.useCallback(() => {
|
|
2990
|
-
const node = scrollRef.current;
|
|
2991
|
-
if (!node) return;
|
|
2992
|
-
if (typeof node.scrollTo === "function") {
|
|
2993
|
-
node.scrollTo({ top: node.scrollHeight, behavior: "auto" });
|
|
2994
|
-
} else {
|
|
2995
|
-
node.scrollTop = node.scrollHeight;
|
|
2996
|
-
}
|
|
2997
|
-
}, []);
|
|
2998
|
-
const handleScroll = reactExports.useCallback(
|
|
2999
|
-
(event) => {
|
|
3000
|
-
const target = event.currentTarget;
|
|
3001
|
-
const nextFollow = isScrolledToBottom(target);
|
|
3002
|
-
setFollow((prev) => prev === nextFollow ? prev : nextFollow);
|
|
3003
|
-
},
|
|
3004
|
-
[isScrolledToBottom]
|
|
3005
|
-
);
|
|
3006
|
-
reactExports.useCallback(() => {
|
|
3007
|
-
setFollow(false);
|
|
3008
|
-
loadFullHistory();
|
|
3009
|
-
}, [loadFullHistory]);
|
|
3010
|
-
reactExports.useEffect(() => {
|
|
3011
|
-
if (!questId || messages.length === 0) return;
|
|
3012
|
-
if (!follow) return;
|
|
3013
|
-
window.requestAnimationFrame(() => scrollToBottom());
|
|
3014
|
-
}, [follow, messages, questId, scrollToBottom]);
|
|
3015
|
-
const listOffset = showLoadFullHistory ? 1 : 0;
|
|
3016
|
-
const listCount = messages.length + listOffset;
|
|
3017
|
-
const shouldVirtualize = listCount > 20;
|
|
3018
|
-
const rowVirtualizer = useVirtualizer({
|
|
3019
|
-
count: listCount,
|
|
3020
|
-
getScrollElement: () => scrollRef.current,
|
|
3021
|
-
estimateSize: (index) => showLoadFullHistory && index === 0 ? 56 : 160,
|
|
3022
|
-
getItemKey: (index) => {
|
|
3023
|
-
if (showLoadFullHistory && index === 0) return "lab-group-history-banner";
|
|
3024
|
-
const message = messages[index - listOffset];
|
|
3025
|
-
return message?.id ?? `lab-group-${index}`;
|
|
3026
|
-
},
|
|
3027
|
-
overscan: 6
|
|
3028
|
-
});
|
|
3029
|
-
const groupSearchResults = reactExports.useMemo(() => {
|
|
3030
|
-
const query = searchQuery.trim().toLowerCase();
|
|
3031
|
-
if (!query) return [];
|
|
3032
|
-
const results = [];
|
|
3033
|
-
messages.forEach((item, index) => {
|
|
3034
|
-
const content = messageContentById.get(item.id) ?? "";
|
|
3035
|
-
if (!content) return;
|
|
3036
|
-
if (!content.toLowerCase().includes(query)) return;
|
|
3037
|
-
const title = resolveMessageTitle(item);
|
|
3038
|
-
results.push({
|
|
3039
|
-
id: item.id,
|
|
3040
|
-
index,
|
|
3041
|
-
title,
|
|
3042
|
-
excerpt: buildSearchSnippet(content, query)
|
|
3043
|
-
});
|
|
3044
|
-
});
|
|
3045
|
-
return results;
|
|
3046
|
-
}, [messageContentById, messages, searchQuery]);
|
|
3047
|
-
const handleAddAgents = reactExports.useCallback(
|
|
3048
|
-
async (agentIds) => {
|
|
3049
|
-
if (!questId || readOnly || agentIds.length === 0) return;
|
|
3050
|
-
setRosterBusy(true);
|
|
3051
|
-
try {
|
|
3052
|
-
const results = await Promise.allSettled(
|
|
3053
|
-
agentIds.map(
|
|
3054
|
-
(agentId) => assignLabAgent(projectId, agentId, { quest_id: questId, quest_node_id: null })
|
|
3055
|
-
)
|
|
3056
|
-
);
|
|
3057
|
-
const failed = [];
|
|
3058
|
-
results.forEach((result, index) => {
|
|
3059
|
-
if (result.status !== "fulfilled") {
|
|
3060
|
-
failed.push(agentIds[index]);
|
|
3061
|
-
}
|
|
3062
|
-
});
|
|
3063
|
-
if (failed.length) {
|
|
3064
|
-
const names = failed.map((agentId) => {
|
|
3065
|
-
const agent = agentsById.get(agentId);
|
|
3066
|
-
return agent ? resolveAgentDisplayName(agent) : agentId;
|
|
3067
|
-
});
|
|
3068
|
-
addToast({
|
|
3069
|
-
type: "error",
|
|
3070
|
-
title: "Unable to add agents",
|
|
3071
|
-
description: `Failed to add: ${names.join(", ")}`
|
|
3072
|
-
});
|
|
3073
|
-
}
|
|
3074
|
-
queryClient.invalidateQueries({ queryKey: ["lab-agents", projectId] });
|
|
3075
|
-
} finally {
|
|
3076
|
-
setRosterBusy(false);
|
|
3077
|
-
}
|
|
3078
|
-
},
|
|
3079
|
-
[addToast, agentsById, projectId, queryClient, questId, readOnly]
|
|
3080
|
-
);
|
|
3081
|
-
const handleRemoveAgent = reactExports.useCallback(
|
|
3082
|
-
async (agentId) => {
|
|
3083
|
-
if (!questId || readOnly) return;
|
|
3084
|
-
setRosterBusy(true);
|
|
3085
|
-
try {
|
|
3086
|
-
await assignLabAgent(projectId, agentId, { quest_id: null, quest_node_id: null });
|
|
3087
|
-
queryClient.invalidateQueries({ queryKey: ["lab-agents", projectId] });
|
|
3088
|
-
} catch (error) {
|
|
3089
|
-
const agent = agentsById.get(agentId);
|
|
3090
|
-
const label = agent ? resolveAgentDisplayName(agent) : agentId;
|
|
3091
|
-
addToast({
|
|
3092
|
-
type: "error",
|
|
3093
|
-
title: "Unable to remove agent",
|
|
3094
|
-
description: `Failed to remove ${label} from this quest.`
|
|
3095
|
-
});
|
|
3096
|
-
} finally {
|
|
3097
|
-
setRosterBusy(false);
|
|
3098
|
-
}
|
|
3099
|
-
},
|
|
3100
|
-
[addToast, agentsById, projectId, queryClient, questId, readOnly]
|
|
3101
|
-
);
|
|
3102
|
-
const flashMessageHighlight = reactExports.useCallback((messageId) => {
|
|
3103
|
-
setHighlightedMessageId(messageId);
|
|
3104
|
-
if (typeof window !== "undefined") {
|
|
3105
|
-
window.setTimeout(() => setHighlightedMessageId(null), 1200);
|
|
3106
|
-
}
|
|
3107
|
-
}, []);
|
|
3108
|
-
const jumpToGroupResult = reactExports.useCallback(
|
|
3109
|
-
(result) => {
|
|
3110
|
-
if (!result) return;
|
|
3111
|
-
if (!scrollRef.current) return;
|
|
3112
|
-
setFollow(false);
|
|
3113
|
-
if (shouldVirtualize) {
|
|
3114
|
-
rowVirtualizer.scrollToIndex(result.index, { align: "center" });
|
|
3115
|
-
} else {
|
|
3116
|
-
const node = scrollRef.current?.querySelector(
|
|
3117
|
-
`[data-group-message-id="${result.id}"]`
|
|
3118
|
-
);
|
|
3119
|
-
if (node) {
|
|
3120
|
-
node.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
3121
|
-
}
|
|
3122
|
-
}
|
|
3123
|
-
flashMessageHighlight(result.id);
|
|
3124
|
-
},
|
|
3125
|
-
[flashMessageHighlight, listOffset, rowVirtualizer, shouldVirtualize]
|
|
3126
|
-
);
|
|
3127
|
-
reactExports.useEffect(() => {
|
|
3128
|
-
if (!searchQuery.trim()) return;
|
|
3129
|
-
if (groupSearchResults.length === 0) return;
|
|
3130
|
-
jumpToGroupResult(groupSearchResults[0]);
|
|
3131
|
-
}, [groupSearchResults, jumpToGroupResult, searchQuery]);
|
|
3132
|
-
const menuPortal = headerPortalTarget ? reactDomExports.createPortal(
|
|
3133
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3134
|
-
LabCopilotOverflowMenu,
|
|
3135
|
-
{
|
|
3136
|
-
label: "Group",
|
|
3137
|
-
entities: menuEntities,
|
|
3138
|
-
emptyEntitiesLabel: emptyRosterLabel,
|
|
3139
|
-
searchValue: searchQuery,
|
|
3140
|
-
onSearchChange: setSearchQuery,
|
|
3141
|
-
searchResults: groupSearchResults,
|
|
3142
|
-
onSearchSelect: jumpToGroupResult,
|
|
3143
|
-
searchPlaceholder: "Search group chat",
|
|
3144
|
-
questId,
|
|
3145
|
-
quests,
|
|
3146
|
-
onQuestChange,
|
|
3147
|
-
canManageRoster: !readOnly && Boolean(questId),
|
|
3148
|
-
rosterBusy,
|
|
3149
|
-
availableAgents,
|
|
3150
|
-
onAddAgents: handleAddAgents,
|
|
3151
|
-
onRemoveAgent: handleRemoveAgent
|
|
3152
|
-
}
|
|
3153
|
-
),
|
|
3154
|
-
headerPortalTarget
|
|
3155
|
-
) : null;
|
|
3156
|
-
const messageContextMenuPortal = messageContextMenu && typeof document !== "undefined" ? reactDomExports.createPortal(
|
|
3157
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3158
|
-
"div",
|
|
3159
|
-
{
|
|
3160
|
-
className: "lab-copilot-context-menu",
|
|
3161
|
-
style: { left: messageContextMenu.x, top: messageContextMenu.y },
|
|
3162
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: handleOpenDirect, disabled: !canOpenDirect, children: "Open direct session" })
|
|
3163
|
-
}
|
|
3164
|
-
),
|
|
3165
|
-
document.body
|
|
3166
|
-
) : null;
|
|
3167
|
-
const renderGroupMessage = (message) => {
|
|
3168
|
-
const displayMessage = resolveGroupDisplayMessage(message);
|
|
3169
|
-
const content = displayMessage.content;
|
|
3170
|
-
const displayStreaming = message.type === "text_delta" && content.status === "in_progress";
|
|
3171
|
-
const isHighlighted = highlightedMessageId === message.id;
|
|
3172
|
-
const role = getMessageRole(displayMessage);
|
|
3173
|
-
const metadata = getMessageMetadata(displayMessage);
|
|
3174
|
-
const timestampLabel = role === "assistant" ? formatAbsoluteTimestamp(content?.timestamp) : "";
|
|
3175
|
-
const quote = resolveGroupQuote(displayMessage);
|
|
3176
|
-
const senderLabel = typeof metadata?.sender_label === "string" ? metadata.sender_label : typeof metadata?.agent_label === "string" ? metadata.agent_label : null;
|
|
3177
|
-
const targetLabel = typeof metadata?.target_label === "string" ? metadata.target_label : null;
|
|
3178
|
-
const replyState = typeof metadata?.reply_state === "string" ? metadata.reply_state : null;
|
|
3179
|
-
const replyStateLabel = resolveReplyStateLabel(t, replyState);
|
|
3180
|
-
const proposalId = typeof metadata?.proposal_id === "string" ? metadata.proposal_id : null;
|
|
3181
|
-
const selectionLabel = resolveSelectionLabel(
|
|
3182
|
-
metadata?.selection_context && typeof metadata.selection_context === "object" ? {
|
|
3183
|
-
...metadata.selection_context,
|
|
3184
|
-
label: typeof metadata.selection_context.branch_name === "string" ? metadata.selection_context.branch_name : void 0
|
|
3185
|
-
} : null
|
|
3186
|
-
);
|
|
3187
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3188
|
-
"div",
|
|
3189
|
-
{
|
|
3190
|
-
className: cn("rounded-[12px] px-1", isHighlighted && "lab-message-highlight"),
|
|
3191
|
-
"data-group-message-id": message.id,
|
|
3192
|
-
children: [
|
|
3193
|
-
senderLabel || targetLabel || replyStateLabel || proposalId || selectionLabel ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-2 flex flex-wrap items-center gap-2 px-2", children: [
|
|
3194
|
-
senderLabel || targetLabel ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center rounded-full border border-[var(--lab-border)] bg-[var(--lab-surface)] px-2 py-0.5 text-[10px] font-medium text-[var(--lab-text-secondary)]", children: [
|
|
3195
|
-
senderLabel || t("copilot_group_sender_fallback", void 0, "Agent"),
|
|
3196
|
-
targetLabel ? ` → ${targetLabel}` : ""
|
|
3197
|
-
] }) : null,
|
|
3198
|
-
replyStateLabel ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-flex items-center rounded-full border border-[rgba(83,176,174,0.2)] bg-[rgba(83,176,174,0.1)] px-2 py-0.5 text-[10px] font-medium text-[var(--lab-text-secondary)]", children: replyStateLabel }) : null,
|
|
3199
|
-
proposalId ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center rounded-full border border-[var(--lab-border)] bg-[var(--lab-background)] px-2 py-0.5 text-[10px] text-[var(--lab-text-secondary)]", children: [
|
|
3200
|
-
t("copilot_group_proposal_badge", void 0, "Proposal"),
|
|
3201
|
-
" · ",
|
|
3202
|
-
proposalId.slice(0, 8)
|
|
3203
|
-
] }) : null,
|
|
3204
|
-
selectionLabel ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-flex items-center rounded-full border border-[var(--lab-border)] bg-[var(--lab-background)] px-2 py-0.5 text-[10px] text-[var(--lab-text-secondary)]", children: selectionLabel }) : null
|
|
3205
|
-
] }) : null,
|
|
3206
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3207
|
-
ChatMessage,
|
|
3208
|
-
{
|
|
3209
|
-
message: displayMessage,
|
|
3210
|
-
compact: true,
|
|
3211
|
-
onAvatarContextMenu: handleAvatarContextMenu,
|
|
3212
|
-
displayStreaming,
|
|
3213
|
-
streamActive: connection.status === "open" || connection.status === "connecting" || connection.status === "reconnecting"
|
|
3214
|
-
}
|
|
3215
|
-
),
|
|
3216
|
-
quote ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3217
|
-
"div",
|
|
3218
|
-
{
|
|
3219
|
-
className: cn("mt-2 flex w-full", role === "user" ? "justify-end" : "justify-start"),
|
|
3220
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "max-w-[85%] rounded-[8px] bg-[rgba(0,0,0,0.06)] px-3 py-2 text-[10px] leading-relaxed text-[var(--text-secondary)]", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "line-clamp-2", children: [
|
|
3221
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-medium text-[var(--text-tertiary)]", children: [
|
|
3222
|
-
quote.sender,
|
|
3223
|
-
":"
|
|
3224
|
-
] }),
|
|
3225
|
-
" ",
|
|
3226
|
-
quote.content
|
|
3227
|
-
] }) })
|
|
3228
|
-
}
|
|
3229
|
-
) : null,
|
|
3230
|
-
timestampLabel ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-message-timestamp", children: timestampLabel }) : null
|
|
3231
|
-
]
|
|
3232
|
-
},
|
|
3233
|
-
message.id
|
|
3234
|
-
);
|
|
3235
|
-
};
|
|
3236
|
-
const renderHistoryBanner = () => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-3 flex justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3237
|
-
"div",
|
|
3238
|
-
{
|
|
3239
|
-
className: cn(
|
|
3240
|
-
"inline-flex max-w-full items-center gap-2 rounded-full border px-3 py-1 font-medium",
|
|
3241
|
-
"border-[var(--lab-border)] bg-[var(--lab-surface)] text-[var(--lab-text-secondary)]",
|
|
3242
|
-
"text-[11px]"
|
|
3243
|
-
),
|
|
3244
|
-
children: [
|
|
3245
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: historyLabel }),
|
|
3246
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3247
|
-
"button",
|
|
3248
|
-
{
|
|
3249
|
-
type: "button",
|
|
3250
|
-
onClick: () => {
|
|
3251
|
-
setFollow(false);
|
|
3252
|
-
loadFullHistory();
|
|
3253
|
-
},
|
|
3254
|
-
disabled: historyLoadingFull,
|
|
3255
|
-
className: "text-[var(--lab-text-primary)] underline decoration-dotted underline-offset-4 disabled:cursor-not-allowed disabled:opacity-50",
|
|
3256
|
-
children: historyLoadingFull ? "Loading full history..." : "Load full history"
|
|
3257
|
-
}
|
|
3258
|
-
)
|
|
3259
|
-
]
|
|
3260
|
-
}
|
|
3261
|
-
) });
|
|
3262
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [
|
|
3263
|
-
menuPortal,
|
|
3264
|
-
messageContextMenuPortal,
|
|
3265
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ai-manus-root ai-manus-copilot ai-manus-embedded flex flex-1 min-h-0 flex-col", children: [
|
|
3266
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex flex-1 min-h-0 flex-col", children: [
|
|
3267
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { ref: scrollRef, className: "flex-1 min-h-0", onScroll: handleScroll, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex min-h-full flex-col px-4 py-4", children: !questId ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-1 items-center justify-center text-[12px] text-[var(--text-tertiary)]", children: emptyStateLabel }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
3268
|
-
connectionStatus ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-3 text-center text-[11px] text-[var(--text-tertiary)]", children: connectionStatus }) : null,
|
|
3269
|
-
shouldVirtualize ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { height: rowVirtualizer.getTotalSize(), position: "relative" }, children: rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
|
3270
|
-
if (showLoadFullHistory && virtualRow.index === 0) {
|
|
3271
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3272
|
-
"div",
|
|
3273
|
-
{
|
|
3274
|
-
"data-index": virtualRow.index,
|
|
3275
|
-
ref: rowVirtualizer.measureElement,
|
|
3276
|
-
style: {
|
|
3277
|
-
position: "absolute",
|
|
3278
|
-
top: 0,
|
|
3279
|
-
left: 0,
|
|
3280
|
-
width: "100%",
|
|
3281
|
-
transform: `translateY(${virtualRow.start}px)`
|
|
3282
|
-
},
|
|
3283
|
-
children: renderHistoryBanner()
|
|
3284
|
-
},
|
|
3285
|
-
virtualRow.key
|
|
3286
|
-
);
|
|
3287
|
-
}
|
|
3288
|
-
const message = messages[virtualRow.index - listOffset];
|
|
3289
|
-
if (!message) return null;
|
|
3290
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3291
|
-
"div",
|
|
3292
|
-
{
|
|
3293
|
-
"data-index": virtualRow.index,
|
|
3294
|
-
ref: rowVirtualizer.measureElement,
|
|
3295
|
-
style: {
|
|
3296
|
-
position: "absolute",
|
|
3297
|
-
top: 0,
|
|
3298
|
-
left: 0,
|
|
3299
|
-
width: "100%",
|
|
3300
|
-
transform: `translateY(${virtualRow.start}px)`
|
|
3301
|
-
},
|
|
3302
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pb-3", children: renderGroupMessage(message) })
|
|
3303
|
-
},
|
|
3304
|
-
virtualRow.key
|
|
3305
|
-
);
|
|
3306
|
-
}) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col", children: [
|
|
3307
|
-
showLoadFullHistory ? renderHistoryBanner() : null,
|
|
3308
|
-
messages.map((message) => renderGroupMessage(message))
|
|
3309
|
-
] })
|
|
3310
|
-
] }) }) }),
|
|
3311
|
-
showHistoryLoadingOverlay ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 z-10 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3312
|
-
"div",
|
|
3313
|
-
{
|
|
3314
|
-
className: cn(
|
|
3315
|
-
"flex items-center gap-2 rounded-full border px-3 py-1.5 text-[11px] font-medium shadow-sm",
|
|
3316
|
-
"border-[var(--lab-border)] bg-[var(--lab-surface)] text-[var(--lab-text-secondary)]"
|
|
3317
|
-
),
|
|
3318
|
-
children: [
|
|
3319
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-4 w-4 animate-spin" }),
|
|
3320
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("common_loading", void 0, "Loading...") })
|
|
3321
|
-
]
|
|
3322
|
-
}
|
|
3323
|
-
) }) : null
|
|
3324
|
-
] }),
|
|
3325
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-[var(--border-main)] px-4 py-3", children: [
|
|
3326
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3327
|
-
LabControlReferenceChips,
|
|
3328
|
-
{
|
|
3329
|
-
selection,
|
|
3330
|
-
proposal: activeProposal ? {
|
|
3331
|
-
proposal_id: activeProposal.proposal_id,
|
|
3332
|
-
action_type: activeProposal.action_type,
|
|
3333
|
-
status: activeProposal.status
|
|
3334
|
-
} : null,
|
|
3335
|
-
onClearSelection: () => setSelection(null),
|
|
3336
|
-
onClearProposal: () => setActiveProposal(null),
|
|
3337
|
-
selectionPrefix: t("copilot_selection_chip", void 0, "Ref"),
|
|
3338
|
-
proposalPrefix: t("copilot_proposal_chip", void 0, "Proposal")
|
|
3339
|
-
}
|
|
3340
|
-
),
|
|
3341
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3342
|
-
ChatBox,
|
|
3343
|
-
{
|
|
3344
|
-
value: input,
|
|
3345
|
-
onChange: setInput,
|
|
3346
|
-
onSubmit: handleSend,
|
|
3347
|
-
isRunning: false,
|
|
3348
|
-
mentionables,
|
|
3349
|
-
mentionEnabled: !readOnly,
|
|
3350
|
-
includeDefaultAgent: false,
|
|
3351
|
-
lockLeadingMentionSpace: true,
|
|
3352
|
-
attachments,
|
|
3353
|
-
onAttachmentsChange: setAttachments,
|
|
3354
|
-
attachmentsEnabled: false,
|
|
3355
|
-
readOnly,
|
|
3356
|
-
inputDisabled: !questId,
|
|
3357
|
-
rows: 2,
|
|
3358
|
-
placeholder: !questId ? emptyStateLabel : readOnly ? t(
|
|
3359
|
-
"copilot_group_placeholder_offline",
|
|
3360
|
-
void 0,
|
|
3361
|
-
"Your execution server is offline. Messages will be sent once it reconnects."
|
|
3362
|
-
) : t(
|
|
3363
|
-
"copilot_group_placeholder",
|
|
3364
|
-
void 0,
|
|
3365
|
-
"Message @ALL or a quest agent"
|
|
3366
|
-
),
|
|
3367
|
-
compact: true,
|
|
3368
|
-
containerClassName: "pb-0"
|
|
3369
|
-
}
|
|
3370
|
-
)
|
|
3371
|
-
] })
|
|
3372
|
-
] })
|
|
3373
|
-
] });
|
|
3374
|
-
}
|
|
3375
|
-
function FriendsMomentCard({
|
|
3376
|
-
message,
|
|
3377
|
-
readOnly,
|
|
3378
|
-
liked,
|
|
3379
|
-
busy,
|
|
3380
|
-
likeUsers,
|
|
3381
|
-
comments,
|
|
3382
|
-
onToggleLike,
|
|
3383
|
-
onCommentSubmit,
|
|
3384
|
-
onAvatarContextMenu
|
|
3385
|
-
}) {
|
|
3386
|
-
const metadata = getMessageMetadata(message);
|
|
3387
|
-
const metadataRecord = metadata;
|
|
3388
|
-
const momentId = typeof metadata?.moment_id === "string" ? metadata.moment_id : "";
|
|
3389
|
-
const contentText = getMessageText(message);
|
|
3390
|
-
const mediaItems = resolveMomentMedia(metadata?.moment_media);
|
|
3391
|
-
const senderName = metadata?.sender_name || resolveMessageTitle(message);
|
|
3392
|
-
const senderLabel = metadata?.sender_label || metadata?.agent_label;
|
|
3393
|
-
const avatarUrl = metadata?.sender_avatar_url || metadata?.agent_logo || "";
|
|
3394
|
-
const avatarColor = metadata?.sender_avatar_color || metadata?.agent_avatar_color || "";
|
|
3395
|
-
const timestampLabel = formatAbsoluteTimestamp(resolveMomentTimestamp(message));
|
|
3396
|
-
const contentHtml = contentText ? renderMarkdown(contentText) : "";
|
|
3397
|
-
const [commentOpen, setCommentOpen] = reactExports.useState(false);
|
|
3398
|
-
const [commentDraft, setCommentDraft] = reactExports.useState("");
|
|
3399
|
-
reactExports.useEffect(() => {
|
|
3400
|
-
setCommentOpen(false);
|
|
3401
|
-
setCommentDraft("");
|
|
3402
|
-
}, [momentId]);
|
|
3403
|
-
const countValue = (value) => {
|
|
3404
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
3405
|
-
if (typeof value === "string") {
|
|
3406
|
-
const parsed = Number(value);
|
|
3407
|
-
if (Number.isFinite(parsed)) return parsed;
|
|
3408
|
-
}
|
|
3409
|
-
return 0;
|
|
3410
|
-
};
|
|
3411
|
-
const likeCount = countValue(metadata?.moment_like_count);
|
|
3412
|
-
const commentCount = countValue(metadata?.moment_comment_count);
|
|
3413
|
-
const avatarInitial = senderName?.trim().slice(0, 1).toUpperCase() || "?";
|
|
3414
|
-
const displayName = (() => {
|
|
3415
|
-
const raw = senderLabel || senderName || "Agent";
|
|
3416
|
-
const trimmed = raw.trim();
|
|
3417
|
-
if (!trimmed) return "@Agent";
|
|
3418
|
-
return trimmed.startsWith("@") ? trimmed : `@${trimmed}`;
|
|
3419
|
-
})();
|
|
3420
|
-
const resolvedLikeUsers = mergeMomentLikes(
|
|
3421
|
-
resolveMomentLikes(
|
|
3422
|
-
metadataRecord?.moment_like_users ?? metadataRecord?.moment_likes ?? metadataRecord?.like_users ?? metadataRecord?.likes ?? metadataRecord?.moment_reactions ?? null
|
|
3423
|
-
),
|
|
3424
|
-
likeUsers.map((name) => ({ name }))
|
|
3425
|
-
);
|
|
3426
|
-
const resolvedComments = mergeMomentComments(
|
|
3427
|
-
resolveMomentComments(
|
|
3428
|
-
metadataRecord?.moment_comments ?? metadataRecord?.moment_comment_list ?? metadataRecord?.comments ?? metadataRecord?.moment_comment_items ?? metadataRecord?.comment_items ?? null
|
|
3429
|
-
),
|
|
3430
|
-
comments
|
|
3431
|
-
);
|
|
3432
|
-
const showReactions = resolvedLikeUsers.length > 0 || resolvedComments.length > 0;
|
|
3433
|
-
const handleLikeClick = async () => {
|
|
3434
|
-
if (!momentId || readOnly || busy) return;
|
|
3435
|
-
await onToggleLike(momentId, !liked);
|
|
3436
|
-
};
|
|
3437
|
-
const handleCommentToggle = () => {
|
|
3438
|
-
if (readOnly) return;
|
|
3439
|
-
setCommentOpen((prev) => !prev);
|
|
3440
|
-
};
|
|
3441
|
-
const handleCommentSubmit = async () => {
|
|
3442
|
-
if (!momentId || readOnly || busy) return;
|
|
3443
|
-
const trimmed = commentDraft.trim();
|
|
3444
|
-
if (!trimmed) return;
|
|
3445
|
-
const ok = await onCommentSubmit(momentId, trimmed);
|
|
3446
|
-
if (ok) {
|
|
3447
|
-
setCommentDraft("");
|
|
3448
|
-
setCommentOpen(false);
|
|
3449
|
-
}
|
|
3450
|
-
};
|
|
3451
|
-
const handleCommentKeyDown = (event) => {
|
|
3452
|
-
if (event.key === "Enter" && !event.shiftKey) {
|
|
3453
|
-
event.preventDefault();
|
|
3454
|
-
void handleCommentSubmit();
|
|
3455
|
-
}
|
|
3456
|
-
};
|
|
3457
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-moment-card", children: [
|
|
3458
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3459
|
-
"div",
|
|
3460
|
-
{
|
|
3461
|
-
className: "lab-avatar lab-avatar-sm lab-moment-avatar",
|
|
3462
|
-
onContextMenu: (event) => onAvatarContextMenu?.(event, message),
|
|
3463
|
-
children: [
|
|
3464
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-avatar-ring", style: avatarColor ? { borderColor: avatarColor } : void 0 }),
|
|
3465
|
-
avatarUrl ? /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: avatarUrl, alt: senderName }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: avatarInitial })
|
|
3466
|
-
]
|
|
3467
|
-
}
|
|
3468
|
-
),
|
|
3469
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-moment-main", children: [
|
|
3470
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-moment-name", children: displayName }),
|
|
3471
|
-
contentHtml ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3472
|
-
"div",
|
|
3473
|
-
{
|
|
3474
|
-
className: "lab-moment-content",
|
|
3475
|
-
dangerouslySetInnerHTML: { __html: contentHtml }
|
|
3476
|
-
}
|
|
3477
|
-
) : null,
|
|
3478
|
-
mediaItems.length ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-moment-media", children: mediaItems.map((item, index) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-moment-media-item", children: /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: item.url, alt: item.label || `Moment media ${index + 1}` }) }, `${item.url}-${index}`)) }) : null,
|
|
3479
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-moment-meta-row", children: [
|
|
3480
|
-
timestampLabel ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-moment-timestamp", children: timestampLabel }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", {}),
|
|
3481
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-moment-actions", children: [
|
|
3482
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3483
|
-
"button",
|
|
3484
|
-
{
|
|
3485
|
-
type: "button",
|
|
3486
|
-
className: cn("lab-moment-action", liked && "is-active"),
|
|
3487
|
-
onClick: handleLikeClick,
|
|
3488
|
-
disabled: readOnly || busy,
|
|
3489
|
-
"aria-label": "Like moment",
|
|
3490
|
-
children: [
|
|
3491
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(ThumbsUp, { size: 14 }),
|
|
3492
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: likeCount })
|
|
3493
|
-
]
|
|
3494
|
-
}
|
|
3495
|
-
),
|
|
3496
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3497
|
-
"button",
|
|
3498
|
-
{
|
|
3499
|
-
type: "button",
|
|
3500
|
-
className: "lab-moment-action",
|
|
3501
|
-
onClick: handleCommentToggle,
|
|
3502
|
-
disabled: readOnly || busy,
|
|
3503
|
-
"aria-label": "Comment on moment",
|
|
3504
|
-
children: [
|
|
3505
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(MessageCircle, { size: 14 }),
|
|
3506
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: commentCount })
|
|
3507
|
-
]
|
|
3508
|
-
}
|
|
3509
|
-
)
|
|
3510
|
-
] })
|
|
3511
|
-
] }),
|
|
3512
|
-
showReactions ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-moment-reactions", children: [
|
|
3513
|
-
resolvedLikeUsers.length ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-moment-like-row", children: [
|
|
3514
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(ThumbsUp, { size: 12 }),
|
|
3515
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-moment-like-names", children: resolvedLikeUsers.join(", ") })
|
|
3516
|
-
] }) : null,
|
|
3517
|
-
resolvedComments.map((comment, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-moment-comment-row", children: [
|
|
3518
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-moment-comment-name", children: comment.name }),
|
|
3519
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-moment-comment-sep", children: ":" }),
|
|
3520
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-moment-comment-text", children: comment.content })
|
|
3521
|
-
] }, `${comment.name}-${index}`))
|
|
3522
|
-
] }) : null,
|
|
3523
|
-
commentOpen ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-moment-comment", children: [
|
|
3524
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3525
|
-
"textarea",
|
|
3526
|
-
{
|
|
3527
|
-
value: commentDraft,
|
|
3528
|
-
onChange: (event) => setCommentDraft(event.target.value),
|
|
3529
|
-
onKeyDown: handleCommentKeyDown,
|
|
3530
|
-
placeholder: "Write a comment",
|
|
3531
|
-
className: "lab-moment-comment-input",
|
|
3532
|
-
rows: 2,
|
|
3533
|
-
disabled: readOnly || busy
|
|
3534
|
-
}
|
|
3535
|
-
),
|
|
3536
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-moment-comment-actions", children: [
|
|
3537
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3538
|
-
"button",
|
|
3539
|
-
{
|
|
3540
|
-
type: "button",
|
|
3541
|
-
className: "lab-moment-action lab-moment-action-secondary",
|
|
3542
|
-
onClick: () => setCommentOpen(false),
|
|
3543
|
-
disabled: readOnly || busy,
|
|
3544
|
-
children: "Cancel"
|
|
3545
|
-
}
|
|
3546
|
-
),
|
|
3547
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3548
|
-
"button",
|
|
3549
|
-
{
|
|
3550
|
-
type: "button",
|
|
3551
|
-
className: "lab-moment-action lab-moment-action-primary",
|
|
3552
|
-
onClick: handleCommentSubmit,
|
|
3553
|
-
disabled: readOnly || busy || !commentDraft.trim(),
|
|
3554
|
-
children: "Send"
|
|
3555
|
-
}
|
|
3556
|
-
)
|
|
3557
|
-
] })
|
|
3558
|
-
] }) : null
|
|
3559
|
-
] })
|
|
3560
|
-
] });
|
|
3561
|
-
}
|
|
3562
|
-
function LabFriendsFeed({
|
|
3563
|
-
projectId,
|
|
3564
|
-
agents,
|
|
3565
|
-
templatesById,
|
|
3566
|
-
readOnly,
|
|
3567
|
-
quest,
|
|
3568
|
-
quests,
|
|
3569
|
-
onQuestChange
|
|
3570
|
-
}) {
|
|
3571
|
-
const { addToast } = useToast();
|
|
3572
|
-
const queryClient = useQueryClient();
|
|
3573
|
-
const [rosterBusy, setRosterBusy] = reactExports.useState(false);
|
|
3574
|
-
const [momentBusy, setMomentBusy] = reactExports.useState(/* @__PURE__ */ new Set());
|
|
3575
|
-
const [likedMoments, setLikedMoments] = reactExports.useState({});
|
|
3576
|
-
const [momentLikeUsers, setMomentLikeUsers] = reactExports.useState({});
|
|
3577
|
-
const [momentComments, setMomentComments] = reactExports.useState({});
|
|
3578
|
-
const [messageContextMenu, setMessageContextMenu] = reactExports.useState(null);
|
|
3579
|
-
const scrollRef = reactExports.useRef(null);
|
|
3580
|
-
const lastSeenMessageIdRef = reactExports.useRef(null);
|
|
3581
|
-
const [highlightedMessageId, setHighlightedMessageId] = reactExports.useState(null);
|
|
3582
|
-
const [follow, setFollow] = reactExports.useState(true);
|
|
3583
|
-
const setMode = useLabCopilotStore((state) => state.setMode);
|
|
3584
|
-
const setActiveAgent = useLabCopilotStore((state) => state.setActiveAgent);
|
|
3585
|
-
const setSessionIdForSurface = useChatSessionStore((state) => state.setSessionIdForSurface);
|
|
3586
|
-
const user = useAuthStore((state) => state.user);
|
|
3587
|
-
const currentUserLabel = reactExports.useMemo(() => {
|
|
3588
|
-
const raw = user?.username || user?.email || user?.id || "You";
|
|
3589
|
-
const trimmed = raw?.trim?.() ?? "";
|
|
3590
|
-
return trimmed || "You";
|
|
3591
|
-
}, [user?.email, user?.id, user?.username]);
|
|
3592
|
-
const headerPortalTarget = useCopilotDockHeaderPortal();
|
|
3593
|
-
const [searchQuery, setSearchQuery] = reactExports.useState("");
|
|
3594
|
-
const agentsById = reactExports.useMemo(() => new Map(agents.map((agent) => [agent.instance_id, agent])), [agents]);
|
|
3595
|
-
const questId = quest?.quest_id ?? null;
|
|
3596
|
-
const {
|
|
3597
|
-
messages,
|
|
3598
|
-
connection,
|
|
3599
|
-
historyTruncated,
|
|
3600
|
-
historyLimit,
|
|
3601
|
-
historyLoadingFull,
|
|
3602
|
-
historyLoading,
|
|
3603
|
-
hasLoadedOnce,
|
|
3604
|
-
loadFullHistory
|
|
3605
|
-
} = useLabSurfaceSession({
|
|
3606
|
-
projectId,
|
|
3607
|
-
questId,
|
|
3608
|
-
surface: "friends",
|
|
3609
|
-
enabled: Boolean(questId)
|
|
3610
|
-
});
|
|
3611
|
-
const friendEntities = reactExports.useMemo(() => {
|
|
3612
|
-
if (!questId) return [];
|
|
3613
|
-
return agents.filter((agent) => agent.active_quest_id === questId).map((agent) => {
|
|
3614
|
-
const template = agent.template_id ? templatesById.get(agent.template_id) ?? null : null;
|
|
3615
|
-
return {
|
|
3616
|
-
id: agent.instance_id,
|
|
3617
|
-
name: resolveAgentDisplayName(agent),
|
|
3618
|
-
avatar: resolveAgentLogo(agent, template)
|
|
3619
|
-
};
|
|
3620
|
-
});
|
|
3621
|
-
}, [agents, questId, templatesById]);
|
|
3622
|
-
const availableAgents = reactExports.useMemo(() => {
|
|
3623
|
-
if (!questId) return [];
|
|
3624
|
-
return agents.filter((agent) => agent.active_quest_id !== questId).map((agent) => {
|
|
3625
|
-
const template = agent.template_id ? templatesById.get(agent.template_id) ?? null : null;
|
|
3626
|
-
return {
|
|
3627
|
-
id: agent.instance_id,
|
|
3628
|
-
name: resolveAgentDisplayName(agent),
|
|
3629
|
-
avatar: resolveAgentLogo(agent, template)
|
|
3630
|
-
};
|
|
3631
|
-
});
|
|
3632
|
-
}, [agents, questId, templatesById]);
|
|
3633
|
-
const emptyRosterLabel = questId ? "No agents assigned to this quest yet." : "Select a quest to see its agents.";
|
|
3634
|
-
const emptyStateLabel = reactExports.useMemo(() => {
|
|
3635
|
-
if (!questId) return "Select a quest to view friend updates.";
|
|
3636
|
-
return "No friend updates yet for this quest.";
|
|
3637
|
-
}, [questId]);
|
|
3638
|
-
const connectionStatus = reactExports.useMemo(() => {
|
|
3639
|
-
if (connection.status === "rate_limited") return "Rate limited. Retrying...";
|
|
3640
|
-
if (connection.status === "reconnecting") return "Reconnecting...";
|
|
3641
|
-
if (connection.status === "error") return connection.error || "Connection error";
|
|
3642
|
-
return null;
|
|
3643
|
-
}, [connection.error, connection.status]);
|
|
3644
|
-
const showLoadFullHistory = historyTruncated && Boolean(questId);
|
|
3645
|
-
const showHistoryLoadingOverlay = Boolean(questId) && historyLoading && messages.length === 0 && !hasLoadedOnce;
|
|
3646
|
-
const historyLabel = typeof historyLimit === "number" && historyLimit > 0 ? `Showing latest ${historyLimit} messages.` : "Showing recent messages.";
|
|
3647
|
-
reactExports.useEffect(() => {
|
|
3648
|
-
if (!messageContextMenu) return;
|
|
3649
|
-
const handleDismiss = () => setMessageContextMenu(null);
|
|
3650
|
-
const handleKey = (event) => {
|
|
3651
|
-
if (event.key === "Escape") {
|
|
3652
|
-
setMessageContextMenu(null);
|
|
3653
|
-
}
|
|
3654
|
-
};
|
|
3655
|
-
window.addEventListener("click", handleDismiss);
|
|
3656
|
-
window.addEventListener("contextmenu", handleDismiss);
|
|
3657
|
-
window.addEventListener("keydown", handleKey);
|
|
3658
|
-
window.addEventListener("scroll", handleDismiss, true);
|
|
3659
|
-
return () => {
|
|
3660
|
-
window.removeEventListener("click", handleDismiss);
|
|
3661
|
-
window.removeEventListener("contextmenu", handleDismiss);
|
|
3662
|
-
window.removeEventListener("keydown", handleKey);
|
|
3663
|
-
window.removeEventListener("scroll", handleDismiss, true);
|
|
3664
|
-
};
|
|
3665
|
-
}, [messageContextMenu]);
|
|
3666
|
-
reactExports.useEffect(() => {
|
|
3667
|
-
setSearchQuery("");
|
|
3668
|
-
setHighlightedMessageId(null);
|
|
3669
|
-
lastSeenMessageIdRef.current = null;
|
|
3670
|
-
setFollow(true);
|
|
3671
|
-
setMomentBusy(/* @__PURE__ */ new Set());
|
|
3672
|
-
setLikedMoments({});
|
|
3673
|
-
setMomentLikeUsers({});
|
|
3674
|
-
setMomentComments({});
|
|
3675
|
-
}, [questId]);
|
|
3676
|
-
reactExports.useEffect(() => {
|
|
3677
|
-
const latest = messages[messages.length - 1];
|
|
3678
|
-
if (!latest?.id) return;
|
|
3679
|
-
if (!lastSeenMessageIdRef.current) {
|
|
3680
|
-
lastSeenMessageIdRef.current = latest.id;
|
|
3681
|
-
return;
|
|
3682
|
-
}
|
|
3683
|
-
if (latest.id !== lastSeenMessageIdRef.current) {
|
|
3684
|
-
lastSeenMessageIdRef.current = latest.id;
|
|
3685
|
-
setHighlightedMessageId(latest.id);
|
|
3686
|
-
const timer = window.setTimeout(() => setHighlightedMessageId(null), 1200);
|
|
3687
|
-
return () => window.clearTimeout(timer);
|
|
3688
|
-
}
|
|
3689
|
-
}, [messages]);
|
|
3690
|
-
const messageContentById = reactExports.useMemo(() => {
|
|
3691
|
-
const map = /* @__PURE__ */ new Map();
|
|
3692
|
-
messages.forEach((message) => {
|
|
3693
|
-
const content = getMessageText(message);
|
|
3694
|
-
if (content) {
|
|
3695
|
-
map.set(message.id, content);
|
|
3696
|
-
}
|
|
3697
|
-
});
|
|
3698
|
-
return map;
|
|
3699
|
-
}, [messages]);
|
|
3700
|
-
const messagesById = reactExports.useMemo(() => {
|
|
3701
|
-
return new Map(messages.map((message) => [message.id, message]));
|
|
3702
|
-
}, [messages]);
|
|
3703
|
-
const friendMessages = reactExports.useMemo(() => {
|
|
3704
|
-
return messages.length ? [...messages].reverse() : [];
|
|
3705
|
-
}, [messages]);
|
|
3706
|
-
const handleAvatarContextMenu = reactExports.useCallback(
|
|
3707
|
-
(event, message) => {
|
|
3708
|
-
const metadata = getMessageMetadata(message);
|
|
3709
|
-
const agentId = metadata?.agent_instance_id;
|
|
3710
|
-
const sessionId = metadata?.session_id;
|
|
3711
|
-
if (!agentId && !sessionId) return;
|
|
3712
|
-
setMessageContextMenu({ messageId: message.id, x: event.clientX, y: event.clientY });
|
|
3713
|
-
},
|
|
3714
|
-
[]
|
|
3715
|
-
);
|
|
3716
|
-
const contextTarget = messageContextMenu ? messagesById.get(messageContextMenu.messageId) ?? null : null;
|
|
3717
|
-
const contextMetadata = contextTarget ? getMessageMetadata(contextTarget) : null;
|
|
3718
|
-
const contextAgentId = contextMetadata?.agent_instance_id ?? null;
|
|
3719
|
-
const contextSessionId = typeof contextMetadata?.session_id === "string" ? contextMetadata.session_id : null;
|
|
3720
|
-
const canOpenDirect = Boolean(contextAgentId);
|
|
3721
|
-
const handleOpenDirect = reactExports.useCallback(() => {
|
|
3722
|
-
if (!contextAgentId) return;
|
|
3723
|
-
setActiveAgent(contextAgentId);
|
|
3724
|
-
if (contextSessionId) {
|
|
3725
|
-
setSessionIdForSurface(projectId, "lab-direct", contextSessionId);
|
|
3726
|
-
}
|
|
3727
|
-
setMode("direct");
|
|
3728
|
-
setMessageContextMenu(null);
|
|
3729
|
-
}, [contextAgentId, contextSessionId, projectId, setActiveAgent, setMode, setSessionIdForSurface]);
|
|
3730
|
-
const listCount = friendMessages.length + (showLoadFullHistory ? 1 : 0);
|
|
3731
|
-
const historyBannerIndex = showLoadFullHistory ? friendMessages.length : -1;
|
|
3732
|
-
const shouldVirtualize = listCount > 20;
|
|
3733
|
-
const rowVirtualizer = useVirtualizer({
|
|
3734
|
-
count: listCount,
|
|
3735
|
-
getScrollElement: () => scrollRef.current,
|
|
3736
|
-
estimateSize: (index) => showLoadFullHistory && index === historyBannerIndex ? 56 : 180,
|
|
3737
|
-
getItemKey: (index) => {
|
|
3738
|
-
if (showLoadFullHistory && index === historyBannerIndex) return "lab-friends-history-banner";
|
|
3739
|
-
const message = friendMessages[index];
|
|
3740
|
-
return message?.id ?? `lab-friends-${index}`;
|
|
3741
|
-
},
|
|
3742
|
-
overscan: 6
|
|
3743
|
-
});
|
|
3744
|
-
const friendSearchResults = reactExports.useMemo(() => {
|
|
3745
|
-
const query = searchQuery.trim().toLowerCase();
|
|
3746
|
-
if (!query) return [];
|
|
3747
|
-
const results = [];
|
|
3748
|
-
friendMessages.forEach((item, index) => {
|
|
3749
|
-
const content = messageContentById.get(item.id) ?? "";
|
|
3750
|
-
if (!content) return;
|
|
3751
|
-
if (!content.toLowerCase().includes(query)) return;
|
|
3752
|
-
const title = resolveMessageTitle(item);
|
|
3753
|
-
results.push({
|
|
3754
|
-
id: item.id,
|
|
3755
|
-
index,
|
|
3756
|
-
title,
|
|
3757
|
-
excerpt: buildSearchSnippet(content, query)
|
|
3758
|
-
});
|
|
3759
|
-
});
|
|
3760
|
-
return results;
|
|
3761
|
-
}, [friendMessages, messageContentById, searchQuery]);
|
|
3762
|
-
const handleAddAgents = reactExports.useCallback(
|
|
3763
|
-
async (agentIds) => {
|
|
3764
|
-
if (!questId || readOnly || agentIds.length === 0) return;
|
|
3765
|
-
setRosterBusy(true);
|
|
3766
|
-
try {
|
|
3767
|
-
const results = await Promise.allSettled(
|
|
3768
|
-
agentIds.map(
|
|
3769
|
-
(agentId) => assignLabAgent(projectId, agentId, { quest_id: questId, quest_node_id: null })
|
|
3770
|
-
)
|
|
3771
|
-
);
|
|
3772
|
-
const failed = [];
|
|
3773
|
-
results.forEach((result, index) => {
|
|
3774
|
-
if (result.status !== "fulfilled") {
|
|
3775
|
-
failed.push(agentIds[index]);
|
|
3776
|
-
}
|
|
3777
|
-
});
|
|
3778
|
-
if (failed.length) {
|
|
3779
|
-
const names = failed.map((agentId) => {
|
|
3780
|
-
const agent = agentsById.get(agentId);
|
|
3781
|
-
return agent ? resolveAgentDisplayName(agent) : agentId;
|
|
3782
|
-
});
|
|
3783
|
-
addToast({
|
|
3784
|
-
type: "error",
|
|
3785
|
-
title: "Unable to add agents",
|
|
3786
|
-
description: `Failed to add: ${names.join(", ")}`
|
|
3787
|
-
});
|
|
3788
|
-
}
|
|
3789
|
-
queryClient.invalidateQueries({ queryKey: ["lab-agents", projectId] });
|
|
3790
|
-
} finally {
|
|
3791
|
-
setRosterBusy(false);
|
|
3792
|
-
}
|
|
3793
|
-
},
|
|
3794
|
-
[addToast, agentsById, projectId, queryClient, questId, readOnly]
|
|
3795
|
-
);
|
|
3796
|
-
const handleRemoveAgent = reactExports.useCallback(
|
|
3797
|
-
async (agentId) => {
|
|
3798
|
-
if (!questId || readOnly) return;
|
|
3799
|
-
setRosterBusy(true);
|
|
3800
|
-
try {
|
|
3801
|
-
await assignLabAgent(projectId, agentId, { quest_id: null, quest_node_id: null });
|
|
3802
|
-
queryClient.invalidateQueries({ queryKey: ["lab-agents", projectId] });
|
|
3803
|
-
} catch (error) {
|
|
3804
|
-
const agent = agentsById.get(agentId);
|
|
3805
|
-
const label = agent ? resolveAgentDisplayName(agent) : agentId;
|
|
3806
|
-
addToast({
|
|
3807
|
-
type: "error",
|
|
3808
|
-
title: "Unable to remove agent",
|
|
3809
|
-
description: `Failed to remove ${label} from this quest.`
|
|
3810
|
-
});
|
|
3811
|
-
} finally {
|
|
3812
|
-
setRosterBusy(false);
|
|
3813
|
-
}
|
|
3814
|
-
},
|
|
3815
|
-
[addToast, agentsById, projectId, queryClient, questId, readOnly]
|
|
3816
|
-
);
|
|
3817
|
-
const setMomentBusyState = reactExports.useCallback((momentId, busy) => {
|
|
3818
|
-
setMomentBusy((prev) => {
|
|
3819
|
-
const next = new Set(prev);
|
|
3820
|
-
if (busy) {
|
|
3821
|
-
next.add(momentId);
|
|
3822
|
-
} else {
|
|
3823
|
-
next.delete(momentId);
|
|
3824
|
-
}
|
|
3825
|
-
return next;
|
|
3826
|
-
});
|
|
3827
|
-
}, []);
|
|
3828
|
-
const handleMomentToggleLike = reactExports.useCallback(
|
|
3829
|
-
async (momentId, nextLiked) => {
|
|
3830
|
-
if (!momentId || readOnly) return;
|
|
3831
|
-
setMomentBusyState(momentId, true);
|
|
3832
|
-
try {
|
|
3833
|
-
if (nextLiked) {
|
|
3834
|
-
await likeLabMoment(projectId, momentId);
|
|
3835
|
-
} else {
|
|
3836
|
-
await unlikeLabMoment(projectId, momentId);
|
|
3837
|
-
}
|
|
3838
|
-
setLikedMoments((prev) => ({ ...prev, [momentId]: nextLiked }));
|
|
3839
|
-
setMomentLikeUsers((prev) => {
|
|
3840
|
-
const existing = prev[momentId] ?? [];
|
|
3841
|
-
const next = new Set(existing);
|
|
3842
|
-
if (nextLiked) {
|
|
3843
|
-
next.add(currentUserLabel);
|
|
3844
|
-
} else {
|
|
3845
|
-
next.delete(currentUserLabel);
|
|
3846
|
-
}
|
|
3847
|
-
return { ...prev, [momentId]: Array.from(next) };
|
|
3848
|
-
});
|
|
3849
|
-
} catch {
|
|
3850
|
-
addToast({
|
|
3851
|
-
type: "error",
|
|
3852
|
-
title: "Unable to update like",
|
|
3853
|
-
description: "Please try again once your connection stabilizes."
|
|
3854
|
-
});
|
|
3855
|
-
} finally {
|
|
3856
|
-
setMomentBusyState(momentId, false);
|
|
3857
|
-
}
|
|
3858
|
-
},
|
|
3859
|
-
[addToast, currentUserLabel, projectId, readOnly, setMomentBusyState]
|
|
3860
|
-
);
|
|
3861
|
-
const handleMomentComment = reactExports.useCallback(
|
|
3862
|
-
async (momentId, content) => {
|
|
3863
|
-
if (!momentId || readOnly) return false;
|
|
3864
|
-
setMomentBusyState(momentId, true);
|
|
3865
|
-
try {
|
|
3866
|
-
await commentLabMoment(projectId, momentId, { content });
|
|
3867
|
-
setMomentComments((prev) => {
|
|
3868
|
-
const existing = prev[momentId] ?? [];
|
|
3869
|
-
const nextItem = { name: currentUserLabel, content };
|
|
3870
|
-
return { ...prev, [momentId]: [...existing, nextItem] };
|
|
3871
|
-
});
|
|
3872
|
-
return true;
|
|
3873
|
-
} catch {
|
|
3874
|
-
addToast({
|
|
3875
|
-
type: "error",
|
|
3876
|
-
title: "Unable to send comment",
|
|
3877
|
-
description: "Please try again once your connection stabilizes."
|
|
3878
|
-
});
|
|
3879
|
-
return false;
|
|
3880
|
-
} finally {
|
|
3881
|
-
setMomentBusyState(momentId, false);
|
|
3882
|
-
}
|
|
3883
|
-
},
|
|
3884
|
-
[addToast, currentUserLabel, projectId, readOnly, setMomentBusyState]
|
|
3885
|
-
);
|
|
3886
|
-
const flashPostHighlight = reactExports.useCallback((messageId) => {
|
|
3887
|
-
setHighlightedMessageId(messageId);
|
|
3888
|
-
if (typeof window !== "undefined") {
|
|
3889
|
-
window.setTimeout(() => setHighlightedMessageId(null), 1200);
|
|
3890
|
-
}
|
|
3891
|
-
}, []);
|
|
3892
|
-
const jumpToFriendResult = reactExports.useCallback(
|
|
3893
|
-
(result) => {
|
|
3894
|
-
if (!result) return;
|
|
3895
|
-
if (!scrollRef.current) return;
|
|
3896
|
-
if (shouldVirtualize) {
|
|
3897
|
-
rowVirtualizer.scrollToIndex(result.index, { align: "center" });
|
|
3898
|
-
} else {
|
|
3899
|
-
const node = scrollRef.current?.querySelector(
|
|
3900
|
-
`[data-friend-post-id="${result.id}"]`
|
|
3901
|
-
);
|
|
3902
|
-
if (node) {
|
|
3903
|
-
node.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
3904
|
-
}
|
|
3905
|
-
}
|
|
3906
|
-
flashPostHighlight(result.id);
|
|
3907
|
-
},
|
|
3908
|
-
[flashPostHighlight, rowVirtualizer, shouldVirtualize]
|
|
3909
|
-
);
|
|
3910
|
-
reactExports.useEffect(() => {
|
|
3911
|
-
if (!searchQuery.trim()) return;
|
|
3912
|
-
if (friendSearchResults.length === 0) return;
|
|
3913
|
-
jumpToFriendResult(friendSearchResults[0]);
|
|
3914
|
-
}, [friendSearchResults, jumpToFriendResult, searchQuery]);
|
|
3915
|
-
const menuPortal = headerPortalTarget ? reactDomExports.createPortal(
|
|
3916
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3917
|
-
LabCopilotOverflowMenu,
|
|
3918
|
-
{
|
|
3919
|
-
label: "Friends",
|
|
3920
|
-
entities: friendEntities,
|
|
3921
|
-
emptyEntitiesLabel: emptyRosterLabel,
|
|
3922
|
-
searchValue: searchQuery,
|
|
3923
|
-
onSearchChange: setSearchQuery,
|
|
3924
|
-
searchResults: friendSearchResults,
|
|
3925
|
-
onSearchSelect: jumpToFriendResult,
|
|
3926
|
-
searchPlaceholder: "Search friend updates",
|
|
3927
|
-
questId,
|
|
3928
|
-
quests,
|
|
3929
|
-
onQuestChange,
|
|
3930
|
-
canManageRoster: !readOnly && Boolean(questId),
|
|
3931
|
-
rosterBusy,
|
|
3932
|
-
availableAgents,
|
|
3933
|
-
onAddAgents: handleAddAgents,
|
|
3934
|
-
onRemoveAgent: handleRemoveAgent
|
|
3935
|
-
}
|
|
3936
|
-
),
|
|
3937
|
-
headerPortalTarget
|
|
3938
|
-
) : null;
|
|
3939
|
-
const messageContextMenuPortal = messageContextMenu && typeof document !== "undefined" ? reactDomExports.createPortal(
|
|
3940
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3941
|
-
"div",
|
|
3942
|
-
{
|
|
3943
|
-
className: "lab-copilot-context-menu",
|
|
3944
|
-
style: { left: messageContextMenu.x, top: messageContextMenu.y },
|
|
3945
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: handleOpenDirect, disabled: !canOpenDirect, children: "Open direct session" })
|
|
3946
|
-
}
|
|
3947
|
-
),
|
|
3948
|
-
document.body
|
|
3949
|
-
) : null;
|
|
3950
|
-
const handleScroll = reactExports.useCallback(
|
|
3951
|
-
(event) => {
|
|
3952
|
-
const target = event.currentTarget;
|
|
3953
|
-
const nextFollow = isScrolledToTop(target);
|
|
3954
|
-
setFollow((prev) => prev === nextFollow ? prev : nextFollow);
|
|
3955
|
-
},
|
|
3956
|
-
[isScrolledToTop]
|
|
3957
|
-
);
|
|
3958
|
-
const renderFriendMessage = (message) => {
|
|
3959
|
-
const content = message.content;
|
|
3960
|
-
const metadata = getMessageMetadata(message);
|
|
3961
|
-
const displayStreaming = message.type === "text_delta" && content.status === "in_progress";
|
|
3962
|
-
const isHighlighted = highlightedMessageId === message.id;
|
|
3963
|
-
const isMoment = metadata?.message_kind === "moment";
|
|
3964
|
-
const momentId = typeof metadata?.moment_id === "string" ? metadata.moment_id : "";
|
|
3965
|
-
const localLikeUsers = momentId ? momentLikeUsers[momentId] ?? [] : [];
|
|
3966
|
-
const localComments = momentId ? momentComments[momentId] ?? [] : [];
|
|
3967
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3968
|
-
"div",
|
|
3969
|
-
{
|
|
3970
|
-
className: cn("rounded-[12px] px-1", isHighlighted && "lab-message-highlight"),
|
|
3971
|
-
"data-friend-post-id": message.id,
|
|
3972
|
-
children: isMoment ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3973
|
-
FriendsMomentCard,
|
|
3974
|
-
{
|
|
3975
|
-
message,
|
|
3976
|
-
readOnly,
|
|
3977
|
-
liked: momentId ? Boolean(likedMoments[momentId]) : false,
|
|
3978
|
-
busy: momentId ? momentBusy.has(momentId) : false,
|
|
3979
|
-
likeUsers: localLikeUsers,
|
|
3980
|
-
comments: localComments,
|
|
3981
|
-
onToggleLike: handleMomentToggleLike,
|
|
3982
|
-
onCommentSubmit: handleMomentComment,
|
|
3983
|
-
onAvatarContextMenu: handleAvatarContextMenu
|
|
3984
|
-
}
|
|
3985
|
-
) : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3986
|
-
ChatMessage,
|
|
3987
|
-
{
|
|
3988
|
-
message,
|
|
3989
|
-
compact: true,
|
|
3990
|
-
onAvatarContextMenu: handleAvatarContextMenu,
|
|
3991
|
-
displayStreaming,
|
|
3992
|
-
streamActive: connection.status === "open" || connection.status === "connecting" || connection.status === "reconnecting"
|
|
3993
|
-
}
|
|
3994
|
-
)
|
|
3995
|
-
},
|
|
3996
|
-
message.id
|
|
3997
|
-
);
|
|
3998
|
-
};
|
|
3999
|
-
const renderHistoryBanner = () => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-4 flex justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4000
|
-
"div",
|
|
4001
|
-
{
|
|
4002
|
-
className: cn(
|
|
4003
|
-
"inline-flex max-w-full items-center gap-2 rounded-full border px-3 py-1 font-medium",
|
|
4004
|
-
"border-[var(--lab-border)] bg-[var(--lab-surface)] text-[var(--lab-text-secondary)]",
|
|
4005
|
-
"text-[11px]"
|
|
4006
|
-
),
|
|
4007
|
-
children: [
|
|
4008
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: historyLabel }),
|
|
4009
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4010
|
-
"button",
|
|
4011
|
-
{
|
|
4012
|
-
type: "button",
|
|
4013
|
-
onClick: () => {
|
|
4014
|
-
setFollow(false);
|
|
4015
|
-
loadFullHistory();
|
|
4016
|
-
},
|
|
4017
|
-
disabled: historyLoadingFull,
|
|
4018
|
-
className: "text-[var(--lab-text-primary)] underline decoration-dotted underline-offset-4 disabled:cursor-not-allowed disabled:opacity-50",
|
|
4019
|
-
children: historyLoadingFull ? "Loading full history..." : "Load full history"
|
|
4020
|
-
}
|
|
4021
|
-
)
|
|
4022
|
-
]
|
|
4023
|
-
}
|
|
4024
|
-
) });
|
|
4025
|
-
const scrollToTop = reactExports.useCallback(() => {
|
|
4026
|
-
const node = scrollRef.current;
|
|
4027
|
-
if (!node) return;
|
|
4028
|
-
if (typeof node.scrollTo === "function") {
|
|
4029
|
-
node.scrollTo({ top: 0, behavior: "auto" });
|
|
4030
|
-
} else {
|
|
4031
|
-
node.scrollTop = 0;
|
|
4032
|
-
}
|
|
4033
|
-
}, []);
|
|
4034
|
-
reactExports.useEffect(() => {
|
|
4035
|
-
if (!questId || messages.length === 0) return;
|
|
4036
|
-
if (!follow) return;
|
|
4037
|
-
window.requestAnimationFrame(() => scrollToTop());
|
|
4038
|
-
}, [follow, messages, questId, scrollToTop]);
|
|
4039
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [
|
|
4040
|
-
menuPortal,
|
|
4041
|
-
messageContextMenuPortal,
|
|
4042
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ai-manus-root ai-manus-copilot ai-manus-embedded flex flex-1 min-h-0 flex-col", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex flex-1 min-h-0 flex-col", children: [
|
|
4043
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { ref: scrollRef, className: "flex-1 min-h-0", onScroll: handleScroll, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-5 py-6", children: [
|
|
4044
|
-
connectionStatus ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-3 text-center text-[11px] text-[var(--text-tertiary)]", children: connectionStatus }) : null,
|
|
4045
|
-
!questId ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex min-h-[200px] items-center justify-center text-[12px] text-[var(--text-tertiary)]", children: emptyStateLabel }) : messages.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex min-h-[200px] items-center justify-center text-[12px] text-[var(--text-tertiary)]", children: emptyStateLabel }) : shouldVirtualize ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { height: rowVirtualizer.getTotalSize(), position: "relative" }, children: rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
|
4046
|
-
if (showLoadFullHistory && virtualRow.index === historyBannerIndex) {
|
|
4047
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4048
|
-
"div",
|
|
4049
|
-
{
|
|
4050
|
-
"data-index": virtualRow.index,
|
|
4051
|
-
ref: rowVirtualizer.measureElement,
|
|
4052
|
-
style: {
|
|
4053
|
-
position: "absolute",
|
|
4054
|
-
top: 0,
|
|
4055
|
-
left: 0,
|
|
4056
|
-
width: "100%",
|
|
4057
|
-
transform: `translateY(${virtualRow.start}px)`
|
|
4058
|
-
},
|
|
4059
|
-
children: renderHistoryBanner()
|
|
4060
|
-
},
|
|
4061
|
-
virtualRow.key
|
|
4062
|
-
);
|
|
4063
|
-
}
|
|
4064
|
-
const message = friendMessages[virtualRow.index];
|
|
4065
|
-
if (!message) return null;
|
|
4066
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4067
|
-
"div",
|
|
4068
|
-
{
|
|
4069
|
-
"data-index": virtualRow.index,
|
|
4070
|
-
ref: rowVirtualizer.measureElement,
|
|
4071
|
-
style: {
|
|
4072
|
-
position: "absolute",
|
|
4073
|
-
top: 0,
|
|
4074
|
-
left: 0,
|
|
4075
|
-
width: "100%",
|
|
4076
|
-
transform: `translateY(${virtualRow.start}px)`
|
|
4077
|
-
},
|
|
4078
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pb-5", children: renderFriendMessage(message) })
|
|
4079
|
-
},
|
|
4080
|
-
virtualRow.key
|
|
4081
|
-
);
|
|
4082
|
-
}) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col lab-friends-list", children: [
|
|
4083
|
-
friendMessages.map((message) => renderFriendMessage(message)),
|
|
4084
|
-
showLoadFullHistory ? renderHistoryBanner() : null
|
|
4085
|
-
] })
|
|
4086
|
-
] }) }),
|
|
4087
|
-
showHistoryLoadingOverlay ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 z-10 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4088
|
-
"div",
|
|
4089
|
-
{
|
|
4090
|
-
className: cn(
|
|
4091
|
-
"flex items-center gap-2 rounded-full border px-3 py-1.5 text-[11px] font-medium shadow-sm",
|
|
4092
|
-
"border-[var(--lab-border)] bg-[var(--lab-surface)] text-[var(--lab-text-secondary)]"
|
|
4093
|
-
),
|
|
4094
|
-
children: [
|
|
4095
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-4 w-4 animate-spin" }),
|
|
4096
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Loading..." })
|
|
4097
|
-
]
|
|
4098
|
-
}
|
|
4099
|
-
) }) : null
|
|
4100
|
-
] }) })
|
|
4101
|
-
] });
|
|
4102
|
-
}
|
|
4103
|
-
|
|
4104
|
-
export { LabCopilotHeader, LabCopilotPanel as default };
|