@nextclaw/ui 0.15.2 → 0.15.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/assets/{api-B7Qigvtm.js → api-iNBpppzj.js} +2 -2
- package/dist/assets/{appearance-settings-page-CuE0Iiqj.js → appearance-settings-page-DMffQFR3.js} +1 -1
- package/dist/assets/{book-open-Ba2f7YiG.js → book-open-C3mJGwiy.js} +1 -1
- package/dist/assets/{channels-list-page-CTsM6TcP.js → channels-list-page-CCtp0BRS.js} +1 -1
- package/dist/assets/chat-page-H5a0dWvO.js +1 -0
- package/dist/assets/{config-split-page-Fbb4PtTf.js → config-split-page-CrOJgB-Y.js} +1 -1
- package/dist/assets/{confirm-dialog-D2EG3nqE.js → confirm-dialog-Chv50USy.js} +2 -2
- package/dist/assets/{desktop-update-config-BGR3HUi1.js → desktop-update-config-KzTuXRf_.js} +1 -1
- package/dist/assets/{dist-7JTjycxE.js → dist-BunzmC4B.js} +1 -1
- package/dist/assets/{dist-wXLhk4hl.js → dist-ChmGwvtK.js} +1 -1
- package/dist/assets/doc-browser-DNRL4Zvd.js +1 -0
- package/dist/assets/doc-browser-Dg0pmkOg.js +1 -0
- package/dist/assets/{doc-browser-context-CYAGm5d5.js → doc-browser-context-BHY-0FSD.js} +1 -1
- package/dist/assets/{es2015-j1SRCIX5.js → es2015-DQZ5JWwK.js} +1 -1
- package/dist/assets/{external-link-DxuPaDoK.js → external-link-CU0kzzIP.js} +1 -1
- package/dist/assets/{form-actions-BztL7P_T.js → form-actions-BvCp_0lS.js} +1 -1
- package/dist/assets/{i18n-provider-B4oMxOFx.js → i18n-provider-Cu8XyZb_.js} +1 -1
- package/dist/assets/index-CzfBdvK1.css +1 -0
- package/dist/assets/index-DGqHne1Q.js +104 -0
- package/dist/assets/loader-circle-CDSFuzmk.js +1 -0
- package/dist/assets/mcp-marketplace-page-BeEQK0DL.js +9 -0
- package/dist/assets/mcp-marketplace-page-D6idOTLp.js +1 -0
- package/dist/assets/{model-config-page-DeKk9gvE.js → model-config-page-bNNo-XyG.js} +1 -1
- package/dist/assets/plus-D4DsdLN-.js +1 -0
- package/dist/assets/{provider-scoped-model-input-DsH2LF1q.js → provider-scoped-model-input-IlgQCsoT.js} +1 -1
- package/dist/assets/{providers-config-page-BFnGp68F.js → providers-config-page-wLo6CAJw.js} +1 -1
- package/dist/assets/{react-UirzUuST.js → react-BPIsQIlO.js} +1 -1
- package/dist/assets/{refresh-cw-eT8BF-8B.js → refresh-cw-CILd1vAP.js} +1 -1
- package/dist/assets/remote-uKEYp7AR.js +1 -0
- package/dist/assets/rotate-cw-wPFPg1kH.js +1 -0
- package/dist/assets/{runtime-config-page-BelAqiNG.js → runtime-config-page-DDn1sP4G.js} +1 -1
- package/dist/assets/{save-Cb2g0BOi.js → save-ByJqcPTP.js} +1 -1
- package/dist/assets/{search-BcHjwdsb.js → search-DVIV6pfD.js} +1 -1
- package/dist/assets/{search-config-page-zPm6--Ig.js → search-config-page-B-Cf4iiB.js} +1 -1
- package/dist/assets/{secrets-config-page-Cxyp-KuQ.js → secrets-config-page-DPKsY8Iw.js} +1 -1
- package/dist/assets/security-config-e9SSBlKr.js +1 -0
- package/dist/assets/{select-CdSEOwVM.js → select-BdgczSNw.js} +1 -1
- package/dist/assets/{setting-row-CTAtGexr.js → setting-row-BkTVUTKx.js} +1 -1
- package/dist/assets/{settings-2-2DWauOjY.js → settings-2-CH5shGg_.js} +1 -1
- package/dist/assets/{side-dock-DPvakJ0a.js → side-dock-BsBvcJ9i.js} +2 -2
- package/dist/assets/{skeleton-P5oM14E1.js → skeleton-DeX1FKw7.js} +1 -1
- package/dist/assets/switch-Cv8Ekb3x.js +1 -0
- package/dist/assets/{tag-chip-D-WsKoT1.js → tag-chip-RB9uKcoC.js} +1 -1
- package/dist/assets/{tooltip-CjkOaHW7.js → tooltip-oQlEN-l6.js} +1 -1
- package/dist/assets/{use-config--hx5oxQn.js → use-config-l3W7y8Qz.js} +1 -1
- package/dist/assets/{use-confirm-dialog-CD9a_SK1.js → use-confirm-dialog-BAxaVOJI.js} +1 -1
- package/dist/assets/x-BBMffEN0.js +1 -0
- package/dist/index.html +26 -26
- package/package.json +9 -9
- package/src/app/components/layout/sidebar-items.tsx +3 -3
- package/src/app/components/layout/sidebar-rail.styles.ts +2 -2
- package/src/app/components/layout/sidebar.tsx +1 -1
- package/src/app/styles/design-system.css +17 -4
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +1 -1
- package/src/features/chat/features/conversation/components/session-queued-input-rows.tsx +1 -1
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +62 -1
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +47 -1
- package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +11 -1
- package/src/features/chat/features/message/utils/__tests__/chat-message-texts.utils.test.ts +23 -0
- package/src/features/chat/features/message/utils/chat-message-process-summary.utils.ts +5 -0
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +138 -0
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +2 -2
- package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +1 -1
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +1 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +67 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +1 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +2 -2
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +1 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +174 -22
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +4 -0
- package/src/features/chat/managers/chat-thread.manager.ts +2 -0
- package/src/features/chat/stores/chat-thread.store.ts +3 -0
- package/src/features/cron/components/cron-config.tsx +63 -80
- package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx +44 -6
- package/src/features/marketplace/components/marketplace-detail-doc.tsx +8 -4
- package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
- package/src/features/marketplace/hooks/use-marketplace-detail-doc-entry.ts +84 -0
- package/src/features/panel-apps/components/panel-apps-list.tsx +2 -2
- package/src/features/right-panel-resources/components/right-panel-resource-home-page.tsx +1 -1
- package/src/features/side-dock/components/side-dock.tsx +3 -3
- package/src/index.css +239 -13
- package/src/shared/components/actions/action-link.tsx +1 -1
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +1 -1
- package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
- package/src/shared/components/ui/button.tsx +1 -1
- package/src/shared/components/ui/dialog.tsx +1 -1
- package/src/shared/components/ui/input.tsx +1 -1
- package/src/shared/components/ui/select.tsx +1 -1
- package/src/shared/components/ui/switch.tsx +1 -1
- package/src/shared/components/ui/tabs.tsx +1 -1
- package/src/shared/components/ui/textarea.tsx +1 -1
- package/src/shared/lib/i18n/locales/en-US/chat.json +83 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +83 -0
- package/dist/assets/chat-page-C_5dcuE3.js +0 -1
- package/dist/assets/doc-browser-bHyXnxlj.js +0 -1
- package/dist/assets/doc-browser-gTrQ3jCF.js +0 -1
- package/dist/assets/index-CvrvblgX.css +0 -1
- package/dist/assets/index-Obd4JYU2.js +0 -104
- package/dist/assets/loader-circle-CdzgK4eH.js +0 -1
- package/dist/assets/mcp-marketplace-page-Bt7CuOoq.js +0 -9
- package/dist/assets/mcp-marketplace-page-Ck3t78_n.js +0 -1
- package/dist/assets/plus-Bx7wQbhG.js +0 -1
- package/dist/assets/remote-CDkHZtA8.js +0 -1
- package/dist/assets/rotate-cw-1JqxHTuw.js +0 -1
- package/dist/assets/security-config-B_gCq9fn.js +0 -1
- package/dist/assets/switch-BU_UEULb.js +0 -1
- package/dist/assets/x-DT5x45JU.js +0 -1
|
@@ -108,7 +108,7 @@ export function ChatSidebarUtilityMenu({
|
|
|
108
108
|
<button
|
|
109
109
|
type="button"
|
|
110
110
|
aria-label={t("settingsMenu")}
|
|
111
|
-
className="flex w-full items-center gap-2.5 rounded-xl px-3 py-2 text-[13px] font-medium text-muted-foreground transition-colors duration-base hover:bg-gray-200/60 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-
|
|
111
|
+
className="flex w-full items-center gap-2.5 rounded-xl px-3 py-2 text-[13px] font-medium text-muted-foreground transition-colors duration-base hover:bg-gray-200/60 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
112
112
|
>
|
|
113
113
|
<Settings className="h-4 w-4 shrink-0 text-muted-foreground/70" />
|
|
114
114
|
<span className="min-w-0 flex-1 text-left">{t("settings")}</span>
|
|
@@ -44,7 +44,7 @@ function QueuedInputIconButton({
|
|
|
44
44
|
aria-label={label}
|
|
45
45
|
disabled={disabled}
|
|
46
46
|
onClick={onClick}
|
|
47
|
-
className="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-
|
|
47
|
+
className="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border disabled:cursor-not-allowed disabled:text-muted-foreground/50"
|
|
48
48
|
>
|
|
49
49
|
{children}
|
|
50
50
|
</button>
|
package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx
CHANGED
|
@@ -9,6 +9,8 @@ const captures = vi.hoisted(() => ({
|
|
|
9
9
|
messages: unknown[];
|
|
10
10
|
onToolAction?: (action: unknown) => void;
|
|
11
11
|
onFileOpen?: (action: unknown) => void;
|
|
12
|
+
onAttachmentOpen?: (file: unknown) => void;
|
|
13
|
+
onInlineTokenClick?: (token: unknown) => void;
|
|
12
14
|
renderInlineDisplay?: (display: unknown) => unknown;
|
|
13
15
|
renderPanelAppCard?: (panelApp: unknown) => unknown;
|
|
14
16
|
texts?: Record<string, unknown>;
|
|
@@ -16,6 +18,7 @@ const captures = vi.hoisted(() => ({
|
|
|
16
18
|
language: "en",
|
|
17
19
|
openFilePreview: vi.fn(),
|
|
18
20
|
handleToolAction: vi.fn(),
|
|
21
|
+
showContent: vi.fn(),
|
|
19
22
|
}));
|
|
20
23
|
|
|
21
24
|
vi.mock("@nextclaw/agent-chat-ui", async (importOriginal) => {
|
|
@@ -26,6 +29,7 @@ vi.mock("@nextclaw/agent-chat-ui", async (importOriginal) => {
|
|
|
26
29
|
messages: unknown[];
|
|
27
30
|
onToolAction?: (action: unknown) => void;
|
|
28
31
|
onFileOpen?: (action: unknown) => void;
|
|
32
|
+
onAttachmentOpen?: (file: unknown) => void;
|
|
29
33
|
renderInlineDisplay?: (display: unknown) => unknown;
|
|
30
34
|
renderPanelAppCard?: (panelApp: unknown) => unknown;
|
|
31
35
|
texts?: Record<string, unknown>;
|
|
@@ -42,6 +46,9 @@ vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
|
|
|
42
46
|
openFilePreview: captures.openFilePreview,
|
|
43
47
|
handleToolAction: captures.handleToolAction,
|
|
44
48
|
},
|
|
49
|
+
chatUiManager: {
|
|
50
|
+
showContent: captures.showContent,
|
|
51
|
+
},
|
|
45
52
|
}),
|
|
46
53
|
}));
|
|
47
54
|
|
|
@@ -59,6 +66,7 @@ beforeEach(() => {
|
|
|
59
66
|
captures.language = "en";
|
|
60
67
|
captures.openFilePreview.mockReset();
|
|
61
68
|
captures.handleToolAction.mockReset();
|
|
69
|
+
captures.showContent.mockReset();
|
|
62
70
|
});
|
|
63
71
|
|
|
64
72
|
it("reuses adapted message references when the source message object is unchanged", () => {
|
|
@@ -300,6 +308,41 @@ it("wires markdown file link actions to the workspace file preview manager", ()
|
|
|
300
308
|
expect(captures.openFilePreview).toHaveBeenCalledWith(action);
|
|
301
309
|
});
|
|
302
310
|
|
|
311
|
+
it("opens message attachments in the workspace file preview", () => {
|
|
312
|
+
const message = {
|
|
313
|
+
id: "assistant-attachment",
|
|
314
|
+
sessionId: "session-1",
|
|
315
|
+
role: "assistant",
|
|
316
|
+
status: "final",
|
|
317
|
+
timestamp: "2026-03-31T10:05:00.000Z",
|
|
318
|
+
parts: [
|
|
319
|
+
{
|
|
320
|
+
type: "file",
|
|
321
|
+
name: "spec.pdf",
|
|
322
|
+
mimeType: "application/pdf",
|
|
323
|
+
contentBase64: "cGRm",
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
} satisfies NcpMessage;
|
|
327
|
+
|
|
328
|
+
render(<ChatMessageListContainer messages={[message]} isSending={false} />);
|
|
329
|
+
captures.renders[captures.renders.length - 1]?.onAttachmentOpen?.({
|
|
330
|
+
label: "spec.pdf",
|
|
331
|
+
mimeType: "application/pdf",
|
|
332
|
+
dataUrl: "data:application/pdf;base64,cGRm",
|
|
333
|
+
isImage: false,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
expect(captures.openFilePreview).toHaveBeenCalledWith({
|
|
337
|
+
path: "spec.pdf",
|
|
338
|
+
label: "spec.pdf",
|
|
339
|
+
viewMode: "preview",
|
|
340
|
+
contentUrl: "data:application/pdf;base64,cGRm",
|
|
341
|
+
mimeType: "application/pdf",
|
|
342
|
+
});
|
|
343
|
+
expect(captures.showContent).not.toHaveBeenCalled();
|
|
344
|
+
});
|
|
345
|
+
|
|
303
346
|
it("renders context inheritance as a divider without repeating inherited messages", () => {
|
|
304
347
|
const inheritedMessage = {
|
|
305
348
|
id: "child-session:inherited:1",
|
|
@@ -387,7 +430,7 @@ it("keeps Hermes tool invocation parts as tool cards instead of flattening them
|
|
|
387
430
|
});
|
|
388
431
|
});
|
|
389
432
|
|
|
390
|
-
it("passes localized
|
|
433
|
+
it("passes localized message presentation texts to the shared chat UI", () => {
|
|
391
434
|
captures.language = "zh";
|
|
392
435
|
|
|
393
436
|
render(<ChatMessageListContainer messages={[]} isSending={false} />);
|
|
@@ -400,6 +443,24 @@ it("passes localized attachment card texts to the shared chat UI", () => {
|
|
|
400
443
|
pdf: "chatAttachmentCategoryPdf",
|
|
401
444
|
generic: "chatAttachmentCategoryGeneric",
|
|
402
445
|
},
|
|
446
|
+
reasoningCharacterCountTemplates: {
|
|
447
|
+
inProgress: "chatReasoningInProgressCharacterCount",
|
|
448
|
+
completed: "chatReasoningCompletedCharacterCount",
|
|
449
|
+
},
|
|
450
|
+
toolStatusLabels: {
|
|
451
|
+
terminal: {
|
|
452
|
+
running: "chatToolTerminalRunning",
|
|
453
|
+
success: "chatToolTerminalSuccess",
|
|
454
|
+
error: "chatToolTerminalError",
|
|
455
|
+
cancelled: "chatToolTerminalCancelled",
|
|
456
|
+
},
|
|
457
|
+
fileEdit: {
|
|
458
|
+
running: "chatToolFileEditRunning",
|
|
459
|
+
success: "chatToolFileEditSuccess",
|
|
460
|
+
error: "chatToolFileEditError",
|
|
461
|
+
cancelled: "chatToolFileEditCancelled",
|
|
462
|
+
},
|
|
463
|
+
},
|
|
403
464
|
});
|
|
404
465
|
});
|
|
405
466
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
1
|
+
import { useCallback, useMemo } from "react";
|
|
2
2
|
import type { NcpMessage } from "@nextclaw/ncp";
|
|
3
3
|
import {
|
|
4
4
|
type ChatInlineDisplayViewModel,
|
|
5
|
+
type ChatInlineTokenViewModel,
|
|
5
6
|
type ChatMessageViewModel,
|
|
6
7
|
ChatMessageList,
|
|
7
8
|
} from "@nextclaw/agent-chat-ui";
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
} from "@/features/chat/features/session/utils/ncp-session-context-metadata.utils";
|
|
24
25
|
import { AgentIdentityAvatar } from "@/shared/components/common/agent-identity";
|
|
25
26
|
import { ChatInlinePanelAppCard } from "@/features/chat/features/message/components/chat-inline-panel-app-card";
|
|
27
|
+
import { useChatQueryStore } from "@/features/chat/stores/ncp-chat-query.store";
|
|
26
28
|
import { useI18n } from "@/app/components/i18n-provider";
|
|
27
29
|
import { formatDateTime, t } from "@/shared/lib/i18n";
|
|
28
30
|
|
|
@@ -342,6 +344,48 @@ export function ChatMessageListContainer({
|
|
|
342
344
|
() => buildTimelineItems({ rawMessages, messages }),
|
|
343
345
|
[messages, rawMessages],
|
|
344
346
|
);
|
|
347
|
+
const sessionSkillsQuery = useChatQueryStore((state) => state.snapshot.sessionSkillsQuery);
|
|
348
|
+
const handleInlineTokenClick = useCallback(
|
|
349
|
+
(token: ChatInlineTokenViewModel) => {
|
|
350
|
+
if (token.kind !== "skill") {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const skillKey = token.key.trim();
|
|
354
|
+
if (!skillKey) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const records = sessionSkillsQuery?.data?.records ?? [];
|
|
358
|
+
const matched = records.find((record) => record.ref === skillKey || record.name === skillKey);
|
|
359
|
+
const skillPath = matched?.path?.trim();
|
|
360
|
+
if (!skillPath) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
presenter.chatThreadManager.openFilePreview({
|
|
364
|
+
path: skillPath,
|
|
365
|
+
label: token.label || matched?.name || skillKey,
|
|
366
|
+
viewMode: "preview",
|
|
367
|
+
previewViewer: "rendered",
|
|
368
|
+
});
|
|
369
|
+
},
|
|
370
|
+
[presenter.chatThreadManager, sessionSkillsQuery],
|
|
371
|
+
);
|
|
372
|
+
const handleAttachmentOpen = useCallback(
|
|
373
|
+
(file: { label: string; mimeType: string; dataUrl?: string; sizeBytes?: number; isImage: boolean }) => {
|
|
374
|
+
const contentUrl = file.dataUrl?.trim();
|
|
375
|
+
if (!contentUrl) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const label = file.label.trim() || "attachment";
|
|
379
|
+
presenter.chatThreadManager.openFilePreview({
|
|
380
|
+
path: label,
|
|
381
|
+
label,
|
|
382
|
+
viewMode: "preview",
|
|
383
|
+
contentUrl,
|
|
384
|
+
mimeType: file.mimeType,
|
|
385
|
+
});
|
|
386
|
+
},
|
|
387
|
+
[presenter.chatThreadManager],
|
|
388
|
+
);
|
|
345
389
|
return (
|
|
346
390
|
<div className={className}>
|
|
347
391
|
{timelineItems.map((item, index) =>
|
|
@@ -358,6 +402,8 @@ export function ChatMessageListContainer({
|
|
|
358
402
|
texts={messageTexts}
|
|
359
403
|
onToolAction={presenter.chatThreadManager.handleToolAction}
|
|
360
404
|
onFileOpen={presenter.chatThreadManager.openFilePreview}
|
|
405
|
+
onAttachmentOpen={handleAttachmentOpen}
|
|
406
|
+
onInlineTokenClick={handleInlineTokenClick}
|
|
361
407
|
renderInlineDisplay={renderChatInlineDisplay}
|
|
362
408
|
renderToolAgent={(agentId) => (
|
|
363
409
|
<AgentIdentityAvatar
|
|
@@ -18,7 +18,7 @@ const baseAssistantMessage = {
|
|
|
18
18
|
toolCallId: "tool-1",
|
|
19
19
|
toolName: "exec_command",
|
|
20
20
|
state: "result",
|
|
21
|
-
args:
|
|
21
|
+
args: '{"cmd":"git status"}',
|
|
22
22
|
result: "clean",
|
|
23
23
|
},
|
|
24
24
|
{
|
|
@@ -56,4 +56,14 @@ describe("buildChatMessageProcessSummary", () => {
|
|
|
56
56
|
label: "Processed 3m 51s",
|
|
57
57
|
});
|
|
58
58
|
});
|
|
59
|
+
|
|
60
|
+
it("stays free of tool-activity semantics", () => {
|
|
61
|
+
const summary = buildChatMessageProcessSummary({
|
|
62
|
+
message: baseAssistantMessage,
|
|
63
|
+
processedLabel: "Processed",
|
|
64
|
+
});
|
|
65
|
+
expect(summary?.label).toBe("Processed");
|
|
66
|
+
expect(summary?.label.toLowerCase()).not.toContain("bash");
|
|
67
|
+
expect(summary?.label.toLowerCase()).not.toContain("read");
|
|
68
|
+
});
|
|
59
69
|
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { expect, it, vi } from "vitest";
|
|
2
|
+
import { buildChatMessageTexts } from "@/features/chat/features/message/utils/chat-message-texts.utils";
|
|
3
|
+
|
|
4
|
+
vi.mock("@/shared/lib/i18n", () => ({
|
|
5
|
+
t: (key: string) => key,
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
it("provides localized status keys for built-in tool categories", () => {
|
|
9
|
+
expect(buildChatMessageTexts("zh").toolStatusLabels?.builtIn).toMatchObject({
|
|
10
|
+
directory: {
|
|
11
|
+
running: "chatToolDirectoryRunning",
|
|
12
|
+
success: "chatToolDirectorySuccess",
|
|
13
|
+
error: "chatToolDirectoryError",
|
|
14
|
+
cancelled: "chatToolDirectoryCancelled",
|
|
15
|
+
},
|
|
16
|
+
memory: {
|
|
17
|
+
running: "chatToolMemoryRunning",
|
|
18
|
+
success: "chatToolMemorySuccess",
|
|
19
|
+
error: "chatToolMemoryError",
|
|
20
|
+
cancelled: "chatToolMemoryCancelled",
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -68,6 +68,11 @@ function formatLifecycleDuration(message: NcpMessage): string | null {
|
|
|
68
68
|
return `${seconds}s`;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Outer process collapse only.
|
|
73
|
+
* Tool activity grouping is a separate layer over consecutive tool-cards
|
|
74
|
+
* and must not be mixed into this summary.
|
|
75
|
+
*/
|
|
71
76
|
export function buildChatMessageProcessSummary({
|
|
72
77
|
message,
|
|
73
78
|
processedLabel,
|
|
@@ -32,6 +32,71 @@ export function buildChatMessageAdapterTexts(
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function buildBuiltInToolStatusLabels() {
|
|
36
|
+
return {
|
|
37
|
+
directory: {
|
|
38
|
+
running: t("chatToolDirectoryRunning"),
|
|
39
|
+
success: t("chatToolDirectorySuccess"),
|
|
40
|
+
error: t("chatToolDirectoryError"),
|
|
41
|
+
cancelled: t("chatToolDirectoryCancelled"),
|
|
42
|
+
},
|
|
43
|
+
web: {
|
|
44
|
+
running: t("chatToolWebRunning"),
|
|
45
|
+
success: t("chatToolWebSuccess"),
|
|
46
|
+
error: t("chatToolWebError"),
|
|
47
|
+
cancelled: t("chatToolWebCancelled"),
|
|
48
|
+
},
|
|
49
|
+
message: {
|
|
50
|
+
running: t("chatToolMessageRunning"),
|
|
51
|
+
success: t("chatToolMessageSuccess"),
|
|
52
|
+
error: t("chatToolMessageError"),
|
|
53
|
+
cancelled: t("chatToolMessageCancelled"),
|
|
54
|
+
},
|
|
55
|
+
session: {
|
|
56
|
+
running: t("chatToolSessionRunning"),
|
|
57
|
+
success: t("chatToolSessionSuccess"),
|
|
58
|
+
error: t("chatToolSessionError"),
|
|
59
|
+
cancelled: t("chatToolSessionCancelled"),
|
|
60
|
+
},
|
|
61
|
+
agent: {
|
|
62
|
+
running: t("chatToolAgentRunning"),
|
|
63
|
+
success: t("chatToolAgentSuccess"),
|
|
64
|
+
error: t("chatToolAgentError"),
|
|
65
|
+
cancelled: t("chatToolAgentCancelled"),
|
|
66
|
+
},
|
|
67
|
+
memory: {
|
|
68
|
+
running: t("chatToolMemoryRunning"),
|
|
69
|
+
success: t("chatToolMemorySuccess"),
|
|
70
|
+
error: t("chatToolMemoryError"),
|
|
71
|
+
cancelled: t("chatToolMemoryCancelled"),
|
|
72
|
+
},
|
|
73
|
+
schedule: {
|
|
74
|
+
running: t("chatToolScheduleRunning"),
|
|
75
|
+
success: t("chatToolScheduleSuccess"),
|
|
76
|
+
error: t("chatToolScheduleError"),
|
|
77
|
+
cancelled: t("chatToolScheduleCancelled"),
|
|
78
|
+
},
|
|
79
|
+
system: {
|
|
80
|
+
running: t("chatToolSystemRunning"),
|
|
81
|
+
success: t("chatToolSystemSuccess"),
|
|
82
|
+
error: t("chatToolSystemError"),
|
|
83
|
+
cancelled: t("chatToolSystemCancelled"),
|
|
84
|
+
},
|
|
85
|
+
image: {
|
|
86
|
+
running: t("chatToolImageRunning"),
|
|
87
|
+
success: t("chatToolImageSuccess"),
|
|
88
|
+
error: t("chatToolImageError"),
|
|
89
|
+
cancelled: t("chatToolImageCancelled"),
|
|
90
|
+
},
|
|
91
|
+
display: {
|
|
92
|
+
running: t("chatToolDisplayRunning"),
|
|
93
|
+
success: t("chatToolDisplaySuccess"),
|
|
94
|
+
error: t("chatToolDisplayError"),
|
|
95
|
+
cancelled: t("chatToolDisplayCancelled"),
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
35
100
|
export function buildChatMessageTexts(
|
|
36
101
|
language: string,
|
|
37
102
|
): ChatMessageTexts {
|
|
@@ -42,8 +107,41 @@ export function buildChatMessageTexts(
|
|
|
42
107
|
copyMessageLabel: t("chatMessageCopy"),
|
|
43
108
|
copiedMessageLabel: t("chatMessageCopied"),
|
|
44
109
|
typingLabel: t("chatTyping"),
|
|
110
|
+
reasoningCharacterCountTemplates: {
|
|
111
|
+
inProgress: t("chatReasoningInProgressCharacterCount"),
|
|
112
|
+
completed: t("chatReasoningCompletedCharacterCount"),
|
|
113
|
+
},
|
|
114
|
+
toolStatusLabels: {
|
|
115
|
+
terminal: {
|
|
116
|
+
running: t("chatToolTerminalRunning"),
|
|
117
|
+
success: t("chatToolTerminalSuccess"),
|
|
118
|
+
error: t("chatToolTerminalError"),
|
|
119
|
+
cancelled: t("chatToolTerminalCancelled"),
|
|
120
|
+
},
|
|
121
|
+
fileRead: {
|
|
122
|
+
running: t("chatToolFileReadRunning"),
|
|
123
|
+
success: t("chatToolFileReadSuccess"),
|
|
124
|
+
error: t("chatToolFileReadError"),
|
|
125
|
+
cancelled: t("chatToolFileReadCancelled"),
|
|
126
|
+
},
|
|
127
|
+
fileEdit: {
|
|
128
|
+
running: t("chatToolFileEditRunning"),
|
|
129
|
+
success: t("chatToolFileEditSuccess"),
|
|
130
|
+
error: t("chatToolFileEditError"),
|
|
131
|
+
cancelled: t("chatToolFileEditCancelled"),
|
|
132
|
+
},
|
|
133
|
+
search: {
|
|
134
|
+
running: t("chatToolSearchRunning"),
|
|
135
|
+
success: t("chatToolSearchSuccess"),
|
|
136
|
+
error: t("chatToolSearchError"),
|
|
137
|
+
cancelled: t("chatToolSearchCancelled"),
|
|
138
|
+
},
|
|
139
|
+
builtIn: buildBuiltInToolStatusLabels(),
|
|
140
|
+
},
|
|
45
141
|
attachmentOpenLabel: t("chatAttachmentOpen"),
|
|
46
142
|
attachmentAttachedLabel: t("chatAttachmentAttached"),
|
|
143
|
+
attachmentExpandLabel: t("chatAttachmentExpand"),
|
|
144
|
+
attachmentCloseLabel: t("chatAttachmentClosePreview"),
|
|
47
145
|
attachmentCategoryLabels: {
|
|
48
146
|
archive: t("chatAttachmentCategoryArchive"),
|
|
49
147
|
audio: t("chatAttachmentCategoryAudio"),
|
|
@@ -56,5 +154,45 @@ export function buildChatMessageTexts(
|
|
|
56
154
|
sheet: t("chatAttachmentCategorySheet"),
|
|
57
155
|
video: t("chatAttachmentCategoryVideo"),
|
|
58
156
|
},
|
|
157
|
+
toolActivitySegmentTemplates: {
|
|
158
|
+
read: {
|
|
159
|
+
one: t("chatToolActivityReadOne"),
|
|
160
|
+
other: t("chatToolActivityReadOther"),
|
|
161
|
+
},
|
|
162
|
+
edit: {
|
|
163
|
+
one: t("chatToolActivityEditOne"),
|
|
164
|
+
other: t("chatToolActivityEditOther"),
|
|
165
|
+
},
|
|
166
|
+
directory: {
|
|
167
|
+
one: t("chatToolActivityDirectoryOne"),
|
|
168
|
+
other: t("chatToolActivityDirectoryOther"),
|
|
169
|
+
},
|
|
170
|
+
search: {
|
|
171
|
+
one: t("chatToolActivitySearchOne"),
|
|
172
|
+
other: t("chatToolActivitySearchOther"),
|
|
173
|
+
},
|
|
174
|
+
bash: {
|
|
175
|
+
one: t("chatToolActivityBashOne"),
|
|
176
|
+
other: t("chatToolActivityBashOther"),
|
|
177
|
+
},
|
|
178
|
+
web: {
|
|
179
|
+
one: t("chatToolActivityWebOne"),
|
|
180
|
+
other: t("chatToolActivityWebOther"),
|
|
181
|
+
},
|
|
182
|
+
agent: {
|
|
183
|
+
one: t("chatToolActivityAgentOne"),
|
|
184
|
+
other: t("chatToolActivityAgentOther"),
|
|
185
|
+
},
|
|
186
|
+
panel: {
|
|
187
|
+
one: t("chatToolActivityPanelOne"),
|
|
188
|
+
other: t("chatToolActivityPanelOther"),
|
|
189
|
+
},
|
|
190
|
+
other: {
|
|
191
|
+
one: t("chatToolActivityOtherOne"),
|
|
192
|
+
other: t("chatToolActivityOtherOther"),
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
toolActivityFailedLabel: t("chatProcessSummaryFailed"),
|
|
196
|
+
toolActivityCancelledLabel: t("chatProcessSummaryCancelled"),
|
|
59
197
|
};
|
|
60
198
|
}
|
|
@@ -64,7 +64,7 @@ export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
|
|
|
64
64
|
? 'chatSidebarExpandProject'
|
|
65
65
|
: 'chatSidebarCollapseProject',
|
|
66
66
|
)}
|
|
67
|
-
className="flex h-full w-full min-w-0 items-center gap-1.5 pr-14 text-left focus-visible:outline-none focus-visible:ring-
|
|
67
|
+
className="flex h-full w-full min-w-0 items-center gap-1.5 pr-14 text-left focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
68
68
|
onClick={() =>
|
|
69
69
|
presenter.chatSessionListManager.toggleProjectCollapsed(
|
|
70
70
|
group.projectRoot,
|
package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx
CHANGED
|
@@ -40,7 +40,7 @@ import { t } from "@/shared/lib/i18n";
|
|
|
40
40
|
import { cn } from "@/shared/lib/utils";
|
|
41
41
|
|
|
42
42
|
const SWITCHER_TRIGGER_CLASS =
|
|
43
|
-
"group inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-md px-1.5 py-1 text-sm font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-
|
|
43
|
+
"group inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-md px-1.5 py-1 text-sm font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border";
|
|
44
44
|
|
|
45
45
|
function ChatSessionTitle({ title }: { title: string }) {
|
|
46
46
|
return (
|
|
@@ -111,7 +111,7 @@ function ChatSessionSwitchItem({
|
|
|
111
111
|
type="button"
|
|
112
112
|
aria-current={active ? "page" : undefined}
|
|
113
113
|
className={cn(
|
|
114
|
-
"flex w-full min-w-0 items-start gap-2 rounded-lg px-2.5 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-
|
|
114
|
+
"flex w-full min-w-0 items-start gap-2 rounded-lg px-2.5 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
|
|
115
115
|
active
|
|
116
116
|
? "bg-accent text-accent-foreground"
|
|
117
117
|
: "text-popover-foreground hover:bg-accent/70",
|
|
@@ -49,7 +49,7 @@ export function ChatWelcomeProjectPicker({
|
|
|
49
49
|
<PopoverTrigger asChild>
|
|
50
50
|
<button
|
|
51
51
|
type="button"
|
|
52
|
-
className="inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-
|
|
52
|
+
className="inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
53
53
|
title={projectRoot ?? undefined}
|
|
54
54
|
aria-label={t('chatWelcomeProjectPickerLabel')}
|
|
55
55
|
disabled={!selectable || isSaving}
|
|
@@ -39,7 +39,7 @@ export function ChatWelcomeSessionTypePicker({
|
|
|
39
39
|
<PopoverTrigger asChild>
|
|
40
40
|
<button
|
|
41
41
|
type="button"
|
|
42
|
-
className="inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-
|
|
42
|
+
className="inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
43
43
|
aria-label={t('chatWelcomeSessionTypePickerLabel')}
|
|
44
44
|
>
|
|
45
45
|
{selectedOption.icon?.src ? (
|
|
@@ -167,6 +167,11 @@ function mockWorkspaceBreadcrumbBrowseTree() {
|
|
|
167
167
|
beforeEach(() => {
|
|
168
168
|
serverPathReadMock.mockReset();
|
|
169
169
|
serverPathBrowseMock.mockReset();
|
|
170
|
+
serverPathReadMock.mockReturnValue({
|
|
171
|
+
isLoading: false,
|
|
172
|
+
error: null,
|
|
173
|
+
data: null,
|
|
174
|
+
});
|
|
170
175
|
serverPathBrowseMock.mockReturnValue({
|
|
171
176
|
isLoading: false,
|
|
172
177
|
error: new Error("server path must point to a directory"),
|
|
@@ -175,6 +180,68 @@ beforeEach(() => {
|
|
|
175
180
|
});
|
|
176
181
|
|
|
177
182
|
describe("ChatSessionWorkspaceFilePreview rendering", () => {
|
|
183
|
+
it("renders attachment content URLs as workspace-native media previews", () => {
|
|
184
|
+
renderWorkspaceFilePreview({
|
|
185
|
+
file: {
|
|
186
|
+
key: "attachment-image",
|
|
187
|
+
path: "photo.png",
|
|
188
|
+
label: "photo.png",
|
|
189
|
+
viewMode: "preview",
|
|
190
|
+
contentUrl: "/api/ncp/assets/content?uri=asset%3A%2F%2Fstore%2Fphoto",
|
|
191
|
+
mimeType: "image/png",
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
expect(serverPathReadMock).toHaveBeenCalledWith(
|
|
196
|
+
expect.objectContaining({ enabled: false }),
|
|
197
|
+
);
|
|
198
|
+
expect(serverPathBrowseMock).toHaveBeenCalledWith(
|
|
199
|
+
expect.objectContaining({ enabled: false }),
|
|
200
|
+
);
|
|
201
|
+
const image = screen.getByTestId("workspace-content-image");
|
|
202
|
+
expect(image.getAttribute("src")).toBe(
|
|
203
|
+
"/api/ncp/assets/content?uri=asset%3A%2F%2Fstore%2Fphoto",
|
|
204
|
+
);
|
|
205
|
+
expect(image.getAttribute("alt")).toBe("photo.png");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("offers download and system open actions for unsupported office attachments", () => {
|
|
209
|
+
renderWorkspaceFilePreview({
|
|
210
|
+
file: {
|
|
211
|
+
key: "attachment-docx",
|
|
212
|
+
path: "overview.zh-CN.docx",
|
|
213
|
+
label: "overview.zh-CN.docx",
|
|
214
|
+
viewMode: "preview",
|
|
215
|
+
contentUrl: "/api/ncp/assets/content?uri=asset%3A%2F%2Fstore%2Foverview",
|
|
216
|
+
mimeType:
|
|
217
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
expect(screen.getByTestId("workspace-content-unsupported")).toBeTruthy();
|
|
222
|
+
expect(
|
|
223
|
+
screen.getByTestId("workspace-content-unsupported").textContent,
|
|
224
|
+
).toContain("overview.zh-CN.docx");
|
|
225
|
+
expect(screen.getByText(t("chatWorkspacePreviewUnsupported"))).toBeTruthy();
|
|
226
|
+
expect(screen.getByText(t("chatWorkspacePreviewUnsupportedHint"))).toBeTruthy();
|
|
227
|
+
|
|
228
|
+
const download = screen.getByRole("link", {
|
|
229
|
+
name: t("chatWorkspacePreviewDownload"),
|
|
230
|
+
});
|
|
231
|
+
expect(download.getAttribute("href")).toBe(
|
|
232
|
+
"/api/ncp/assets/content?uri=asset%3A%2F%2Fstore%2Foverview",
|
|
233
|
+
);
|
|
234
|
+
expect(download.getAttribute("download")).toBe("overview.zh-CN.docx");
|
|
235
|
+
|
|
236
|
+
const openExternally = screen.getByRole("link", {
|
|
237
|
+
name: t("chatWorkspacePreviewOpenExternally"),
|
|
238
|
+
});
|
|
239
|
+
expect(openExternally.getAttribute("href")).toBe(
|
|
240
|
+
"/api/ncp/assets/content?uri=asset%3A%2F%2Fstore%2Foverview",
|
|
241
|
+
);
|
|
242
|
+
expect(openExternally.getAttribute("target")).toBe("_blank");
|
|
243
|
+
});
|
|
244
|
+
|
|
178
245
|
it("renders preview files inside a full-height workspace code surface", () => {
|
|
179
246
|
mockTextRead();
|
|
180
247
|
renderWorkspaceFilePreview();
|
package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx
CHANGED
|
@@ -81,7 +81,7 @@ export function ChatSessionWorkspaceDirectoryBrowser({
|
|
|
81
81
|
type="button"
|
|
82
82
|
aria-label={buildDirectoryEntryLabel(entry)}
|
|
83
83
|
className={cn(
|
|
84
|
-
"flex h-8 w-full min-w-0 items-center gap-2 rounded-sm px-2 text-left text-sm text-gray-700 transition-colors hover:bg-gray-100 focus-visible:outline-none focus-visible:ring-
|
|
84
|
+
"flex h-8 w-full min-w-0 items-center gap-2 rounded-sm px-2 text-left text-sm text-gray-700 transition-colors hover:bg-gray-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
|
|
85
85
|
entry.hidden ? "opacity-65" : null,
|
|
86
86
|
)}
|
|
87
87
|
onClick={() => openEntry(entry)}
|
|
@@ -67,7 +67,7 @@ export function WorkspaceBreadcrumbBrowser({
|
|
|
67
67
|
<Fragment key={breadcrumb.path}>
|
|
68
68
|
<button
|
|
69
69
|
type="button"
|
|
70
|
-
className="h-5 rounded-sm px-1 text-left hover:bg-gray-100 focus-visible:outline-none focus-visible:ring-
|
|
70
|
+
className="h-5 rounded-sm px-1 text-left hover:bg-gray-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
71
71
|
onClick={() => onBrowsePathChange(breadcrumb.path)}
|
|
72
72
|
>
|
|
73
73
|
{breadcrumb.label}
|
|
@@ -97,7 +97,7 @@ export function WorkspaceBreadcrumbBrowser({
|
|
|
97
97
|
key={entry.path}
|
|
98
98
|
type="button"
|
|
99
99
|
className={cn(
|
|
100
|
-
"flex h-6 w-full items-center gap-1.5 rounded-sm px-1.5 text-left text-[11px] text-gray-700 transition-colors hover:bg-gray-100 focus-visible:outline-none focus-visible:ring-
|
|
100
|
+
"flex h-6 w-full items-center gap-1.5 rounded-sm px-1.5 text-left text-[11px] text-gray-700 transition-colors hover:bg-gray-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
|
|
101
101
|
entry.hidden ? "opacity-65" : null,
|
|
102
102
|
)}
|
|
103
103
|
onClick={() => openEntry(entry)}
|
package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx
CHANGED
|
@@ -48,7 +48,7 @@ function WorkspaceBreadcrumbSegmentButton({
|
|
|
48
48
|
type="button"
|
|
49
49
|
aria-current={segment.isCurrent ? "page" : undefined}
|
|
50
50
|
className={cn(
|
|
51
|
-
"inline-flex h-5 items-center gap-1 rounded-sm px-1 text-[11px] leading-none transition-colors hover:bg-gray-200/80 focus-visible:outline-none focus-visible:ring-
|
|
51
|
+
"inline-flex h-5 items-center gap-1 rounded-sm px-1 text-[11px] leading-none transition-colors hover:bg-gray-200/80 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
|
|
52
52
|
segment.kind === "workspace"
|
|
53
53
|
? "bg-primary/8 text-primary hover:bg-primary/12"
|
|
54
54
|
: segment.isCurrent
|