@nextclaw/ui 0.14.1 → 0.14.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 +41 -0
- package/dist/assets/{api-DPcvCLfa.js → api-BP_8IaxB.js} +7 -7
- package/dist/assets/channels-list-page-DEh5Jjqx.js +8 -0
- package/dist/assets/chat-page-BRB0N5WW.js +105 -0
- package/dist/assets/config-split-page-CRhlZ_pH.js +1 -0
- package/dist/assets/{confirm-dialog-CWh5FfK2.js → confirm-dialog-DsivFJ4r.js} +2 -2
- package/dist/assets/{desktop-update-config-DsRhBizi.js → desktop-update-config-B0w9p2ep.js} +1 -1
- package/dist/assets/{dist-DPrgcEC0.js → dist-CikkhOlq.js} +1 -1
- package/dist/assets/{dist-DsE4OsSS.js → dist-PVA_xL1D.js} +1 -1
- package/dist/assets/doc-browser-BoNJ7-tE.js +1 -0
- package/dist/assets/doc-browser-CdJ_d4ik.js +1 -0
- package/dist/assets/doc-browser-context-MTw4EvKd.js +1 -0
- package/dist/assets/{ellipsis-B81FStLm.js → ellipsis-y6vDtq4M.js} +1 -1
- package/dist/assets/{external-link-C_dmG-WJ.js → external-link-DVJIQf_G.js} +1 -1
- package/dist/assets/index-BiE-K8-q.css +1 -0
- package/dist/assets/index-D9BAE_L_.js +105 -0
- package/dist/assets/mcp-marketplace-page-B1SZNY5m.js +40 -0
- package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +1 -0
- package/dist/assets/middleware-CkFT3R4v.js +1 -0
- package/dist/assets/model-config-page-DotaTUHb.js +1 -0
- package/dist/assets/plus-BbZqquJe.js +1 -0
- package/dist/assets/provider-scoped-model-input-DsFRuWJH.js +1 -0
- package/dist/assets/providers-config-page-DW4tQSLN.js +1 -0
- package/dist/assets/{react-sZz5HSCm.js → react-VNqT5Zp7.js} +1 -1
- package/dist/assets/remote-B8TiUfwW.js +1 -0
- package/dist/assets/rotate-cw-DjHDTkCv.js +1 -0
- package/dist/assets/runtime-config-page-S6ErRHR8.js +1 -0
- package/dist/assets/{save-MzjoiHri.js → save-BOJ477H-.js} +1 -1
- package/dist/assets/{search-B29dE4GN.js → search-BEti5beA.js} +1 -1
- package/dist/assets/{search-config-page-Dl5sZzob.js → search-config-page-DGj5am03.js} +1 -1
- package/dist/assets/{secrets-config-page-C_l-IFCK.js → secrets-config-page--IzrIOid.js} +2 -2
- package/dist/assets/{select-BKmh05hd.js → select-hoj8dK4K.js} +2 -2
- package/dist/assets/{tag-chip-BS_7Ueom.js → tag-chip-T8-h-die.js} +1 -1
- package/dist/assets/use-config-CjRV2hwL.js +1 -0
- package/dist/assets/x-D9PXs_xP.js +1 -0
- package/dist/index.html +70 -19
- package/index.html +55 -5
- package/package.json +9 -9
- package/src/app/components/app-presenter-provider.tsx +11 -2
- package/src/app/components/layout/__tests__/app-layout.test.tsx +11 -2
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +110 -79
- package/src/app/components/layout/app-layout.tsx +18 -4
- package/src/app/components/layout/header.tsx +3 -3
- package/src/app/components/layout/page-layout.tsx +2 -2
- package/src/app/components/layout/runtime-status-entry.tsx +7 -7
- package/src/app/components/layout/sidebar-items.tsx +194 -71
- package/src/app/components/layout/sidebar-rail.styles.ts +15 -0
- package/src/app/components/layout/sidebar.tsx +340 -83
- package/src/app/managers/viewport-layout.manager.ts +10 -0
- package/src/app/stores/viewport-layout.store.ts +39 -3
- package/src/app/styles/design-system.css +388 -26
- package/src/app/test/vitest-setup.ts +31 -0
- package/src/features/agents/components/__tests__/agents-page.test.tsx +62 -39
- package/src/features/agents/components/agents-page.tsx +10 -6
- package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +3 -3
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +145 -0
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +4 -2
- package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +69 -762
- package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +132 -0
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +22 -15
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +15 -16
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +7 -6
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +19 -19
- package/src/features/chat/components/conversation/chat-conversation-skeleton.tsx +13 -13
- package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +10 -4
- package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +52 -11
- package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +18 -16
- package/src/features/chat/components/layout/chat-page-shell.tsx +1 -1
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +247 -0
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +93 -20
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +93 -36
- package/src/features/chat/components/layout/chat-sidebar.tsx +60 -93
- package/src/features/chat/components/providers/chat-presenter.provider.tsx +0 -6
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +225 -0
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +199 -0
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +308 -0
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +439 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +194 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +66 -0
- package/src/features/chat/features/{input/hooks/use-chat-input-bar-query-state.ts → conversation/hooks/use-session-conversation-input-query.ts} +51 -20
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +281 -0
- package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +158 -0
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +56 -0
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +117 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
- package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
- package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
- package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
- package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
- package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
- package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
- package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
- package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +7 -7
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +99 -18
- package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
- package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
- package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +7 -7
- package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +6 -6
- package/src/features/chat/features/session/components/session-context-icon.tsx +2 -2
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-badge.test.tsx +2 -2
- package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
- package/src/features/chat/features/session/components/session-run-badge.tsx +1 -1
- package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
- package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
- package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +2 -2
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +2 -0
- package/src/features/chat/features/session-type/components/chat-session-type-menu.tsx +2 -2
- package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +2 -2
- package/src/features/chat/features/session-type/hooks/use-chat-session-type-state.ts +10 -2
- package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
- package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -9
- package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
- package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +3 -3
- package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +4 -4
- package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +14 -19
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +5 -5
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +6 -6
- package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +6 -8
- package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +1 -16
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
- package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
- package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
- package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
- package/src/features/chat/index.ts +0 -1
- package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
- package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
- package/src/features/chat/managers/chat-thread.manager.ts +108 -34
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
- package/src/features/chat/presenters/chat.presenter.ts +2 -19
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
- package/src/features/chat/stores/chat-thread.store.ts +42 -26
- package/src/features/panel-apps/components/panel-app-list-item.tsx +8 -8
- package/src/features/panel-apps/components/panel-app-toolbar.tsx +4 -4
- package/src/features/panel-apps/components/panel-apps-list.tsx +20 -20
- package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
- package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +70 -6
- package/src/features/pwa/managers/pwa-shell-theme.manager.ts +10 -5
- package/src/features/service-apps/components/service-action-authorization-dialog.tsx +8 -8
- package/src/features/service-apps/components/service-apps-panel.tsx +24 -24
- package/src/features/settings/components/config/provider-advanced-settings-section.tsx +5 -5
- package/src/features/settings/components/config/provider-auth-section.tsx +7 -7
- package/src/features/settings/components/config/provider-form.tsx +10 -10
- package/src/features/settings/components/config/provider-models-section.tsx +16 -16
- package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
- package/src/features/settings/pages/providers-config-page.tsx +17 -17
- package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
- package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
- package/src/shared/components/config-split-page.tsx +11 -11
- package/src/shared/components/doc-browser/doc-browser-home-page.tsx +2 -2
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +4 -4
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +4 -4
- package/src/shared/components/doc-browser/doc-browser.tsx +3 -3
- package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +1 -1
- package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
- package/src/shared/components/ui/button.tsx +4 -4
- package/src/shared/components/ui/card.tsx +4 -4
- package/src/shared/components/ui/dialog.tsx +5 -5
- package/src/shared/components/ui/input.tsx +1 -1
- package/src/shared/components/ui/label.tsx +1 -1
- package/src/shared/components/ui/popover.tsx +1 -1
- package/src/shared/components/ui/select.tsx +3 -3
- package/src/shared/components/ui/switch.tsx +3 -3
- package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
- package/src/shared/components/ui/tabs.tsx +4 -4
- package/src/shared/components/ui/textarea.tsx +1 -1
- package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
- package/src/shared/lib/i18n/locales/en-US/core.json +9 -2
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
- package/src/shared/lib/i18n/locales/zh-CN/core.json +9 -2
- package/src/shared/lib/theme/index.ts +94 -52
- package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
- package/src/shared/lib/ui-document-title/index.ts +50 -21
- package/dist/assets/channels-list-page-D6jtziu4.js +0 -8
- package/dist/assets/chat-page-BBnNJoKG.js +0 -105
- package/dist/assets/config-split-page-BCJhqdK9.js +0 -1
- package/dist/assets/doc-browser-C8xOF_9F.js +0 -1
- package/dist/assets/doc-browser-DgQedeAn.js +0 -1
- package/dist/assets/doc-browser-context-B46tQGmO.js +0 -1
- package/dist/assets/index-7o1WdUbU.js +0 -103
- package/dist/assets/index-DQWmEWzk.css +0 -1
- package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +0 -1
- package/dist/assets/mcp-marketplace-page-Dzull_4-.js +0 -40
- package/dist/assets/model-config-page-Cv0O3BIj.js +0 -1
- package/dist/assets/plus-Bnt56oqG.js +0 -1
- package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +0 -1
- package/dist/assets/providers-config-page-CPDz0Lxc.js +0 -1
- package/dist/assets/remote-B0LMs8u7.js +0 -1
- package/dist/assets/rotate-cw-BfyG_KEF.js +0 -1
- package/dist/assets/runtime-config-page-q0fvv8ME.js +0 -1
- package/dist/assets/use-config-DZkBWIdL.js +0 -1
- package/dist/assets/x-CW2KO-Hz.js +0 -1
- package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
- package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
- package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
- package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
- package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
- package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
- package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
- package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
- package/src/features/chat/managers/chat-input.manager.ts +0 -430
- package/src/features/chat/managers/chat-run.manager.ts +0 -110
- package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
- package/src/features/chat/stores/chat-input.store.ts +0 -104
- /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-BkYBc7z0.js} +0 -0
package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import type { SetStateAction } from "react";
|
|
2
3
|
import { render, screen } from "@testing-library/react";
|
|
3
4
|
import userEvent from "@testing-library/user-event";
|
|
4
5
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
6
|
import { ChatConversationWelcome } from "@/features/chat/features/welcome/components/chat-conversation-welcome";
|
|
6
|
-
import { useChatInputStore } from "@/features/chat/stores/chat-input.store";
|
|
7
7
|
import { useChatQueryStore } from "@/features/chat/stores/ncp-chat-query.store";
|
|
8
8
|
import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
|
|
9
9
|
import { useChatThreadStore } from "@/features/chat/stores/chat-thread.store";
|
|
@@ -11,14 +11,24 @@ import type { ChatQuerySnapshot } from "@/features/chat/stores/ncp-chat-query.st
|
|
|
11
11
|
|
|
12
12
|
const mocks = vi.hoisted(() => ({
|
|
13
13
|
setSelectedAgentId: vi.fn(),
|
|
14
|
-
applyPromptSuggestion: vi.fn(),
|
|
15
|
-
setPendingProjectRoot: vi.fn(),
|
|
16
|
-
setPendingSessionType: vi.fn(),
|
|
17
14
|
agents: [
|
|
18
15
|
{ id: "main", displayName: "Main", runtime: "native" },
|
|
19
16
|
{ id: "engineer", displayName: "Engineer", runtime: "codex" },
|
|
20
17
|
],
|
|
21
18
|
}));
|
|
19
|
+
const persistStorage = new Map<string, unknown>();
|
|
20
|
+
|
|
21
|
+
function createPersistStorage() {
|
|
22
|
+
return {
|
|
23
|
+
getItem: (name: string) => persistStorage.get(name) ?? null,
|
|
24
|
+
setItem: (name: string, value: unknown) => {
|
|
25
|
+
persistStorage.set(name, value);
|
|
26
|
+
},
|
|
27
|
+
removeItem: (name: string) => {
|
|
28
|
+
persistStorage.delete(name);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
22
32
|
|
|
23
33
|
vi.mock("@/features/chat/features/welcome/components/chat-welcome", () => ({
|
|
24
34
|
ChatWelcome: ({
|
|
@@ -60,11 +70,6 @@ vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
|
|
|
60
70
|
chatSessionListManager: {
|
|
61
71
|
setSelectedAgentId: mocks.setSelectedAgentId,
|
|
62
72
|
},
|
|
63
|
-
chatInputManager: {
|
|
64
|
-
applyPromptSuggestion: mocks.applyPromptSuggestion,
|
|
65
|
-
setPendingProjectRoot: mocks.setPendingProjectRoot,
|
|
66
|
-
setPendingSessionType: mocks.setPendingSessionType,
|
|
67
|
-
},
|
|
68
73
|
}),
|
|
69
74
|
}));
|
|
70
75
|
|
|
@@ -88,19 +93,10 @@ function createFetchedQuery<TData>(data: TData) {
|
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
function resetWelcomeTestState() {
|
|
96
|
+
persistStorage.clear();
|
|
97
|
+
useChatSessionListStore.persist.setOptions({ storage: createPersistStorage() as never });
|
|
98
|
+
useChatThreadStore.persist.setOptions({ storage: createPersistStorage() as never });
|
|
91
99
|
mocks.setSelectedAgentId.mockReset();
|
|
92
|
-
mocks.applyPromptSuggestion.mockReset();
|
|
93
|
-
mocks.setPendingProjectRoot.mockReset();
|
|
94
|
-
mocks.setPendingSessionType.mockReset();
|
|
95
|
-
useChatInputStore.setState({
|
|
96
|
-
snapshot: {
|
|
97
|
-
...useChatInputStore.getState().snapshot,
|
|
98
|
-
defaultSessionType: "native",
|
|
99
|
-
pendingSessionType: "native",
|
|
100
|
-
selectedSessionType: undefined,
|
|
101
|
-
pendingProjectRoot: null,
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
100
|
useChatSessionListStore.setState({
|
|
105
101
|
snapshot: {
|
|
106
102
|
...useChatSessionListStore.getState().snapshot,
|
|
@@ -138,56 +134,78 @@ function resetWelcomeTestState() {
|
|
|
138
134
|
});
|
|
139
135
|
}
|
|
140
136
|
|
|
137
|
+
function renderWelcome(overrides: Partial<{
|
|
138
|
+
onSelectPrompt: (prompt: string) => void;
|
|
139
|
+
onSelectProjectRoot: (projectRoot: string | null) => void;
|
|
140
|
+
onSelectSessionType: (sessionType: SetStateAction<string>) => void;
|
|
141
|
+
}> = {}) {
|
|
142
|
+
return render(
|
|
143
|
+
<ChatConversationWelcome
|
|
144
|
+
inputSlot={<div data-testid="input-slot" />}
|
|
145
|
+
pendingProjectRoot={null}
|
|
146
|
+
pendingSessionType="native"
|
|
147
|
+
selectedSessionTypeValue={null}
|
|
148
|
+
onSelectPrompt={overrides.onSelectPrompt ?? vi.fn()}
|
|
149
|
+
onSelectProjectRoot={overrides.onSelectProjectRoot ?? vi.fn()}
|
|
150
|
+
onSelectSessionType={overrides.onSelectSessionType ?? vi.fn()}
|
|
151
|
+
/>,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
141
155
|
describe("ChatConversationWelcome", () => {
|
|
142
156
|
beforeEach(resetWelcomeTestState);
|
|
143
157
|
|
|
144
|
-
it("
|
|
158
|
+
it("emits the selected welcome project through the local input callback", async () => {
|
|
145
159
|
const user = userEvent.setup();
|
|
160
|
+
const onSelectProjectRoot = vi.fn();
|
|
146
161
|
|
|
147
|
-
|
|
162
|
+
renderWelcome({ onSelectProjectRoot });
|
|
148
163
|
|
|
149
164
|
await user.click(
|
|
150
165
|
screen.getByRole("button", { name: "select draft project" }),
|
|
151
166
|
);
|
|
152
167
|
|
|
153
|
-
expect(
|
|
168
|
+
expect(onSelectProjectRoot).toHaveBeenCalledWith("/tmp/project-alpha");
|
|
154
169
|
});
|
|
155
170
|
|
|
156
|
-
it("
|
|
171
|
+
it("emits a selected prompt suggestion through the local input callback", async () => {
|
|
157
172
|
const user = userEvent.setup();
|
|
173
|
+
const onSelectPrompt = vi.fn();
|
|
158
174
|
|
|
159
|
-
|
|
175
|
+
renderWelcome({ onSelectPrompt });
|
|
160
176
|
|
|
161
177
|
await user.click(
|
|
162
178
|
screen.getByRole("button", { name: "pick prompt" }),
|
|
163
179
|
);
|
|
164
180
|
|
|
165
|
-
expect(
|
|
181
|
+
expect(onSelectPrompt).toHaveBeenCalledWith("example prompt");
|
|
166
182
|
});
|
|
167
183
|
|
|
168
184
|
|
|
169
185
|
it("syncs the pending session type when switching the draft agent", async () => {
|
|
170
186
|
const user = userEvent.setup();
|
|
187
|
+
const onSelectSessionType = vi.fn();
|
|
171
188
|
|
|
172
|
-
|
|
189
|
+
renderWelcome({ onSelectSessionType });
|
|
173
190
|
|
|
174
191
|
await user.click(
|
|
175
192
|
screen.getByRole("button", { name: "switch draft agent" }),
|
|
176
193
|
);
|
|
177
194
|
|
|
178
195
|
expect(mocks.setSelectedAgentId).toHaveBeenCalledWith("engineer");
|
|
179
|
-
expect(
|
|
196
|
+
expect(onSelectSessionType).toHaveBeenCalledWith("codex");
|
|
180
197
|
});
|
|
181
198
|
|
|
182
199
|
it("stores an explicit welcome session type selection", async () => {
|
|
183
200
|
const user = userEvent.setup();
|
|
201
|
+
const onSelectSessionType = vi.fn();
|
|
184
202
|
|
|
185
|
-
|
|
203
|
+
renderWelcome({ onSelectSessionType });
|
|
186
204
|
|
|
187
205
|
await user.click(
|
|
188
206
|
screen.getByRole("button", { name: "switch session type" }),
|
|
189
207
|
);
|
|
190
208
|
|
|
191
|
-
expect(
|
|
209
|
+
expect(onSelectSessionType).toHaveBeenCalledWith("codex");
|
|
192
210
|
});
|
|
193
211
|
});
|
|
@@ -10,8 +10,8 @@ vi.mock(
|
|
|
10
10
|
);
|
|
11
11
|
|
|
12
12
|
const sessionTypeOptions = [
|
|
13
|
-
{ value: 'native', label: 'Native', ready: true },
|
|
14
|
-
{ value: 'codex', label: 'Codex', ready: true },
|
|
13
|
+
{ value: 'native', label: 'Native', icon: null, ready: true },
|
|
14
|
+
{ value: 'codex', label: 'Codex', icon: null, ready: true },
|
|
15
15
|
];
|
|
16
16
|
|
|
17
17
|
function renderWelcome(
|
|
@@ -89,17 +89,10 @@ describe('ChatWelcome', () => {
|
|
|
89
89
|
defaultProjectRoot: '/Users/demo/.nextclaw/workspace',
|
|
90
90
|
onSelectProjectRoot,
|
|
91
91
|
projectOptions: [
|
|
92
|
-
{
|
|
93
|
-
projectRoot: '/Users/demo/.nextclaw/workspace',
|
|
94
|
-
projectName: 'workspace',
|
|
95
|
-
sessionCount: 0,
|
|
96
|
-
isDefault: true,
|
|
97
|
-
},
|
|
98
92
|
{
|
|
99
93
|
projectRoot: '/tmp/project-alpha',
|
|
100
94
|
projectName: 'project-alpha',
|
|
101
95
|
sessionCount: 3,
|
|
102
|
-
isDefault: false,
|
|
103
96
|
},
|
|
104
97
|
],
|
|
105
98
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import type { SetStateAction } from "react";
|
|
2
3
|
import { usePresenter } from "@/features/chat/components/providers/chat-presenter.provider";
|
|
3
4
|
import {
|
|
4
5
|
buildSessionTypeOptions,
|
|
@@ -11,7 +12,6 @@ import {
|
|
|
11
12
|
resolveChatWelcomeSelectedSessionType,
|
|
12
13
|
} from "@/features/chat/features/welcome/utils/chat-welcome-selection.utils";
|
|
13
14
|
import { buildChatWelcomeProjectOptions } from "@/features/chat/features/welcome/utils/chat-welcome-project-options.utils";
|
|
14
|
-
import { useChatInputStore } from "@/features/chat/stores/chat-input.store";
|
|
15
15
|
import { useChatQueryStore } from "@/features/chat/stores/ncp-chat-query.store";
|
|
16
16
|
import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
|
|
17
17
|
import { useChatThreadStore } from "@/features/chat/stores/chat-thread.store";
|
|
@@ -23,13 +23,24 @@ const EMPTY_NCP_SESSION_SUMMARIES: NcpSessionSummaryView[] = [];
|
|
|
23
23
|
|
|
24
24
|
type ChatConversationWelcomeProps = {
|
|
25
25
|
inputSlot: ReactNode;
|
|
26
|
+
pendingProjectRoot: string | null;
|
|
27
|
+
pendingSessionType: string;
|
|
28
|
+
selectedSessionTypeValue: string | null;
|
|
29
|
+
onSelectProjectRoot: (projectRoot: string | null) => void;
|
|
30
|
+
onSelectPrompt: (prompt: string) => void;
|
|
31
|
+
onSelectSessionType: (sessionType: SetStateAction<string>) => void;
|
|
26
32
|
};
|
|
27
33
|
|
|
28
34
|
export function ChatConversationWelcome({
|
|
29
35
|
inputSlot,
|
|
36
|
+
pendingProjectRoot,
|
|
37
|
+
pendingSessionType,
|
|
38
|
+
selectedSessionTypeValue,
|
|
39
|
+
onSelectProjectRoot,
|
|
40
|
+
onSelectPrompt,
|
|
41
|
+
onSelectSessionType,
|
|
30
42
|
}: ChatConversationWelcomeProps) {
|
|
31
43
|
const presenter = usePresenter();
|
|
32
|
-
const inputSnapshot = useChatInputStore((state) => state.snapshot);
|
|
33
44
|
const selectedAgentId = useChatSessionListStore(
|
|
34
45
|
(state) => state.snapshot.selectedAgentId,
|
|
35
46
|
);
|
|
@@ -53,18 +64,18 @@ export function ChatConversationWelcome({
|
|
|
53
64
|
config?.agents.defaults.workspace,
|
|
54
65
|
);
|
|
55
66
|
const selectedProjectRoot = normalizeSessionProjectRootValue(
|
|
56
|
-
|
|
67
|
+
pendingProjectRoot,
|
|
57
68
|
);
|
|
58
69
|
const sessionTypeOptions = buildSessionTypeOptions(
|
|
59
70
|
sessionTypesData?.options ?? [],
|
|
60
71
|
);
|
|
61
72
|
const defaultSessionType = normalizeSessionType(
|
|
62
|
-
sessionTypesData?.defaultType ??
|
|
73
|
+
sessionTypesData?.defaultType ?? DEFAULT_SESSION_TYPE,
|
|
63
74
|
);
|
|
64
75
|
const selectedSessionType = resolveChatWelcomeSelectedSessionType({
|
|
65
76
|
defaultSessionType,
|
|
66
|
-
pendingSessionType
|
|
67
|
-
selectedSessionType:
|
|
77
|
+
pendingSessionType,
|
|
78
|
+
selectedSessionType: selectedSessionTypeValue,
|
|
68
79
|
sessionTypeOptions,
|
|
69
80
|
});
|
|
70
81
|
const projectOptions = buildChatWelcomeProjectOptions({
|
|
@@ -73,7 +84,7 @@ export function ChatConversationWelcome({
|
|
|
73
84
|
});
|
|
74
85
|
const selectDraftAgent = (agentId: string) => {
|
|
75
86
|
presenter.chatSessionListManager.setSelectedAgentId(agentId);
|
|
76
|
-
|
|
87
|
+
onSelectSessionType(
|
|
77
88
|
resolveChatWelcomeSelectedSessionType({
|
|
78
89
|
agents: availableAgents,
|
|
79
90
|
agentId,
|
|
@@ -96,9 +107,9 @@ export function ChatConversationWelcome({
|
|
|
96
107
|
selectedSessionType={selectedSessionType}
|
|
97
108
|
sessionTypeOptions={sessionTypeOptions}
|
|
98
109
|
onSelectAgent={selectDraftAgent}
|
|
99
|
-
onSelectPrompt={
|
|
100
|
-
onSelectProjectRoot={
|
|
101
|
-
onSelectSessionType={
|
|
110
|
+
onSelectPrompt={onSelectPrompt}
|
|
111
|
+
onSelectProjectRoot={onSelectProjectRoot}
|
|
112
|
+
onSelectSessionType={onSelectSessionType}
|
|
102
113
|
/>
|
|
103
114
|
);
|
|
104
115
|
}
|
|
@@ -28,7 +28,7 @@ export function ChatWelcomeAgentPicker({
|
|
|
28
28
|
<Select value={selectedAgentId} onValueChange={onSelectAgent}>
|
|
29
29
|
<SelectTrigger
|
|
30
30
|
aria-label={t('chatDraftAgentTitle')}
|
|
31
|
-
className="h-auto w-auto min-w-0 gap-1 rounded-lg border-0 bg-transparent px-2 py-1.5 text-
|
|
31
|
+
className="h-auto w-auto min-w-0 gap-1 rounded-lg border-0 bg-transparent px-2 py-1.5 text-muted-foreground shadow-none hover:bg-accent hover:text-accent-foreground focus:ring-0"
|
|
32
32
|
>
|
|
33
33
|
<span className="sr-only">{t('chatDraftAgentTitle')}</span>
|
|
34
34
|
<div className="flex min-w-0 items-center gap-1.5">
|
|
@@ -48,7 +48,7 @@ export function ChatWelcomeAgentPicker({
|
|
|
48
48
|
</div>
|
|
49
49
|
</SelectTrigger>
|
|
50
50
|
<SelectContent
|
|
51
|
-
className="rounded-xl border-
|
|
51
|
+
className="rounded-xl border-border shadow-lg"
|
|
52
52
|
style={{ maxHeight: AGENT_PICKER_MAX_HEIGHT }}
|
|
53
53
|
>
|
|
54
54
|
{agents.map((agent) => (
|
|
@@ -60,7 +60,7 @@ export function ChatWelcomeAgentPicker({
|
|
|
60
60
|
avatarUrl={agent.avatarUrl}
|
|
61
61
|
className="h-5 w-5 shrink-0"
|
|
62
62
|
/>
|
|
63
|
-
<span className="truncate text-sm font-medium text-
|
|
63
|
+
<span className="truncate text-sm font-medium text-foreground">
|
|
64
64
|
{agent.displayName?.trim() || agent.id}
|
|
65
65
|
</span>
|
|
66
66
|
</div>
|
|
@@ -35,15 +35,15 @@ export function ChatWelcomeCapabilityGrid({
|
|
|
35
35
|
<button
|
|
36
36
|
key={capability.titleKey}
|
|
37
37
|
onClick={() => onSelectPrompt(t(capability.promptKey))}
|
|
38
|
-
className="min-w-0 rounded-2xl border border-
|
|
38
|
+
className="min-w-0 rounded-2xl border border-border bg-card p-3 text-left text-card-foreground shadow-card transition-[border-color,box-shadow,transform] hover:border-primary/30 hover:shadow-card-hover sm:p-4"
|
|
39
39
|
>
|
|
40
|
-
<div className="mb-3 flex h-9 w-9 items-center justify-center rounded-xl bg-
|
|
40
|
+
<div className="mb-3 flex h-9 w-9 items-center justify-center rounded-xl bg-accent">
|
|
41
41
|
<Icon className="h-4.5 w-4.5 text-primary" />
|
|
42
42
|
</div>
|
|
43
|
-
<div className="mb-1 text-sm font-semibold text-
|
|
43
|
+
<div className="mb-1 text-sm font-semibold text-foreground">
|
|
44
44
|
{t(capability.titleKey)}
|
|
45
45
|
</div>
|
|
46
|
-
<div className="text-[11px] leading-relaxed text-
|
|
46
|
+
<div className="text-[11px] leading-relaxed text-muted-foreground">
|
|
47
47
|
{t(capability.descKey)}
|
|
48
48
|
</div>
|
|
49
49
|
</button>
|
|
@@ -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-
|
|
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-2 focus-visible:ring-primary/25"
|
|
53
53
|
title={projectRoot ?? undefined}
|
|
54
54
|
aria-label={t('chatWelcomeProjectPickerLabel')}
|
|
55
55
|
disabled={!selectable || isSaving}
|
|
@@ -57,19 +57,19 @@ export function ChatWelcomeProjectPicker({
|
|
|
57
57
|
<FolderOpen className="h-4 w-4 shrink-0" />
|
|
58
58
|
<span className="truncate">{projectLabel}</span>
|
|
59
59
|
{isDefaultProject ? (
|
|
60
|
-
<span className="shrink-0 text-xs font-normal text-
|
|
60
|
+
<span className="shrink-0 text-xs font-normal text-muted-foreground/70">
|
|
61
61
|
{t('chatWelcomeProjectDefaultBadge')}
|
|
62
62
|
</span>
|
|
63
63
|
) : null}
|
|
64
|
-
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-
|
|
64
|
+
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground/70" />
|
|
65
65
|
</button>
|
|
66
66
|
</PopoverTrigger>
|
|
67
67
|
<PopoverContent
|
|
68
68
|
align="start"
|
|
69
|
-
className="flex w-[min(20rem,calc(100vw-1rem))] flex-col overflow-hidden rounded-2xl border border-
|
|
69
|
+
className="flex w-[min(20rem,calc(100vw-1rem))] flex-col overflow-hidden rounded-2xl border border-border bg-popover p-0 text-popover-foreground shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
|
|
70
70
|
style={{ maxHeight: PROJECT_PICKER_MAX_HEIGHT }}
|
|
71
71
|
>
|
|
72
|
-
<div className="shrink-0 px-3 pb-2 pt-3 text-[10px] font-semibold uppercase tracking-[0.18em] text-
|
|
72
|
+
<div className="shrink-0 px-3 pb-2 pt-3 text-[10px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
|
|
73
73
|
{t('chatWelcomeProjectRecentTitle')}
|
|
74
74
|
</div>
|
|
75
75
|
<div className="min-h-0 flex-1 overflow-y-auto px-1.5 pb-1.5">
|
|
@@ -78,51 +78,46 @@ export function ChatWelcomeProjectPicker({
|
|
|
78
78
|
<button
|
|
79
79
|
key={option.projectRoot}
|
|
80
80
|
type="button"
|
|
81
|
-
className="flex w-full min-w-0 items-start gap-2 rounded-xl px-2 py-2 text-left transition-colors hover:bg-
|
|
81
|
+
className="flex w-full min-w-0 items-start gap-2 rounded-xl px-2 py-2 text-left transition-colors hover:bg-accent"
|
|
82
82
|
title={option.projectRoot}
|
|
83
83
|
onClick={() => {
|
|
84
84
|
void selectProjectRoot(option.projectRoot);
|
|
85
85
|
}}
|
|
86
86
|
>
|
|
87
|
-
<FolderOpen className="mt-0.5 h-4 w-4 shrink-0 text-
|
|
87
|
+
<FolderOpen className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground/70" />
|
|
88
88
|
<span className="min-w-0 flex-1">
|
|
89
89
|
<span className="flex min-w-0 items-center gap-1.5">
|
|
90
|
-
<span className="truncate text-[13px] font-semibold text-
|
|
90
|
+
<span className="truncate text-[13px] font-semibold text-foreground">
|
|
91
91
|
{option.projectName}
|
|
92
92
|
</span>
|
|
93
|
-
{option.isDefault ? (
|
|
94
|
-
<span className="shrink-0 text-[10px] font-medium text-gray-400">
|
|
95
|
-
{t('chatWelcomeProjectDefaultBadge')}
|
|
96
|
-
</span>
|
|
97
|
-
) : null}
|
|
98
93
|
</span>
|
|
99
|
-
<span className="block truncate text-[11px] leading-4 text-
|
|
94
|
+
<span className="block truncate text-[11px] leading-4 text-muted-foreground">
|
|
100
95
|
{option.projectRoot}
|
|
101
96
|
</span>
|
|
102
97
|
</span>
|
|
103
98
|
{option.sessionCount > 0 ? (
|
|
104
|
-
<span className="mt-0.5 shrink-0 rounded-full bg-
|
|
99
|
+
<span className="mt-0.5 shrink-0 rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
|
|
105
100
|
{option.sessionCount}
|
|
106
101
|
</span>
|
|
107
102
|
) : null}
|
|
108
103
|
</button>
|
|
109
104
|
))
|
|
110
105
|
) : (
|
|
111
|
-
<div className="px-2 py-5 text-center text-xs text-
|
|
106
|
+
<div className="px-2 py-5 text-center text-xs text-muted-foreground">
|
|
112
107
|
{t('chatWelcomeProjectNoRecent')}
|
|
113
108
|
</div>
|
|
114
109
|
)}
|
|
115
110
|
</div>
|
|
116
|
-
<div className="shrink-0 border-t border-
|
|
111
|
+
<div className="shrink-0 border-t border-border p-1.5">
|
|
117
112
|
<button
|
|
118
113
|
type="button"
|
|
119
|
-
className="flex w-full items-center gap-2 rounded-xl px-2.5 py-2 text-left text-[13px] font-semibold text-
|
|
114
|
+
className="flex w-full items-center gap-2 rounded-xl px-2.5 py-2 text-left text-[13px] font-semibold text-foreground transition-colors hover:bg-accent"
|
|
120
115
|
onClick={() => {
|
|
121
116
|
setIsOpen(false);
|
|
122
117
|
onOpenProjectDialog();
|
|
123
118
|
}}
|
|
124
119
|
>
|
|
125
|
-
<FolderOpen className="h-4 w-4 shrink-0 text-
|
|
120
|
+
<FolderOpen className="h-4 w-4 shrink-0 text-muted-foreground/70" />
|
|
126
121
|
<span>{t('chatWelcomeProjectOpenFolder')}</span>
|
|
127
122
|
</button>
|
|
128
123
|
</div>
|
|
@@ -2,7 +2,7 @@ import { useState } from 'react';
|
|
|
2
2
|
import { Bot, ChevronDown } from 'lucide-react';
|
|
3
3
|
import { SessionContextIconNode } from '@/features/chat/features/session/components/session-context-icon';
|
|
4
4
|
import { ChatSessionTypeOptionItem } from '@/features/chat/features/session-type/components/chat-session-type-option-item';
|
|
5
|
-
import type {
|
|
5
|
+
import type { ChatSessionTypeOption } from '@/features/chat/features/session-type/utils/chat-session-type.utils';
|
|
6
6
|
import {
|
|
7
7
|
Popover,
|
|
8
8
|
PopoverContent,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from '@/shared/components/ui/popover';
|
|
12
12
|
import { t } from '@/shared/lib/i18n';
|
|
13
13
|
|
|
14
|
-
type SessionTypeOption =
|
|
14
|
+
type SessionTypeOption = ChatSessionTypeOption;
|
|
15
15
|
|
|
16
16
|
type ChatWelcomeSessionTypePickerProps = {
|
|
17
17
|
options: readonly SessionTypeOption[];
|
|
@@ -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-
|
|
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-2 focus-visible:ring-primary/25"
|
|
43
43
|
aria-label={t('chatWelcomeSessionTypePickerLabel')}
|
|
44
44
|
>
|
|
45
45
|
{selectedOption.icon?.src ? (
|
|
@@ -56,12 +56,12 @@ export function ChatWelcomeSessionTypePicker({
|
|
|
56
56
|
<Bot className="h-4 w-4 shrink-0" />
|
|
57
57
|
)}
|
|
58
58
|
<span className="truncate">{selectedOption.label}</span>
|
|
59
|
-
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-
|
|
59
|
+
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground/70" />
|
|
60
60
|
</button>
|
|
61
61
|
</PopoverTrigger>
|
|
62
62
|
<PopoverContent
|
|
63
63
|
align="start"
|
|
64
|
-
className="flex w-[min(16rem,calc(100vw-1rem))] flex-col overflow-hidden rounded-2xl border border-
|
|
64
|
+
className="flex w-[min(16rem,calc(100vw-1rem))] flex-col overflow-hidden rounded-2xl border border-border bg-popover p-1.5 text-popover-foreground shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
|
|
65
65
|
style={{ maxHeight: SESSION_TYPE_PICKER_MAX_HEIGHT }}
|
|
66
66
|
>
|
|
67
67
|
<div className="min-h-0 flex-1 overflow-y-auto">
|
|
@@ -7,11 +7,11 @@ import { ChatWelcomeCapabilityGrid } from '@/features/chat/features/welcome/comp
|
|
|
7
7
|
import { ChatWelcomeProjectPicker } from '@/features/chat/features/welcome/components/chat-welcome-project-picker';
|
|
8
8
|
import { ChatWelcomeSessionTypePicker } from '@/features/chat/features/welcome/components/chat-welcome-session-type-picker';
|
|
9
9
|
import type { ChatWelcomeProjectOption } from '@/features/chat/features/welcome/utils/chat-welcome-project-options.utils';
|
|
10
|
-
import type {
|
|
10
|
+
import type { ChatSessionTypeOption } from '@/features/chat/features/session-type/utils/chat-session-type.utils';
|
|
11
11
|
import type { AgentProfileView } from '@/shared/lib/api';
|
|
12
12
|
import { t } from '@/shared/lib/i18n';
|
|
13
13
|
|
|
14
|
-
type SessionTypeOption =
|
|
14
|
+
type SessionTypeOption = ChatSessionTypeOption;
|
|
15
15
|
|
|
16
16
|
type ChatWelcomeProps = {
|
|
17
17
|
agents: AgentProfileView[];
|
|
@@ -64,22 +64,22 @@ export function ChatWelcome({
|
|
|
64
64
|
return (
|
|
65
65
|
<div className="flex min-h-full items-center justify-center px-4 py-8 sm:p-8">
|
|
66
66
|
<div className="min-w-0 w-full max-w-[min(760px,100%)]">
|
|
67
|
-
<div className="mx-auto mb-5 flex h-14 w-14 items-center justify-center rounded-2xl bg-
|
|
67
|
+
<div className="mx-auto mb-5 flex h-14 w-14 items-center justify-center rounded-2xl border border-border bg-accent shadow-card">
|
|
68
68
|
<Bot className="h-8 w-8 text-primary" />
|
|
69
69
|
</div>
|
|
70
70
|
|
|
71
71
|
<div className="text-center">
|
|
72
|
-
<h2 className="text-2xl font-semibold text-
|
|
72
|
+
<h2 className="text-2xl font-semibold text-foreground sm:text-3xl">
|
|
73
73
|
{t('chatWelcomeTitle')}
|
|
74
74
|
</h2>
|
|
75
|
-
<p className="mt-3 text-sm font-medium text-
|
|
75
|
+
<p className="mt-3 text-sm font-medium text-muted-foreground sm:text-base">
|
|
76
76
|
{t('chatWelcomeSubtitle')}
|
|
77
77
|
</p>
|
|
78
78
|
</div>
|
|
79
79
|
|
|
80
80
|
{inputSlot ? <div className="mt-8">{inputSlot}</div> : null}
|
|
81
81
|
|
|
82
|
-
<div className="mt-3 flex flex-wrap items-center gap-2 px-1 text-sm text-
|
|
82
|
+
<div className="mt-3 flex flex-wrap items-center gap-2 px-1 text-sm text-muted-foreground">
|
|
83
83
|
{onSelectProjectRoot ? (
|
|
84
84
|
<ChatWelcomeProjectPicker
|
|
85
85
|
defaultProjectRoot={defaultProjectRoot}
|
|
@@ -18,10 +18,15 @@ function createSummary(
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
describe("buildChatWelcomeProjectOptions", () => {
|
|
21
|
-
it("builds recent project options
|
|
21
|
+
it("builds recent project options without treating the default workspace as a project", () => {
|
|
22
22
|
const options = buildChatWelcomeProjectOptions({
|
|
23
23
|
defaultProjectRoot: "/Users/demo/.nextclaw/workspace",
|
|
24
24
|
sessionSummaries: [
|
|
25
|
+
createSummary({
|
|
26
|
+
sessionId: "session-default",
|
|
27
|
+
lastMessageAt: "2026-06-12T10:00:00.000Z",
|
|
28
|
+
metadata: { project_root: "/Users/demo/.nextclaw/workspace" },
|
|
29
|
+
}),
|
|
25
30
|
createSummary({
|
|
26
31
|
sessionId: "session-1",
|
|
27
32
|
lastMessageAt: "2026-06-10T10:00:00.000Z",
|
|
@@ -36,17 +41,10 @@ describe("buildChatWelcomeProjectOptions", () => {
|
|
|
36
41
|
});
|
|
37
42
|
|
|
38
43
|
expect(options).toEqual([
|
|
39
|
-
{
|
|
40
|
-
projectRoot: "/Users/demo/.nextclaw/workspace",
|
|
41
|
-
projectName: "workspace",
|
|
42
|
-
sessionCount: 0,
|
|
43
|
-
isDefault: true,
|
|
44
|
-
},
|
|
45
44
|
{
|
|
46
45
|
projectRoot: "/tmp/project-alpha",
|
|
47
46
|
projectName: "project-alpha",
|
|
48
47
|
sessionCount: 2,
|
|
49
|
-
isDefault: false,
|
|
50
48
|
},
|
|
51
49
|
]);
|
|
52
50
|
});
|
|
@@ -6,7 +6,6 @@ export type ChatWelcomeProjectOption = {
|
|
|
6
6
|
projectRoot: string;
|
|
7
7
|
projectName: string;
|
|
8
8
|
sessionCount: number;
|
|
9
|
-
isDefault: boolean;
|
|
10
9
|
};
|
|
11
10
|
|
|
12
11
|
function readSessionActivityAt(session: {
|
|
@@ -28,7 +27,7 @@ export function buildChatWelcomeProjectOptions(params: {
|
|
|
28
27
|
|
|
29
28
|
for (const session of adaptNcpSessionSummaries([...sessionSummaries])) {
|
|
30
29
|
const projectRoot = session.projectRoot?.trim();
|
|
31
|
-
if (!projectRoot) {
|
|
30
|
+
if (!projectRoot || projectRoot === defaultProjectRoot) {
|
|
32
31
|
continue;
|
|
33
32
|
}
|
|
34
33
|
const existing = groups.get(projectRoot);
|
|
@@ -45,26 +44,12 @@ export function buildChatWelcomeProjectOptions(params: {
|
|
|
45
44
|
getSessionProjectName(projectRoot) ||
|
|
46
45
|
projectRoot,
|
|
47
46
|
sessionCount: 1,
|
|
48
|
-
isDefault: projectRoot === defaultProjectRoot,
|
|
49
47
|
latestUpdatedAt,
|
|
50
48
|
});
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
if (defaultProjectRoot && !groups.has(defaultProjectRoot)) {
|
|
54
|
-
groups.set(defaultProjectRoot, {
|
|
55
|
-
projectRoot: defaultProjectRoot,
|
|
56
|
-
projectName: getSessionProjectName(defaultProjectRoot) ?? defaultProjectRoot,
|
|
57
|
-
sessionCount: 0,
|
|
58
|
-
isDefault: true,
|
|
59
|
-
latestUpdatedAt: Number.POSITIVE_INFINITY,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
51
|
return [...groups.values()]
|
|
64
52
|
.sort((left, right) => {
|
|
65
|
-
if (left.isDefault !== right.isDefault) {
|
|
66
|
-
return left.isDefault ? -1 : 1;
|
|
67
|
-
}
|
|
68
53
|
return right.latestUpdatedAt - left.latestUpdatedAt;
|
|
69
54
|
})
|
|
70
55
|
.map(({ latestUpdatedAt: _latestUpdatedAt, ...option }) => option);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { expect, it, vi } from "vitest";
|
|
3
|
+
import { ChatSessionWorkspacePanelContent } from "@/features/chat/features/workspace/components/chat-session-workspace-panel-content";
|
|
4
|
+
import type { ResolvedChildSessionTab } from "@/features/chat/features/ncp/hooks/use-ncp-child-session-tabs-view";
|
|
5
|
+
|
|
6
|
+
vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
|
|
7
|
+
usePresenter: () => ({
|
|
8
|
+
chatThreadManager: {
|
|
9
|
+
materializeSideChatDraft: vi.fn(),
|
|
10
|
+
openFilePreview: vi.fn(),
|
|
11
|
+
},
|
|
12
|
+
}),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock("@/features/chat/features/conversation/components/session-conversation-area", () => ({
|
|
16
|
+
SessionConversationArea: () => <div data-testid="session-conversation-area" />,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
function createChildTab(): ResolvedChildSessionTab {
|
|
20
|
+
return {
|
|
21
|
+
sessionKey: "child-1",
|
|
22
|
+
parentSessionKey: "parent-1",
|
|
23
|
+
title: "Child title",
|
|
24
|
+
agentId: "agent-1",
|
|
25
|
+
updatedAt: null,
|
|
26
|
+
lastMessageAt: null,
|
|
27
|
+
readAt: null,
|
|
28
|
+
runStatus: undefined,
|
|
29
|
+
sessionTypeLabel: "原生",
|
|
30
|
+
preferredModel: "minimax/MiniMax-M3",
|
|
31
|
+
projectName: "nextbot",
|
|
32
|
+
projectRoot: "/Users/peiwang/Projects/nextbot",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
it("shows compact child session metadata without repeating the tab title", () => {
|
|
37
|
+
render(
|
|
38
|
+
<ChatSessionWorkspacePanelContent
|
|
39
|
+
activeSelection={{
|
|
40
|
+
kind: "child-session",
|
|
41
|
+
tab: createChildTab(),
|
|
42
|
+
}}
|
|
43
|
+
sessionCronJobs={[]}
|
|
44
|
+
sessionProjectRoot={null}
|
|
45
|
+
sessionWorkingDir={null}
|
|
46
|
+
/>,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(screen.queryByText("Child title")).toBeNull();
|
|
50
|
+
expect(screen.getByText("原生")).toBeTruthy();
|
|
51
|
+
expect(screen.getByText("minimax/MiniMax-M3")).toBeTruthy();
|
|
52
|
+
expect(screen.getByText("nextbot")).toBeTruthy();
|
|
53
|
+
expect(screen.getByTitle("/Users/peiwang/Projects/nextbot")).toBeTruthy();
|
|
54
|
+
});
|