@nextclaw/ui 0.15.21 → 0.15.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/assets/{api-CHo2gekJ.js → api-B1yHQYs-.js} +3 -3
- package/dist/assets/app-presenter-provider-uP9waOzv.js +108 -0
- package/dist/assets/appearance-settings-page-C9Zq3hP9.js +1 -0
- package/dist/assets/{book-open-BrZL4o9L.js → book-open-oSmyVXea.js} +1 -1
- package/dist/assets/{card-B5rRLu_6.js → card-CGvJZTZp.js} +1 -1
- package/dist/assets/{channels-list-page-C1dnm68I.js → channels-list-page-5BTxpQHw.js} +2 -2
- package/dist/assets/chat-page-jTvli8Jp.js +1 -0
- package/dist/assets/config-split-page-BgBsUmp1.js +1 -0
- package/dist/assets/confirm-dialog-h849QRWO.js +5 -0
- package/dist/assets/desktop-update-config-BGGHxClR.js +1 -0
- package/dist/assets/{dist-C9EX46wM.js → dist-CxUhurq9.js} +1 -1
- package/dist/assets/doc-browser-0eL8AX42.js +1 -0
- package/dist/assets/{doc-browser-DH-3hDnI.js → doc-browser-DEqE9lsh.js} +1 -1
- package/dist/assets/{doc-browser-context-TU2heon0.js → doc-browser-context-DkzFIUoK.js} +1 -1
- package/dist/assets/{eye-B-3dyZNA.js → eye-gOWQGt8N.js} +1 -1
- package/dist/assets/{form-actions-DvvxCpL4.js → form-actions-CgjYZUzM.js} +1 -1
- package/dist/assets/{index-k_eWScvN.js → index-DSAUWCfl.js} +2 -2
- package/dist/assets/index-G7cBryJ4.css +1 -0
- package/dist/assets/loader-circle-BAuycGf6.js +1 -0
- package/dist/assets/{logo-badge-DB51c0cS.js → logo-badge-BNcsDsUM.js} +1 -1
- package/dist/assets/{mcp-marketplace-page-DWtsIqml.js → mcp-marketplace-page-BX39MtPa.js} +3 -3
- package/dist/assets/mcp-marketplace-page-CkDtOEA4.js +1 -0
- package/dist/assets/{model-config-page-D6cGNnMM.js → model-config-page-7MIXg2fQ.js} +1 -1
- package/dist/assets/{navigation-link-BSJEUiwD.js → navigation-link-B3iFOdxx.js} +1 -1
- package/dist/assets/plus-yvhFgfal.js +1 -0
- package/dist/assets/{popover-Bp9IpE_N.js → popover-Cpep_rJe.js} +1 -1
- package/dist/assets/{provider-models-CXUgDxGR.js → provider-models-CUrptZ-o.js} +1 -1
- package/dist/assets/provider-scoped-model-input-CHmOzz7x.js +1 -0
- package/dist/assets/providers-config-page-A6J7Y0II.js +1 -0
- package/dist/assets/react-BCJGCjrP.js +8 -0
- package/dist/assets/{refresh-cw-C2e94pmp.js → refresh-cw-CkKwPztT.js} +1 -1
- package/dist/assets/remote-FD-ZfSIn.js +1 -0
- package/dist/assets/rotate-cw-DrJMfHhp.js +1 -0
- package/dist/assets/{runtime-config-page-C57OLdPm.js → runtime-config-page-DwjO-Vs6.js} +1 -1
- package/dist/assets/{save-CkwDOgvt.js → save-BINMWate.js} +1 -1
- package/dist/assets/{search-CLXM2reO.js → search-7M38azRp.js} +1 -1
- package/dist/assets/{search-config-page-tnJXaaSr.js → search-config-page-T6tMhXbP.js} +1 -1
- package/dist/assets/{secrets-config-page-DQ_Curaj.js → secrets-config-page-2mVejXf2.js} +2 -2
- package/dist/assets/security-config-DD5RrjDe.js +1 -0
- package/dist/assets/{select-CNFBf00l.js → select-BvidP5Oa.js} +1 -1
- package/dist/assets/{settings-2-B9IT2HGd.js → settings-2-DqZstREV.js} +1 -1
- package/dist/assets/skeleton-BEQW2Dva.js +1 -0
- package/dist/assets/{tabs-BuNr8LSO.js → tabs-BeLxVmkt.js} +1 -1
- package/dist/assets/{tag-chip-CDkB5smU.js → tag-chip-BoDaLnWt.js} +1 -1
- package/dist/assets/{trash-2-GchaJuEM.js → trash-2-CYUskMMN.js} +1 -1
- package/dist/assets/{use-config-C69rR15i.js → use-config-CxKRXXCl.js} +1 -1
- package/dist/assets/use-confirm-dialog-HASTvOP2.js +1 -0
- package/dist/assets/{use-viewport-layout-BACNZyMY.js → use-viewport-layout-CJxkjoRz.js} +1 -1
- package/dist/assets/x-DdUwjre5.js +1 -0
- package/dist/index.html +29 -29
- package/package.json +7 -7
- package/src/app/components/layout/sidebar-rail.styles.ts +0 -2
- package/src/app/components/layout/sidebar.tsx +2 -2
- package/src/app/styles/design-system.css +12 -0
- package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +2 -2
- package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +19 -1
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +1 -1
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +5 -6
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +1 -1
- package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +1 -1
- package/src/features/chat/components/layout/__tests__/chat-sidebar-session-area.test.tsx +52 -0
- package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +11 -17
- package/src/features/chat/components/layout/chat-page-shell.tsx +2 -11
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +32 -42
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +13 -64
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +1 -4
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +1 -1
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +15 -4
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +7 -7
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +4 -2
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +1 -0
- package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +1 -0
- package/src/features/chat/features/conversation/utils/session-queued-input.utils.ts +1 -0
- package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +3 -0
- package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +0 -1
- package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +3 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-timeline.utils.test.ts +29 -0
- package/src/features/chat/features/message/utils/chat-message-timeline.utils.ts +4 -1
- package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +10 -10
- package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +2 -2
- 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-project-badge.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +1 -1
- package/src/features/chat/features/session/utils/__tests__/chat-session-display.utils.test.ts +26 -0
- package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +4 -2
- package/src/features/chat/features/session/utils/chat-session-display.utils.ts +48 -12
- package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +13 -4
- package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +6 -0
- package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +10 -7
- package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +1 -1
- package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +3 -14
- package/src/features/chat/features/welcome/components/chat-welcome-prompt-suggestions.tsx +37 -0
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +1 -1
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +33 -31
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +1 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +1 -1
- package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +15 -3
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +3 -10
- package/src/features/chat/managers/chat-session-list.manager.ts +3 -23
- package/src/features/chat/managers/chat-thread.manager.ts +2 -5
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +3 -9
- package/src/features/chat/pages/ncp-chat-page.tsx +0 -1
- package/src/features/chat/types/chat-input-bar.types.ts +1 -0
- package/src/features/cron/components/cron-task-discovery.tsx +1 -1
- package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
- package/src/features/notifications/components/__tests__/app-notification-toast.test.tsx +3 -0
- package/src/features/notifications/components/app-notification-toast.tsx +1 -1
- package/src/features/panel-apps/components/panel-app-list-item.tsx +1 -1
- package/src/features/panel-apps/components/panel-app-toolbar.tsx +2 -2
- package/src/features/panel-apps/components/panel-apps-list.tsx +4 -4
- package/src/features/service-apps/components/service-apps-panel.tsx +1 -1
- package/src/features/settings/components/config/provider-auth-section.tsx +90 -59
- package/src/features/settings/components/config/provider-form.tsx +10 -17
- package/src/features/settings/components/config/provider-status-badge.tsx +4 -2
- package/src/features/settings/pages/__tests__/providers-config-page.test.tsx +12 -0
- package/src/features/settings/pages/providers-config-page.tsx +2 -1
- package/src/platforms/mobile/components/__tests__/mobile-app-shell.test.tsx +13 -7
- package/src/platforms/mobile/components/__tests__/mobile-bottom-nav.test.tsx +18 -20
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +1 -1
- package/src/shared/components/path-picker/server-path-picker-directory-list.tsx +1 -1
- package/src/shared/components/path-picker/server-path-picker-locations.tsx +1 -1
- package/src/shared/components/path-picker/server-path-picker-navigation-bar.tsx +3 -3
- package/src/shared/components/ui/actions/__tests__/icon-action-button.test.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/dialog.tsx +1 -1
- package/src/shared/components/ui/input.tsx +1 -1
- package/src/shared/components/ui/scroll-area.tsx +2 -0
- package/src/shared/components/ui/select.tsx +1 -1
- package/src/shared/components/ui/switch.tsx +1 -1
- package/src/shared/components/ui/tabs.tsx +1 -1
- package/src/shared/components/ui/textarea.tsx +1 -1
- package/src/shared/lib/api/ncp-session.types.ts +1 -0
- package/src/shared/lib/api/types.ts +2 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +13 -14
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +13 -14
- package/src/shared/lib/provider-models/__tests__/anonymous-provider-configuration.test.ts +40 -0
- package/src/shared/lib/provider-models/index.ts +4 -3
- package/dist/assets/app-presenter-provider-2_DmWYT9.js +0 -108
- package/dist/assets/appearance-settings-page-BEy2UE7R.js +0 -1
- package/dist/assets/chat-page-Dz6tvZ91.js +0 -1
- package/dist/assets/config-split-page-6y6W6dz4.js +0 -1
- package/dist/assets/confirm-dialog-q1CpEmR8.js +0 -5
- package/dist/assets/desktop-update-config-DrdlwP4j.js +0 -1
- package/dist/assets/doc-browser-BfttDSti.js +0 -1
- package/dist/assets/index-CXDPLDFD.css +0 -1
- package/dist/assets/loader-circle-B35NlHTI.js +0 -1
- package/dist/assets/mcp-marketplace-page-CkPtwPBk.js +0 -1
- package/dist/assets/plus-BRVe5jSJ.js +0 -1
- package/dist/assets/provider-scoped-model-input-dk7fz9NL.js +0 -1
- package/dist/assets/providers-config-page-DTP2YfxA.js +0 -1
- package/dist/assets/react-D6ShNq95.js +0 -8
- package/dist/assets/remote-FllhXW3o.js +0 -1
- package/dist/assets/rotate-cw-CTC-Qszi.js +0 -1
- package/dist/assets/security-config-S00DjFfO.js +0 -1
- package/dist/assets/skeleton-Dc06gatK.js +0 -1
- package/dist/assets/use-confirm-dialog-DF_EgPYx.js +0 -1
- package/dist/assets/x-BXFquY9e.js +0 -1
- package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +0 -59
|
@@ -3,9 +3,11 @@ import {
|
|
|
3
3
|
useEffect,
|
|
4
4
|
useMemo,
|
|
5
5
|
useState,
|
|
6
|
+
type ReactElement,
|
|
6
7
|
type MutableRefObject,
|
|
7
8
|
} from 'react';
|
|
8
9
|
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
10
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
9
11
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
10
12
|
import {
|
|
11
13
|
createChatComposerTextNode,
|
|
@@ -30,6 +32,15 @@ import { useChatMessageLayoutStore } from '@/features/chat/stores/chat-message-l
|
|
|
30
32
|
|
|
31
33
|
const uploadNcpAssetsMock = vi.hoisted(() => vi.fn());
|
|
32
34
|
|
|
35
|
+
function renderInput(input: ReactElement) {
|
|
36
|
+
const queryClient = new QueryClient({
|
|
37
|
+
defaultOptions: { queries: { retry: false } },
|
|
38
|
+
});
|
|
39
|
+
return render(
|
|
40
|
+
<QueryClientProvider client={queryClient}>{input}</QueryClientProvider>,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
33
44
|
afterEach(() => {
|
|
34
45
|
useChatMessageLayoutStore.getState().setLayout('card');
|
|
35
46
|
});
|
|
@@ -254,7 +265,7 @@ describe('SessionConversationInput streaming stability', () => {
|
|
|
254
265
|
useChatMessageLayoutStore.getState().setLayout('flat');
|
|
255
266
|
const controlRef: MutableRefObject<StreamingInputControl | null> = { current: null };
|
|
256
267
|
|
|
257
|
-
|
|
268
|
+
renderInput(<StreamingSessionConversationInputHarness controlRef={controlRef} />);
|
|
258
269
|
|
|
259
270
|
const track = document.querySelector(
|
|
260
271
|
'[data-chat-conversation-track="flat"][data-chat-conversation-track-width="composer"]',
|
|
@@ -266,7 +277,7 @@ describe('SessionConversationInput streaming stability', () => {
|
|
|
266
277
|
|
|
267
278
|
it('keeps a numbered IME candidate commit stable while streamed output rerenders the owner', async () => {
|
|
268
279
|
const controlRef: MutableRefObject<StreamingInputControl | null> = { current: null };
|
|
269
|
-
|
|
280
|
+
renderInput(<StreamingSessionConversationInputHarness controlRef={controlRef} />);
|
|
270
281
|
|
|
271
282
|
const textbox = screen.getByRole('textbox');
|
|
272
283
|
fireEvent.focus(textbox);
|
|
@@ -300,7 +311,7 @@ describe('SessionConversationInput streaming stability', () => {
|
|
|
300
311
|
const controlRef: MutableRefObject<StreamingInputControl | null> = { current: null };
|
|
301
312
|
const deleteQueuedInput = vi.fn();
|
|
302
313
|
const editQueuedInput = vi.fn();
|
|
303
|
-
|
|
314
|
+
renderInput(
|
|
304
315
|
<StreamingSessionConversationInputHarness
|
|
305
316
|
controlRef={controlRef}
|
|
306
317
|
controllerOverride={{
|
|
@@ -427,7 +438,7 @@ describe('SessionConversationInput attachment submit', () => {
|
|
|
427
438
|
]);
|
|
428
439
|
const send = vi.fn<AttachmentSubmitAgentSend>(async () => createAttachmentRunHandle());
|
|
429
440
|
|
|
430
|
-
|
|
441
|
+
renderInput(<AttachmentSubmitHarness send={send} />);
|
|
431
442
|
|
|
432
443
|
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement | null;
|
|
433
444
|
expect(fileInput).toBeTruthy();
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
resolveNcpChatSendErrorMessage,
|
|
14
14
|
} from "@/features/chat/features/runtime/utils/ncp-chat-runtime-availability.utils";
|
|
15
15
|
import { buildChatContextWindowIndicator } from "@/features/chat/features/session/utils/chat-context-window-indicator.utils";
|
|
16
|
+
import { sessionActivityPreviewText } from "@/features/chat/features/session/utils/chat-session-display.utils";
|
|
16
17
|
import { readNcpContextWindowValue } from "@/features/chat/features/session/utils/ncp-session-context-metadata.utils";
|
|
17
18
|
import { ChatConversationWelcome } from "@/features/chat/features/welcome/components/chat-conversation-welcome";
|
|
18
19
|
import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
|
|
@@ -326,12 +327,10 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
|
|
|
326
327
|
}),
|
|
327
328
|
[inputSnapshot, lastSendError],
|
|
328
329
|
);
|
|
329
|
-
const
|
|
330
|
+
const { selectedSession } = inputQuery;
|
|
330
331
|
const sessionFailureMessage =
|
|
331
|
-
|
|
332
|
-
?
|
|
333
|
-
sessionFailurePreview.replyText?.trim() ||
|
|
334
|
-
null
|
|
332
|
+
selectedSession?.activityPreview?.state === "failed"
|
|
333
|
+
? sessionActivityPreviewText(selectedSession)
|
|
335
334
|
: null;
|
|
336
335
|
const sessionFailureSlot = sessionFailureMessage ? (
|
|
337
336
|
<div className="rounded-lg border border-red-200/80 bg-red-50/80 px-3 py-2.5 shadow-sm">
|
|
@@ -348,7 +347,7 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
|
|
|
348
347
|
applyPromptSuggestion: inputActions.applyPromptSuggestion,
|
|
349
348
|
});
|
|
350
349
|
const renderInput = useCallback(
|
|
351
|
-
(surface: "default" | "embedded") => (
|
|
350
|
+
(surface: "default" | "embedded", placeholder?: string) => (
|
|
352
351
|
<SessionConversationInput
|
|
353
352
|
contextWindow={contextWindow}
|
|
354
353
|
controller={inputController}
|
|
@@ -356,6 +355,7 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
|
|
|
356
355
|
inputQuery={inputQuery}
|
|
357
356
|
inputSnapshot={displayInputSnapshot}
|
|
358
357
|
onContextCompactingChange={handleContextCompactingChange}
|
|
358
|
+
placeholder={placeholder}
|
|
359
359
|
surface={surface}
|
|
360
360
|
/>
|
|
361
361
|
),
|
|
@@ -396,7 +396,7 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
|
|
|
396
396
|
onLoadPreviousMessages={agent.loadPreviousMessages}
|
|
397
397
|
welcomeSlot={
|
|
398
398
|
<ChatConversationWelcome
|
|
399
|
-
inputSlot={renderInput("embedded")}
|
|
399
|
+
inputSlot={renderInput("embedded", t("chatWelcomeInputPlaceholder"))}
|
|
400
400
|
pendingProjectRoot={inputSnapshot.pendingProjectRoot}
|
|
401
401
|
pendingSessionType={inputSnapshot.pendingSessionType}
|
|
402
402
|
selectedSessionTypeValue={inputSnapshot.selectedSessionType}
|
|
@@ -193,6 +193,7 @@ type SessionConversationInputProps = {
|
|
|
193
193
|
readonly inputQuery: SessionConversationInputQuery;
|
|
194
194
|
readonly inputSnapshot: SessionConversationInputSnapshot;
|
|
195
195
|
readonly onContextCompactingChange?: (sessionId: string, isCompacting: boolean) => void;
|
|
196
|
+
readonly placeholder?: string;
|
|
196
197
|
readonly surface?: 'default' | 'embedded';
|
|
197
198
|
};
|
|
198
199
|
|
|
@@ -204,6 +205,7 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
|
|
|
204
205
|
inputQuery,
|
|
205
206
|
inputSnapshot,
|
|
206
207
|
onContextCompactingChange,
|
|
208
|
+
placeholder,
|
|
207
209
|
surface = 'default',
|
|
208
210
|
} = props;
|
|
209
211
|
const presenter = usePresenter();
|
|
@@ -277,7 +279,7 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
|
|
|
277
279
|
const attachmentSupported = true;
|
|
278
280
|
const textareaPlaceholder = isModelOptionsEmpty
|
|
279
281
|
? t('chatModelNoOptions')
|
|
280
|
-
: t(isMobile ? 'chatInputPlaceholderCompact' : 'chatInputPlaceholder');
|
|
282
|
+
: placeholder ?? t(isMobile ? 'chatInputPlaceholderCompact' : 'chatInputPlaceholder');
|
|
281
283
|
const selectedModelOption = modelRecords.find((option) => option.value === selectedModel);
|
|
282
284
|
const thinkingSupportedLevels = selectedModelOption?.thinkingCapability?.supported ?? [];
|
|
283
285
|
const { handleFilesAdd, handleFileInputChange } = useSessionConversationInputAttachments({
|
|
@@ -426,6 +428,7 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
|
|
|
426
428
|
},
|
|
427
429
|
})}
|
|
428
430
|
toolbar={{
|
|
431
|
+
addMenuLabel: t('chatInputAdd'),
|
|
429
432
|
selects: [],
|
|
430
433
|
trailingSelects: toolbarSelects,
|
|
431
434
|
accessories: [
|
|
@@ -433,7 +436,6 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
|
|
|
433
436
|
key: 'attach',
|
|
434
437
|
label: t('chatInputAttach'),
|
|
435
438
|
icon: 'paperclip' as const,
|
|
436
|
-
iconOnly: true,
|
|
437
439
|
disabled: !attachmentSupported || inputDisabled,
|
|
438
440
|
onClick: () => fileInputRef.current?.click(),
|
|
439
441
|
},
|
package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts
CHANGED
|
@@ -46,6 +46,7 @@ export function useSessionConversationInputAttachments(params: UseSessionConvers
|
|
|
46
46
|
insertedAttachments.map((attachment) => ({
|
|
47
47
|
tokenKey: attachment.id,
|
|
48
48
|
label: attachment.name,
|
|
49
|
+
previewUrl: attachment.mimeType.startsWith('image/') ? attachment.url : undefined,
|
|
49
50
|
})),
|
|
50
51
|
);
|
|
51
52
|
}
|
|
@@ -22,6 +22,7 @@ function createModelTexts() {
|
|
|
22
22
|
favoriteModelsLabel: 'Favorites',
|
|
23
23
|
favoriteModelLabel: 'Favorite model',
|
|
24
24
|
unfavoriteModelLabel: 'Remove favorite',
|
|
25
|
+
manageModelsLabel: 'Manage models and providers',
|
|
25
26
|
recentModelsLabel: 'Recent',
|
|
26
27
|
allModelsLabel: 'All models'
|
|
27
28
|
};
|
|
@@ -262,6 +263,8 @@ describe('buildModelToolbarSelect', () => {
|
|
|
262
263
|
value: 'minimax/MiniMax-M2.7',
|
|
263
264
|
label: 'MiniMax/MiniMax-M2.7'
|
|
264
265
|
});
|
|
266
|
+
expect(select.manageLabel).toBe('Manage models and providers');
|
|
267
|
+
expect(select.manageHref).toBe('/providers');
|
|
265
268
|
});
|
|
266
269
|
|
|
267
270
|
it('keeps the full provider/model label in shared state while exposing a compact mobile label', () => {
|
|
@@ -278,7 +278,6 @@ export function buildSkillPickerModel(params: {
|
|
|
278
278
|
const recentKeySet = new Set(groupedRecentSkillValues ?? []);
|
|
279
279
|
return {
|
|
280
280
|
title: texts.title,
|
|
281
|
-
allGroupsLabel: texts.allSkillsLabel,
|
|
282
281
|
searchPlaceholder: texts.searchPlaceholder,
|
|
283
282
|
emptyLabel: texts.emptyLabel,
|
|
284
283
|
loadingLabel: texts.loadingLabel,
|
|
@@ -86,6 +86,7 @@ export function buildModelToolbarSelect({
|
|
|
86
86
|
| "favoriteModelsLabel"
|
|
87
87
|
| "favoriteModelLabel"
|
|
88
88
|
| "unfavoriteModelLabel"
|
|
89
|
+
| "manageModelsLabel"
|
|
89
90
|
| "recentModelsLabel"
|
|
90
91
|
| "allModelsLabel"
|
|
91
92
|
>;
|
|
@@ -172,6 +173,8 @@ export function buildModelToolbarSelect({
|
|
|
172
173
|
onToggle: onFavoriteToggle,
|
|
173
174
|
}
|
|
174
175
|
: undefined,
|
|
176
|
+
manageLabel: texts.manageModelsLabel,
|
|
177
|
+
manageHref: "/providers",
|
|
175
178
|
onValueChange,
|
|
176
179
|
};
|
|
177
180
|
}
|
package/src/features/chat/features/message/utils/__tests__/chat-message-timeline.utils.test.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
NCP_INTERNAL_VISIBILITY_METADATA_KEY,
|
|
4
|
+
type NcpMessage,
|
|
5
|
+
} from "@nextclaw/ncp";
|
|
6
|
+
import { isVisibleChatMessage } from "@/features/chat/features/message/utils/chat-message-timeline.utils";
|
|
7
|
+
|
|
8
|
+
const visibleMessage = {
|
|
9
|
+
id: "assistant-visible",
|
|
10
|
+
sessionId: "session-1",
|
|
11
|
+
role: "assistant",
|
|
12
|
+
status: "final",
|
|
13
|
+
timestamp: "2026-08-07T00:00:00.000Z",
|
|
14
|
+
parts: [{ type: "text", text: "visible" }],
|
|
15
|
+
} satisfies NcpMessage;
|
|
16
|
+
|
|
17
|
+
describe("chat message timeline visibility", () => {
|
|
18
|
+
it("hides internal and legacy silent messages", () => {
|
|
19
|
+
expect(isVisibleChatMessage({
|
|
20
|
+
...visibleMessage,
|
|
21
|
+
metadata: { [NCP_INTERNAL_VISIBILITY_METADATA_KEY]: "hidden" },
|
|
22
|
+
})).toBe(false);
|
|
23
|
+
expect(isVisibleChatMessage({
|
|
24
|
+
...visibleMessage,
|
|
25
|
+
parts: [{ type: "text", text: "\\n\\n<noreply/>" }],
|
|
26
|
+
})).toBe(false);
|
|
27
|
+
expect(isVisibleChatMessage(visibleMessage)).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { isHiddenNcpMessage, type NcpMessage } from "@nextclaw/ncp";
|
|
2
|
+
import { isSilentReplyNcpMessage } from "@nextclaw/shared";
|
|
2
3
|
import type { ChatMessageViewModel } from "@nextclaw/agent-chat-ui";
|
|
3
4
|
import {
|
|
4
5
|
readContextCompactionTimeline,
|
|
@@ -48,6 +49,8 @@ function readInheritedSourceSessionId(message: NcpMessage): string | null {
|
|
|
48
49
|
|
|
49
50
|
export function isVisibleChatMessage(message: NcpMessage): boolean {
|
|
50
51
|
return (
|
|
52
|
+
!isHiddenNcpMessage(message) &&
|
|
53
|
+
!isSilentReplyNcpMessage(message) &&
|
|
51
54
|
!readContextCompactionTimeline(message) &&
|
|
52
55
|
!readInheritedSourceSessionId(message)
|
|
53
56
|
);
|
|
@@ -63,12 +63,12 @@ function ChatSidebarSessionEditingView({
|
|
|
63
63
|
}
|
|
64
64
|
}}
|
|
65
65
|
placeholder={t('sessionsLabelPlaceholder')}
|
|
66
|
-
className="h-8 rounded-lg border-
|
|
66
|
+
className="h-8 rounded-lg border-border bg-background text-xs"
|
|
67
67
|
autoFocus
|
|
68
68
|
disabled={isSaving}
|
|
69
69
|
/>
|
|
70
70
|
<div className="flex items-center justify-between gap-2">
|
|
71
|
-
<div className="min-w-0 text-[11px] text-
|
|
71
|
+
<div className="min-w-0 truncate text-[11px] text-muted-foreground/65">{sessionKey}</div>
|
|
72
72
|
<div className="flex items-center gap-1">
|
|
73
73
|
<IconActionButton
|
|
74
74
|
icon={<Check className="h-3.5 w-3.5" />}
|
|
@@ -134,8 +134,8 @@ function ChatSidebarSessionDisplayView({
|
|
|
134
134
|
className={cn(
|
|
135
135
|
'shrink-0 rounded-full border px-1.5 py-0.5 text-[10px] font-semibold leading-none',
|
|
136
136
|
active
|
|
137
|
-
? 'border-
|
|
138
|
-
: 'border-
|
|
137
|
+
? 'border-border/70 bg-background/80 text-foreground/75'
|
|
138
|
+
: 'border-transparent bg-muted/70 text-muted-foreground'
|
|
139
139
|
)}
|
|
140
140
|
>
|
|
141
141
|
{context.label}
|
|
@@ -143,12 +143,12 @@ function ChatSidebarSessionDisplayView({
|
|
|
143
143
|
) : null}
|
|
144
144
|
{context.icon ? (
|
|
145
145
|
<span className="inline-flex h-4 w-4 shrink-0 items-center justify-center">
|
|
146
|
-
<SessionContextIconNode icon={context.icon} className={cn('h-[13px] w-[13px]', active ? 'text-
|
|
146
|
+
<SessionContextIconNode icon={context.icon} className={cn('h-[13px] w-[13px]', active ? 'text-foreground/75' : 'text-muted-foreground')} />
|
|
147
147
|
</span>
|
|
148
148
|
) : null}
|
|
149
149
|
</span>
|
|
150
150
|
</div>
|
|
151
|
-
<div className="mt-
|
|
151
|
+
<div className="mt-0.5 flex items-center gap-2 text-[11px] text-muted-foreground/65">
|
|
152
152
|
<span className="min-w-0 truncate">
|
|
153
153
|
{previewText}
|
|
154
154
|
</span>
|
|
@@ -169,7 +169,7 @@ function ChatSidebarSessionDisplayView({
|
|
|
169
169
|
event.stopPropagation();
|
|
170
170
|
onOpenChildSessions();
|
|
171
171
|
}}
|
|
172
|
-
className="pointer-events-none absolute right-12 top-0 inline-flex h-6 items-center gap-1 rounded-md px-1.5 text-[10px] font-medium text-muted-foreground opacity-0 transition-colors hover:bg-
|
|
172
|
+
className="pointer-events-none absolute right-12 top-0 inline-flex h-6 items-center gap-1 rounded-md px-1.5 text-[10px] font-medium text-muted-foreground opacity-0 transition-colors hover:bg-background/80 hover:text-foreground group-hover/session:pointer-events-auto group-hover/session:opacity-100 focus-visible:pointer-events-auto focus-visible:opacity-100"
|
|
173
173
|
aria-label={t('chatSessionOpenChildSessions')}
|
|
174
174
|
title={t('chatSessionOpenChildSessions')}
|
|
175
175
|
>
|
|
@@ -245,10 +245,10 @@ export function ChatSidebarSessionItem({
|
|
|
245
245
|
return (
|
|
246
246
|
<div
|
|
247
247
|
className={cn(
|
|
248
|
-
'w-full rounded-
|
|
248
|
+
'w-full rounded-lg px-2.5 py-2 text-left text-[13px] transition-colors',
|
|
249
249
|
active
|
|
250
|
-
? 'bg-
|
|
251
|
-
: 'text-
|
|
250
|
+
? 'bg-background/90 font-medium text-foreground'
|
|
251
|
+
: 'text-foreground/80 hover:bg-background/65 hover:text-foreground'
|
|
252
252
|
)}
|
|
253
253
|
>
|
|
254
254
|
{isEditing ? (
|
|
@@ -61,10 +61,10 @@ export function ChatSidebarSessionList({
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
return (
|
|
64
|
-
<div className="space-y-
|
|
64
|
+
<div className="space-y-2">
|
|
65
65
|
{groups.map((group) => (
|
|
66
66
|
<div key={group.label}>
|
|
67
|
-
<div className="px-2
|
|
67
|
+
<div className="px-2 pb-1 pt-0.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground/65">
|
|
68
68
|
{group.label}
|
|
69
69
|
</div>
|
|
70
70
|
<div className="space-y-0.5">{group.items.map(renderSessionItem)}</div>
|
|
@@ -23,7 +23,7 @@ export function ChatSessionHeaderMenuItem({
|
|
|
23
23
|
'flex w-full items-center gap-2 rounded-xl px-3 py-2 text-left text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-50',
|
|
24
24
|
destructive
|
|
25
25
|
? 'text-destructive hover:bg-destructive/10'
|
|
26
|
-
: 'text-muted-foreground hover:bg-
|
|
26
|
+
: 'text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground'
|
|
27
27
|
)}
|
|
28
28
|
onClick={onClick}
|
|
29
29
|
disabled={disabled}
|
package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx
CHANGED
|
@@ -57,7 +57,7 @@ export function ChatSessionProjectBadge({
|
|
|
57
57
|
<button
|
|
58
58
|
type="button"
|
|
59
59
|
title={projectRoot ?? undefined}
|
|
60
|
-
className="min-w-0 max-w-[320px] shrink rounded-full border border-border bg-muted px-2 py-0.5 text-[11px] font-medium text-muted-foreground transition-colors hover:border-primary/35 hover:bg-
|
|
60
|
+
className="min-w-0 max-w-[320px] shrink rounded-full border border-border bg-muted px-2 py-0.5 text-[11px] font-medium text-muted-foreground transition-colors hover:border-primary/35 hover:bg-[var(--interaction-hover)] hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-60"
|
|
61
61
|
aria-label={t('chatSessionSetProject')}
|
|
62
62
|
disabled={isProjectPending}
|
|
63
63
|
>
|
|
@@ -69,7 +69,7 @@ function ChatSessionSwitchItem({
|
|
|
69
69
|
"flex w-full min-w-0 items-start gap-2 rounded-lg px-2.5 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
|
|
70
70
|
active
|
|
71
71
|
? "bg-accent text-accent-foreground"
|
|
72
|
-
: "text-foreground hover:bg-
|
|
72
|
+
: "text-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground",
|
|
73
73
|
)}
|
|
74
74
|
onClick={() => onSelect(session.key)}
|
|
75
75
|
>
|
package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx
CHANGED
|
@@ -29,7 +29,7 @@ import { cn } from "@/shared/lib/utils";
|
|
|
29
29
|
import { useProjects } from "@/shared/hooks/use-projects";
|
|
30
30
|
|
|
31
31
|
const SWITCHER_TRIGGER_CLASS =
|
|
32
|
-
"group inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-md px-1.5 py-1 text-sm font-medium text-foreground transition-colors hover:bg-
|
|
32
|
+
"group inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-md px-1.5 py-1 text-sm font-medium text-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border";
|
|
33
33
|
|
|
34
34
|
function ChatSessionTitle({ title }: { title: string }) {
|
|
35
35
|
return (
|
package/src/features/chat/features/session/utils/__tests__/chat-session-display.utils.test.ts
CHANGED
|
@@ -73,6 +73,32 @@ describe('chat-session-display', () => {
|
|
|
73
73
|
).toBe('正在调用工具:shell');
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
+
it('localizes structured tool activity previews', () => {
|
|
77
|
+
const session = createSession({
|
|
78
|
+
activityPreview: {
|
|
79
|
+
state: 'running',
|
|
80
|
+
statusKind: 'tool-completed',
|
|
81
|
+
statusText: 'gateway',
|
|
82
|
+
timestamp: '2026-05-16T01:00:00.000Z'
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(sessionActivityPreviewText(session, 'zh')).toBe('工具调用 · 已完成 · gateway');
|
|
87
|
+
expect(sessionActivityPreviewText(session, 'en')).toBe('Tool Call · Completed · gateway');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('localizes persisted legacy tool activity previews', () => {
|
|
91
|
+
const session = createSession({
|
|
92
|
+
activityPreview: {
|
|
93
|
+
state: 'running',
|
|
94
|
+
statusText: 'Tool call completed: gateway',
|
|
95
|
+
timestamp: '2026-05-16T01:00:00.000Z'
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(sessionActivityPreviewText(session, 'zh')).toBe('工具调用 · 已完成 · gateway');
|
|
100
|
+
});
|
|
101
|
+
|
|
76
102
|
it('shows the final assistant reply after completion', () => {
|
|
77
103
|
expect(
|
|
78
104
|
sessionActivityPreviewText(
|
package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts
CHANGED
|
@@ -106,7 +106,8 @@ describe('adaptNcpSessionSummary', () => {
|
|
|
106
106
|
last_activity_preview: {
|
|
107
107
|
state: 'completed',
|
|
108
108
|
replyText: 'Plan is ready',
|
|
109
|
-
|
|
109
|
+
statusKind: 'tool-completed',
|
|
110
|
+
statusText: 'read_file',
|
|
110
111
|
timestamp: '2026-05-16T01:00:00.000Z',
|
|
111
112
|
},
|
|
112
113
|
},
|
|
@@ -116,7 +117,8 @@ describe('adaptNcpSessionSummary', () => {
|
|
|
116
117
|
expect(adapted.activityPreview).toEqual({
|
|
117
118
|
state: 'completed',
|
|
118
119
|
replyText: 'Plan is ready',
|
|
119
|
-
|
|
120
|
+
statusKind: 'tool-completed',
|
|
121
|
+
statusText: 'read_file',
|
|
120
122
|
timestamp: '2026-05-16T01:00:00.000Z',
|
|
121
123
|
});
|
|
122
124
|
});
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import type { SessionEntryView } from '@/shared/lib/api';
|
|
1
|
+
import type { SessionActivityPreviewView, SessionEntryView } from '@/shared/lib/api';
|
|
2
2
|
import { getLanguage, getLocale, t, type I18nLanguage } from '@/shared/lib/i18n';
|
|
3
3
|
|
|
4
|
+
const LEGACY_ACTIVITY_STATUS_LABELS: Array<[
|
|
5
|
+
string,
|
|
6
|
+
NonNullable<SessionActivityPreviewView['statusKind']>
|
|
7
|
+
]> = [
|
|
8
|
+
['Thinking', 'thinking'],
|
|
9
|
+
['Calling tool', 'tool-running'],
|
|
10
|
+
['Tool call completed', 'tool-completed'],
|
|
11
|
+
['Run failed', 'run-failed'],
|
|
12
|
+
['Run interrupted', 'run-interrupted']
|
|
13
|
+
];
|
|
14
|
+
|
|
4
15
|
export function sessionDisplayName(session: SessionEntryView): string {
|
|
5
16
|
const label = session.label?.trim();
|
|
6
17
|
if (label) {
|
|
@@ -10,21 +21,46 @@ export function sessionDisplayName(session: SessionEntryView): string {
|
|
|
10
21
|
return chunks[chunks.length - 1] || session.key;
|
|
11
22
|
}
|
|
12
23
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
function formatSessionActivityStatus(
|
|
25
|
+
preview: SessionActivityPreviewView,
|
|
26
|
+
lang: I18nLanguage
|
|
27
|
+
): string | null {
|
|
28
|
+
const detail = preview.statusText?.trim();
|
|
29
|
+
const legacyStatus = preview.statusKind || !detail
|
|
30
|
+
? undefined
|
|
31
|
+
: LEGACY_ACTIVITY_STATUS_LABELS.find(([label]) => detail === label || detail.startsWith(`${label}: `));
|
|
32
|
+
const statusKind = preview.statusKind ?? legacyStatus?.[1];
|
|
33
|
+
const localizedDetail = legacyStatus
|
|
34
|
+
? detail?.slice(legacyStatus[0].length).replace(/^:\s*/, '') || undefined
|
|
35
|
+
: detail;
|
|
36
|
+
switch (statusKind) {
|
|
37
|
+
case 'thinking':
|
|
38
|
+
return t('chatTyping', lang);
|
|
39
|
+
case 'tool-running':
|
|
40
|
+
return [t('chatToolCall', lang), t('chatToolStatusRunning', lang), localizedDetail].filter(Boolean).join(' · ');
|
|
41
|
+
case 'tool-completed':
|
|
42
|
+
return [t('chatToolCall', lang), t('chatToolStatusCompleted', lang), localizedDetail].filter(Boolean).join(' · ');
|
|
43
|
+
case 'run-failed':
|
|
44
|
+
return [t('chatToolStatusFailed', lang), localizedDetail].filter(Boolean).join(' · ');
|
|
45
|
+
case 'run-interrupted':
|
|
46
|
+
return t('chatSessionActivityInterrupted', lang);
|
|
47
|
+
default:
|
|
48
|
+
return localizedDetail ?? null;
|
|
17
49
|
}
|
|
18
|
-
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function sessionActivityPreviewText(
|
|
53
|
+
session: SessionEntryView,
|
|
54
|
+
lang: I18nLanguage = getLanguage()
|
|
55
|
+
): string | null {
|
|
56
|
+
const preview = session.activityPreview;
|
|
57
|
+
if (!preview || preview.state === 'cancelled') {
|
|
19
58
|
return null;
|
|
20
59
|
}
|
|
21
|
-
if (preview.state === '
|
|
22
|
-
return preview.
|
|
23
|
-
}
|
|
24
|
-
if (preview.state === 'completed') {
|
|
25
|
-
return preview.replyText ?? preview.statusText ?? null;
|
|
60
|
+
if (preview.state === 'completed' && preview.replyText) {
|
|
61
|
+
return preview.replyText;
|
|
26
62
|
}
|
|
27
|
-
return preview
|
|
63
|
+
return formatSessionActivityStatus(preview, lang) ?? preview.replyText ?? null;
|
|
28
64
|
}
|
|
29
65
|
|
|
30
66
|
function startOfLocalDate(date: Date): number {
|
|
@@ -21,6 +21,13 @@ const SESSION_ACTIVITY_PREVIEW_STATE_SET = new Set<SessionActivityPreviewView['s
|
|
|
21
21
|
'cancelled',
|
|
22
22
|
'idle'
|
|
23
23
|
]);
|
|
24
|
+
const SESSION_ACTIVITY_PREVIEW_STATUS_KIND_SET = new Set<NonNullable<SessionActivityPreviewView['statusKind']>>([
|
|
25
|
+
'thinking',
|
|
26
|
+
'tool-running',
|
|
27
|
+
'tool-completed',
|
|
28
|
+
'run-failed',
|
|
29
|
+
'run-interrupted'
|
|
30
|
+
]);
|
|
24
31
|
|
|
25
32
|
function stringifyUnknown(value: unknown): string {
|
|
26
33
|
if (typeof value === 'string') {
|
|
@@ -66,13 +73,15 @@ function readNcpSessionActivityPreview(summary: NcpSessionSummaryView): SessionA
|
|
|
66
73
|
if (!SESSION_ACTIVITY_PREVIEW_STATE_SET.has(state as SessionActivityPreviewView['state']) || !timestamp) {
|
|
67
74
|
return null;
|
|
68
75
|
}
|
|
69
|
-
const
|
|
70
|
-
const replyText = readOptionalString(previewRecord.replyText);
|
|
76
|
+
const rawStatusKind = readOptionalString(previewRecord.statusKind);
|
|
71
77
|
return {
|
|
72
78
|
state: state as SessionActivityPreviewView['state'],
|
|
73
79
|
timestamp,
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
statusKind: SESSION_ACTIVITY_PREVIEW_STATUS_KIND_SET.has(rawStatusKind as NonNullable<SessionActivityPreviewView['statusKind']>)
|
|
81
|
+
? rawStatusKind as NonNullable<SessionActivityPreviewView['statusKind']>
|
|
82
|
+
: undefined,
|
|
83
|
+
statusText: readOptionalString(previewRecord.statusText) ?? undefined,
|
|
84
|
+
replyText: readOptionalString(previewRecord.replyText) ?? undefined
|
|
76
85
|
};
|
|
77
86
|
}
|
|
78
87
|
|
package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx
CHANGED
|
@@ -79,6 +79,12 @@ vi.mock("@/shared/hooks/use-agents", () => ({
|
|
|
79
79
|
}),
|
|
80
80
|
}));
|
|
81
81
|
|
|
82
|
+
vi.mock("@/shared/hooks/use-projects", () => ({
|
|
83
|
+
useProjects: () => ({
|
|
84
|
+
data: { projects: [] },
|
|
85
|
+
}),
|
|
86
|
+
}));
|
|
87
|
+
|
|
82
88
|
function createFetchedQuery<TData>(data: TData) {
|
|
83
89
|
return {
|
|
84
90
|
data,
|
|
@@ -50,21 +50,24 @@ describe('ChatWelcome', () => {
|
|
|
50
50
|
expect(screen.queryByText('Current Agent:')).toBeNull();
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
it('
|
|
53
|
+
it('renders a single-purpose welcome surface with compact prompt suggestions', () => {
|
|
54
54
|
renderWelcome({ agents: [{ id: 'main', displayName: 'Main' }] });
|
|
55
55
|
|
|
56
|
-
expect(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
expect(screen.getByRole('heading', { name: 'What would you like to get done?' })).toBeTruthy();
|
|
57
|
+
expect(screen.queryByText(/Describe the goal/)).toBeNull();
|
|
58
|
+
expect(screen.getByRole('group', { name: 'Task context' })).toBeTruthy();
|
|
59
|
+
const suggestion = screen.getByRole('button', { name: 'Review this project' });
|
|
60
|
+
expect(suggestion.className).toContain('rounded-full');
|
|
61
|
+
expect(suggestion.className).not.toContain('border');
|
|
62
|
+
expect(suggestion.parentElement?.className).toContain('flex-wrap');
|
|
60
63
|
});
|
|
61
64
|
|
|
62
|
-
it('fills the composer with a prompt suggestion when clicking a
|
|
65
|
+
it('fills the composer with a prompt suggestion when clicking a suggestion', () => {
|
|
63
66
|
const onSelectPrompt = vi.fn();
|
|
64
67
|
|
|
65
68
|
renderWelcome({ onSelectPrompt });
|
|
66
69
|
|
|
67
|
-
fireEvent.click(screen.getByRole('button', { name:
|
|
70
|
+
fireEvent.click(screen.getByRole('button', { name: 'Review this project' }));
|
|
68
71
|
|
|
69
72
|
expect(onSelectPrompt).toHaveBeenCalledWith(
|
|
70
73
|
expect.stringContaining('next three concrete things'),
|
|
@@ -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-muted-foreground shadow-none hover:bg-
|
|
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-[var(--interaction-hover)] 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">
|