@nextclaw/ui 0.15.20 → 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 +42 -0
- package/dist/assets/{api-tWUa6l9J.js → api-B1yHQYs-.js} +6 -6
- 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-BQ29ZmDE.js → book-open-oSmyVXea.js} +1 -1
- package/dist/assets/{card-CtKueklV.js → card-CGvJZTZp.js} +1 -1
- package/dist/assets/{channels-list-page-C8y3rqYt.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-rYHnBo8t.js → dist-CxUhurq9.js} +1 -1
- package/dist/assets/doc-browser-0eL8AX42.js +1 -0
- package/dist/assets/{doc-browser-D8ng4EUc.js → doc-browser-DEqE9lsh.js} +1 -1
- package/dist/assets/{doc-browser-context-CPHNzQ8m.js → doc-browser-context-DkzFIUoK.js} +1 -1
- package/dist/assets/{eye-CiJz3ODY.js → eye-gOWQGt8N.js} +1 -1
- package/dist/assets/{form-actions-BnZVzLZf.js → form-actions-CgjYZUzM.js} +1 -1
- package/dist/assets/index-DSAUWCfl.js +2 -0
- package/dist/assets/index-G7cBryJ4.css +1 -0
- package/dist/assets/loader-circle-BAuycGf6.js +1 -0
- package/dist/assets/{logo-badge-BXI-Kdhd.js → logo-badge-BNcsDsUM.js} +1 -1
- package/dist/assets/{mcp-marketplace-page-CxNe-Q9Q.js → mcp-marketplace-page-BX39MtPa.js} +3 -3
- package/dist/assets/mcp-marketplace-page-CkDtOEA4.js +1 -0
- package/dist/assets/{model-config-page-CR_mgEVp.js → model-config-page-7MIXg2fQ.js} +1 -1
- package/dist/assets/{navigation-link-C1cvB7y4.js → navigation-link-B3iFOdxx.js} +1 -1
- package/dist/assets/plus-yvhFgfal.js +1 -0
- package/dist/assets/{popover-ZoDwVwwd.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-DKewcdAl.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-DwjO-Vs6.js +1 -0
- package/dist/assets/{save-BrckuAXY.js → save-BINMWate.js} +1 -1
- package/dist/assets/{search-BhXQB2TO.js → search-7M38azRp.js} +1 -1
- package/dist/assets/{search-config-page-DFOQ6SDJ.js → search-config-page-T6tMhXbP.js} +1 -1
- package/dist/assets/{secrets-config-page-BqqeClqE.js → secrets-config-page-2mVejXf2.js} +2 -2
- package/dist/assets/security-config-DD5RrjDe.js +1 -0
- package/dist/assets/{select-CVzGGT38.js → select-BvidP5Oa.js} +1 -1
- package/dist/assets/{settings-2-BP6L2enG.js → settings-2-DqZstREV.js} +1 -1
- package/dist/assets/skeleton-BEQW2Dva.js +1 -0
- package/dist/assets/{tabs-B-QWjKCL.js → tabs-BeLxVmkt.js} +1 -1
- package/dist/assets/{tag-chip-C88mxjwx.js → tag-chip-BoDaLnWt.js} +1 -1
- package/dist/assets/{trash-2-ck3t33I3.js → trash-2-CYUskMMN.js} +1 -1
- package/dist/assets/{use-config-BAr5bH2c.js → use-config-CxKRXXCl.js} +1 -1
- package/dist/assets/use-confirm-dialog-HASTvOP2.js +1 -0
- package/dist/assets/{use-viewport-layout-k1isXfNm.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 +8 -8
- package/src/app/components/__tests__/app-notification-runtime.test.tsx +32 -0
- package/src/app/components/app-notification-runtime.tsx +15 -0
- package/src/app/components/layout/sidebar-items.tsx +23 -9
- package/src/app/components/layout/sidebar-rail.styles.ts +0 -2
- package/src/app/components/layout/sidebar.tsx +2 -2
- package/src/app/configs/app-navigation.config.ts +21 -0
- package/src/app/index.tsx +22 -2
- package/src/app/presenters/app.presenter.ts +8 -0
- 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 +9 -13
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +40 -38
- 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/index.ts +1 -0
- package/src/features/chat/managers/__tests__/chat-completion-notification.manager.test.ts +177 -0
- 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-completion-notification.manager.ts +146 -0
- 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 +32 -11
- package/src/features/chat/pages/ncp-chat-page.tsx +9 -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/inbox/components/inbox-delivery-content.test.tsx +57 -0
- package/src/features/inbox/components/inbox-delivery-content.tsx +118 -0
- package/src/features/inbox/components/inbox-reader-dialog.test.tsx +77 -0
- package/src/features/inbox/components/inbox-reader-dialog.tsx +224 -0
- package/src/features/inbox/components/inbox-runtime.tsx +14 -0
- package/src/features/inbox/hooks/use-inbox-deliveries.ts +14 -0
- package/src/features/inbox/index.ts +7 -0
- package/src/features/inbox/managers/inbox.manager.test.ts +86 -0
- package/src/features/inbox/managers/inbox.manager.ts +178 -0
- package/src/features/inbox/pages/inbox-page.test.ts +28 -0
- package/src/features/inbox/pages/inbox-page.tsx +385 -0
- package/src/features/inbox/stores/inbox.store.ts +23 -0
- package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
- package/src/features/notifications/components/__tests__/app-notification-toast.test.tsx +51 -0
- package/src/features/notifications/components/app-notification-toast.tsx +80 -0
- package/src/features/notifications/index.ts +8 -0
- package/src/features/notifications/managers/__tests__/app-notification.manager.test.ts +75 -0
- package/src/features/notifications/managers/app-notification.manager.ts +41 -0
- 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/platforms/mobile/components/mobile-bottom-nav.tsx +14 -7
- 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/index.ts +4 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +16 -14
- package/src/shared/lib/i18n/locales/en-US/inbox.json +30 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +16 -14
- package/src/shared/lib/i18n/locales/zh-CN/inbox.json +30 -0
- 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-s-FrOinZ.js +0 -106
- package/dist/assets/appearance-settings-page-dMRBD7ni.js +0 -1
- package/dist/assets/chat-page-MXBltS3G.js +0 -1
- package/dist/assets/config-split-page-uC6d_r9c.js +0 -1
- package/dist/assets/confirm-dialog-C1pbqHZX.js +0 -5
- package/dist/assets/desktop-update-config-DssP8LCk.js +0 -1
- package/dist/assets/doc-browser-AXF1zRsM.js +0 -1
- package/dist/assets/index-CocHWEsI.js +0 -2
- package/dist/assets/index-DN6KM7WD.css +0 -1
- package/dist/assets/loader-circle-AOpK5kkW.js +0 -1
- package/dist/assets/mcp-marketplace-page-5ogeBHnl.js +0 -1
- package/dist/assets/plus-DI37QuHc.js +0 -1
- package/dist/assets/provider-scoped-model-input-DIL6Ex_d.js +0 -1
- package/dist/assets/providers-config-page-D2NXVctW.js +0 -1
- package/dist/assets/react-39CDNZlM.js +0 -8
- package/dist/assets/remote-DYOOO1YG.js +0 -1
- package/dist/assets/rotate-cw-PmqA8LQT.js +0 -1
- package/dist/assets/runtime-config-page-0lb5NyKc.js +0 -1
- package/dist/assets/security-config-13AtPIlm.js +0 -1
- package/dist/assets/skeleton-OwCNQJ7M.js +0 -1
- package/dist/assets/use-confirm-dialog-C4e0nG_4.js +0 -1
- package/dist/assets/x-C1ijNWcE.js +0 -1
- package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +0 -59
|
@@ -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">
|
|
@@ -11,7 +11,6 @@ import { t } from '@/shared/lib/i18n';
|
|
|
11
11
|
import { getSessionProjectName } from '@/shared/lib/session-project';
|
|
12
12
|
|
|
13
13
|
type ChatWelcomeProjectPickerProps = {
|
|
14
|
-
defaultProjectRoot?: string | null;
|
|
15
14
|
isSaving: boolean;
|
|
16
15
|
projectOptions: readonly ChatWelcomeProjectOption[];
|
|
17
16
|
projectRoot: string | null;
|
|
@@ -23,7 +22,6 @@ type ChatWelcomeProjectPickerProps = {
|
|
|
23
22
|
const PROJECT_PICKER_MAX_HEIGHT = createPopoverAvailableHeightLimit('20rem');
|
|
24
23
|
|
|
25
24
|
export function ChatWelcomeProjectPicker({
|
|
26
|
-
defaultProjectRoot,
|
|
27
25
|
isSaving,
|
|
28
26
|
projectOptions,
|
|
29
27
|
projectRoot,
|
|
@@ -34,10 +32,6 @@ export function ChatWelcomeProjectPicker({
|
|
|
34
32
|
const [isOpen, setIsOpen] = useState(false);
|
|
35
33
|
const projectLabel =
|
|
36
34
|
getSessionProjectName(projectRoot) ?? t('chatWelcomeProjectPickerPlaceholder');
|
|
37
|
-
const isDefaultProject =
|
|
38
|
-
defaultProjectRoot !== null &&
|
|
39
|
-
defaultProjectRoot !== undefined &&
|
|
40
|
-
projectRoot === defaultProjectRoot;
|
|
41
35
|
|
|
42
36
|
const selectProjectRoot = async (nextProjectRoot: string) => {
|
|
43
37
|
setIsOpen(false);
|
|
@@ -49,18 +43,13 @@ export function ChatWelcomeProjectPicker({
|
|
|
49
43
|
<PopoverTrigger asChild>
|
|
50
44
|
<button
|
|
51
45
|
type="button"
|
|
52
|
-
className="inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left font-medium text-muted-foreground transition-colors hover:bg-
|
|
46
|
+
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-[var(--interaction-hover)] hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
53
47
|
title={projectRoot ?? undefined}
|
|
54
48
|
aria-label={t('chatWelcomeProjectPickerLabel')}
|
|
55
49
|
disabled={!selectable || isSaving}
|
|
56
50
|
>
|
|
57
51
|
<FolderOpen className="h-4 w-4 shrink-0" />
|
|
58
52
|
<span className="truncate">{projectLabel}</span>
|
|
59
|
-
{isDefaultProject ? (
|
|
60
|
-
<span className="shrink-0 text-xs font-normal text-muted-foreground/70">
|
|
61
|
-
{t('chatWelcomeProjectDefaultBadge')}
|
|
62
|
-
</span>
|
|
63
|
-
) : null}
|
|
64
53
|
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground/70" />
|
|
65
54
|
</button>
|
|
66
55
|
</PopoverTrigger>
|
|
@@ -78,7 +67,7 @@ export function ChatWelcomeProjectPicker({
|
|
|
78
67
|
<button
|
|
79
68
|
key={option.projectRoot}
|
|
80
69
|
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-
|
|
70
|
+
className="flex w-full min-w-0 items-start gap-2 rounded-xl px-2 py-2 text-left transition-colors hover:bg-[var(--interaction-hover)]"
|
|
82
71
|
title={option.projectRoot}
|
|
83
72
|
onClick={() => {
|
|
84
73
|
void selectProjectRoot(option.projectRoot);
|
|
@@ -111,7 +100,7 @@ export function ChatWelcomeProjectPicker({
|
|
|
111
100
|
<div className="shrink-0 border-t border-border p-1.5">
|
|
112
101
|
<button
|
|
113
102
|
type="button"
|
|
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-
|
|
103
|
+
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-[var(--interaction-hover)]"
|
|
115
104
|
onClick={() => {
|
|
116
105
|
setIsOpen(false);
|
|
117
106
|
onOpenProjectDialog();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { t } from '@/shared/lib/i18n';
|
|
2
|
+
|
|
3
|
+
const WELCOME_PROMPT_SUGGESTIONS = [
|
|
4
|
+
{
|
|
5
|
+
labelKey: 'chatWelcomeSuggestion1Label' as const,
|
|
6
|
+
promptKey: 'chatWelcomeSuggestion1Prompt' as const,
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
labelKey: 'chatWelcomeSuggestion2Label' as const,
|
|
10
|
+
promptKey: 'chatWelcomeSuggestion2Prompt' as const,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
labelKey: 'chatWelcomeSuggestion3Label' as const,
|
|
14
|
+
promptKey: 'chatWelcomeSuggestion3Prompt' as const,
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export function ChatWelcomePromptSuggestions({
|
|
19
|
+
onSelectPrompt,
|
|
20
|
+
}: {
|
|
21
|
+
onSelectPrompt: (prompt: string) => void;
|
|
22
|
+
}) {
|
|
23
|
+
return (
|
|
24
|
+
<div className="mt-5 flex flex-wrap justify-center gap-2">
|
|
25
|
+
{WELCOME_PROMPT_SUGGESTIONS.map((suggestion) => (
|
|
26
|
+
<button
|
|
27
|
+
key={suggestion.labelKey}
|
|
28
|
+
type="button"
|
|
29
|
+
onClick={() => onSelectPrompt(t(suggestion.promptKey))}
|
|
30
|
+
className="rounded-full bg-muted/55 px-3.5 py-2 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
31
|
+
>
|
|
32
|
+
{t(suggestion.labelKey)}
|
|
33
|
+
</button>
|
|
34
|
+
))}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -39,7 +39,7 @@ export function ChatWelcomeSessionTypePicker({
|
|
|
39
39
|
<PopoverTrigger asChild>
|
|
40
40
|
<button
|
|
41
41
|
type="button"
|
|
42
|
-
className="inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left font-medium text-muted-foreground transition-colors hover:bg-
|
|
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-[var(--interaction-hover)] hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
43
43
|
aria-label={t('chatWelcomeSessionTypePickerLabel')}
|
|
44
44
|
>
|
|
45
45
|
{selectedOption.icon?.src ? (
|
|
@@ -2,7 +2,7 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { ChatSessionProjectDialog } from '@/features/chat/features/session/components/session-header/chat-session-project-dialog';
|
|
4
4
|
import { ChatWelcomeAgentPicker } from '@/features/chat/features/welcome/components/chat-welcome-agent-picker';
|
|
5
|
-
import {
|
|
5
|
+
import { ChatWelcomePromptSuggestions } from '@/features/chat/features/welcome/components/chat-welcome-prompt-suggestions';
|
|
6
6
|
import { ChatWelcomeProjectPicker } from '@/features/chat/features/welcome/components/chat-welcome-project-picker';
|
|
7
7
|
import { ChatWelcomeSessionTypePicker } from '@/features/chat/features/welcome/components/chat-welcome-session-type-picker';
|
|
8
8
|
import type { ChatWelcomeProjectOption } from '@/features/chat/features/welcome/utils/chat-welcome-project-options.utils';
|
|
@@ -61,45 +61,47 @@ export function ChatWelcome({
|
|
|
61
61
|
const selectableProjectRoot = Boolean(onSelectProjectRoot);
|
|
62
62
|
|
|
63
63
|
return (
|
|
64
|
-
<div className="flex min-h-full items-center justify-center px-4 py-
|
|
65
|
-
<div className="min-w-0
|
|
64
|
+
<div className="flex min-h-full items-center justify-center px-4 py-10 sm:p-8">
|
|
65
|
+
<div className="w-full min-w-0 max-w-[min(680px,100%)]">
|
|
66
66
|
<div className="text-center">
|
|
67
|
-
<h2 className="text-2xl font-semibold text-foreground sm:text-
|
|
67
|
+
<h2 className="text-2xl font-semibold leading-tight text-foreground sm:text-[2rem]">
|
|
68
68
|
{t('chatWelcomeTitle')}
|
|
69
69
|
</h2>
|
|
70
|
-
<p className="mt-3 text-sm font-medium text-muted-foreground sm:text-base">
|
|
71
|
-
{t('chatWelcomeSubtitle')}
|
|
72
|
-
</p>
|
|
73
70
|
</div>
|
|
74
71
|
|
|
75
|
-
{inputSlot ? <div className="mt-
|
|
72
|
+
{inputSlot ? <div className="mt-6">{inputSlot}</div> : null}
|
|
76
73
|
|
|
77
|
-
<div className="mt-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
74
|
+
<div className="mt-2 flex justify-center px-1">
|
|
75
|
+
<div
|
|
76
|
+
role="group"
|
|
77
|
+
aria-label={t('chatWelcomeContextLabel')}
|
|
78
|
+
className="flex min-w-0 max-w-full flex-wrap items-center justify-center gap-0.5 rounded-xl bg-muted/40 p-1 text-sm text-muted-foreground"
|
|
79
|
+
>
|
|
80
|
+
{onSelectProjectRoot ? (
|
|
81
|
+
<ChatWelcomeProjectPicker
|
|
82
|
+
isSaving={isProjectSaving}
|
|
83
|
+
projectOptions={projectOptions}
|
|
84
|
+
projectRoot={resolvedProjectRoot}
|
|
85
|
+
selectable={selectableProjectRoot}
|
|
86
|
+
onOpenProjectDialog={() => setIsProjectDialogOpen(true)}
|
|
87
|
+
onSelectProjectRoot={saveProjectRoot}
|
|
88
|
+
/>
|
|
89
|
+
) : null}
|
|
90
|
+
<ChatWelcomeAgentPicker
|
|
91
|
+
agents={agents}
|
|
92
|
+
selectedAgent={selectedAgent}
|
|
93
|
+
selectedAgentId={selectedAgentId}
|
|
94
|
+
onSelectAgent={onSelectAgent}
|
|
87
95
|
/>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
/>
|
|
95
|
-
<ChatWelcomeSessionTypePicker
|
|
96
|
-
options={sessionTypeOptions}
|
|
97
|
-
selectedSessionType={selectedSessionType}
|
|
98
|
-
onSelectSessionType={onSelectSessionType}
|
|
99
|
-
/>
|
|
96
|
+
<ChatWelcomeSessionTypePicker
|
|
97
|
+
options={sessionTypeOptions}
|
|
98
|
+
selectedSessionType={selectedSessionType}
|
|
99
|
+
onSelectSessionType={onSelectSessionType}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
100
102
|
</div>
|
|
101
103
|
|
|
102
|
-
<
|
|
104
|
+
<ChatWelcomePromptSuggestions onSelectPrompt={onSelectPrompt} />
|
|
103
105
|
</div>
|
|
104
106
|
|
|
105
107
|
{onSelectProjectRoot ? (
|
|
@@ -113,7 +113,7 @@ it("shows all session workspace entries in the overview", async () => {
|
|
|
113
113
|
name: /Child sessions/,
|
|
114
114
|
});
|
|
115
115
|
const cronJobsButton = screen.getByRole("button", {
|
|
116
|
-
name: /
|
|
116
|
+
name: /Scheduled tasks/,
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
expect((childSessionsButton as HTMLButtonElement).disabled).toBe(false);
|
|
@@ -96,7 +96,7 @@ describe("ChatSessionWorkspacePanel", () => {
|
|
|
96
96
|
await user.click(screen.getByRole("button", { name: "Refresh preview" }));
|
|
97
97
|
|
|
98
98
|
expect(mocks.invalidateQueries).toHaveBeenCalledWith({
|
|
99
|
-
queryKey: ["server-path-read", "README.md", null],
|
|
99
|
+
queryKey: ["server-path-read", "README.md", null, null],
|
|
100
100
|
});
|
|
101
101
|
expect(mocks.invalidateQueries).toHaveBeenCalledWith({
|
|
102
102
|
queryKey: ["server-path-browse", "README.md", "", true],
|
|
@@ -4,6 +4,7 @@ export { ChatPresenterProvider } from "./components/providers/chat-presenter.pro
|
|
|
4
4
|
export { usePresenter } from "./components/providers/chat-presenter.provider";
|
|
5
5
|
export { ChatPresenter } from "./presenters/chat.presenter";
|
|
6
6
|
export { ChatDraftIntentManager } from "./managers/chat-draft-intent.manager";
|
|
7
|
+
export { ChatCompletionNotificationManager } from "./managers/chat-completion-notification.manager";
|
|
7
8
|
export { useChatSessionListStore } from "./stores/chat-session-list.store";
|
|
8
9
|
export { useChatThreadStore } from "./stores/chat-thread.store";
|
|
9
10
|
export { useChatMessageLayoutStore } from "./stores/chat-message-layout.store";
|