@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
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type SidebarNavListItem,
|
|
17
17
|
} from "@/app/components/layout/sidebar-items";
|
|
18
18
|
import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
|
|
19
|
+
import { SCROLL_BOTTOM_EDGE_FADE_CLASS } from "@/shared/components/ui/scroll-area";
|
|
19
20
|
import { useAppPresenter } from "@/app/components/app-presenter-provider";
|
|
20
21
|
import { useRemoteStatus } from "@/features/remote";
|
|
21
22
|
import { getSettingsNavSections } from "@/app/configs/app-navigation.config";
|
|
@@ -29,7 +30,6 @@ import {
|
|
|
29
30
|
SIDEBAR_RAIL_STACK_CLASS,
|
|
30
31
|
SIDEBAR_RAIL_SURFACE_CLASS,
|
|
31
32
|
SIDEBAR_RAIL_WIDTH_CLASS,
|
|
32
|
-
SIDEBAR_SCROLL_EDGE_FADE_CLASS,
|
|
33
33
|
} from "@/app/components/layout/sidebar-rail.styles";
|
|
34
34
|
|
|
35
35
|
type SidebarNavSection = {
|
|
@@ -141,7 +141,7 @@ function SidebarNavigation({
|
|
|
141
141
|
className={cn(
|
|
142
142
|
"custom-scrollbar min-h-0 flex-1 overflow-y-auto",
|
|
143
143
|
isCollapsed ? "pr-0" : "pr-1",
|
|
144
|
-
|
|
144
|
+
SCROLL_BOTTOM_EDGE_FADE_CLASS,
|
|
145
145
|
)}
|
|
146
146
|
>
|
|
147
147
|
{!isCollapsed ? (
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
AlarmClock,
|
|
6
6
|
Cpu,
|
|
7
7
|
Download,
|
|
8
|
+
Inbox,
|
|
8
9
|
KeyRound,
|
|
9
10
|
MessageCircle,
|
|
10
11
|
MessageSquare,
|
|
@@ -44,6 +45,8 @@ export function isMainWorkspaceRoute(pathname: string): boolean {
|
|
|
44
45
|
return (
|
|
45
46
|
normalized === "/chat" ||
|
|
46
47
|
normalized.startsWith("/chat/") ||
|
|
48
|
+
normalized === "/inbox" ||
|
|
49
|
+
normalized.startsWith("/inbox/") ||
|
|
47
50
|
normalized === "/skills" ||
|
|
48
51
|
normalized.startsWith("/skills/") ||
|
|
49
52
|
normalized === "/cron" ||
|
|
@@ -67,6 +70,11 @@ export function getMobileBottomNavItems(
|
|
|
67
70
|
label: translate("chat"),
|
|
68
71
|
icon: MessageCircle,
|
|
69
72
|
},
|
|
73
|
+
{
|
|
74
|
+
target: "/inbox",
|
|
75
|
+
label: translate("inboxTitle"),
|
|
76
|
+
icon: Inbox,
|
|
77
|
+
},
|
|
70
78
|
{
|
|
71
79
|
target: "/skills",
|
|
72
80
|
label: translate("marketplaceFilterSkills"),
|
|
@@ -94,6 +102,11 @@ export function getMainSidebarNavItems(
|
|
|
94
102
|
label: translate("chat"),
|
|
95
103
|
icon: MessageCircle,
|
|
96
104
|
},
|
|
105
|
+
{
|
|
106
|
+
target: "/inbox",
|
|
107
|
+
label: translate("inboxTitle"),
|
|
108
|
+
icon: Inbox,
|
|
109
|
+
},
|
|
97
110
|
{
|
|
98
111
|
target: "/chat/cron",
|
|
99
112
|
label: translate("cron"),
|
|
@@ -227,6 +240,14 @@ export function resolveMobileRouteMeta(
|
|
|
227
240
|
};
|
|
228
241
|
}
|
|
229
242
|
|
|
243
|
+
if (normalized === "/inbox" || normalized.startsWith("/inbox/")) {
|
|
244
|
+
return {
|
|
245
|
+
title: translate("inboxTitle"),
|
|
246
|
+
backTarget: normalized === "/inbox" ? null : "/inbox",
|
|
247
|
+
backLabel: translate("inboxTitle"),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
230
251
|
if (normalized === "/skills" || normalized.startsWith("/skills/")) {
|
|
231
252
|
return {
|
|
232
253
|
title: translate("marketplaceFilterSkills"),
|
package/src/app/index.tsx
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { lazy, Suspense, useEffect } from "react";
|
|
2
|
-
import type { ReactElement } from "react";
|
|
2
|
+
import type { CSSProperties, ReactElement } from "react";
|
|
3
3
|
import { QueryClientProvider } from "@tanstack/react-query";
|
|
4
4
|
import { Navigate, Route, Routes } from "react-router-dom";
|
|
5
5
|
import { Toaster } from "sonner";
|
|
6
6
|
import { appQueryClient } from "@/app-query-client";
|
|
7
7
|
import { AppPresenterProvider } from "@/app/components/app-presenter-provider";
|
|
8
|
+
import { AppNotificationRuntime } from "@/app/components/app-notification-runtime";
|
|
8
9
|
import { AppLayout } from "@/app/components/layout/app-layout";
|
|
10
|
+
import { SIDEBAR_RAIL_WIDTH_PX } from "@/app/components/layout/sidebar-rail.styles";
|
|
9
11
|
import { SettingsEntryPage } from "@/app/components/layout/settings-entry-page";
|
|
10
12
|
import { LoginPage } from "@/components/auth/login-page";
|
|
11
13
|
import { AccountPanel } from "@/features/account";
|
|
14
|
+
import { InboxRuntime } from "@/features/inbox";
|
|
12
15
|
import { ServiceActionAuthorizationDialog } from "@/features/service-apps";
|
|
13
16
|
import { runtimeUpdateManager, useSystemStatusSources } from "@/features/system-status";
|
|
14
17
|
import {
|
|
@@ -21,6 +24,8 @@ import {
|
|
|
21
24
|
} from "@/pwa/components/pwa-install-entry";
|
|
22
25
|
import { startNextClawPwa } from "@/pwa/register-pwa";
|
|
23
26
|
|
|
27
|
+
const NOTIFICATION_TOASTER_STYLE = { "--width": "320px" } as CSSProperties;
|
|
28
|
+
|
|
24
29
|
const ModelConfigPage = lazy(async () => ({
|
|
25
30
|
default: (await import("@/features/settings/pages/model-config-page"))
|
|
26
31
|
.ModelConfigPage,
|
|
@@ -117,6 +122,10 @@ const protectedRouteDefinitions: ProtectedRouteDefinition[] = [
|
|
|
117
122
|
path: "/cron",
|
|
118
123
|
element: createLazyElement(<ChatPage view="cron" />),
|
|
119
124
|
},
|
|
125
|
+
{
|
|
126
|
+
path: "/inbox/:deliveryId?",
|
|
127
|
+
element: createLazyElement(<ChatPage view="inbox" />),
|
|
128
|
+
},
|
|
120
129
|
{
|
|
121
130
|
path: "/appearance",
|
|
122
131
|
element: createLazyElement(<AppearanceSettingsPage />),
|
|
@@ -219,6 +228,8 @@ function ProtectedApp() {
|
|
|
219
228
|
|
|
220
229
|
return (
|
|
221
230
|
<AppPresenterProvider>
|
|
231
|
+
<AppNotificationRuntime />
|
|
232
|
+
<InboxRuntime />
|
|
222
233
|
<AppLayout>
|
|
223
234
|
<ProtectedRoutes />
|
|
224
235
|
</AppLayout>
|
|
@@ -257,7 +268,16 @@ export default function AppContent() {
|
|
|
257
268
|
<QueryClientProvider client={appQueryClient}>
|
|
258
269
|
<AuthGate />
|
|
259
270
|
<PwaInstallBanner />
|
|
260
|
-
<Toaster
|
|
271
|
+
<Toaster
|
|
272
|
+
position="top-right"
|
|
273
|
+
richColors
|
|
274
|
+
offset={{
|
|
275
|
+
top: window.nextclawDesktop?.platform === "win32" ? 56 : 44,
|
|
276
|
+
right: SIDEBAR_RAIL_WIDTH_PX + 16,
|
|
277
|
+
}}
|
|
278
|
+
mobileOffset={window.nextclawDesktop?.platform === "win32" ? 56 : undefined}
|
|
279
|
+
style={NOTIFICATION_TOASTER_STYLE}
|
|
280
|
+
/>
|
|
261
281
|
</QueryClientProvider>
|
|
262
282
|
);
|
|
263
283
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { AccountManager } from '@/features/account';
|
|
2
2
|
import { viewportLayoutManager } from '@/app/managers/viewport-layout.manager';
|
|
3
3
|
import {
|
|
4
|
+
ChatCompletionNotificationManager,
|
|
4
5
|
ChatDraftIntentManager,
|
|
5
6
|
useChatThreadStore,
|
|
6
7
|
} from '@/features/chat';
|
|
8
|
+
import { AppNotificationManager } from '@/features/notifications';
|
|
7
9
|
import { PanelAppBridgeManager } from '@/features/panel-apps';
|
|
10
|
+
import { InboxManager } from '@/features/inbox';
|
|
8
11
|
import { RightPanelResourceRouteResolver } from '@/features/right-panel-resources';
|
|
9
12
|
import { RemoteAccessManager } from '@/features/remote';
|
|
10
13
|
import { ServiceActionAuthorizationManager } from '@/features/service-apps';
|
|
@@ -19,6 +22,11 @@ function isChatWorkspacePanelOpen(snapshot: ChatThreadSnapshot): boolean {
|
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
export class AppPresenter {
|
|
25
|
+
notificationManager = new AppNotificationManager();
|
|
26
|
+
inboxManager = new InboxManager();
|
|
27
|
+
chatCompletionNotificationManager = new ChatCompletionNotificationManager(
|
|
28
|
+
this.notificationManager,
|
|
29
|
+
);
|
|
22
30
|
accountManager = new AccountManager();
|
|
23
31
|
rightPanelResourceRouteResolver = new RightPanelResourceRouteResolver();
|
|
24
32
|
notifyRightPanelOpened = () => {
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
@layer base {
|
|
8
|
+
input:focus,
|
|
9
|
+
textarea:focus {
|
|
10
|
+
outline: none;
|
|
11
|
+
box-shadow: none;
|
|
12
|
+
}
|
|
13
|
+
|
|
8
14
|
:root {
|
|
9
15
|
color-scheme: light;
|
|
10
16
|
|
|
@@ -70,6 +76,9 @@
|
|
|
70
76
|
/* Accent */
|
|
71
77
|
--accent: var(--brand-50);
|
|
72
78
|
--accent-foreground: var(--brand-700);
|
|
79
|
+
--interaction-hover: hsl(0 0% 96%);
|
|
80
|
+
--interaction-selection: hsl(211 100% 92%);
|
|
81
|
+
--interaction-selection-border: hsl(211 75% 84%);
|
|
73
82
|
|
|
74
83
|
/* Muted */
|
|
75
84
|
--muted: var(--gray-100);
|
|
@@ -717,6 +726,9 @@
|
|
|
717
726
|
--secondary-foreground: var(--gray-800);
|
|
718
727
|
--accent: 151 32% 18%;
|
|
719
728
|
--accent-foreground: 148 34% 84%;
|
|
729
|
+
--interaction-hover: hsl(var(--gray-200));
|
|
730
|
+
--interaction-selection: hsl(211 48% 30%);
|
|
731
|
+
--interaction-selection-border: hsl(211 48% 42%);
|
|
720
732
|
--muted: var(--gray-100);
|
|
721
733
|
--muted-foreground: var(--gray-500);
|
|
722
734
|
--destructive: 0 70% 52%;
|
|
@@ -10,7 +10,7 @@ export function ChatSidebarListModeSwitch({ isProjectFirstView, onSelectMode }:
|
|
|
10
10
|
type="button"
|
|
11
11
|
aria-pressed={!isProjectFirstView}
|
|
12
12
|
onClick={() => onSelectMode('time-first')}
|
|
13
|
-
className={cn('transition-colors', isProjectFirstView ? 'text-muted-foreground/65 hover:text-muted-foreground' : 'font-medium text-foreground')}
|
|
13
|
+
className={cn('rounded-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border', isProjectFirstView ? 'text-muted-foreground/65 hover:text-muted-foreground' : 'font-medium text-foreground')}
|
|
14
14
|
>
|
|
15
15
|
{t('chatSidebarViewTime')}
|
|
16
16
|
</button>
|
|
@@ -19,7 +19,7 @@ export function ChatSidebarListModeSwitch({ isProjectFirstView, onSelectMode }:
|
|
|
19
19
|
type="button"
|
|
20
20
|
aria-pressed={isProjectFirstView}
|
|
21
21
|
onClick={() => onSelectMode('project-first')}
|
|
22
|
-
className={cn('transition-colors', isProjectFirstView ? 'font-medium text-foreground' : 'text-muted-foreground/65 hover:text-muted-foreground')}
|
|
22
|
+
className={cn('rounded-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border', isProjectFirstView ? 'font-medium text-foreground' : 'text-muted-foreground/65 hover:text-muted-foreground')}
|
|
23
23
|
>
|
|
24
24
|
{t('chatSidebarViewProject')}
|
|
25
25
|
</button>
|
package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx
CHANGED
|
@@ -60,6 +60,7 @@ function renderContent(
|
|
|
60
60
|
isContextCompacting?: boolean;
|
|
61
61
|
isLoadingPreviousMessages?: boolean;
|
|
62
62
|
onLoadPreviousMessages?: () => Promise<void>;
|
|
63
|
+
showWelcome?: boolean;
|
|
63
64
|
} = {},
|
|
64
65
|
) {
|
|
65
66
|
return render(
|
|
@@ -74,8 +75,9 @@ function renderContent(
|
|
|
74
75
|
isSending={false}
|
|
75
76
|
messages={messages}
|
|
76
77
|
sessionKey="session-1"
|
|
77
|
-
showWelcome={false}
|
|
78
|
+
showWelcome={options.showWelcome ?? false}
|
|
78
79
|
onLoadPreviousMessages={options.onLoadPreviousMessages ?? vi.fn()}
|
|
80
|
+
welcomeSlot={<div data-testid="conversation-welcome" />}
|
|
79
81
|
/>,
|
|
80
82
|
);
|
|
81
83
|
}
|
|
@@ -94,6 +96,22 @@ it("renders manual context compaction feedback after the message list", () => {
|
|
|
94
96
|
expect(screen.getByTestId("context-compaction-pending")).toBeTruthy();
|
|
95
97
|
});
|
|
96
98
|
|
|
99
|
+
it("fades the conversation into the docked input without obscuring the final message", () => {
|
|
100
|
+
const { container } = renderContent();
|
|
101
|
+
const scrollContainer = container.querySelector<HTMLElement>(
|
|
102
|
+
'[data-chat-scroll-container="true"]',
|
|
103
|
+
);
|
|
104
|
+
expect(scrollContainer?.className).toContain("[mask-image:linear-gradient");
|
|
105
|
+
expect(scrollContainer?.className).toContain("[-webkit-mask-image:linear-gradient");
|
|
106
|
+
expect(scrollContainer?.firstElementChild?.className).toContain("pb-7");
|
|
107
|
+
|
|
108
|
+
const { container: welcomeContainer } = renderContent({ showWelcome: true });
|
|
109
|
+
const welcomeScrollContainer = welcomeContainer.querySelector<HTMLElement>(
|
|
110
|
+
'[data-chat-scroll-container="true"]',
|
|
111
|
+
);
|
|
112
|
+
expect(welcomeScrollContainer?.className).not.toContain("[mask-image:linear-gradient");
|
|
113
|
+
});
|
|
114
|
+
|
|
97
115
|
it("uses the centered reading track in flat message layout", () => {
|
|
98
116
|
useChatMessageLayoutStore.getState().setLayout("flat");
|
|
99
117
|
|
|
@@ -279,7 +279,7 @@ describe("ChatConversationHeaderSection", () => {
|
|
|
279
279
|
renderHeaderSection("mobile");
|
|
280
280
|
await user.click(screen.getByRole("button", { name: "New Task" }));
|
|
281
281
|
|
|
282
|
-
expect(mocks.createSession).toHaveBeenCalledWith("codex");
|
|
282
|
+
expect(mocks.createSession).toHaveBeenCalledWith({ sessionType: "codex" });
|
|
283
283
|
});
|
|
284
284
|
|
|
285
285
|
it("keeps the new-session shortcut mobile-only", () => {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "@/features/chat/features/message/components/chat-message-list.container";
|
|
14
14
|
import { ChatConversationTrack } from "@/features/chat/components/conversation/chat-conversation-track";
|
|
15
15
|
import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
|
|
16
|
+
import { SCROLL_BOTTOM_EDGE_FADE_CLASS } from "@/shared/components/ui/scroll-area";
|
|
16
17
|
import { t } from "@/shared/lib/i18n";
|
|
17
18
|
|
|
18
19
|
type ChatConversationContentProps = {
|
|
@@ -81,13 +82,13 @@ export function ChatConversationContent({
|
|
|
81
82
|
ref={threadRef}
|
|
82
83
|
onScroll={handleScroll}
|
|
83
84
|
data-chat-scroll-container="true"
|
|
84
|
-
className="h-full overflow-y-auto custom-scrollbar"
|
|
85
|
+
className={showWelcome ? "h-full overflow-y-auto custom-scrollbar" : `h-full overflow-y-auto custom-scrollbar ${SCROLL_BOTTOM_EDGE_FADE_CLASS}`}
|
|
85
86
|
style={{ overflowAnchor: "none" }}
|
|
86
87
|
>
|
|
87
88
|
{showWelcome ? (
|
|
88
89
|
(welcomeSlot ?? null)
|
|
89
90
|
) : (
|
|
90
|
-
<div ref={contentRef}>
|
|
91
|
+
<div ref={contentRef} className="pb-7">
|
|
91
92
|
{hasMessages ? (
|
|
92
93
|
<ChatConversationTrack className="relative py-4 sm:py-5">
|
|
93
94
|
{historyError ? (
|
|
@@ -109,9 +110,7 @@ export function ChatConversationContent({
|
|
|
109
110
|
) : null}
|
|
110
111
|
<ChatMessageListContainer
|
|
111
112
|
messages={messages}
|
|
112
|
-
isSending={
|
|
113
|
-
hasMessages && isSending && isAwaitingAssistantOutput
|
|
114
|
-
}
|
|
113
|
+
isSending={isSending && isAwaitingAssistantOutput}
|
|
115
114
|
scrollRef={threadRef}
|
|
116
115
|
sessionKey={sessionKey}
|
|
117
116
|
/>
|
|
@@ -132,7 +131,7 @@ export function ChatConversationContent({
|
|
|
132
131
|
label={t("chatScrollToBottom")}
|
|
133
132
|
onClick={scrollToBottom}
|
|
134
133
|
tooltipSide="top"
|
|
135
|
-
className="absolute bottom-4 left-1/2 z-10 h-9 w-9 -translate-x-1/2 rounded-full border border-border bg-background/90 text-foreground shadow-lg backdrop-blur hover:bg-
|
|
134
|
+
className="absolute bottom-4 left-1/2 z-10 h-9 w-9 -translate-x-1/2 rounded-full border border-border bg-background/90 text-foreground shadow-lg backdrop-blur hover:bg-[var(--interaction-hover)] hover:text-accent-foreground"
|
|
136
135
|
/>
|
|
137
136
|
) : null}
|
|
138
137
|
</div>
|
|
@@ -76,7 +76,7 @@ export function ChatConversationHeader({
|
|
|
76
76
|
icon={<ArrowLeft className="h-4 w-4" />}
|
|
77
77
|
label={t("chat")}
|
|
78
78
|
onClick={onBackToList}
|
|
79
|
-
className="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-
|
|
79
|
+
className="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-accent-foreground"
|
|
80
80
|
/>
|
|
81
81
|
) : null}
|
|
82
82
|
{leading}
|
|
@@ -189,7 +189,7 @@ describe("ChatSidebar read state sync", () => {
|
|
|
189
189
|
const { container } = renderReadStateSidebar(createTestQueryClient());
|
|
190
190
|
|
|
191
191
|
expect(
|
|
192
|
-
|
|
192
|
+
container.querySelector(".custom-scrollbar")?.className,
|
|
193
193
|
).toContain("[mask-image:linear-gradient");
|
|
194
194
|
expect(
|
|
195
195
|
screen.getByRole("button", { name: "Settings menu" }).parentElement
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { ChatSidebarSessionArea } from "@/features/chat/components/layout/chat-sidebar-desktop-layout";
|
|
4
|
+
|
|
5
|
+
function renderSessionArea(isProjectFirstView: boolean) {
|
|
6
|
+
const onAddProject = vi.fn();
|
|
7
|
+
const onSelectMode = vi.fn();
|
|
8
|
+
|
|
9
|
+
render(
|
|
10
|
+
<ChatSidebarSessionArea
|
|
11
|
+
defaultSessionType="native"
|
|
12
|
+
groups={[]}
|
|
13
|
+
isCollapsed={false}
|
|
14
|
+
isLoading={false}
|
|
15
|
+
isProjectFirstView={isProjectFirstView}
|
|
16
|
+
onAddProject={onAddProject}
|
|
17
|
+
onSelectMode={onSelectMode}
|
|
18
|
+
projectGroups={[]}
|
|
19
|
+
renderSessionItem={() => <></>}
|
|
20
|
+
sessionTypeOptions={[]}
|
|
21
|
+
/>,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return { onAddProject, onSelectMode };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("ChatSidebarSessionArea", () => {
|
|
28
|
+
it("reserves the action-row height while switching list modes", () => {
|
|
29
|
+
const { onSelectMode } = renderSessionArea(false);
|
|
30
|
+
|
|
31
|
+
expect(
|
|
32
|
+
screen.getByRole("button", { name: "Time" }).parentElement?.parentElement?.className,
|
|
33
|
+
).toContain("h-7");
|
|
34
|
+
fireEvent.click(screen.getByRole("button", { name: "Project" }));
|
|
35
|
+
|
|
36
|
+
expect(onSelectMode).toHaveBeenCalledWith("project-first");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("uses a folder-plus icon for the add-project action", () => {
|
|
40
|
+
const { onAddProject } = renderSessionArea(true);
|
|
41
|
+
const addProjectButton = screen.getByRole("button", { name: "Add Project" });
|
|
42
|
+
|
|
43
|
+
expect(
|
|
44
|
+
addProjectButton
|
|
45
|
+
.querySelector("svg")
|
|
46
|
+
?.classList.contains("lucide-folder-plus"),
|
|
47
|
+
).toBe(true);
|
|
48
|
+
fireEvent.click(addProjectButton);
|
|
49
|
+
|
|
50
|
+
expect(onAddProject).toHaveBeenCalledOnce();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -81,6 +81,9 @@ function expectCodexSelectedInSessionTypeMenu() {
|
|
|
81
81
|
screen.getByRole("button", { name: /Codex/i }).getAttribute("aria-pressed"),
|
|
82
82
|
).toBe("true");
|
|
83
83
|
}
|
|
84
|
+
function expectSessionCreated(sessionType: string, projectRoot?: string) {
|
|
85
|
+
expect(mocks.createSession).toHaveBeenCalledWith({ projectRoot, sessionType });
|
|
86
|
+
}
|
|
84
87
|
|
|
85
88
|
vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
|
|
86
89
|
usePresenter: () => ({
|
|
@@ -249,7 +252,7 @@ describe("ChatSidebar create and list basics", () => {
|
|
|
249
252
|
|
|
250
253
|
fireEvent.click(screen.getByRole("button", { name: "New Task" }));
|
|
251
254
|
|
|
252
|
-
|
|
255
|
+
expectSessionCreated("codex");
|
|
253
256
|
});
|
|
254
257
|
|
|
255
258
|
it("hydrates the desktop new-session type from stored preferences", async () => {
|
|
@@ -265,7 +268,7 @@ describe("ChatSidebar create and list basics", () => {
|
|
|
265
268
|
|
|
266
269
|
fireEvent.click(screen.getByRole("button", { name: "New Task" }));
|
|
267
270
|
|
|
268
|
-
|
|
271
|
+
expectSessionCreated("codex");
|
|
269
272
|
});
|
|
270
273
|
|
|
271
274
|
it("does not animate the desktop create task button width during runtime option hydration", () => {
|
|
@@ -311,7 +314,7 @@ describe("ChatSidebar create and list basics", () => {
|
|
|
311
314
|
it("renders the lightweight list mode switch in the session header row and toggles to project view", () => {
|
|
312
315
|
renderSidebar();
|
|
313
316
|
|
|
314
|
-
expect(screen.
|
|
317
|
+
expect(screen.queryByText("Sessions")).toBeNull();
|
|
315
318
|
fireEvent.click(screen.getByRole("button", { name: "Project" }));
|
|
316
319
|
|
|
317
320
|
expect(mocks.setListMode).toHaveBeenCalledWith("project-first");
|
|
@@ -332,7 +335,7 @@ describe("ChatSidebar create and list basics", () => {
|
|
|
332
335
|
fireEvent.click(screen.getByText("Codex"));
|
|
333
336
|
|
|
334
337
|
expect(mocks.setQuery).toHaveBeenCalledWith("release notes");
|
|
335
|
-
|
|
338
|
+
expectSessionCreated("codex");
|
|
336
339
|
expect(mocks.goToChatRoot).not.toHaveBeenCalled();
|
|
337
340
|
});
|
|
338
341
|
|
|
@@ -377,7 +380,7 @@ describe("ChatSidebar create and list basics", () => {
|
|
|
377
380
|
|
|
378
381
|
fireEvent.click(screen.getByRole("button", { name: "New Task" }));
|
|
379
382
|
|
|
380
|
-
|
|
383
|
+
expectSessionCreated("native");
|
|
381
384
|
expect(mocks.goToChatRoot).not.toHaveBeenCalled();
|
|
382
385
|
});
|
|
383
386
|
|
|
@@ -582,10 +585,7 @@ describe("ChatSidebar project-first mode", () => {
|
|
|
582
585
|
);
|
|
583
586
|
fireEvent.click(screen.getByText("Codex"));
|
|
584
587
|
|
|
585
|
-
|
|
586
|
-
"codex",
|
|
587
|
-
"/tmp/project-beta",
|
|
588
|
-
);
|
|
588
|
+
expectSessionCreated("codex", "/tmp/project-beta");
|
|
589
589
|
expect(mocks.goToSession).not.toHaveBeenCalled();
|
|
590
590
|
});
|
|
591
591
|
|
|
@@ -617,10 +617,7 @@ describe("ChatSidebar project-first mode", () => {
|
|
|
617
617
|
screen.getByRole("button", { name: "New Task · project-gamma" }),
|
|
618
618
|
);
|
|
619
619
|
|
|
620
|
-
|
|
621
|
-
"native",
|
|
622
|
-
"/tmp/project-gamma",
|
|
623
|
-
);
|
|
620
|
+
expectSessionCreated("native", "/tmp/project-gamma");
|
|
624
621
|
expect(mocks.goToSession).not.toHaveBeenCalled();
|
|
625
622
|
});
|
|
626
623
|
|
|
@@ -652,10 +649,7 @@ describe("ChatSidebar project-first mode", () => {
|
|
|
652
649
|
);
|
|
653
650
|
fireEvent.click(screen.getByText("Codex"));
|
|
654
651
|
|
|
655
|
-
|
|
656
|
-
"codex",
|
|
657
|
-
"/tmp/project-mobile",
|
|
658
|
-
);
|
|
652
|
+
expectSessionCreated("codex", "/tmp/project-mobile");
|
|
659
653
|
expect(mocks.goToChatRoot).not.toHaveBeenCalled();
|
|
660
654
|
});
|
|
661
655
|
});
|
|
@@ -6,34 +6,26 @@ import { CronConfig } from "@/features/cron";
|
|
|
6
6
|
import { MarketplacePage } from "@/features/marketplace";
|
|
7
7
|
import { useViewportLayout } from "@/app/hooks/use-viewport-layout";
|
|
8
8
|
import { ChatMobileShell } from "@/platforms/mobile";
|
|
9
|
-
|
|
9
|
+
import { InboxPage } from "@/features/inbox";
|
|
10
|
+
export type MainPanelView = "chat" | "cron" | "skills" | "agents" | "inbox";
|
|
10
11
|
export type ChatPageProps = {
|
|
11
12
|
view: MainPanelView;
|
|
12
13
|
};
|
|
13
14
|
type UseChatSessionSyncParams = {
|
|
14
|
-
view: MainPanelView;
|
|
15
15
|
routeSessionKey: string | null;
|
|
16
|
-
syncRouteSessionSelection: (
|
|
17
|
-
isChatView: boolean;
|
|
18
|
-
routeSessionKey: string | null;
|
|
19
|
-
}) => void;
|
|
16
|
+
syncRouteSessionSelection: (routeSessionKey: string | null) => void;
|
|
20
17
|
};
|
|
21
18
|
export function useChatSessionSync(params: UseChatSessionSyncParams): void {
|
|
22
19
|
const {
|
|
23
|
-
view,
|
|
24
20
|
routeSessionKey,
|
|
25
21
|
syncRouteSessionSelection,
|
|
26
22
|
} = params;
|
|
27
23
|
|
|
28
24
|
useLayoutEffect(() => {
|
|
29
|
-
syncRouteSessionSelection(
|
|
30
|
-
isChatView: view === "chat",
|
|
31
|
-
routeSessionKey,
|
|
32
|
-
});
|
|
25
|
+
syncRouteSessionSelection(routeSessionKey);
|
|
33
26
|
}, [
|
|
34
27
|
routeSessionKey,
|
|
35
28
|
syncRouteSessionSelection,
|
|
36
|
-
view,
|
|
37
29
|
]);
|
|
38
30
|
}
|
|
39
31
|
type ChatPageLayoutProps = {
|
|
@@ -51,7 +43,11 @@ export function ChatPageLayout({ view, confirmDialog }: ChatPageLayoutProps) {
|
|
|
51
43
|
isMobile ? <ChatMobileShell /> : <ChatConversationPanel />
|
|
52
44
|
) : (
|
|
53
45
|
<section className="flex-1 min-h-0 overflow-hidden bg-background">
|
|
54
|
-
{view === "
|
|
46
|
+
{view === "inbox" ? (
|
|
47
|
+
<div className="mx-auto flex h-full min-h-0 w-full max-w-[min(1180px,100%)] flex-col py-4 sm:px-6 sm:py-5">
|
|
48
|
+
<InboxPage />
|
|
49
|
+
</div>
|
|
50
|
+
) : view === "cron" ? (
|
|
55
51
|
<div className="h-full overflow-auto custom-scrollbar">
|
|
56
52
|
<div className="mx-auto w-full max-w-[min(1120px,100%)] px-4 py-4 sm:px-6 sm:py-5">
|
|
57
53
|
<CronConfig />
|