@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
|
@@ -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],
|
|
@@ -70,6 +70,18 @@ function resetChatSessionListManagerState() {
|
|
|
70
70
|
describe("ChatSessionListManager draft and selection flow", () => {
|
|
71
71
|
beforeEach(resetChatSessionListManagerState);
|
|
72
72
|
|
|
73
|
+
it("clears the route-derived session selection outside session routes", () => {
|
|
74
|
+
const manager = new ChatSessionListManager(
|
|
75
|
+
{} as ConstructorParameters<typeof ChatSessionListManager>[0],
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
manager.syncRouteSessionSelection(null);
|
|
79
|
+
|
|
80
|
+
expect(
|
|
81
|
+
useChatSessionListStore.getState().snapshot.selectedSessionKey,
|
|
82
|
+
).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
|
|
73
85
|
it("applies the requested session type when creating a session", () => {
|
|
74
86
|
const uiManager = {
|
|
75
87
|
goToChatRoot: vi.fn(),
|
|
@@ -93,7 +105,7 @@ describe("ChatSessionListManager draft and selection flow", () => {
|
|
|
93
105
|
});
|
|
94
106
|
expect(
|
|
95
107
|
useChatSessionListStore.getState().snapshot.selectedSessionKey,
|
|
96
|
-
).
|
|
108
|
+
).toBe("session-1");
|
|
97
109
|
expect(useChatThreadStore.getState().snapshot.sessionKey).toBeNull();
|
|
98
110
|
expect(
|
|
99
111
|
useChatThreadStore.getState().snapshot.hasSubmittedDraftMessage,
|
|
@@ -126,7 +138,7 @@ describe("ChatSessionListManager draft and selection flow", () => {
|
|
|
126
138
|
);
|
|
127
139
|
expect(
|
|
128
140
|
useChatSessionListStore.getState().snapshot.selectedSessionKey,
|
|
129
|
-
).
|
|
141
|
+
).toBe("session-1");
|
|
130
142
|
expect(useChatThreadStore.getState().snapshot.sessionKey).toBeNull();
|
|
131
143
|
expect(
|
|
132
144
|
useChatThreadStore.getState().snapshot.hasSubmittedDraftMessage,
|
|
@@ -198,7 +210,7 @@ describe("ChatSessionListManager draft and selection flow", () => {
|
|
|
198
210
|
|
|
199
211
|
expect(
|
|
200
212
|
useChatSessionListStore.getState().snapshot.selectedSessionKey,
|
|
201
|
-
).
|
|
213
|
+
).toBe("session-1");
|
|
202
214
|
expect(uiManager.navigateTo).toHaveBeenCalledWith(
|
|
203
215
|
"/chat/draft",
|
|
204
216
|
expect.any(Object),
|
|
@@ -836,7 +836,7 @@ describe('ChatThreadManager visible workspace selection', () => {
|
|
|
836
836
|
});
|
|
837
837
|
|
|
838
838
|
describe('ChatThreadManager deletion', () => {
|
|
839
|
-
it('clears
|
|
839
|
+
it('clears thread state while leaving route-derived selection to navigation', async () => {
|
|
840
840
|
const removeQueries = vi.spyOn(appQueryClient, 'removeQueries').mockImplementation(async () => undefined);
|
|
841
841
|
const uiManager = {
|
|
842
842
|
goToSession: vi.fn(),
|
|
@@ -844,13 +844,7 @@ describe('ChatThreadManager deletion', () => {
|
|
|
844
844
|
goToProviders: vi.fn(),
|
|
845
845
|
confirm: vi.fn(async () => true),
|
|
846
846
|
} as unknown as ConstructorParameters<typeof ChatThreadManager>[0];
|
|
847
|
-
const sessionListManager = {
|
|
848
|
-
setSelectedSessionKey: vi.fn((value: string | null) => {
|
|
849
|
-
useChatSessionListStore.getState().setSnapshot({
|
|
850
|
-
selectedSessionKey: value,
|
|
851
|
-
});
|
|
852
|
-
}),
|
|
853
|
-
} as unknown as ConstructorParameters<typeof ChatThreadManager>[1];
|
|
847
|
+
const sessionListManager = {} as ConstructorParameters<typeof ChatThreadManager>[1];
|
|
854
848
|
const manager = new ChatThreadManager(
|
|
855
849
|
uiManager,
|
|
856
850
|
sessionListManager,
|
|
@@ -858,8 +852,7 @@ describe('ChatThreadManager deletion', () => {
|
|
|
858
852
|
|
|
859
853
|
await manager.deleteSession();
|
|
860
854
|
|
|
861
|
-
expect(
|
|
862
|
-
expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBeNull();
|
|
855
|
+
expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBe('parent-session-1');
|
|
863
856
|
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
864
857
|
sessionKey: null,
|
|
865
858
|
canDeleteSession: false,
|
|
@@ -100,9 +100,8 @@ export class ChatSessionListManager {
|
|
|
100
100
|
this.setSelectedAgentId(normalizedAgentId);
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
-
setSelectedSessionKey = (
|
|
103
|
+
private setSelectedSessionKey = (value: string | null) => {
|
|
104
104
|
const prev = useChatSessionListStore.getState().snapshot.selectedSessionKey;
|
|
105
|
-
const value = this.resolveUpdateValue(prev, next);
|
|
106
105
|
if (value === prev) {
|
|
107
106
|
return;
|
|
108
107
|
}
|
|
@@ -111,24 +110,8 @@ export class ChatSessionListManager {
|
|
|
111
110
|
.setSnapshot({ selectedSessionKey: value });
|
|
112
111
|
};
|
|
113
112
|
|
|
114
|
-
syncRouteSessionSelection = (
|
|
115
|
-
|
|
116
|
-
routeSessionKey: string | null;
|
|
117
|
-
}) => {
|
|
118
|
-
const { isChatView, routeSessionKey } = params;
|
|
119
|
-
if (!isChatView) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const { selectedSessionKey } = useChatSessionListStore.getState().snapshot;
|
|
123
|
-
if (routeSessionKey) {
|
|
124
|
-
if (selectedSessionKey !== routeSessionKey) {
|
|
125
|
-
this.setSelectedSessionKey(routeSessionKey);
|
|
126
|
-
}
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (selectedSessionKey !== null) {
|
|
130
|
-
this.setSelectedSessionKey(null);
|
|
131
|
-
}
|
|
113
|
+
syncRouteSessionSelection = (routeSessionKey: string | null) => {
|
|
114
|
+
this.setSelectedSessionKey(routeSessionKey);
|
|
132
115
|
};
|
|
133
116
|
|
|
134
117
|
setListMode = (next: SetStateAction<"time-first" | "project-first">) => {
|
|
@@ -207,9 +190,6 @@ export class ChatSessionListManager {
|
|
|
207
190
|
);
|
|
208
191
|
const normalizedProjectRoot = normalizeSessionProjectRootValue(projectRoot);
|
|
209
192
|
const normalizedPrompt = prompt?.trim() || null;
|
|
210
|
-
useChatSessionListStore.getState().setSnapshot({
|
|
211
|
-
selectedSessionKey: null,
|
|
212
|
-
});
|
|
213
193
|
this.syncDraftThreadState();
|
|
214
194
|
this.uiManager.navigateTo(CHAT_DRAFT_SESSION_PATH, {
|
|
215
195
|
replace: this.uiManager.isAtChatRoot(),
|
|
@@ -75,10 +75,7 @@ export class ChatThreadManager {
|
|
|
75
75
|
useChatThreadStore.getState().setSnapshot(patch);
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
-
private clearDeletedSessionState = (
|
|
79
|
-
if (useChatSessionListStore.getState().snapshot.selectedSessionKey === sessionKey) {
|
|
80
|
-
this.sessionListManager.setSelectedSessionKey(null);
|
|
81
|
-
}
|
|
78
|
+
private clearDeletedSessionState = () => {
|
|
82
79
|
useChatThreadStore.getState().setSnapshot({
|
|
83
80
|
sessionKey: null,
|
|
84
81
|
sessionTypeLabel: null,
|
|
@@ -569,7 +566,7 @@ export class ChatThreadManager {
|
|
|
569
566
|
await deleteNcpSessionApi(selectedSessionKey);
|
|
570
567
|
deleteNcpSessionSummaryInQueryClient(appQueryClient, selectedSessionKey);
|
|
571
568
|
appQueryClient.removeQueries({ queryKey: ['ncp-session-messages', selectedSessionKey] });
|
|
572
|
-
this.clearDeletedSessionState(
|
|
569
|
+
this.clearDeletedSessionState();
|
|
573
570
|
this.uiManager.goToChatRoot({ replace: true });
|
|
574
571
|
} finally {
|
|
575
572
|
useChatThreadStore.getState().setSnapshot({ isDeletePending: false });
|
|
@@ -39,17 +39,11 @@ vi.mock('@/shared/hooks/use-confirm-dialog', () => ({
|
|
|
39
39
|
vi.mock('@/features/chat/components/layout/chat-page-shell', () => ({
|
|
40
40
|
ChatPageLayout: () => <div data-testid="chat-page-layout" />,
|
|
41
41
|
useChatSessionSync: (params: {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
routeSessionKey: string | null;
|
|
45
|
-
}) => void;
|
|
42
|
+
routeSessionKey: string | null;
|
|
43
|
+
syncRouteSessionSelection: (routeSessionKey: string | null) => void;
|
|
46
44
|
}) => {
|
|
47
45
|
mocks.useChatSessionSync(params);
|
|
48
|
-
|
|
49
|
-
syncRouteSessionSelection({
|
|
50
|
-
isChatView: true,
|
|
51
|
-
routeSessionKey: null,
|
|
52
|
-
});
|
|
46
|
+
params.syncRouteSessionSelection(params.routeSessionKey);
|
|
53
47
|
},
|
|
54
48
|
}));
|
|
55
49
|
|
|
@@ -44,6 +44,7 @@ export type ChatInputBarAdapterTexts = {
|
|
|
44
44
|
modelSearchEmptyLabel: string;
|
|
45
45
|
favoriteModelLabel: string;
|
|
46
46
|
unfavoriteModelLabel: string;
|
|
47
|
+
manageModelsLabel: string;
|
|
47
48
|
thinkingLabels: Record<ChatThinkingLevel, string>;
|
|
48
49
|
noModelOptionsLabel: string;
|
|
49
50
|
configureProviderLabel: string;
|
|
@@ -94,7 +94,7 @@ export const CronTaskComposer = forwardRef<
|
|
|
94
94
|
ref={ref}
|
|
95
95
|
value={value}
|
|
96
96
|
onChange={(event) => onChange(event.target.value)}
|
|
97
|
-
className="pl-9 pr-11
|
|
97
|
+
className="pl-9 pr-11"
|
|
98
98
|
placeholder={t("cronComposerPlaceholder")}
|
|
99
99
|
aria-label={t("cronComposerLabel")}
|
|
100
100
|
/>
|
|
@@ -37,7 +37,7 @@ export function FilterPanel({
|
|
|
37
37
|
value={searchText}
|
|
38
38
|
onChange={(event) => onSearchTextChange(event.target.value)}
|
|
39
39
|
placeholder={searchPlaceholder}
|
|
40
|
-
className="h-9 w-full rounded-xl border border-border/75 bg-card pl-9 pr-9 text-sm transition-colors focus:outline-none focus:ring-0
|
|
40
|
+
className="h-9 w-full rounded-xl border border-border/75 bg-card pl-9 pr-9 text-sm transition-colors focus:outline-none focus:ring-0"
|
|
41
41
|
/>
|
|
42
42
|
{isRefreshing && (
|
|
43
43
|
<Loader2
|
|
@@ -24,6 +24,9 @@ describe("AppNotificationToast", () => {
|
|
|
24
24
|
name: "Open the background task reply",
|
|
25
25
|
});
|
|
26
26
|
expect(notification.getAttribute("href")).toBe("/chat/session-1");
|
|
27
|
+
expect(notification.className).not.toMatch(
|
|
28
|
+
/(?:hover|active):bg-\S*\/\d+/,
|
|
29
|
+
);
|
|
27
30
|
expect(screen.getByText("Background task")).toBeTruthy();
|
|
28
31
|
expect(screen.getByText("The requested summary is ready.")).toBeTruthy();
|
|
29
32
|
|
|
@@ -10,7 +10,7 @@ export type AppNotificationToastProps = {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
const NOTIFICATION_CARD_CLASS =
|
|
13
|
-
"group ml-auto flex min-h-[74px] w-[320px] max-w-[calc(100vw-2rem)] items-center gap-3 rounded-[20px] border border-border/80 bg-background px-[18px] py-3 text-left text-foreground shadow-[0_8px_18px_rgba(0,0,0,0.12),0_2px_5px_rgba(0,0,0,0.06)]
|
|
13
|
+
"group ml-auto flex min-h-[74px] w-[320px] max-w-[calc(100vw-2rem)] items-center gap-3 rounded-[20px] border border-border/80 bg-background px-[18px] py-3 text-left text-foreground shadow-[0_8px_18px_rgba(0,0,0,0.12),0_2px_5px_rgba(0,0,0,0.06)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background";
|
|
14
14
|
|
|
15
15
|
function AppNotificationContent({
|
|
16
16
|
title,
|
|
@@ -118,7 +118,7 @@ function PanelAppMenuItem({
|
|
|
118
118
|
type="button"
|
|
119
119
|
className={cn(
|
|
120
120
|
'flex w-full items-center gap-2 rounded-lg px-2.5 py-2 text-left text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-50',
|
|
121
|
-
destructive ? 'text-destructive hover:bg-destructive/10' : 'text-muted-foreground hover:bg-
|
|
121
|
+
destructive ? 'text-destructive hover:bg-destructive/10' : 'text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground',
|
|
122
122
|
)}
|
|
123
123
|
disabled={disabled}
|
|
124
124
|
onClick={onClick}
|
|
@@ -17,7 +17,7 @@ export function PanelAppToolbar({
|
|
|
17
17
|
<button
|
|
18
18
|
type="button"
|
|
19
19
|
onClick={onOpenApps}
|
|
20
|
-
className="inline-flex shrink-0 items-center rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-
|
|
20
|
+
className="inline-flex shrink-0 items-center rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-accent-foreground"
|
|
21
21
|
title={t('appsTitle')}
|
|
22
22
|
aria-label={t('appsTitle')}
|
|
23
23
|
>
|
|
@@ -32,7 +32,7 @@ export function PanelAppToolbar({
|
|
|
32
32
|
<button
|
|
33
33
|
type="button"
|
|
34
34
|
onClick={onRefresh}
|
|
35
|
-
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-
|
|
35
|
+
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-accent-foreground"
|
|
36
36
|
title={t('panelAppsRefreshCurrent')}
|
|
37
37
|
aria-label={t('panelAppsRefreshCurrent')}
|
|
38
38
|
>
|
|
@@ -104,7 +104,7 @@ export function PanelAppsList({
|
|
|
104
104
|
<TooltipProvider delayDuration={250}>
|
|
105
105
|
<Tooltip>
|
|
106
106
|
<TooltipTrigger asChild>
|
|
107
|
-
<button type="button" className="rounded p-0.5 text-muted-foreground/70 transition-colors hover:bg-
|
|
107
|
+
<button type="button" className="rounded p-0.5 text-muted-foreground/70 transition-colors hover:bg-[var(--interaction-hover)] hover:text-accent-foreground" aria-label={t('panelAppsTitle')}><HelpCircle className="h-3.5 w-3.5" /></button>
|
|
108
108
|
</TooltipTrigger>
|
|
109
109
|
<TooltipContent side="bottom" className="max-w-[320px] break-all font-mono text-xs">{panelApps.data.panelsPath}</TooltipContent>
|
|
110
110
|
</Tooltip>
|
|
@@ -114,7 +114,7 @@ export function PanelAppsList({
|
|
|
114
114
|
<button
|
|
115
115
|
type="button"
|
|
116
116
|
onClick={() => void panelApps.refetch()}
|
|
117
|
-
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-
|
|
117
|
+
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-accent-foreground"
|
|
118
118
|
title={t('panelAppsRefresh')}
|
|
119
119
|
aria-label={t('panelAppsRefresh')}
|
|
120
120
|
>
|
|
@@ -204,7 +204,7 @@ function PanelAppsEmptyGuide({
|
|
|
204
204
|
<button
|
|
205
205
|
type="button"
|
|
206
206
|
onClick={onRefresh}
|
|
207
|
-
className="mt-4 inline-flex items-center justify-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-
|
|
207
|
+
className="mt-4 inline-flex items-center justify-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs 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"
|
|
208
208
|
>
|
|
209
209
|
<RefreshCw className="h-3.5 w-3.5" />
|
|
210
210
|
{t('panelAppsRefresh')}
|
|
@@ -245,7 +245,7 @@ function PanelAppsEmptyGuideStep({
|
|
|
245
245
|
<button
|
|
246
246
|
type="button"
|
|
247
247
|
onClick={onAction}
|
|
248
|
-
className="flex w-full gap-2 rounded-md bg-muted/60 px-3 py-2.5 text-left transition-colors hover:bg-
|
|
248
|
+
className="flex w-full gap-2 rounded-md bg-muted/60 px-3 py-2.5 text-left transition-colors hover:bg-[var(--interaction-hover)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
249
249
|
>
|
|
250
250
|
{content}
|
|
251
251
|
</button>
|
|
@@ -463,7 +463,7 @@ function ServiceAppMenuItem({
|
|
|
463
463
|
type="button"
|
|
464
464
|
className={cn(
|
|
465
465
|
'flex w-full items-center gap-2 rounded-lg px-2.5 py-2 text-left text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-50',
|
|
466
|
-
destructive ? 'text-destructive hover:bg-destructive/10' : 'text-muted-foreground hover:bg-
|
|
466
|
+
destructive ? 'text-destructive hover:bg-destructive/10' : 'text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground',
|
|
467
467
|
)}
|
|
468
468
|
disabled={disabled}
|
|
469
469
|
onClick={onClick}
|