@nextclaw/ui 0.15.2 → 0.15.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/dist/assets/aiden0z-pptx-renderer.es-Bd99vut2.js +94 -0
- package/dist/assets/api-BOXOrpcJ.js +13 -0
- package/dist/assets/app-presenter-provider-CkHLbvZQ.js +106 -0
- package/dist/assets/app-presenter-provider-D3BuuOyZ.css +1 -0
- package/dist/assets/appearance-settings-page-fLIJbx1C.js +1 -0
- package/dist/assets/{book-open-Ba2f7YiG.js → book-open-DWUjS6aW.js} +1 -1
- package/dist/assets/channels-list-page-CvCTJraa.js +8 -0
- package/dist/assets/chat-page-B42UUIFR.js +1 -0
- package/dist/assets/chunk-DseTPa7n.js +1 -0
- package/dist/assets/config-split-page-DBgt9S8b.js +1 -0
- package/dist/assets/confirm-dialog-GSXIvoRU.js +5 -0
- package/dist/assets/desktop-update-config-7bmNeWIC.js +1 -0
- package/dist/assets/dist-DC7f4TJH.js +41 -0
- package/dist/assets/doc-browser-CgdoLUT92.js +1 -0
- package/dist/assets/doc-browser-D0monHjG.js +1 -0
- package/dist/assets/doc-browser-context-CGwEOhBy.js +1 -0
- package/dist/assets/docx-preview-eFH9DRsS.js +20 -0
- package/dist/assets/download-DiP9zx-J.js +1 -0
- package/dist/assets/{external-link-DxuPaDoK.js → external-link-C6ZeSPef.js} +1 -1
- package/dist/assets/eye-suvKmtwn.js +1 -0
- package/dist/assets/folder-CMELbQGG.js +1 -0
- package/dist/assets/{form-actions-BztL7P_T.js → form-actions-CUAzpNe_.js} +1 -1
- package/dist/assets/index-BFeNDQ4R.css +1 -0
- package/dist/assets/index-D_6U-M94.js +2 -0
- package/dist/assets/jszip.min-Das2IAnb.js +2 -0
- package/dist/assets/loader-circle-tYHStefV.js +1 -0
- package/dist/assets/logo-badge-Cu5UGH_4.js +1 -0
- package/dist/assets/mcp-marketplace-page-BUzHDwW6.js +9 -0
- package/dist/assets/mcp-marketplace-page-ohUAdfqJ.js +1 -0
- package/dist/assets/model-config-page-CWP6pyBm.js +1 -0
- package/dist/assets/plus-tywizkeY.js +1 -0
- package/dist/assets/popover-DImguRok.js +1 -0
- package/dist/assets/provider-models-C_yOh6DE.js +1 -0
- package/dist/assets/provider-scoped-model-input-CE7p4SbJ.js +1 -0
- package/dist/assets/providers-config-page-BKPo8SjH.js +1 -0
- package/dist/assets/react-5sjAT_6d.js +8 -0
- package/dist/assets/{refresh-cw-eT8BF-8B.js → refresh-cw-CHLBzWWj.js} +1 -1
- package/dist/assets/remote-DXJryUxB.js +1 -0
- package/dist/assets/rotate-cw-BefO5brU.js +1 -0
- package/dist/assets/runtime-config-page-1Axv4QGu.js +1 -0
- package/dist/assets/{save-Cb2g0BOi.js → save-BABzyrjB.js} +1 -1
- package/dist/assets/{search-BcHjwdsb.js → search-BTqxG3ZG.js} +1 -1
- package/dist/assets/search-config-page-BkKKmxmS.js +1 -0
- package/dist/assets/secrets-config-page-CWJMyu-k.js +3 -0
- package/dist/assets/security-config-DeQUIXfi.js +1 -0
- package/dist/assets/select-TYZDBNmq.js +1 -0
- package/dist/assets/setting-row-DR23Z_5I.js +1 -0
- package/dist/assets/{settings-2-2DWauOjY.js → settings-2-D8WWcBnw.js} +1 -1
- package/dist/assets/{skeleton-P5oM14E1.js → skeleton-CloHeg0S.js} +1 -1
- package/dist/assets/tabs-CX-ckWoo.js +1 -0
- package/dist/assets/tag-chip-mCc23Rhb.js +1 -0
- package/dist/assets/trash-2-SAW1Z4Po.js +1 -0
- package/dist/assets/use-config-BucdaK23.js +1 -0
- package/dist/assets/use-confirm-dialog-C95F6sT1.js +1 -0
- package/dist/assets/use-viewport-layout-C6m3Nte7.js +1 -0
- package/dist/assets/x-DtCPHwCu.js +1 -0
- package/dist/assets/xlsx-B6zVt1RK.js +64 -0
- package/dist/index.html +34 -28
- package/package.json +14 -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/__tests__/chat-popover-content.test.tsx +41 -0
- package/src/features/chat/components/chat-popover-content.tsx +26 -0
- package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +13 -0
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +21 -11
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +2 -2
- package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +28 -1
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +5 -4
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +9 -18
- package/src/features/chat/components/conversation/chat-conversation-track.tsx +35 -0
- package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +1 -0
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +7 -7
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +4 -15
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +20 -1
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +69 -54
- 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 +63 -2
- package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts +30 -0
- 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/__tests__/chat-sidebar-session-item.test.tsx +37 -0
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +5 -4
- package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +3 -8
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +65 -20
- package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +26 -28
- package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
- package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +5 -5
- package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +4 -4
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +4 -4
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +463 -99
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +113 -2
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +1 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +197 -44
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +11 -5
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +8 -6
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +99 -67
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +239 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +56 -6
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +29 -7
- package/src/features/chat/features/workspace/components/file-content-preview/use-workspace-file-buffer.ts +42 -0
- package/src/features/chat/features/workspace/components/file-content-preview/workspace-document-preview-state.tsx +18 -0
- package/src/features/chat/features/workspace/components/file-content-preview/workspace-docx-preview.css +89 -0
- package/src/features/chat/features/workspace/components/file-content-preview/workspace-docx-preview.tsx +87 -0
- package/src/features/chat/features/workspace/components/file-content-preview/workspace-presentation-preview.tsx +82 -0
- package/src/features/chat/features/workspace/components/file-content-preview/workspace-spreadsheet-preview.tsx +180 -0
- package/src/features/chat/features/workspace/components/workspace-file-content-preview.tsx +191 -0
- package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +1 -4
- package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +53 -10
- package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +68 -2
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +21 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +63 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-file-viewer.utils.ts +49 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils.ts +13 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +120 -38
- package/src/features/chat/index.ts +1 -0
- package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +76 -0
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +44 -60
- package/src/features/chat/managers/chat-thread.manager.ts +123 -124
- package/src/features/chat/pages/ncp-chat-page.tsx +1 -2
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +78 -1
- package/src/features/chat/stores/chat-message-layout.store.test.ts +37 -0
- package/src/features/chat/stores/chat-message-layout.store.ts +41 -0
- package/src/features/chat/stores/chat-thread.store.ts +73 -13
- 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/settings/pages/__tests__/appearance-settings-page.test.tsx +25 -8
- package/src/features/settings/pages/appearance-settings-page.tsx +92 -13
- package/src/features/side-dock/components/side-dock.tsx +3 -3
- package/src/index.css +275 -15
- package/src/shared/components/__tests__/file-type-icon.test.tsx +61 -0
- package/src/shared/components/actions/action-link.tsx +1 -1
- package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +19 -0
- package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +16 -0
- package/src/shared/components/doc-browser/doc-browser-context.tsx +1 -0
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +1 -1
- package/src/shared/components/doc-browser/doc-browser.tsx +11 -3
- package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +5 -0
- package/src/shared/components/doc-browser/stores/doc-browser.store.ts +6 -1
- package/src/shared/components/doc-browser/types/doc-browser.types.ts +2 -0
- package/src/shared/components/doc-browser/utils/doc-browser-state.utils.ts +11 -0
- package/src/shared/components/file-type-icon.tsx +272 -0
- package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +22 -0
- package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +17 -2
- 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/tab-strip/__tests__/compact-tab-strip.test.tsx +38 -0
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +82 -2
- package/src/shared/components/ui/tabs.tsx +1 -1
- package/src/shared/components/ui/textarea.tsx +1 -1
- package/src/shared/lib/api/__tests__/server-path.utils.test.ts +28 -0
- package/src/shared/lib/api/utils/server-path.utils.ts +19 -20
- package/src/shared/lib/i18n/locales/en-US/chat.json +99 -0
- package/src/shared/lib/i18n/locales/en-US/core.json +6 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +99 -0
- package/src/shared/lib/i18n/locales/zh-CN/core.json +6 -0
- package/dist/assets/api-B7Qigvtm.js +0 -13
- package/dist/assets/appearance-settings-page-CuE0Iiqj.js +0 -1
- package/dist/assets/channels-list-page-CTsM6TcP.js +0 -8
- package/dist/assets/chat-page-C_5dcuE3.js +0 -1
- package/dist/assets/config-split-page-Fbb4PtTf.js +0 -1
- package/dist/assets/confirm-dialog-D2EG3nqE.js +0 -5
- package/dist/assets/desktop-update-config-BGR3HUi1.js +0 -1
- package/dist/assets/dist-7JTjycxE.js +0 -1
- package/dist/assets/dist-wXLhk4hl.js +0 -1
- package/dist/assets/doc-browser-bHyXnxlj.js +0 -1
- package/dist/assets/doc-browser-context-CYAGm5d5.js +0 -1
- package/dist/assets/doc-browser-gTrQ3jCF.js +0 -1
- package/dist/assets/es2015-j1SRCIX5.js +0 -41
- package/dist/assets/i18n-provider-B4oMxOFx.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/model-config-page-DeKk9gvE.js +0 -1
- package/dist/assets/plus-Bx7wQbhG.js +0 -1
- package/dist/assets/provider-scoped-model-input-DsH2LF1q.js +0 -1
- package/dist/assets/providers-config-page-BFnGp68F.js +0 -1
- package/dist/assets/react-UirzUuST.js +0 -8
- package/dist/assets/remote-CDkHZtA8.js +0 -1
- package/dist/assets/rotate-cw-1JqxHTuw.js +0 -1
- package/dist/assets/runtime-config-page-BelAqiNG.js +0 -1
- package/dist/assets/search-config-page-zPm6--Ig.js +0 -1
- package/dist/assets/secrets-config-page-Cxyp-KuQ.js +0 -3
- package/dist/assets/security-config-B_gCq9fn.js +0 -1
- package/dist/assets/select-CdSEOwVM.js +0 -1
- package/dist/assets/setting-row-CTAtGexr.js +0 -1
- package/dist/assets/side-dock-DPvakJ0a.js +0 -3
- package/dist/assets/switch-BU_UEULb.js +0 -1
- package/dist/assets/tag-chip-D-WsKoT1.js +0 -1
- package/dist/assets/tooltip-CjkOaHW7.js +0 -1
- package/dist/assets/use-config--hx5oxQn.js +0 -1
- package/dist/assets/use-confirm-dialog-CD9a_SK1.js +0 -1
- package/dist/assets/x-DT5x45JU.js +0 -1
- /package/dist/assets/{config-hints-C_iP1zl_.js → config-hints-fDrYfl0l.js} +0 -0
- /package/dist/assets/{host-capabilities-9bB_998Z.js → host-capabilities-zZvY9U63.js} +0 -0
- /package/dist/assets/{middleware-BILWbJE4.js → middleware-BLMHv9YF.js} +0 -0
|
@@ -3,9 +3,9 @@ import { IconActionButton } from "@/shared/components/ui/actions/icon-action-but
|
|
|
3
3
|
import { Input } from "@/shared/components/ui/input";
|
|
4
4
|
import {
|
|
5
5
|
Popover,
|
|
6
|
-
PopoverContent,
|
|
7
6
|
PopoverTrigger,
|
|
8
7
|
} from "@/shared/components/ui/popover";
|
|
8
|
+
import { ChatPopoverContent } from "@/features/chat/components/chat-popover-content";
|
|
9
9
|
import { SessionContextIconNode } from "@/features/chat/features/session/components/session-context-icon";
|
|
10
10
|
import { ChatSessionTypeMenu } from "@/features/chat/features/session-type/components/chat-session-type-menu";
|
|
11
11
|
import type { ChatSessionTypeOption } from "@/features/chat/features/session-type/utils/chat-session-type.utils";
|
|
@@ -175,7 +175,7 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
|
|
|
175
175
|
)}
|
|
176
176
|
/>
|
|
177
177
|
</PopoverTrigger>
|
|
178
|
-
<
|
|
178
|
+
<ChatPopoverContent
|
|
179
179
|
align="start"
|
|
180
180
|
side="right"
|
|
181
181
|
className="w-56 rounded-2xl border border-border bg-popover p-1.5 text-popover-foreground shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
|
|
@@ -188,7 +188,7 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
|
|
|
188
188
|
onCreateMenuOpenChange(false);
|
|
189
189
|
}}
|
|
190
190
|
/>
|
|
191
|
-
</
|
|
191
|
+
</ChatPopoverContent>
|
|
192
192
|
</Popover>
|
|
193
193
|
) : null}
|
|
194
194
|
</div>
|
|
@@ -238,7 +238,7 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
|
|
|
238
238
|
)}
|
|
239
239
|
/>
|
|
240
240
|
</PopoverTrigger>
|
|
241
|
-
<
|
|
241
|
+
<ChatPopoverContent
|
|
242
242
|
align="end"
|
|
243
243
|
className="w-56 rounded-2xl border border-border bg-popover p-1.5 text-popover-foreground shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
|
|
244
244
|
>
|
|
@@ -250,7 +250,7 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
|
|
|
250
250
|
onCreateMenuOpenChange(false);
|
|
251
251
|
}}
|
|
252
252
|
/>
|
|
253
|
-
</
|
|
253
|
+
</ChatPopoverContent>
|
|
254
254
|
</Popover>
|
|
255
255
|
) : null}
|
|
256
256
|
</div>
|
|
@@ -315,7 +315,7 @@ export function ChatSidebarMobileToolbar(props: ChatSidebarToolbarProps) {
|
|
|
315
315
|
className="h-9 w-9 shrink-0 rounded-full bg-muted text-foreground shadow-none hover:bg-accent hover:text-accent-foreground"
|
|
316
316
|
/>
|
|
317
317
|
</PopoverTrigger>
|
|
318
|
-
<
|
|
318
|
+
<ChatPopoverContent
|
|
319
319
|
align="end"
|
|
320
320
|
className="w-60 rounded-3xl border border-border bg-popover p-2 text-popover-foreground shadow-[0_24px_70px_-30px_rgba(15,23,42,0.45)]"
|
|
321
321
|
>
|
|
@@ -329,7 +329,7 @@ export function ChatSidebarMobileToolbar(props: ChatSidebarToolbarProps) {
|
|
|
329
329
|
onCreateMenuOpenChange(false);
|
|
330
330
|
}}
|
|
331
331
|
/>
|
|
332
|
-
</
|
|
332
|
+
</ChatPopoverContent>
|
|
333
333
|
</Popover>
|
|
334
334
|
) : (
|
|
335
335
|
<IconActionButton
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { NavLink } from "react-router-dom";
|
|
2
2
|
import {
|
|
3
3
|
Popover,
|
|
4
|
-
PopoverContent,
|
|
5
4
|
PopoverTrigger,
|
|
6
5
|
} from "@/shared/components/ui/popover";
|
|
6
|
+
import { ChatPopoverContent } from "@/features/chat/components/chat-popover-content";
|
|
7
7
|
import {
|
|
8
8
|
Select,
|
|
9
9
|
SelectContent,
|
|
@@ -52,12 +52,6 @@ type ChatSidebarUtilityMenuProps = {
|
|
|
52
52
|
collapsed?: boolean;
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
function isChatComposerFocusTarget(target: EventTarget | null): boolean {
|
|
56
|
-
return target instanceof HTMLElement && Boolean(
|
|
57
|
-
target.closest('.nextclaw-chat-input-bar-shell [role="textbox"][contenteditable="true"]'),
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
55
|
export function ChatSidebarUtilityMenu({
|
|
62
56
|
isOpen,
|
|
63
57
|
onOpenChange,
|
|
@@ -108,7 +102,7 @@ export function ChatSidebarUtilityMenu({
|
|
|
108
102
|
<button
|
|
109
103
|
type="button"
|
|
110
104
|
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-
|
|
105
|
+
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
106
|
>
|
|
113
107
|
<Settings className="h-4 w-4 shrink-0 text-muted-foreground/70" />
|
|
114
108
|
<span className="min-w-0 flex-1 text-left">{t("settings")}</span>
|
|
@@ -118,15 +112,10 @@ export function ChatSidebarUtilityMenu({
|
|
|
118
112
|
</button>
|
|
119
113
|
</PopoverTrigger>
|
|
120
114
|
)}
|
|
121
|
-
<
|
|
115
|
+
<ChatPopoverContent
|
|
122
116
|
side={collapsed ? "right" : "top"}
|
|
123
117
|
align={collapsed ? "end" : "start"}
|
|
124
118
|
className="w-64 p-2"
|
|
125
|
-
onFocusOutside={(event) => {
|
|
126
|
-
if (isChatComposerFocusTarget(event.detail.originalEvent.target)) {
|
|
127
|
-
event.preventDefault();
|
|
128
|
-
}
|
|
129
|
-
}}
|
|
130
119
|
>
|
|
131
120
|
<div className="space-y-1">
|
|
132
121
|
<NavLink
|
|
@@ -177,7 +166,7 @@ export function ChatSidebarUtilityMenu({
|
|
|
177
166
|
onSelect={onSelectLanguage}
|
|
178
167
|
onCloseMenu={() => onOpenChange(false)}
|
|
179
168
|
/>
|
|
180
|
-
</
|
|
169
|
+
</ChatPopoverContent>
|
|
181
170
|
</Popover>
|
|
182
171
|
);
|
|
183
172
|
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type MutableRefObject,
|
|
7
7
|
} from 'react';
|
|
8
8
|
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
9
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
9
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
10
10
|
import {
|
|
11
11
|
createChatComposerTextNode,
|
|
12
12
|
type ChatComposerNode,
|
|
@@ -26,9 +26,14 @@ import type {
|
|
|
26
26
|
SessionConversationInputSnapshot,
|
|
27
27
|
} from '@/features/chat/features/conversation/hooks/use-session-conversation-input-state';
|
|
28
28
|
import { useSessionConversationInputState } from '@/features/chat/features/conversation/hooks/use-session-conversation-input-state';
|
|
29
|
+
import { useChatMessageLayoutStore } from '@/features/chat/stores/chat-message-layout.store';
|
|
29
30
|
|
|
30
31
|
const uploadNcpAssetsMock = vi.hoisted(() => vi.fn());
|
|
31
32
|
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
useChatMessageLayoutStore.getState().setLayout('card');
|
|
35
|
+
});
|
|
36
|
+
|
|
32
37
|
vi.mock('@/app/hooks/use-viewport-layout', () => ({
|
|
33
38
|
useViewportLayout: () => ({ isDesktop: true, isMobile: false }),
|
|
34
39
|
}));
|
|
@@ -190,6 +195,20 @@ function StreamingSessionConversationInputHarness({
|
|
|
190
195
|
}
|
|
191
196
|
|
|
192
197
|
describe('SessionConversationInput streaming stability', () => {
|
|
198
|
+
it('aligns the default input with the flat message reading track', () => {
|
|
199
|
+
useChatMessageLayoutStore.getState().setLayout('flat');
|
|
200
|
+
const controlRef: MutableRefObject<StreamingInputControl | null> = { current: null };
|
|
201
|
+
|
|
202
|
+
render(<StreamingSessionConversationInputHarness controlRef={controlRef} />);
|
|
203
|
+
|
|
204
|
+
const track = document.querySelector(
|
|
205
|
+
'[data-chat-conversation-track="flat"][data-chat-conversation-track-width="composer"]',
|
|
206
|
+
);
|
|
207
|
+
expect(track?.className).toContain('max-w-[min(54rem,100%)]');
|
|
208
|
+
expect(track?.querySelector('.nextclaw-chat-input-bar-shell')).toBeTruthy();
|
|
209
|
+
expect(track?.firstElementChild?.className).toContain('px-0');
|
|
210
|
+
});
|
|
211
|
+
|
|
193
212
|
it('keeps IME composition stable while simulated streamed output rerenders the owner', async () => {
|
|
194
213
|
const controlRef: MutableRefObject<StreamingInputControl | null> = { current: null };
|
|
195
214
|
render(<StreamingSessionConversationInputHarness controlRef={controlRef} />);
|
|
@@ -49,6 +49,8 @@ import {
|
|
|
49
49
|
} from '@/features/chat/managers/chat-recent-skills.manager';
|
|
50
50
|
|
|
51
51
|
import { useSessionConversationInputAttachments } from '@/features/chat/features/conversation/hooks/use-session-conversation-input-attachments';
|
|
52
|
+
import { ChatConversationTrack } from '@/features/chat/components/conversation/chat-conversation-track';
|
|
53
|
+
import { useChatMessageLayoutStore } from '@/features/chat/stores/chat-message-layout.store';
|
|
52
54
|
import type { useSessionConversationInputQuery } from '@/features/chat/features/conversation/hooks/use-session-conversation-input-query';
|
|
53
55
|
import type {
|
|
54
56
|
SessionConversationQueuedInput,
|
|
@@ -187,6 +189,7 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
|
|
|
187
189
|
} = props;
|
|
188
190
|
const presenter = usePresenter();
|
|
189
191
|
const { language } = useI18n();
|
|
192
|
+
const messageLayout = useChatMessageLayoutStore((state) => state.layout);
|
|
190
193
|
const { isMobile } = useViewportLayout();
|
|
191
194
|
const inputBarRef = useRef<ChatInputBarHandle | null>(null);
|
|
192
195
|
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
@@ -389,62 +392,74 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
|
|
|
389
392
|
selectedSkills: inputSnapshot.selectedSkills,
|
|
390
393
|
});
|
|
391
394
|
|
|
395
|
+
const useReadingTrack = surface === 'default' && messageLayout === 'flat';
|
|
396
|
+
const inputBar = (
|
|
397
|
+
<ChatInputBar
|
|
398
|
+
ref={inputBarRef}
|
|
399
|
+
surface={useReadingTrack ? 'embedded' : surface}
|
|
400
|
+
sendError={inputSnapshot.sendError}
|
|
401
|
+
sendErrorDetailsLabel={t('chatErrorDetails', language)}
|
|
402
|
+
topSlot={controller.queuedInputs.length > 0
|
|
403
|
+
? <SessionQueuedInputRows controller={controller} />
|
|
404
|
+
: null}
|
|
405
|
+
composer={{
|
|
406
|
+
nodes: [...inputSnapshot.nodes],
|
|
407
|
+
placeholder: textareaPlaceholder,
|
|
408
|
+
disabled: inputDisabled,
|
|
409
|
+
onNodesChange: handleNodesChange,
|
|
410
|
+
onFilesAdd: handleFilesAdd,
|
|
411
|
+
inputSurfaceTriggerSpecs: inputSurfaceState.triggerSpecs,
|
|
412
|
+
onInputSurfaceTriggerChange: setInputSurfaceTrigger,
|
|
413
|
+
}}
|
|
414
|
+
inputSurface={inputSurfaceState.panel ?? undefined}
|
|
415
|
+
hint={buildModelStateHint({
|
|
416
|
+
isModelOptionsLoading,
|
|
417
|
+
isModelOptionsEmpty,
|
|
418
|
+
onGoToProviders: presenter.chatUiManager.goToProviders,
|
|
419
|
+
texts: {
|
|
420
|
+
noModelOptionsLabel: t('chatModelNoOptions'),
|
|
421
|
+
configureProviderLabel: t('chatGoConfigureProvider'),
|
|
422
|
+
},
|
|
423
|
+
})}
|
|
424
|
+
toolbar={{
|
|
425
|
+
selects: [],
|
|
426
|
+
trailingSelects: toolbarSelects,
|
|
427
|
+
accessories: [
|
|
428
|
+
{
|
|
429
|
+
key: 'attach',
|
|
430
|
+
label: t('chatInputAttach'),
|
|
431
|
+
icon: 'paperclip' as const,
|
|
432
|
+
iconOnly: true,
|
|
433
|
+
disabled: !attachmentSupported || inputDisabled,
|
|
434
|
+
onClick: () => fileInputRef.current?.click(),
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
skillPicker,
|
|
438
|
+
actions: {
|
|
439
|
+
isSending: controller.isSending,
|
|
440
|
+
canStopGeneration: controller.canStopGeneration,
|
|
441
|
+
sendDisabled: controller.sendDisabled,
|
|
442
|
+
stopDisabled: controller.stopDisabled,
|
|
443
|
+
stopHint: t('chatStopUnavailable'),
|
|
444
|
+
sendButtonLabel: controller.isSending ? t('chatQueueSend') : t('chatSend'),
|
|
445
|
+
stopButtonLabel: t('chatStop'),
|
|
446
|
+
contextWindow,
|
|
447
|
+
onSend: controller.send,
|
|
448
|
+
onStop: controller.stop,
|
|
449
|
+
},
|
|
450
|
+
}}
|
|
451
|
+
/>
|
|
452
|
+
);
|
|
453
|
+
|
|
392
454
|
return (
|
|
393
455
|
<>
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
nodes: [...inputSnapshot.nodes],
|
|
402
|
-
placeholder: textareaPlaceholder,
|
|
403
|
-
disabled: inputDisabled,
|
|
404
|
-
onNodesChange: handleNodesChange,
|
|
405
|
-
onFilesAdd: handleFilesAdd,
|
|
406
|
-
inputSurfaceTriggerSpecs: inputSurfaceState.triggerSpecs,
|
|
407
|
-
onInputSurfaceTriggerChange: setInputSurfaceTrigger,
|
|
408
|
-
}}
|
|
409
|
-
inputSurface={inputSurfaceState.panel ?? undefined}
|
|
410
|
-
hint={buildModelStateHint({
|
|
411
|
-
isModelOptionsLoading,
|
|
412
|
-
isModelOptionsEmpty,
|
|
413
|
-
onGoToProviders: presenter.chatUiManager.goToProviders,
|
|
414
|
-
texts: {
|
|
415
|
-
noModelOptionsLabel: t('chatModelNoOptions'),
|
|
416
|
-
configureProviderLabel: t('chatGoConfigureProvider'),
|
|
417
|
-
},
|
|
418
|
-
})}
|
|
419
|
-
toolbar={{
|
|
420
|
-
selects: [],
|
|
421
|
-
trailingSelects: toolbarSelects,
|
|
422
|
-
accessories: [
|
|
423
|
-
{
|
|
424
|
-
key: 'attach',
|
|
425
|
-
label: t('chatInputAttach'),
|
|
426
|
-
icon: 'paperclip' as const,
|
|
427
|
-
iconOnly: true,
|
|
428
|
-
disabled: !attachmentSupported || inputDisabled,
|
|
429
|
-
onClick: () => fileInputRef.current?.click(),
|
|
430
|
-
},
|
|
431
|
-
],
|
|
432
|
-
skillPicker,
|
|
433
|
-
actions: {
|
|
434
|
-
sendError: inputSnapshot.sendError,
|
|
435
|
-
isSending: controller.isSending,
|
|
436
|
-
canStopGeneration: controller.canStopGeneration,
|
|
437
|
-
sendDisabled: controller.sendDisabled,
|
|
438
|
-
stopDisabled: controller.stopDisabled,
|
|
439
|
-
stopHint: t('chatStopUnavailable'),
|
|
440
|
-
sendButtonLabel: controller.isSending ? t('chatQueueSend') : t('chatSend'),
|
|
441
|
-
stopButtonLabel: t('chatStop'),
|
|
442
|
-
contextWindow,
|
|
443
|
-
onSend: controller.send,
|
|
444
|
-
onStop: controller.stop,
|
|
445
|
-
},
|
|
446
|
-
}}
|
|
447
|
-
/>
|
|
456
|
+
{useReadingTrack ? (
|
|
457
|
+
<ChatConversationTrack className="pb-4 pt-2" width="composer">
|
|
458
|
+
{inputBar}
|
|
459
|
+
</ChatConversationTrack>
|
|
460
|
+
) : (
|
|
461
|
+
inputBar
|
|
462
|
+
)}
|
|
448
463
|
<input
|
|
449
464
|
ref={fileInputRef}
|
|
450
465
|
type="file"
|
|
@@ -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,7 +24,12 @@ 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";
|
|
28
|
+
import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
|
|
29
|
+
import { useChatMessageLayoutStore } from "@/features/chat/stores/chat-message-layout.store";
|
|
30
|
+
import { useNcpChatSelectedSession } from "@/features/chat/features/ncp/hooks/use-ncp-chat-derived-state";
|
|
26
31
|
import { useI18n } from "@/app/components/i18n-provider";
|
|
32
|
+
import { buildServerPathContentUrl } from "@/shared/lib/api";
|
|
27
33
|
import { formatDateTime, t } from "@/shared/lib/i18n";
|
|
28
34
|
|
|
29
35
|
type ChatMessageListContainerProps = {
|
|
@@ -56,7 +62,6 @@ function renderChatInlineDisplay(display: ChatInlineDisplayViewModel) {
|
|
|
56
62
|
/>
|
|
57
63
|
);
|
|
58
64
|
}
|
|
59
|
-
|
|
60
65
|
type ContextInheritanceTimelineView = {
|
|
61
66
|
sourceSessionId: string;
|
|
62
67
|
inheritedMessageCount: number;
|
|
@@ -284,6 +289,16 @@ export function ChatMessageListContainer({
|
|
|
284
289
|
}: ChatMessageListContainerProps) {
|
|
285
290
|
const presenter = usePresenter();
|
|
286
291
|
const { language } = useI18n();
|
|
292
|
+
const messageLayout = useChatMessageLayoutStore((state) => state.layout);
|
|
293
|
+
const selectedSessionKey = useChatSessionListStore(
|
|
294
|
+
(state) => state.snapshot.selectedSessionKey,
|
|
295
|
+
);
|
|
296
|
+
const selectedSession = useNcpChatSelectedSession(selectedSessionKey);
|
|
297
|
+
const localFileBasePath = selectedSession?.workingDir ?? selectedSession?.projectRoot ?? null;
|
|
298
|
+
const resolveFileContentUrl = useCallback(
|
|
299
|
+
(action: { path: string }) => buildServerPathContentUrl(action.path, localFileBasePath),
|
|
300
|
+
[localFileBasePath],
|
|
301
|
+
);
|
|
287
302
|
const texts = useMemo(
|
|
288
303
|
() => buildChatMessageAdapterTexts(language),
|
|
289
304
|
[language],
|
|
@@ -342,6 +357,48 @@ export function ChatMessageListContainer({
|
|
|
342
357
|
() => buildTimelineItems({ rawMessages, messages }),
|
|
343
358
|
[messages, rawMessages],
|
|
344
359
|
);
|
|
360
|
+
const sessionSkillsQuery = useChatQueryStore((state) => state.snapshot.sessionSkillsQuery);
|
|
361
|
+
const handleInlineTokenClick = useCallback(
|
|
362
|
+
(token: ChatInlineTokenViewModel) => {
|
|
363
|
+
if (token.kind !== "skill") {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const skillKey = token.key.trim();
|
|
367
|
+
if (!skillKey) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const records = sessionSkillsQuery?.data?.records ?? [];
|
|
371
|
+
const matched = records.find((record) => record.ref === skillKey || record.name === skillKey);
|
|
372
|
+
const skillPath = matched?.path?.trim();
|
|
373
|
+
if (!skillPath) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
presenter.chatThreadManager.openFilePreview({
|
|
377
|
+
path: skillPath,
|
|
378
|
+
label: token.label || matched?.name || skillKey,
|
|
379
|
+
viewMode: "preview",
|
|
380
|
+
previewViewer: "rendered",
|
|
381
|
+
});
|
|
382
|
+
},
|
|
383
|
+
[presenter.chatThreadManager, sessionSkillsQuery],
|
|
384
|
+
);
|
|
385
|
+
const handleAttachmentOpen = useCallback(
|
|
386
|
+
(file: { label: string; mimeType: string; dataUrl?: string; sizeBytes?: number; isImage: boolean }) => {
|
|
387
|
+
const contentUrl = file.dataUrl?.trim();
|
|
388
|
+
if (!contentUrl) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const label = file.label.trim() || "attachment";
|
|
392
|
+
presenter.chatThreadManager.openFilePreview({
|
|
393
|
+
path: label,
|
|
394
|
+
label,
|
|
395
|
+
viewMode: "preview",
|
|
396
|
+
contentUrl,
|
|
397
|
+
mimeType: file.mimeType,
|
|
398
|
+
});
|
|
399
|
+
},
|
|
400
|
+
[presenter.chatThreadManager],
|
|
401
|
+
);
|
|
345
402
|
return (
|
|
346
403
|
<div className={className}>
|
|
347
404
|
{timelineItems.map((item, index) =>
|
|
@@ -352,12 +409,16 @@ export function ChatMessageListContainer({
|
|
|
352
409
|
) : (
|
|
353
410
|
<ChatMessageList
|
|
354
411
|
key={item.key}
|
|
412
|
+
layout={messageLayout}
|
|
355
413
|
messages={item.messages}
|
|
356
414
|
isSending={index === timelineItems.length - 1 ? isSending : false}
|
|
357
415
|
hasAssistantDraft={hasAssistantDraft}
|
|
358
416
|
texts={messageTexts}
|
|
359
417
|
onToolAction={presenter.chatThreadManager.handleToolAction}
|
|
360
418
|
onFileOpen={presenter.chatThreadManager.openFilePreview}
|
|
419
|
+
onAttachmentOpen={handleAttachmentOpen}
|
|
420
|
+
onInlineTokenClick={handleInlineTokenClick}
|
|
421
|
+
resolveFileContentUrl={resolveFileContentUrl}
|
|
361
422
|
renderInlineDisplay={renderChatInlineDisplay}
|
|
362
423
|
renderToolAgent={(agentId) => (
|
|
363
424
|
<AgentIdentityAvatar
|
package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts
CHANGED
|
@@ -63,6 +63,36 @@ it("builds edit-file previews from structured args before the tool finishes", ()
|
|
|
63
63
|
expect(editLines[1]).not.toHaveProperty("newLineNumber");
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
+
it("keeps incomplete edit-file arguments available before preview fields arrive", () => {
|
|
67
|
+
const partialArgs = '{"path":"src/app.ts","oldText":';
|
|
68
|
+
const adapted = adapt([
|
|
69
|
+
{
|
|
70
|
+
id: "assistant-partial-edit-input",
|
|
71
|
+
role: "assistant",
|
|
72
|
+
parts: [
|
|
73
|
+
{
|
|
74
|
+
type: "tool-invocation",
|
|
75
|
+
toolInvocation: {
|
|
76
|
+
status: ToolInvocationStatus.PARTIAL_CALL,
|
|
77
|
+
toolCallId: "edit-partial-1",
|
|
78
|
+
toolName: "edit_file",
|
|
79
|
+
args: partialArgs,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
] as unknown as ChatMessageSource[]);
|
|
85
|
+
|
|
86
|
+
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
87
|
+
type: "tool-card",
|
|
88
|
+
card: {
|
|
89
|
+
toolName: "edit_file",
|
|
90
|
+
input: partialArgs,
|
|
91
|
+
statusTone: "running",
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
66
96
|
it("uses structured edit-file result line numbers after the tool finishes", () => {
|
|
67
97
|
const adapted = adapt([
|
|
68
98
|
{
|
|
@@ -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,
|