@nextclaw/ui 0.14.1 → 0.14.2
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 +29 -0
- package/dist/assets/{api-DPcvCLfa.js → api-a14tR_z1.js} +7 -7
- package/dist/assets/channels-list-page-CFGkm4DT.js +8 -0
- package/dist/assets/chat-page-CEdavJXc.js +105 -0
- package/dist/assets/{config-split-page-BCJhqdK9.js → config-split-page-B5b9B0uE.js} +1 -1
- package/dist/assets/{confirm-dialog-CWh5FfK2.js → confirm-dialog-ClH_Ec7n.js} +1 -1
- package/dist/assets/{desktop-update-config-DsRhBizi.js → desktop-update-config-DPNDnVpk.js} +1 -1
- package/dist/assets/{dist-DPrgcEC0.js → dist-BtCS2Ucq.js} +1 -1
- package/dist/assets/{dist-DsE4OsSS.js → dist-CLGGLm8b.js} +1 -1
- package/dist/assets/doc-browser-5nMWYKBD.js +1 -0
- package/dist/assets/doc-browser-BJRLhztv.js +1 -0
- package/dist/assets/doc-browser-context-D4JwMMxK.js +1 -0
- package/dist/assets/{ellipsis-B81FStLm.js → ellipsis-CtJXjIol.js} +1 -1
- package/dist/assets/{external-link-C_dmG-WJ.js → external-link-CdJiG13p.js} +1 -1
- package/dist/assets/index-BDN1zzMQ.js +105 -0
- package/dist/assets/index-g3uSZXoc.css +1 -0
- package/dist/assets/mcp-marketplace-page-BU0kIjhR.js +40 -0
- package/dist/assets/mcp-marketplace-page-sMNFSq_T.js +1 -0
- package/dist/assets/middleware-CkFT3R4v.js +1 -0
- package/dist/assets/model-config-page-D6hYXROd.js +1 -0
- package/dist/assets/plus-DPjb0piO.js +1 -0
- package/dist/assets/provider-scoped-model-input-DAX2HgmR.js +1 -0
- package/dist/assets/providers-config-page-BzAeaACA.js +1 -0
- package/dist/assets/{react-sZz5HSCm.js → react-C6cZgP44.js} +1 -1
- package/dist/assets/remote-B0G80uPr.js +1 -0
- package/dist/assets/rotate-cw-DnEB1Rju.js +1 -0
- package/dist/assets/runtime-config-page-C5xKaV3i.js +1 -0
- package/dist/assets/{save-MzjoiHri.js → save-DKA0x4zk.js} +1 -1
- package/dist/assets/{search-B29dE4GN.js → search-BNlcoj5c.js} +1 -1
- package/dist/assets/{search-config-page-Dl5sZzob.js → search-config-page-Cq9uJ-N8.js} +1 -1
- package/dist/assets/{secrets-config-page-C_l-IFCK.js → secrets-config-page-CxSbZLBo.js} +2 -2
- package/dist/assets/{select-BKmh05hd.js → select-CvTlDZTQ.js} +1 -1
- package/dist/assets/{tag-chip-BS_7Ueom.js → tag-chip-8ffF2y2C.js} +1 -1
- package/dist/assets/use-config-DmT0o1gv.js +1 -0
- package/dist/assets/x-BiSSpgqB.js +1 -0
- package/dist/index.html +15 -14
- package/package.json +9 -9
- package/src/app/components/app-presenter-provider.tsx +11 -2
- package/src/app/components/layout/__tests__/app-layout.test.tsx +11 -2
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +110 -79
- package/src/app/components/layout/app-layout.tsx +18 -4
- package/src/app/components/layout/sidebar-items.tsx +194 -71
- package/src/app/components/layout/sidebar-rail.styles.ts +15 -0
- package/src/app/components/layout/sidebar.tsx +340 -83
- package/src/app/managers/viewport-layout.manager.ts +10 -0
- package/src/app/stores/viewport-layout.store.ts +39 -3
- package/src/features/agents/components/__tests__/agents-page.test.tsx +62 -39
- package/src/features/agents/components/agents-page.tsx +10 -6
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +145 -0
- package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +69 -762
- package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +132 -0
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +22 -15
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +14 -15
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +18 -18
- package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +10 -4
- package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +52 -11
- package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +16 -14
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +247 -0
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +80 -7
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +82 -25
- package/src/features/chat/components/layout/chat-sidebar.tsx +59 -92
- package/src/features/chat/components/providers/chat-presenter.provider.tsx +0 -6
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +289 -0
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +427 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +204 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +66 -0
- package/src/features/chat/features/{input/hooks/use-chat-input-bar-query-state.ts → conversation/hooks/use-session-conversation-input-query.ts} +32 -17
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +281 -0
- package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +158 -0
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +56 -0
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +117 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
- package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
- package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
- package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
- package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
- package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
- package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
- package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
- package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +93 -12
- package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
- package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
- package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +2 -2
- package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +2 -2
- package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
- package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
- package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +2 -2
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +2 -0
- package/src/features/chat/features/session-type/components/chat-session-type-menu.tsx +2 -2
- package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +2 -2
- package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
- package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -2
- package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +2 -2
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +2 -2
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
- package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
- package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
- package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
- package/src/features/chat/index.ts +0 -1
- package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
- package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
- package/src/features/chat/managers/chat-thread.manager.ts +108 -34
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
- package/src/features/chat/presenters/chat.presenter.ts +2 -19
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
- package/src/features/chat/stores/chat-thread.store.ts +42 -26
- package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
- package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
- package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
- package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
- package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
- package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
- package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
- package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
- package/src/shared/lib/ui-document-title/index.ts +50 -21
- package/dist/assets/channels-list-page-D6jtziu4.js +0 -8
- package/dist/assets/chat-page-BBnNJoKG.js +0 -105
- package/dist/assets/doc-browser-C8xOF_9F.js +0 -1
- package/dist/assets/doc-browser-DgQedeAn.js +0 -1
- package/dist/assets/doc-browser-context-B46tQGmO.js +0 -1
- package/dist/assets/index-7o1WdUbU.js +0 -103
- package/dist/assets/index-DQWmEWzk.css +0 -1
- package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +0 -1
- package/dist/assets/mcp-marketplace-page-Dzull_4-.js +0 -40
- package/dist/assets/model-config-page-Cv0O3BIj.js +0 -1
- package/dist/assets/plus-Bnt56oqG.js +0 -1
- package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +0 -1
- package/dist/assets/providers-config-page-CPDz0Lxc.js +0 -1
- package/dist/assets/remote-B0LMs8u7.js +0 -1
- package/dist/assets/rotate-cw-BfyG_KEF.js +0 -1
- package/dist/assets/runtime-config-page-q0fvv8ME.js +0 -1
- package/dist/assets/use-config-DZkBWIdL.js +0 -1
- package/dist/assets/x-CW2KO-Hz.js +0 -1
- package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
- package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
- package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
- package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
- package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
- package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
- package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
- package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
- package/src/features/chat/managers/chat-input.manager.ts +0 -430
- package/src/features/chat/managers/chat-run.manager.ts +0 -110
- package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
- package/src/features/chat/stores/chat-input.store.ts +0 -104
- /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-BkYBc7z0.js} +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const SIDEBAR_RAIL_WIDTH_PX = 56;
|
|
2
|
+
export const SIDEBAR_RAIL_WIDTH_CLASS = "w-[56px]";
|
|
3
|
+
export const SIDEBAR_RAIL_PADDING_X_CLASS = "px-2";
|
|
4
|
+
export const SIDEBAR_RAIL_CONTROL_CLASS = "h-9 w-9 rounded-xl";
|
|
5
|
+
export const SIDEBAR_RAIL_ICON_CLASS = "h-4 w-4";
|
|
6
|
+
export const SIDEBAR_RAIL_STACK_CLASS = "space-y-1";
|
|
7
|
+
export const SIDEBAR_RAIL_ITEM_GAP_CLASS = "gap-1";
|
|
8
|
+
|
|
9
|
+
export const SIDEBAR_RAIL_SURFACE_CLASS =
|
|
10
|
+
"text-gray-600 transition-colors duration-base hover:bg-gray-200/60 hover:text-gray-900 active:bg-gray-200/75 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35";
|
|
11
|
+
|
|
12
|
+
export const SIDEBAR_RAIL_ACTIVE_SURFACE_CLASS = "bg-gray-200/70 text-gray-900";
|
|
13
|
+
|
|
14
|
+
export const SIDEBAR_RAIL_PRIMARY_SURFACE_CLASS =
|
|
15
|
+
"bg-primary/10 text-primary transition-colors duration-base hover:bg-primary/15 hover:text-primary-700 active:bg-primary/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35";
|
|
@@ -1,28 +1,253 @@
|
|
|
1
|
-
import { cn } from
|
|
2
|
-
import { t } from
|
|
3
|
-
import { THEME_OPTIONS, type UiTheme } from
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import { cn } from "@/shared/lib/utils";
|
|
2
|
+
import { t } from "@/shared/lib/i18n";
|
|
3
|
+
import { THEME_OPTIONS, type UiTheme } from "@/shared/lib/theme";
|
|
4
|
+
import {
|
|
5
|
+
MessageCircle,
|
|
6
|
+
BookOpen,
|
|
7
|
+
BrainCircuit,
|
|
8
|
+
AlarmClock,
|
|
9
|
+
Languages,
|
|
10
|
+
Palette,
|
|
11
|
+
KeyRound,
|
|
12
|
+
Settings,
|
|
13
|
+
ArrowLeft,
|
|
14
|
+
Bot,
|
|
15
|
+
PanelLeftClose,
|
|
16
|
+
PanelLeftOpen,
|
|
17
|
+
type LucideIcon,
|
|
18
|
+
} from "lucide-react";
|
|
19
|
+
import { NavLink } from "react-router-dom";
|
|
20
|
+
import { useDocBrowser } from "@/shared/components/doc-browser";
|
|
21
|
+
import { BrandHeader } from "@/shared/components/common/brand-header";
|
|
22
|
+
import {
|
|
23
|
+
SidebarActionItem,
|
|
24
|
+
SidebarNavLinkItem,
|
|
25
|
+
SidebarSelectItem,
|
|
26
|
+
} from "@/app/components/layout/sidebar-items";
|
|
27
|
+
import { useTheme } from "@/app/components/theme-provider";
|
|
28
|
+
import { SelectItem } from "@/shared/components/ui/select";
|
|
29
|
+
import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
|
|
30
|
+
import { useAppPresenter } from "@/app/components/app-presenter-provider";
|
|
31
|
+
import { useRemoteStatus } from "@/features/remote";
|
|
32
|
+
import { getSettingsNavItems } from "@/app/configs/app-navigation.config";
|
|
33
|
+
import { useLanguagePreference } from "@/features/settings";
|
|
34
|
+
import { viewportLayoutManager } from "@/app/managers/viewport-layout.manager";
|
|
35
|
+
import { useViewportLayoutStore } from "@/app/stores/viewport-layout.store";
|
|
36
|
+
import {
|
|
37
|
+
SIDEBAR_RAIL_CONTROL_CLASS,
|
|
38
|
+
SIDEBAR_RAIL_ICON_CLASS,
|
|
39
|
+
SIDEBAR_RAIL_ITEM_GAP_CLASS,
|
|
40
|
+
SIDEBAR_RAIL_PADDING_X_CLASS,
|
|
41
|
+
SIDEBAR_RAIL_STACK_CLASS,
|
|
42
|
+
SIDEBAR_RAIL_SURFACE_CLASS,
|
|
43
|
+
SIDEBAR_RAIL_WIDTH_CLASS,
|
|
44
|
+
} from "@/app/components/layout/sidebar-rail.styles";
|
|
45
|
+
|
|
46
|
+
type SidebarMode = "main" | "settings";
|
|
17
47
|
|
|
18
48
|
type SidebarProps = {
|
|
19
49
|
mode: SidebarMode;
|
|
20
50
|
};
|
|
21
51
|
|
|
52
|
+
type SidebarNavItem = {
|
|
53
|
+
target: string;
|
|
54
|
+
label: string;
|
|
55
|
+
icon: LucideIcon;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function SidebarCollapseToggle({
|
|
59
|
+
isCollapsed,
|
|
60
|
+
onToggle,
|
|
61
|
+
}: {
|
|
62
|
+
isCollapsed: boolean;
|
|
63
|
+
onToggle: () => void;
|
|
64
|
+
}) {
|
|
65
|
+
const label = isCollapsed ? t("sidebarExpand") : t("sidebarCollapse");
|
|
66
|
+
const Icon = isCollapsed ? PanelLeftOpen : PanelLeftClose;
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<IconActionButton
|
|
70
|
+
icon={<Icon className={SIDEBAR_RAIL_ICON_CLASS} />}
|
|
71
|
+
label={label}
|
|
72
|
+
className={cn(SIDEBAR_RAIL_SURFACE_CLASS, isCollapsed && SIDEBAR_RAIL_CONTROL_CLASS)}
|
|
73
|
+
onClick={onToggle}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function SettingsSidebarHeader({
|
|
79
|
+
isCollapsed,
|
|
80
|
+
onToggle,
|
|
81
|
+
}: {
|
|
82
|
+
isCollapsed: boolean;
|
|
83
|
+
onToggle: () => void;
|
|
84
|
+
}) {
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
className={cn("shrink-0", isCollapsed ? "px-0 py-1.5" : "px-2 py-2")}
|
|
88
|
+
>
|
|
89
|
+
<div
|
|
90
|
+
className={cn(
|
|
91
|
+
"flex items-center py-1",
|
|
92
|
+
isCollapsed
|
|
93
|
+
? cn("flex-col px-0", SIDEBAR_RAIL_ITEM_GAP_CLASS)
|
|
94
|
+
: "gap-2 px-1",
|
|
95
|
+
)}
|
|
96
|
+
data-testid="settings-sidebar-header"
|
|
97
|
+
>
|
|
98
|
+
{isCollapsed ? (
|
|
99
|
+
<SidebarCollapseToggle
|
|
100
|
+
isCollapsed={isCollapsed}
|
|
101
|
+
onToggle={onToggle}
|
|
102
|
+
/>
|
|
103
|
+
) : null}
|
|
104
|
+
<NavLink
|
|
105
|
+
to="/chat"
|
|
106
|
+
aria-label={t("backToMain")}
|
|
107
|
+
className={cn(
|
|
108
|
+
"group inline-flex min-w-0 items-center rounded-lg text-[12px] font-medium text-gray-500 transition-colors hover:bg-gray-200/60 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35",
|
|
109
|
+
isCollapsed
|
|
110
|
+
? cn(
|
|
111
|
+
SIDEBAR_RAIL_CONTROL_CLASS,
|
|
112
|
+
SIDEBAR_RAIL_SURFACE_CLASS,
|
|
113
|
+
"justify-center p-0",
|
|
114
|
+
)
|
|
115
|
+
: "gap-1.5 px-1 py-1",
|
|
116
|
+
)}
|
|
117
|
+
>
|
|
118
|
+
<ArrowLeft
|
|
119
|
+
className={cn(
|
|
120
|
+
isCollapsed ? SIDEBAR_RAIL_ICON_CLASS : "h-3.5 w-3.5",
|
|
121
|
+
"shrink-0 text-gray-500 group-hover:text-gray-800",
|
|
122
|
+
)}
|
|
123
|
+
/>
|
|
124
|
+
<span className={isCollapsed ? "sr-only" : "truncate"}>
|
|
125
|
+
{t("backToMain")}
|
|
126
|
+
</span>
|
|
127
|
+
</NavLink>
|
|
128
|
+
{!isCollapsed ? (
|
|
129
|
+
<>
|
|
130
|
+
<span
|
|
131
|
+
className="h-4 w-px shrink-0 bg-[#dddfe6]"
|
|
132
|
+
aria-hidden="true"
|
|
133
|
+
/>
|
|
134
|
+
<h1 className="truncate text-[15px] font-semibold tracking-[-0.01em] text-gray-800">
|
|
135
|
+
{t("settings")}
|
|
136
|
+
</h1>
|
|
137
|
+
<div className="ml-auto">
|
|
138
|
+
<SidebarCollapseToggle
|
|
139
|
+
isCollapsed={isCollapsed}
|
|
140
|
+
onToggle={onToggle}
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
</>
|
|
144
|
+
) : null}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function MainSidebarHeader({
|
|
151
|
+
isCollapsed,
|
|
152
|
+
onToggle,
|
|
153
|
+
}: {
|
|
154
|
+
isCollapsed: boolean;
|
|
155
|
+
onToggle: () => void;
|
|
156
|
+
}) {
|
|
157
|
+
return (
|
|
158
|
+
<div
|
|
159
|
+
className={cn(
|
|
160
|
+
"shrink-0",
|
|
161
|
+
isCollapsed ? "flex justify-center px-0 py-1.5" : "px-2 py-2",
|
|
162
|
+
)}
|
|
163
|
+
>
|
|
164
|
+
{isCollapsed ? (
|
|
165
|
+
<SidebarCollapseToggle isCollapsed={isCollapsed} onToggle={onToggle} />
|
|
166
|
+
) : (
|
|
167
|
+
<div className="flex items-center gap-2">
|
|
168
|
+
<BrandHeader className="flex min-w-0 flex-1 items-center gap-2.5 cursor-pointer" />
|
|
169
|
+
<SidebarCollapseToggle
|
|
170
|
+
isCollapsed={isCollapsed}
|
|
171
|
+
onToggle={onToggle}
|
|
172
|
+
/>
|
|
173
|
+
</div>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function SidebarHeader({
|
|
180
|
+
isSettingsMode,
|
|
181
|
+
isCollapsed,
|
|
182
|
+
onToggle,
|
|
183
|
+
}: {
|
|
184
|
+
isSettingsMode: boolean;
|
|
185
|
+
isCollapsed: boolean;
|
|
186
|
+
onToggle: () => void;
|
|
187
|
+
}) {
|
|
188
|
+
return isSettingsMode ? (
|
|
189
|
+
<SettingsSidebarHeader isCollapsed={isCollapsed} onToggle={onToggle} />
|
|
190
|
+
) : (
|
|
191
|
+
<MainSidebarHeader isCollapsed={isCollapsed} onToggle={onToggle} />
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function SidebarNavigation({
|
|
196
|
+
isSettingsMode,
|
|
197
|
+
isCollapsed,
|
|
198
|
+
items,
|
|
199
|
+
density,
|
|
200
|
+
}: {
|
|
201
|
+
isSettingsMode: boolean;
|
|
202
|
+
isCollapsed: boolean;
|
|
203
|
+
items: SidebarNavItem[];
|
|
204
|
+
density: "default" | "compact";
|
|
205
|
+
}) {
|
|
206
|
+
return (
|
|
207
|
+
<nav
|
|
208
|
+
className={cn(
|
|
209
|
+
"custom-scrollbar min-h-0 flex-1 overflow-y-auto",
|
|
210
|
+
isCollapsed ? "pr-0" : "pr-1",
|
|
211
|
+
)}
|
|
212
|
+
>
|
|
213
|
+
<ul
|
|
214
|
+
className={cn(
|
|
215
|
+
isCollapsed
|
|
216
|
+
? cn(SIDEBAR_RAIL_STACK_CLASS, "pb-3")
|
|
217
|
+
: isSettingsMode
|
|
218
|
+
? "space-y-0.5 pb-3"
|
|
219
|
+
: "space-y-1 pb-4",
|
|
220
|
+
)}
|
|
221
|
+
>
|
|
222
|
+
{items.map((item) => {
|
|
223
|
+
return (
|
|
224
|
+
<li
|
|
225
|
+
key={item.target}
|
|
226
|
+
className={isCollapsed ? "flex justify-center" : undefined}
|
|
227
|
+
>
|
|
228
|
+
<SidebarNavLinkItem
|
|
229
|
+
to={item.target}
|
|
230
|
+
label={item.label}
|
|
231
|
+
icon={item.icon}
|
|
232
|
+
density={density}
|
|
233
|
+
collapsed={isCollapsed}
|
|
234
|
+
/>
|
|
235
|
+
</li>
|
|
236
|
+
);
|
|
237
|
+
})}
|
|
238
|
+
</ul>
|
|
239
|
+
</nav>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
22
243
|
export function Sidebar({ mode }: SidebarProps) {
|
|
23
244
|
const presenter = useAppPresenter();
|
|
24
245
|
const docBrowser = useDocBrowser();
|
|
25
246
|
const remoteStatus = useRemoteStatus();
|
|
247
|
+
const isCollapsed = useViewportLayoutStore(
|
|
248
|
+
(state) => state.isSidebarCollapsed,
|
|
249
|
+
);
|
|
250
|
+
const toggleCollapsed = viewportLayoutManager.toggleSidebarCollapsed;
|
|
26
251
|
const {
|
|
27
252
|
currentLanguage,
|
|
28
253
|
currentLanguageLabel,
|
|
@@ -30,8 +255,11 @@ export function Sidebar({ mode }: SidebarProps) {
|
|
|
30
255
|
selectLanguage,
|
|
31
256
|
} = useLanguagePreference();
|
|
32
257
|
const { theme, setTheme } = useTheme();
|
|
33
|
-
const isSettingsMode = mode ===
|
|
34
|
-
const currentThemeLabel = t(
|
|
258
|
+
const isSettingsMode = mode === "settings";
|
|
259
|
+
const currentThemeLabel = t(
|
|
260
|
+
THEME_OPTIONS.find((option) => option.value === theme)?.labelKey ??
|
|
261
|
+
"themeWarm",
|
|
262
|
+
);
|
|
35
263
|
const accountEmail = remoteStatus.data?.account.email?.trim();
|
|
36
264
|
const accountConnected = Boolean(remoteStatus.data?.account.loggedIn);
|
|
37
265
|
|
|
@@ -43,138 +271,167 @@ export function Sidebar({ mode }: SidebarProps) {
|
|
|
43
271
|
};
|
|
44
272
|
|
|
45
273
|
// Core navigation items - primary features
|
|
46
|
-
const mainNavItems = [
|
|
274
|
+
const mainNavItems: SidebarNavItem[] = [
|
|
47
275
|
{
|
|
48
|
-
target:
|
|
49
|
-
label: t(
|
|
276
|
+
target: "/chat",
|
|
277
|
+
label: t("chat"),
|
|
50
278
|
icon: MessageCircle,
|
|
51
279
|
},
|
|
52
280
|
{
|
|
53
|
-
target:
|
|
54
|
-
label: t(
|
|
281
|
+
target: "/chat/cron",
|
|
282
|
+
label: t("cron"),
|
|
55
283
|
icon: AlarmClock,
|
|
56
284
|
},
|
|
57
285
|
{
|
|
58
|
-
target:
|
|
59
|
-
label: t(
|
|
286
|
+
target: "/chat/skills",
|
|
287
|
+
label: t("marketplaceFilterSkills"),
|
|
60
288
|
icon: BrainCircuit,
|
|
61
289
|
},
|
|
62
290
|
{
|
|
63
|
-
target:
|
|
64
|
-
label: t(
|
|
291
|
+
target: "/agents",
|
|
292
|
+
label: t("agentsPageTitle"),
|
|
65
293
|
icon: Bot,
|
|
66
|
-
}
|
|
294
|
+
},
|
|
67
295
|
];
|
|
68
296
|
|
|
69
297
|
const settingsNavItems = getSettingsNavItems(t);
|
|
70
298
|
const navItems = isSettingsMode ? settingsNavItems : mainNavItems;
|
|
71
|
-
const sidebarDensity = isSettingsMode ?
|
|
299
|
+
const sidebarDensity = isSettingsMode ? "compact" : "default";
|
|
72
300
|
|
|
73
301
|
return (
|
|
74
|
-
<aside
|
|
75
|
-
{
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
>
|
|
81
|
-
<NavLink
|
|
82
|
-
to="/chat"
|
|
83
|
-
className="group inline-flex min-w-0 items-center gap-1.5 rounded-lg px-1 py-1 text-[12px] font-medium text-gray-500 transition-colors hover:bg-gray-200/60 hover:text-gray-900"
|
|
84
|
-
>
|
|
85
|
-
<ArrowLeft className="h-3.5 w-3.5 shrink-0 text-gray-400 group-hover:text-gray-700" />
|
|
86
|
-
<span className="truncate">{t('backToMain')}</span>
|
|
87
|
-
</NavLink>
|
|
88
|
-
<span className="h-4 w-px shrink-0 bg-[#dddfe6]" aria-hidden="true" />
|
|
89
|
-
<h1 className="truncate text-[15px] font-semibold tracking-[-0.01em] text-gray-800">{t('settings')}</h1>
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
) : (
|
|
93
|
-
<div className="shrink-0 px-2 py-2.5">
|
|
94
|
-
<BrandHeader className="flex items-center gap-2.5 cursor-pointer" />
|
|
95
|
-
</div>
|
|
302
|
+
<aside
|
|
303
|
+
className={cn(
|
|
304
|
+
"shrink-0 flex h-full min-h-0 flex-col overflow-hidden bg-secondary pb-6 transition-[width] duration-200 ease-out",
|
|
305
|
+
isCollapsed
|
|
306
|
+
? cn(SIDEBAR_RAIL_WIDTH_CLASS, SIDEBAR_RAIL_PADDING_X_CLASS)
|
|
307
|
+
: "w-[240px] px-4",
|
|
96
308
|
)}
|
|
309
|
+
data-sidebar-collapsed={isCollapsed ? "true" : "false"}
|
|
310
|
+
>
|
|
311
|
+
<SidebarHeader
|
|
312
|
+
isSettingsMode={isSettingsMode}
|
|
313
|
+
isCollapsed={isCollapsed}
|
|
314
|
+
onToggle={toggleCollapsed}
|
|
315
|
+
/>
|
|
97
316
|
|
|
98
317
|
<div className="flex min-h-0 flex-1 flex-col">
|
|
99
318
|
{/* Navigation */}
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
to={item.target}
|
|
107
|
-
label={item.label}
|
|
108
|
-
icon={item.icon}
|
|
109
|
-
density={sidebarDensity}
|
|
110
|
-
/>
|
|
111
|
-
</li>
|
|
112
|
-
);
|
|
113
|
-
})}
|
|
114
|
-
</ul>
|
|
115
|
-
</nav>
|
|
319
|
+
<SidebarNavigation
|
|
320
|
+
isSettingsMode={isSettingsMode}
|
|
321
|
+
isCollapsed={isCollapsed}
|
|
322
|
+
items={navItems}
|
|
323
|
+
density={sidebarDensity}
|
|
324
|
+
/>
|
|
116
325
|
|
|
117
326
|
{/* Footer actions stay reachable while the nav scrolls independently. */}
|
|
118
|
-
<div
|
|
327
|
+
<div
|
|
328
|
+
className={cn(
|
|
329
|
+
"shrink-0 border-t border-[#dde0ea] bg-secondary",
|
|
330
|
+
isCollapsed
|
|
331
|
+
? "mt-2 pt-2"
|
|
332
|
+
: isSettingsMode
|
|
333
|
+
? "mt-2 pt-3"
|
|
334
|
+
: "mt-3 pt-3",
|
|
335
|
+
)}
|
|
336
|
+
>
|
|
119
337
|
{isSettingsMode ? (
|
|
120
338
|
<SidebarActionItem
|
|
121
339
|
onClick={() => presenter.accountManager.openAccountPanel()}
|
|
122
340
|
icon={KeyRound}
|
|
123
|
-
label={t(
|
|
341
|
+
label={t("remoteAccountEntryManage")}
|
|
124
342
|
density="compact"
|
|
125
|
-
className="mb-1.5"
|
|
126
|
-
trailing={
|
|
343
|
+
className={isCollapsed ? "mb-1" : "mb-1.5"}
|
|
344
|
+
trailing={
|
|
345
|
+
accountConnected
|
|
346
|
+
? accountEmail || t("remoteAccountEntryConnected")
|
|
347
|
+
: t("remoteAccountEntryDisconnected")
|
|
348
|
+
}
|
|
127
349
|
trailingClassName="max-w-[92px] truncate text-right"
|
|
128
350
|
testId="settings-sidebar-account-entry"
|
|
129
351
|
trailingTestId="settings-sidebar-account-status"
|
|
352
|
+
collapsed={isCollapsed}
|
|
130
353
|
/>
|
|
131
354
|
) : null}
|
|
132
|
-
{mode ===
|
|
133
|
-
<div
|
|
355
|
+
{mode === "main" && (
|
|
356
|
+
<div
|
|
357
|
+
className={cn(
|
|
358
|
+
isCollapsed ? "mb-1" : "mb-2",
|
|
359
|
+
isCollapsed ? "flex justify-center" : undefined,
|
|
360
|
+
)}
|
|
361
|
+
>
|
|
134
362
|
<SidebarNavLinkItem
|
|
135
363
|
to="/settings"
|
|
136
|
-
label={t(
|
|
364
|
+
label={t("settings")}
|
|
137
365
|
icon={Settings}
|
|
366
|
+
collapsed={isCollapsed}
|
|
138
367
|
/>
|
|
139
368
|
</div>
|
|
140
369
|
)}
|
|
141
|
-
<div
|
|
370
|
+
<div
|
|
371
|
+
className={cn(
|
|
372
|
+
isCollapsed ? "mb-1" : "mb-2",
|
|
373
|
+
isCollapsed ? "flex justify-center" : undefined,
|
|
374
|
+
)}
|
|
375
|
+
>
|
|
142
376
|
<SidebarSelectItem
|
|
143
377
|
value={theme}
|
|
144
378
|
onValueChange={(value) => handleThemeSwitch(value as UiTheme)}
|
|
145
379
|
icon={Palette}
|
|
146
|
-
label={t(
|
|
380
|
+
label={t("theme")}
|
|
147
381
|
valueLabel={currentThemeLabel}
|
|
148
382
|
density={sidebarDensity}
|
|
383
|
+
collapsed={isCollapsed}
|
|
149
384
|
>
|
|
150
385
|
{THEME_OPTIONS.map((option) => (
|
|
151
|
-
<SelectItem
|
|
386
|
+
<SelectItem
|
|
387
|
+
key={option.value}
|
|
388
|
+
value={option.value}
|
|
389
|
+
className="text-xs"
|
|
390
|
+
>
|
|
152
391
|
{t(option.labelKey)}
|
|
153
392
|
</SelectItem>
|
|
154
393
|
))}
|
|
155
394
|
</SidebarSelectItem>
|
|
156
395
|
</div>
|
|
157
|
-
<div
|
|
396
|
+
<div
|
|
397
|
+
className={cn(
|
|
398
|
+
isCollapsed ? "mb-1" : "mb-2",
|
|
399
|
+
isCollapsed ? "flex justify-center" : undefined,
|
|
400
|
+
)}
|
|
401
|
+
>
|
|
158
402
|
<SidebarSelectItem
|
|
159
403
|
value={currentLanguage}
|
|
160
|
-
onValueChange={(value) =>
|
|
404
|
+
onValueChange={(value) =>
|
|
405
|
+
selectLanguage(value as typeof currentLanguage)
|
|
406
|
+
}
|
|
161
407
|
icon={Languages}
|
|
162
|
-
label={t(
|
|
408
|
+
label={t("language")}
|
|
163
409
|
valueLabel={currentLanguageLabel}
|
|
164
410
|
density={sidebarDensity}
|
|
411
|
+
collapsed={isCollapsed}
|
|
165
412
|
>
|
|
166
413
|
{languageOptions.map((option) => (
|
|
167
|
-
<SelectItem
|
|
414
|
+
<SelectItem
|
|
415
|
+
key={option.value}
|
|
416
|
+
value={option.value}
|
|
417
|
+
className="text-xs"
|
|
418
|
+
>
|
|
168
419
|
{option.label}
|
|
169
420
|
</SelectItem>
|
|
170
421
|
))}
|
|
171
422
|
</SidebarSelectItem>
|
|
172
423
|
</div>
|
|
173
424
|
<SidebarActionItem
|
|
174
|
-
onClick={() =>
|
|
425
|
+
onClick={() =>
|
|
426
|
+
docBrowser.open(undefined, {
|
|
427
|
+
kind: "docs",
|
|
428
|
+
title: t("docBrowserHelp"),
|
|
429
|
+
})
|
|
430
|
+
}
|
|
175
431
|
icon={BookOpen}
|
|
176
|
-
label={t(
|
|
432
|
+
label={t("docBrowserHelp")}
|
|
177
433
|
density={sidebarDensity}
|
|
434
|
+
collapsed={isCollapsed}
|
|
178
435
|
/>
|
|
179
436
|
</div>
|
|
180
437
|
</div>
|
|
@@ -47,6 +47,16 @@ export class ViewportLayoutManager {
|
|
|
47
47
|
|
|
48
48
|
getSnapshot = (): ViewportLayoutSnapshot => useViewportLayoutStore.getState();
|
|
49
49
|
|
|
50
|
+
setSidebarCollapsed = (isSidebarCollapsed: boolean) => {
|
|
51
|
+
useViewportLayoutStore.setState({ isSidebarCollapsed });
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
toggleSidebarCollapsed = () => {
|
|
55
|
+
useViewportLayoutStore.setState((state) => ({
|
|
56
|
+
isSidebarCollapsed: !state.isSidebarCollapsed,
|
|
57
|
+
}));
|
|
58
|
+
};
|
|
59
|
+
|
|
50
60
|
resetForTests = () => {
|
|
51
61
|
this.consumerCount = 0;
|
|
52
62
|
this.stop();
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { create } from "zustand";
|
|
2
|
+
import { createJSONStorage, persist } from "zustand/middleware";
|
|
2
3
|
|
|
3
4
|
export type ViewportLayoutMode = "mobile" | "desktop";
|
|
4
5
|
|
|
5
6
|
export type ViewportLayoutSnapshot = {
|
|
6
7
|
mode: ViewportLayoutMode;
|
|
7
8
|
width: number | null;
|
|
9
|
+
isSidebarCollapsed: boolean;
|
|
8
10
|
};
|
|
9
11
|
|
|
10
12
|
export const MOBILE_VIEWPORT_MAX_WIDTH = 767;
|
|
13
|
+
const VIEWPORT_LAYOUT_STORAGE_KEY = "nextclaw.app.viewport-layout";
|
|
14
|
+
const VIEWPORT_LAYOUT_STORAGE_VERSION = 1;
|
|
15
|
+
|
|
16
|
+
type PersistedViewportLayoutStore = {
|
|
17
|
+
isSidebarCollapsed?: unknown;
|
|
18
|
+
};
|
|
11
19
|
|
|
12
20
|
export function resolveViewportLayoutMode(
|
|
13
21
|
width: number | null | undefined,
|
|
@@ -20,13 +28,41 @@ export function resolveViewportLayoutMode(
|
|
|
20
28
|
|
|
21
29
|
export function createInitialViewportLayoutSnapshot(): ViewportLayoutSnapshot {
|
|
22
30
|
const width =
|
|
23
|
-
typeof window === "undefined" ? null : window.innerWidth ?? null;
|
|
31
|
+
typeof window === "undefined" ? null : (window.innerWidth ?? null);
|
|
24
32
|
return {
|
|
25
33
|
mode: resolveViewportLayoutMode(width),
|
|
26
34
|
width,
|
|
35
|
+
isSidebarCollapsed: false,
|
|
27
36
|
};
|
|
28
37
|
}
|
|
29
38
|
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
function resolvePersistedSidebarCollapsed(
|
|
40
|
+
persistedState: unknown,
|
|
41
|
+
): boolean | null {
|
|
42
|
+
if (!persistedState || typeof persistedState !== "object") {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const { isSidebarCollapsed } = persistedState as PersistedViewportLayoutStore;
|
|
46
|
+
return typeof isSidebarCollapsed === "boolean" ? isSidebarCollapsed : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const useViewportLayoutStore = create<ViewportLayoutSnapshot>()(
|
|
50
|
+
persist(() => createInitialViewportLayoutSnapshot(), {
|
|
51
|
+
name: VIEWPORT_LAYOUT_STORAGE_KEY,
|
|
52
|
+
version: VIEWPORT_LAYOUT_STORAGE_VERSION,
|
|
53
|
+
storage: createJSONStorage(() => window.localStorage),
|
|
54
|
+
partialize: (state): PersistedViewportLayoutStore => ({
|
|
55
|
+
isSidebarCollapsed: state.isSidebarCollapsed,
|
|
56
|
+
}),
|
|
57
|
+
merge: (persistedState, currentState) => {
|
|
58
|
+
const isSidebarCollapsed =
|
|
59
|
+
resolvePersistedSidebarCollapsed(persistedState);
|
|
60
|
+
return isSidebarCollapsed === null
|
|
61
|
+
? currentState
|
|
62
|
+
: {
|
|
63
|
+
...currentState,
|
|
64
|
+
isSidebarCollapsed,
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
32
68
|
);
|