@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
|
@@ -12,14 +12,17 @@ import { t, type I18nLanguage } from "@/shared/lib/i18n";
|
|
|
12
12
|
import type { UiTheme } from "@/shared/lib/theme";
|
|
13
13
|
import { SidebarNavLinkItem } from "@/app/components/layout/sidebar-items";
|
|
14
14
|
import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
|
|
15
|
+
import { SCROLL_BOTTOM_EDGE_FADE_CLASS } from "@/shared/components/ui/scroll-area";
|
|
15
16
|
import {
|
|
16
17
|
AlarmClock,
|
|
17
18
|
Bot,
|
|
18
19
|
BrainCircuit,
|
|
20
|
+
Inbox,
|
|
19
21
|
PanelLeftClose,
|
|
20
22
|
PanelLeftOpen,
|
|
21
|
-
|
|
23
|
+
FolderPlus,
|
|
22
24
|
} from "lucide-react";
|
|
25
|
+
import { useInboxUnreadCount } from "@/features/inbox";
|
|
23
26
|
import { ChatSidebarSessionList } from "@/features/chat/features/session/components/chat-sidebar-session-list";
|
|
24
27
|
import { ChatSidebarUtilityMenu } from "@/features/chat/components/layout/chat-sidebar-utility-menu";
|
|
25
28
|
import { isWindowsDesktopHost } from "@/platforms/desktop";
|
|
@@ -30,10 +33,14 @@ import {
|
|
|
30
33
|
SIDEBAR_RAIL_PADDING_X_CLASS,
|
|
31
34
|
SIDEBAR_RAIL_STACK_CLASS,
|
|
32
35
|
SIDEBAR_RAIL_SURFACE_CLASS,
|
|
33
|
-
SIDEBAR_SCROLL_EDGE_FADE_CLASS,
|
|
34
36
|
} from "@/app/components/layout/sidebar-rail.styles";
|
|
35
37
|
|
|
36
38
|
const navItems = [
|
|
39
|
+
{
|
|
40
|
+
target: "/inbox",
|
|
41
|
+
label: () => t("inboxTitle"),
|
|
42
|
+
icon: Inbox,
|
|
43
|
+
},
|
|
37
44
|
{
|
|
38
45
|
target: "/cron",
|
|
39
46
|
label: () => t("chatSidebarScheduledTasks"),
|
|
@@ -79,7 +86,7 @@ export function ChatSidebarDesktopHeader({
|
|
|
79
86
|
? "justify-center px-2 py-1.5"
|
|
80
87
|
: isWindowsHost
|
|
81
88
|
? "justify-end px-3 py-1.5"
|
|
82
|
-
: "gap-2 px-
|
|
89
|
+
: "gap-2 px-4 py-2",
|
|
83
90
|
isCollapsed && shouldReserveMacWindowControls ? "pt-8" : null,
|
|
84
91
|
)}
|
|
85
92
|
>
|
|
@@ -99,33 +106,31 @@ export function ChatSidebarDesktopNav({
|
|
|
99
106
|
}: {
|
|
100
107
|
isCollapsed: boolean;
|
|
101
108
|
}) {
|
|
109
|
+
const unreadCount = useInboxUnreadCount();
|
|
102
110
|
return (
|
|
103
|
-
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
)}
|
|
127
|
-
/>
|
|
128
|
-
</>
|
|
111
|
+
<div className={cn("pb-1", isCollapsed ? "px-0" : "px-3")}>
|
|
112
|
+
<ul className={isCollapsed ? SIDEBAR_RAIL_STACK_CLASS : "space-y-0.5"}>
|
|
113
|
+
{navItems.map((item) => (
|
|
114
|
+
<li
|
|
115
|
+
key={item.target}
|
|
116
|
+
className={isCollapsed ? "flex justify-center" : undefined}
|
|
117
|
+
>
|
|
118
|
+
<SidebarNavLinkItem
|
|
119
|
+
to={item.target}
|
|
120
|
+
label={item.label()}
|
|
121
|
+
icon={item.icon}
|
|
122
|
+
density="compact"
|
|
123
|
+
collapsed={isCollapsed}
|
|
124
|
+
indicator={item.target === "/inbox" && unreadCount > 0}
|
|
125
|
+
trailing={item.target === "/inbox" && unreadCount > 0
|
|
126
|
+
? unreadCount > 99 ? "99+" : unreadCount
|
|
127
|
+
: undefined}
|
|
128
|
+
className={isCollapsed ? undefined : "rounded-lg px-2.5 py-1.5"}
|
|
129
|
+
/>
|
|
130
|
+
</li>
|
|
131
|
+
))}
|
|
132
|
+
</ul>
|
|
133
|
+
</div>
|
|
129
134
|
);
|
|
130
135
|
}
|
|
131
136
|
|
|
@@ -160,14 +165,11 @@ export function ChatSidebarSessionArea({
|
|
|
160
165
|
|
|
161
166
|
return (
|
|
162
167
|
<>
|
|
163
|
-
<div className="flex items-center justify-
|
|
164
|
-
<div className="
|
|
165
|
-
{t("chatSidebarTaskRecords")}
|
|
166
|
-
</div>
|
|
167
|
-
<div className="flex items-center gap-1">
|
|
168
|
+
<div className="flex items-center justify-end px-4 pb-1 pt-2">
|
|
169
|
+
<div className="flex h-7 items-center gap-1.5">
|
|
168
170
|
{isProjectFirstView ? (
|
|
169
171
|
<IconActionButton
|
|
170
|
-
icon={<
|
|
172
|
+
icon={<FolderPlus className="h-3.5 w-3.5" />}
|
|
171
173
|
label={t("chatProjectAdd")}
|
|
172
174
|
onClick={onAddProject}
|
|
173
175
|
/>
|
|
@@ -181,8 +183,8 @@ export function ChatSidebarSessionArea({
|
|
|
181
183
|
|
|
182
184
|
<div
|
|
183
185
|
className={cn(
|
|
184
|
-
"custom-scrollbar min-h-0 flex-1 overflow-y-auto px-3 pb-
|
|
185
|
-
|
|
186
|
+
"custom-scrollbar min-h-0 flex-1 overflow-y-auto px-3 pb-4 pt-1",
|
|
187
|
+
SCROLL_BOTTOM_EDGE_FADE_CLASS,
|
|
186
188
|
)}
|
|
187
189
|
>
|
|
188
190
|
<ChatSidebarSessionList
|
|
@@ -234,7 +236,7 @@ export function ChatSidebarDesktopFooter({
|
|
|
234
236
|
return (
|
|
235
237
|
<div
|
|
236
238
|
className={cn(
|
|
237
|
-
"py-
|
|
239
|
+
"py-2",
|
|
238
240
|
isCollapsed
|
|
239
241
|
? cn("flex justify-center", SIDEBAR_RAIL_PADDING_X_CLASS)
|
|
240
242
|
: "px-3",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Button
|
|
1
|
+
import { Button } from "@/shared/components/ui/button";
|
|
2
2
|
import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
|
|
3
3
|
import { Input } from "@/shared/components/ui/input";
|
|
4
4
|
import {
|
|
@@ -20,51 +20,6 @@ import {
|
|
|
20
20
|
} from "@/app/components/layout/sidebar-rail.styles";
|
|
21
21
|
|
|
22
22
|
type SessionTypeOption = ChatSessionTypeOption;
|
|
23
|
-
type NewSessionActionStyleVariant =
|
|
24
|
-
| "neutralSurface"
|
|
25
|
-
| "brandSoft"
|
|
26
|
-
| "brandTextSurface"
|
|
27
|
-
| "brandSolid";
|
|
28
|
-
|
|
29
|
-
const NEW_SESSION_ACTION_STYLE_VARIANT: NewSessionActionStyleVariant =
|
|
30
|
-
"brandSoft";
|
|
31
|
-
|
|
32
|
-
const NEW_SESSION_ACTION_STYLE_CLASSES: Record<
|
|
33
|
-
NewSessionActionStyleVariant,
|
|
34
|
-
{
|
|
35
|
-
leftVariant: ButtonProps["variant"];
|
|
36
|
-
leftClassName: string;
|
|
37
|
-
rightClassName: string;
|
|
38
|
-
}
|
|
39
|
-
> = {
|
|
40
|
-
neutralSurface: {
|
|
41
|
-
leftVariant: "ghost",
|
|
42
|
-
leftClassName:
|
|
43
|
-
"bg-card text-foreground shadow-none transition-[background-color,color,box-shadow] hover:bg-accent hover:text-accent-foreground hover:shadow-sm active:bg-accent",
|
|
44
|
-
rightClassName:
|
|
45
|
-
"bg-card text-muted-foreground shadow-none transition-[background-color,color,box-shadow] hover:bg-accent hover:text-accent-foreground hover:shadow-sm active:bg-accent",
|
|
46
|
-
},
|
|
47
|
-
brandSoft: {
|
|
48
|
-
leftVariant: "ghost",
|
|
49
|
-
leftClassName:
|
|
50
|
-
"bg-primary/10 text-primary shadow-none ring-1 ring-primary/10 transition-[background-color,color,box-shadow] hover:bg-primary/15 hover:text-primary-700 hover:shadow-sm active:bg-primary/20",
|
|
51
|
-
rightClassName:
|
|
52
|
-
"bg-primary/10 text-primary shadow-none ring-1 ring-primary/10 transition-[background-color,color,box-shadow] hover:bg-primary/15 hover:text-primary-700 hover:shadow-sm active:bg-primary/20",
|
|
53
|
-
},
|
|
54
|
-
brandTextSurface: {
|
|
55
|
-
leftVariant: "ghost",
|
|
56
|
-
leftClassName:
|
|
57
|
-
"bg-card text-primary shadow-none transition-[background-color,color,box-shadow] hover:bg-accent hover:text-primary-700 hover:shadow-sm active:bg-primary/15",
|
|
58
|
-
rightClassName:
|
|
59
|
-
"bg-card text-primary shadow-none transition-[background-color,color,box-shadow] hover:bg-accent hover:text-primary-700 hover:shadow-sm active:bg-primary/15",
|
|
60
|
-
},
|
|
61
|
-
brandSolid: {
|
|
62
|
-
leftVariant: "primary",
|
|
63
|
-
leftClassName: "",
|
|
64
|
-
rightClassName:
|
|
65
|
-
"bg-primary text-primary-foreground shadow-sm hover:bg-primary-600 hover:text-primary-foreground active:bg-primary-700",
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
23
|
|
|
69
24
|
type ChatSidebarToolbarProps = {
|
|
70
25
|
query: string;
|
|
@@ -117,8 +72,6 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
|
|
|
117
72
|
collapsed = false,
|
|
118
73
|
} = props;
|
|
119
74
|
const supportsSessionTypeSwitch = sessionTypeOptions.length > 1;
|
|
120
|
-
const actionStyle =
|
|
121
|
-
NEW_SESSION_ACTION_STYLE_CLASSES[NEW_SESSION_ACTION_STYLE_VARIANT];
|
|
122
75
|
|
|
123
76
|
if (collapsed) {
|
|
124
77
|
return (
|
|
@@ -183,21 +136,20 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
|
|
|
183
136
|
|
|
184
137
|
return (
|
|
185
138
|
<>
|
|
186
|
-
<div className="px-4 pb-
|
|
187
|
-
<div className="flex
|
|
139
|
+
<div className="px-4 pb-2">
|
|
140
|
+
<div className="flex overflow-hidden rounded-lg bg-primary/10 text-primary ring-1 ring-primary/10">
|
|
188
141
|
<Button
|
|
189
|
-
variant=
|
|
142
|
+
variant="ghost"
|
|
190
143
|
className={cn(
|
|
191
|
-
"min-w-0 rounded-
|
|
192
|
-
|
|
193
|
-
supportsSessionTypeSwitch ? "flex-1 rounded-r-md" : "w-full",
|
|
144
|
+
"min-w-0 rounded-none bg-transparent px-3 text-primary shadow-none hover:bg-primary/10 hover:text-primary-700 active:bg-primary/15",
|
|
145
|
+
supportsSessionTypeSwitch ? "flex-1" : "w-full",
|
|
194
146
|
)}
|
|
195
147
|
onClick={() => {
|
|
196
148
|
onCreateMenuOpenChange(false);
|
|
197
149
|
onCreateSession(selectedNewSessionType);
|
|
198
150
|
}}
|
|
199
151
|
>
|
|
200
|
-
<Plus className="mr-
|
|
152
|
+
<Plus className="mr-1.5 h-4 w-4" />
|
|
201
153
|
{t("chatSidebarNewTask")}
|
|
202
154
|
</Button>
|
|
203
155
|
{supportsSessionTypeSwitch ? (
|
|
@@ -208,19 +160,16 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
|
|
|
208
160
|
<PopoverTrigger asChild>
|
|
209
161
|
<IconActionButton
|
|
210
162
|
icon={
|
|
211
|
-
<span className="inline-flex items-center gap-
|
|
163
|
+
<span className="inline-flex items-center gap-1">
|
|
212
164
|
<SessionTypeTriggerIcon
|
|
213
165
|
option={selectedNewSessionTypeOption}
|
|
214
166
|
/>
|
|
215
|
-
<ChevronDown className="h-3 w-3 opacity-
|
|
167
|
+
<ChevronDown className="h-3 w-3 opacity-50" />
|
|
216
168
|
</span>
|
|
217
169
|
}
|
|
218
170
|
label={t("chatSessionTypeLabel")}
|
|
219
171
|
tooltip={t("chatSessionTypeLabel")}
|
|
220
|
-
className=
|
|
221
|
-
"h-9 w-12 shrink-0 rounded-xl rounded-l-md",
|
|
222
|
-
actionStyle.rightClassName,
|
|
223
|
-
)}
|
|
172
|
+
className="h-9 w-12 shrink-0 rounded-none border-l border-primary/10 bg-transparent text-primary shadow-none hover:bg-primary/10 hover:text-primary-700 active:bg-primary/15"
|
|
224
173
|
/>
|
|
225
174
|
</PopoverTrigger>
|
|
226
175
|
<ChatPopoverContent
|
|
@@ -241,14 +190,14 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
|
|
|
241
190
|
</div>
|
|
242
191
|
</div>
|
|
243
192
|
|
|
244
|
-
<div className="px-4 pb-
|
|
193
|
+
<div className="px-4 pb-2">
|
|
245
194
|
<div className="relative">
|
|
246
|
-
<Search className="pointer-events-none absolute left-
|
|
195
|
+
<Search className="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground/60" />
|
|
247
196
|
<Input
|
|
248
197
|
value={query}
|
|
249
198
|
onChange={(event) => onQueryChange(event.target.value)}
|
|
250
199
|
placeholder={t("chatSidebarSearchPlaceholder")}
|
|
251
|
-
className="h-
|
|
200
|
+
className="h-8 rounded-lg border-transparent bg-background/55 pl-8 text-xs shadow-none hover:bg-background/75"
|
|
252
201
|
/>
|
|
253
202
|
</div>
|
|
254
203
|
</div>
|
|
@@ -102,13 +102,10 @@ export function ChatSidebarUtilityMenu({
|
|
|
102
102
|
<button
|
|
103
103
|
type="button"
|
|
104
104
|
aria-label={t("settingsMenu")}
|
|
105
|
-
className="flex w-full items-center gap-2.5 rounded-
|
|
105
|
+
className="flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-[13px] font-medium text-muted-foreground transition-colors duration-base hover:bg-background/70 hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
106
106
|
>
|
|
107
107
|
<Settings className="h-4 w-4 shrink-0 text-muted-foreground/70" />
|
|
108
108
|
<span className="min-w-0 flex-1 text-left">{t("settings")}</span>
|
|
109
|
-
<span className="max-w-[112px] truncate text-[13px] text-muted-foreground">
|
|
110
|
-
{currentThemeLabel} / {currentLanguageLabel}
|
|
111
|
-
</span>
|
|
112
109
|
</button>
|
|
113
110
|
</PopoverTrigger>
|
|
114
111
|
)}
|
|
@@ -389,7 +389,7 @@ describe("SessionConversationArea input boundary", () => {
|
|
|
389
389
|
|
|
390
390
|
expect(screen.getByTestId("conversation-bottom-slot")).toBeTruthy();
|
|
391
391
|
expect(screen.getByText(/出错了|Something went wrong/)).toBeTruthy();
|
|
392
|
-
expect(screen.getByText(
|
|
392
|
+
expect(screen.getByText(/Failed · Invalid API Key|失败 · Invalid API Key/)).toBeTruthy();
|
|
393
393
|
});
|
|
394
394
|
|
|
395
395
|
it("does not surface user-cancelled previews as conversation errors", () => {
|
|
@@ -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>
|