@nextclaw/ui 0.26.3 → 0.26.5
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 +34 -0
- package/dist/.vite/manifest.json +362 -362
- package/dist/assets/{account-panel-DIdHjKG-.js → account-panel-BAH7FW6z.js} +1 -1
- package/dist/assets/{api-DkLJrSCd.js → api-DjiZvIa0.js} +2 -2
- package/dist/assets/app-presenter-provider-CCRFsFYr.js +232 -0
- package/dist/assets/{appearance-settings-page-DI8aDY2G.js → appearance-settings-page-CdP86QJ7.js} +1 -1
- package/dist/assets/{book-open-BPMkKSci.js → book-open-Bcnn2_uV.js} +1 -1
- package/dist/assets/card-Cqs73hGI.js +1 -0
- package/dist/assets/{channels-list-page-CCIaTQu7.js → channels-list-page-5qGagKuE.js} +2 -2
- package/dist/assets/{circle-dashed-B48a0t6p.js → circle-dashed-EY1ScSMe.js} +1 -1
- package/dist/assets/{client-imrVphQb.js → client-CH7w6yRV.js} +1 -1
- package/dist/assets/config-split-page-CuEGQL8Z.js +1 -0
- package/dist/assets/{confirm-dialog-10Qb3dpZ.js → confirm-dialog-CxfecBzO.js} +1 -1
- package/dist/assets/{context-menu-CRqH2SEk.js → context-menu-BsCj2fRs.js} +1 -1
- package/dist/assets/{createLucideIcon-B4xj2I2g.js → createLucideIcon-Cfo2ySi5.js} +1 -1
- package/dist/assets/{desktop-capabilities-page-CFcT2g8-.js → desktop-capabilities-page-BDYITStl.js} +1 -1
- package/dist/assets/{desktop-update-config-CRJd_An1.js → desktop-update-config-e-zhkJuS.js} +1 -1
- package/dist/assets/{dialog-BgL3vKJQ.js → dialog-QjQoKbIV.js} +1 -1
- package/dist/assets/doc-browser-CLEdkH9z.js +1 -0
- package/dist/assets/{doc-browser-context-DH4aWvkS.js → doc-browser-context-DwgG6wrz.js} +1 -1
- package/dist/assets/{download-Cr337h4W.js → download-BoaKzDJk.js} +1 -1
- package/dist/assets/{es2015-D-CRf3_Z.js → es2015-Bp6cuSGh.js} +1 -1
- package/dist/assets/{extensions-CsmJPz7g.js → extensions-CgShSIkd.js} +1 -1
- package/dist/assets/{eye-DrQRSAfJ.js → eye-kisejBFH.js} +1 -1
- package/dist/assets/{form-actions-BEeWTGUZ.js → form-actions-D9JWSFQu.js} +1 -1
- package/dist/assets/{i18n-OGmF0Ws_.js → i18n-Bm3Ov7Fy.js} +2 -2
- package/dist/assets/{keyboard-shortcuts-settings-page-DQU56SrW.js → keyboard-shortcuts-settings-page-DZ8A1iLg.js} +1 -1
- package/dist/assets/loader-circle-Bd1fTm-x.js +1 -0
- package/dist/assets/{login-page-sgivRYG1.js → login-page-C-DvcG3L.js} +1 -1
- package/dist/assets/login-page-D99JjH9F.js +1 -0
- package/dist/assets/{main-B-oxUq-o.js → main-Bt5r6b21.js} +2 -2
- package/dist/assets/main-DXd_aKJV.css +1 -0
- package/dist/assets/main-i18n-catalog.config-D0Ll36J_.js +1 -0
- package/dist/assets/mcp-marketplace-page-DUoGfSmY.js +1 -0
- package/dist/assets/{model-config-page-CDQBhmVr.js → model-config-page-9NNf-mzK.js} +1 -1
- package/dist/assets/{navigation-link-D3pzNHeA.js → navigation-link-CjxNC8aT.js} +1 -1
- package/dist/assets/panel-app-i18n-catalog.config-Ddf2Kv8A.js +1 -0
- package/dist/assets/panelStandalone-BRg7B1EI.css +1 -0
- package/dist/assets/{panelStandalone-CGkYeMK5.js → panelStandalone-BaUzY8Wy.js} +2 -2
- package/dist/assets/{privacy-settings-page-CjoAkoDt.js → privacy-settings-page-CVMBFuak.js} +1 -1
- package/dist/assets/projects-BEsU3box.js +1 -0
- package/dist/assets/provider-models-Dm-pmWAA.js +1 -0
- package/dist/assets/provider-scoped-model-input-CzrXg4cX.js +1 -0
- package/dist/assets/providers-config-page-839uUADZ.js +1 -0
- package/dist/assets/{puzzle--RLCeYML.js → puzzle-BGvD2DPZ.js} +1 -1
- package/dist/assets/{pwa-DcizGowz.js → pwa-DNNK_8E9.js} +1 -1
- package/dist/assets/{refresh-cw-syCVj_nw.js → refresh-cw-BlXDrVP0.js} +1 -1
- package/dist/assets/remote-B5qIuUbT.js +1 -0
- package/dist/assets/{rotate-cw-BlH9El3x.js → rotate-cw-ei6rPD6z.js} +1 -1
- package/dist/assets/{runtime-config-page-Cco9dS73.js → runtime-config-page-Bf-4JYxQ.js} +1 -1
- package/dist/assets/{save-BWynuRQ2.js → save-DdF-bXgC.js} +1 -1
- package/dist/assets/{search-config-page-Cc74_SAU.js → search-config-page-C0qA3j-T.js} +1 -1
- package/dist/assets/{secrets-config-page-raYDzlFC.js → secrets-config-page-DxfZWpnk.js} +1 -1
- package/dist/assets/security-config-CItfIQZ4.js +1 -0
- package/dist/assets/{select-6Uq4chZW.js → select-CWyjnar7.js} +1 -1
- package/dist/assets/service-apps-panel-D3SOkRVv.js +1 -0
- package/dist/assets/{settings-2-COvVf1XX.js → settings-2-CrNhfInU.js} +1 -1
- package/dist/assets/{status-dot-DZlxa5zW.js → status-dot-C3SRFEta.js} +1 -1
- package/dist/assets/{tag-chip-DXHtZ8AS.js → tag-chip-BIuavK2K.js} +1 -1
- package/dist/assets/{theme-provider-C5PCDBPB.js → theme-provider-CWUz4yO9.js} +1 -1
- package/dist/assets/{trash-2-DazgBvmJ.js → trash-2-hAbJh9VZ.js} +1 -1
- package/dist/assets/{use-config-DRRGF9Zb.js → use-config-evzage09.js} +1 -1
- package/dist/assets/{use-desktop-capabilities-CdfOUjdE.js → use-desktop-capabilities-BYDjFlpt.js} +1 -1
- package/dist/assets/{use-viewport-layout-DdxVnCYc.js → use-viewport-layout-D_9XWGDV.js} +1 -1
- package/dist/assets/x-ChPbNv39.js +1 -0
- package/dist/index.html +34 -34
- package/dist/panel-standalone.html +11 -11
- package/package.json +3 -3
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +2 -2
- package/src/app/components/layout/sidebar-rail.styles.ts +3 -3
- package/src/app/configs/__tests__/app-navigation.config.test.ts +4 -2
- package/src/app/configs/app-navigation.config.ts +7 -2
- package/src/app/styles/charcoal-theme.css +0 -3
- package/src/app/styles/design-system.css +12 -6
- package/src/app/styles/island-theme.css +2 -15
- package/src/features/app-data/components/app-data-removal-choice.tsx +1 -1
- package/src/features/channels/pages/channels-list-page.tsx +1 -1
- package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +1 -1
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +28 -16
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +8 -2
- package/src/features/chat/components/layout/__tests__/chat-sidebar-session-area.test.tsx +6 -4
- package/src/features/chat/components/layout/__tests__/chat-sidebar-toolbar.test.tsx +22 -1
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +10 -14
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +38 -7
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +4 -4
- package/src/features/chat/components/layout/chat-sidebar.tsx +2 -2
- package/src/features/chat/features/conversation/components/session-queued-input-rows.tsx +1 -1
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +12 -3
- package/src/features/chat/features/message/components/chat-tool-call-stress-harness.tsx +32 -2
- package/src/features/chat/features/message/hooks/__tests__/use-chat-message-virtualizer.test.tsx +7 -5
- package/src/features/chat/features/message/hooks/use-chat-message-virtualizer.ts +7 -3
- package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +4 -6
- package/src/features/chat/features/message/utils/chat-message-process-summary.utils.ts +2 -10
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +1 -0
- package/src/features/chat/features/session/components/__tests__/chat-sidebar-project-groups.test.tsx +1 -1
- package/src/features/chat/features/session/components/chat-mobile-session-row.tsx +1 -1
- package/src/features/chat/features/session/components/chat-sidebar-context-card.tsx +1 -1
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +2 -1
- package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +7 -5
- package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +45 -10
- package/src/features/chat/features/session/components/session-header/chat-session-more-actions-menu.tsx +3 -0
- package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +6 -6
- package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +5 -4
- package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +1 -1
- package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +0 -1
- package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +1 -1
- package/src/features/chat/features/welcome/components/chat-welcome-prompt-suggestions.tsx +1 -1
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +1 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +2 -2
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +5 -5
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +1 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +1 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +1 -1
- package/src/features/chat/features/workspace/components/child-sessions/chat-session-child-sessions.tsx +2 -2
- package/src/features/chat/features/workspace/components/file-content-preview/workspace-spreadsheet-preview.tsx +2 -2
- package/src/features/chat/features/workspace/components/overview/chat-session-continuous-attention.tsx +3 -3
- package/src/features/chat/features/workspace/components/overview/chat-session-token-usage.tsx +1 -1
- package/src/features/chat/features/workspace/components/project-files/workspace-directory-tree.tsx +4 -3
- package/src/features/chat/features/workspace/components/project-files/workspace-new-folder-tree-item.tsx +2 -1
- package/src/features/chat/features/workspace/components/session-cron-job-content.tsx +3 -3
- package/src/features/chat/features/workspace/components/workspace-file-content-preview.tsx +2 -2
- package/src/features/chat/features/workspace/components/workspace-markdown-outline.tsx +1 -1
- package/src/features/cron/components/cron-config.tsx +2 -2
- package/src/features/cron/components/cron-job-detail-dialog.tsx +1 -1
- package/src/features/cron/components/cron-job-row.tsx +3 -3
- package/src/features/cron/components/cron-task-discovery.tsx +1 -1
- package/src/features/inbox/pages/inbox-page.test.ts +12 -3
- package/src/features/inbox/pages/inbox-page.tsx +32 -25
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.tsx +2 -2
- package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +2 -2
- package/src/features/marketplace/components/marketplace-list-card.tsx +4 -4
- package/src/features/marketplace/components/marketplace-page.tsx +2 -2
- package/src/features/marketplace/components/mcp/mcp-marketplace-card.tsx +2 -2
- package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +3 -3
- package/src/features/panel-apps/components/panel-app-list-item.tsx +3 -3
- package/src/features/projects/pages/project-home-page.tsx +4 -2
- package/src/features/pwa/components/pwa-install-entry.tsx +1 -1
- package/src/features/right-panel-resources/components/__tests__/page-resource-sidebar-nav.test.tsx +9 -1
- package/src/features/right-panel-resources/components/page-resource-sidebar-nav.tsx +1 -1
- package/src/features/right-panel-resources/components/right-panel-resource-home-page.tsx +1 -1
- package/src/features/service-apps/components/service-action-row.tsx +3 -3
- package/src/features/service-apps/components/service-app-list-item.tsx +4 -4
- package/src/features/service-apps/components/service-apps-panel.tsx +1 -1
- package/src/features/settings/components/config/provider-model-list.tsx +5 -5
- package/src/features/settings/components/config/provider-model-suggestions-panel.tsx +4 -4
- package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
- package/src/features/settings/pages/appearance-settings-page.tsx +2 -2
- package/src/features/settings/pages/providers-config-page.tsx +6 -6
- package/src/features/side-dock/components/side-dock.tsx +4 -4
- package/src/index.css +4 -0
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +1 -1
- package/src/platforms/mobile/components/__tests__/mobile-app-shell.test.tsx +9 -0
- package/src/platforms/mobile/components/mobile-app-shell.tsx +4 -2
- package/src/platforms/mobile/components/mobile-bottom-nav.tsx +3 -3
- package/src/platforms/mobile/components/mobile-topbar.tsx +2 -2
- package/src/shared/components/common/brand-header.tsx +1 -1
- package/src/shared/components/common/searchable-model-input.tsx +2 -2
- package/src/shared/components/config-split-page.tsx +3 -3
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +2 -2
- package/src/shared/components/path-picker/server-path-picker-locations.tsx +1 -1
- package/src/shared/components/ui/button.tsx +2 -2
- package/src/shared/components/ui/card.tsx +1 -1
- package/src/shared/components/ui/context-menu/context-menu-items.tsx +3 -3
- package/src/shared/components/ui/input.tsx +7 -3
- package/src/shared/components/ui/overlays/sheet.tsx +1 -1
- package/src/shared/components/ui/popover.tsx +1 -1
- package/src/shared/components/ui/select.tsx +2 -2
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +4 -4
- package/src/shared/components/ui/tabs.tsx +1 -1
- package/src/shared/components/workbench/workbench-surface.tsx +1 -1
- package/src/shared/lib/i18n/locales/en-US/chat.json +1 -2
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +1 -2
- package/dist/assets/app-presenter-provider-Co8uKOyW.js +0 -124
- package/dist/assets/card-Bqu8zWLq.js +0 -1
- package/dist/assets/config-split-page-Bd-bGKsw.js +0 -1
- package/dist/assets/doc-browser-52t2_rSF.js +0 -1
- package/dist/assets/loader-circle-DbOQWT9C.js +0 -1
- package/dist/assets/login-page-DW1AZXqX.js +0 -1
- package/dist/assets/main-BpL6OjwM.css +0 -1
- package/dist/assets/main-i18n-catalog.config-Bpu5GcwF.js +0 -1
- package/dist/assets/mcp-marketplace-page-DSLRpJEB.js +0 -1
- package/dist/assets/panel-app-i18n-catalog.config-D44qmtfk.js +0 -1
- package/dist/assets/panelStandalone-D-mhAGNa.css +0 -1
- package/dist/assets/projects-C7Zxpcoi.js +0 -1
- package/dist/assets/provider-models-YW7rIeOD.js +0 -1
- package/dist/assets/provider-scoped-model-input-axJ-pTEu.js +0 -1
- package/dist/assets/providers-config-page-_QhBXQ4M.js +0 -1
- package/dist/assets/remote-BLQU7aY5.js +0 -1
- package/dist/assets/security-config-aE_jOwy-.js +0 -1
- package/dist/assets/service-apps-panel-jBWAa4qZ.js +0 -1
- package/dist/assets/x-CQIp_nJT.js +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BrandHeader } from "@/shared/components/common/brand-header";
|
|
2
2
|
import { StatusBadge } from "@/shared/components/common/status-badge";
|
|
3
|
-
import {
|
|
3
|
+
import { ChatSidebarListToolbar } from "@/features/chat/components/layout/chat-sidebar-toolbar";
|
|
4
4
|
import type {
|
|
5
5
|
groupSessionsByDate,
|
|
6
6
|
groupSessionsByProject,
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
Inbox,
|
|
21
21
|
PanelLeftClose,
|
|
22
22
|
PanelLeftOpen,
|
|
23
|
-
FolderPlus,
|
|
24
23
|
} from "lucide-react";
|
|
25
24
|
import { useInboxUnreadCount } from "@/features/inbox";
|
|
26
25
|
import { ChatSidebarSessionList } from "@/features/chat/features/session/components/chat-sidebar-session-list";
|
|
@@ -148,6 +147,8 @@ export function ChatSidebarDesktopNav({
|
|
|
148
147
|
}
|
|
149
148
|
|
|
150
149
|
export function ChatSidebarSessionArea({
|
|
150
|
+
query,
|
|
151
|
+
onQueryChange,
|
|
151
152
|
variant = 'desktop',
|
|
152
153
|
defaultSessionType,
|
|
153
154
|
groups,
|
|
@@ -162,6 +163,8 @@ export function ChatSidebarSessionArea({
|
|
|
162
163
|
renderSessionItem,
|
|
163
164
|
sessionTypeOptions,
|
|
164
165
|
}: {
|
|
166
|
+
query: string;
|
|
167
|
+
onQueryChange: (query: string) => void;
|
|
165
168
|
variant?: 'desktop' | 'mobile';
|
|
166
169
|
defaultSessionType: string;
|
|
167
170
|
groups: ReturnType<typeof groupSessionsByDate>;
|
|
@@ -189,20 +192,13 @@ export function ChatSidebarSessionArea({
|
|
|
189
192
|
|
|
190
193
|
return (
|
|
191
194
|
<>
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
icon={<FolderPlus className="h-3.5 w-3.5" />}
|
|
196
|
-
label={t("chatProjectAdd")}
|
|
197
|
-
tone="surface"
|
|
198
|
-
onClick={onAddProject}
|
|
199
|
-
/>
|
|
200
|
-
) : null}
|
|
201
|
-
<ChatSidebarListModeSwitch
|
|
195
|
+
{variant === 'desktop' ? <ChatSidebarListToolbar
|
|
196
|
+
query={query}
|
|
197
|
+
onQueryChange={onQueryChange}
|
|
202
198
|
isProjectFirstView={isProjectFirstView}
|
|
203
199
|
onSelectMode={onSelectMode}
|
|
204
|
-
|
|
205
|
-
|
|
200
|
+
onAddProject={onAddProject}
|
|
201
|
+
/> : null}
|
|
206
202
|
|
|
207
203
|
<div
|
|
208
204
|
ref={scrollRef}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
1
|
+
import { useId, useRef, useState } from 'react';
|
|
2
|
+
import { ChatSidebarListModeSwitch } from '@/features/chat/components/chat-sidebar-list-mode-switch';
|
|
2
3
|
import { Link } from 'react-router-dom';
|
|
3
4
|
import { AlarmClock, ChevronDown, Clock3, Folder, FolderPlus, Plus, Search, X } from 'lucide-react';
|
|
4
5
|
import { Button } from '@/shared/components/ui/button';
|
|
@@ -19,7 +20,7 @@ type ChatSidebarToolbarProps = {
|
|
|
19
20
|
collapsed?: boolean;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
export function ChatSidebarDesktopToolbar({
|
|
23
|
+
export function ChatSidebarDesktopToolbar({ onCreateSession, collapsed }: Pick<ChatSidebarToolbarProps, 'onCreateSession' | 'collapsed'>) {
|
|
23
24
|
if (collapsed) {
|
|
24
25
|
return <div className="px-2 pb-2">
|
|
25
26
|
<IconActionButton icon={<Plus className={SIDEBAR_RAIL_ICON_CLASS} />} label={t('chatSidebarNewTask')}
|
|
@@ -32,14 +33,44 @@ export function ChatSidebarDesktopToolbar({ query, onQueryChange, onCreateSessio
|
|
|
32
33
|
<Plus className="mr-1.5 h-4 w-4" />{t('chatSidebarNewTask')}
|
|
33
34
|
</Button>
|
|
34
35
|
</div>
|
|
35
|
-
|
|
36
|
+
</>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function ChatSidebarListToolbar({ query, onQueryChange, isProjectFirstView, onSelectMode, onAddProject }: {
|
|
40
|
+
query: string;
|
|
41
|
+
onQueryChange: (query: string) => void;
|
|
42
|
+
isProjectFirstView: boolean;
|
|
43
|
+
onSelectMode: (mode: 'time-first' | 'project-first') => void;
|
|
44
|
+
onAddProject: () => void;
|
|
45
|
+
}) {
|
|
46
|
+
const [searchOpen, setSearchOpen] = useState(Boolean(query));
|
|
47
|
+
const searchButtonRef = useRef<HTMLButtonElement>(null);
|
|
48
|
+
const searchId = useId();
|
|
49
|
+
const closeSearch = () => {
|
|
50
|
+
onQueryChange('');
|
|
51
|
+
setSearchOpen(false);
|
|
52
|
+
searchButtonRef.current?.focus();
|
|
53
|
+
};
|
|
54
|
+
return <div className="shrink-0 px-3">
|
|
55
|
+
<div className="flex h-8 items-center justify-between gap-1">
|
|
56
|
+
<ChatSidebarListModeSwitch isProjectFirstView={isProjectFirstView} onSelectMode={onSelectMode} />
|
|
57
|
+
<IconActionGroup>
|
|
58
|
+
{isProjectFirstView ? <IconActionButton icon={<FolderPlus className="h-3.5 w-3.5" />} label={t('chatProjectAdd')} onClick={onAddProject} /> : null}
|
|
59
|
+
<IconActionButton ref={searchButtonRef} icon={<Search className="h-3.5 w-3.5" />} label={t('chatSidebarSearchPlaceholder')}
|
|
60
|
+
aria-expanded={searchOpen} aria-controls={searchId} onClick={() => { if (searchOpen) closeSearch(); else setSearchOpen(true); }} />
|
|
61
|
+
</IconActionGroup>
|
|
62
|
+
</div>
|
|
63
|
+
{searchOpen ? <div id={searchId} className="pb-2 pt-1">
|
|
36
64
|
<div className="relative">
|
|
37
65
|
<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" />
|
|
38
|
-
<Input data-theme-control="chat-search" value={query} onChange={event => onQueryChange(event.target.value)}
|
|
39
|
-
|
|
66
|
+
<Input autoFocus data-theme-control="chat-search" density="compact" value={query} onChange={event => onQueryChange(event.target.value)}
|
|
67
|
+
onKeyDown={event => { if (event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); closeSearch(); } }}
|
|
68
|
+
aria-label={t('chatSidebarSearchPlaceholder')} placeholder={t('chatSidebarSearchPlaceholder')} className="h-8 rounded-lg border-0 bg-background/55 pl-8 pr-9 shadow-none hover:bg-[var(--interaction-hover)]" />
|
|
69
|
+
<IconActionButton icon={<X className="h-3.5 w-3.5" />} label={t('chatSidebarCloseSearch')} size="sm"
|
|
70
|
+
className="absolute right-1 top-1/2 -translate-y-1/2" onClick={closeSearch} />
|
|
40
71
|
</div>
|
|
41
|
-
</div>
|
|
42
|
-
|
|
72
|
+
</div> : null}
|
|
73
|
+
</div>;
|
|
43
74
|
}
|
|
44
75
|
|
|
45
76
|
export function ChatSidebarMobileToolbar(props: ChatSidebarToolbarProps & {
|
|
@@ -102,7 +102,7 @@ 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-lg px-2.5 py-1.5 text-[13px] font-medium text-muted-foreground transition-colors duration-base hover:bg-
|
|
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-[var(--interaction-hover)] 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>
|
|
@@ -118,7 +118,7 @@ export function ChatSidebarUtilityMenu({
|
|
|
118
118
|
<NavLink
|
|
119
119
|
to="/settings"
|
|
120
120
|
onClick={() => onOpenChange(false)}
|
|
121
|
-
className="flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-
|
|
121
|
+
className="flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-gray-900"
|
|
122
122
|
>
|
|
123
123
|
<Settings className="h-4 w-4 text-muted-foreground/70" />
|
|
124
124
|
<span className="flex-1 text-left">{t("settings")}</span>
|
|
@@ -134,7 +134,7 @@ export function ChatSidebarUtilityMenu({
|
|
|
134
134
|
action();
|
|
135
135
|
onOpenChange(false);
|
|
136
136
|
}}
|
|
137
|
-
className="flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-
|
|
137
|
+
className="flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-gray-900"
|
|
138
138
|
>
|
|
139
139
|
<Icon className="h-4 w-4 text-muted-foreground/70" />
|
|
140
140
|
<span className="flex-1 text-left">{label}</span>
|
|
@@ -197,7 +197,7 @@ function ChatSidebarUtilitySelect<Value extends string>({
|
|
|
197
197
|
>
|
|
198
198
|
<SelectTrigger
|
|
199
199
|
aria-label={label}
|
|
200
|
-
className="h-auto w-full rounded-lg border-0 bg-transparent px-3 py-2 text-[13px] font-medium text-foreground shadow-none hover:bg-
|
|
200
|
+
className="h-auto w-full rounded-lg border-0 bg-transparent px-3 py-2 text-[13px] font-medium text-foreground shadow-none hover:bg-[var(--interaction-hover)] hover:text-gray-900 focus:ring-0"
|
|
201
201
|
indicator={<ChevronRight className="h-4 w-4 text-muted-foreground/70" />}
|
|
202
202
|
>
|
|
203
203
|
<div className="flex min-w-0 items-center gap-2.5">
|
|
@@ -304,9 +304,7 @@ export function ChatSidebar({
|
|
|
304
304
|
/>
|
|
305
305
|
) : (
|
|
306
306
|
<ChatSidebarDesktopToolbar
|
|
307
|
-
query={listSnapshot.query}
|
|
308
307
|
onCreateSession={createSessionAndOpenIfNeeded}
|
|
309
|
-
onQueryChange={presenter.chatSessionListManager.setQuery}
|
|
310
308
|
collapsed={shouldCollapse}
|
|
311
309
|
/>
|
|
312
310
|
)}
|
|
@@ -316,6 +314,8 @@ export function ChatSidebar({
|
|
|
316
314
|
) : null}
|
|
317
315
|
|
|
318
316
|
<ChatSidebarSessionArea
|
|
317
|
+
query={listSnapshot.query}
|
|
318
|
+
onQueryChange={presenter.chatSessionListManager.setQuery}
|
|
319
319
|
variant={variant}
|
|
320
320
|
defaultSessionType={newSessionTypePreference.selectedSessionType}
|
|
321
321
|
groups={groups}
|
|
@@ -91,7 +91,7 @@ function QueuedInputIconButton({
|
|
|
91
91
|
aria-label={label}
|
|
92
92
|
disabled={disabled}
|
|
93
93
|
onClick={onClick}
|
|
94
|
-
className="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-
|
|
94
|
+
className="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border disabled:cursor-not-allowed disabled:text-muted-foreground/50"
|
|
95
95
|
>
|
|
96
96
|
{children}
|
|
97
97
|
</button>
|
|
@@ -89,6 +89,7 @@ type ChatMessageListContainerProps = {
|
|
|
89
89
|
onLoadMessageDetails?: (messageId: string) => Promise<void>;
|
|
90
90
|
sessionKey: string | null;
|
|
91
91
|
scrollRef: RefObject<HTMLDivElement | null>;
|
|
92
|
+
turnAnchorRef?: RefObject<HTMLDivElement>;
|
|
92
93
|
className?: string;
|
|
93
94
|
};
|
|
94
95
|
|
|
@@ -121,8 +122,6 @@ class ChatMessageViewModelAdapter {
|
|
|
121
122
|
message,
|
|
122
123
|
processedLabel,
|
|
123
124
|
language,
|
|
124
|
-
failedLabel: t("chatProcessSummaryError", language),
|
|
125
|
-
stoppedLabel: t("chatProcessSummaryStopped", language),
|
|
126
125
|
});
|
|
127
126
|
const processSummaryLabel = processSummary?.label ?? null;
|
|
128
127
|
const executionPresentation = buildChatMessageExecutionPresentation({
|
|
@@ -231,6 +230,10 @@ function isAwaitingAssistantOutputRow(
|
|
|
231
230
|
return item.kind === "typing" || item.key === activeRowKey;
|
|
232
231
|
}
|
|
233
232
|
|
|
233
|
+
function resolveTurnAnchor(item: ChatTimelineItem, messageId: string | undefined, ref?: RefObject<HTMLDivElement>) {
|
|
234
|
+
return item.kind === "message" && item.message.id === messageId ? ref : undefined;
|
|
235
|
+
}
|
|
236
|
+
|
|
234
237
|
export function ChatMessageListContainer({
|
|
235
238
|
canContinue = false,
|
|
236
239
|
messages: rawMessages,
|
|
@@ -241,6 +244,7 @@ export function ChatMessageListContainer({
|
|
|
241
244
|
onEditMessage,
|
|
242
245
|
onLoadMessageDetails,
|
|
243
246
|
scrollRef,
|
|
247
|
+
turnAnchorRef,
|
|
244
248
|
sessionKey,
|
|
245
249
|
className,
|
|
246
250
|
}: ChatMessageListContainerProps) {
|
|
@@ -386,11 +390,13 @@ export function ChatMessageListContainer({
|
|
|
386
390
|
);
|
|
387
391
|
const [focusedRowKey, setFocusedRowKey] = useState<string | null>(null);
|
|
388
392
|
const activeRowKey = activeAssistantMessage ? `message:${activeAssistantMessage.id}` : null;
|
|
393
|
+
const turnMessageId = messages.findLast((message) => message.role === "user")?.id;
|
|
389
394
|
const { containerRef, virtualizer } = useChatMessageVirtualizer({
|
|
390
395
|
rows: virtualRows,
|
|
391
396
|
scrollRef,
|
|
392
397
|
activeRowKey,
|
|
393
398
|
focusedRowKey,
|
|
399
|
+
anchorRowKey: turnAnchorRef && turnMessageId ? `message:${turnMessageId}` : null,
|
|
394
400
|
});
|
|
395
401
|
const { handleAttachmentOpen, handleInlineTokenClick } =
|
|
396
402
|
useChatInlineTokenActions({ selectedSession, sessionKey });
|
|
@@ -448,7 +454,10 @@ export function ChatMessageListContainer({
|
|
|
448
454
|
) : item.kind === "observation-event" ? (
|
|
449
455
|
<ChatMessageObservationEvent event={item.event} />
|
|
450
456
|
) : (
|
|
451
|
-
<div
|
|
457
|
+
<div
|
|
458
|
+
className={item.kind === "message" ? "pb-5" : undefined}
|
|
459
|
+
ref={resolveTurnAnchor(item, turnMessageId, turnAnchorRef)}
|
|
460
|
+
>
|
|
452
461
|
<ChatMessageList
|
|
453
462
|
assistantAvatarIcon={assistantAvatarIcon}
|
|
454
463
|
showAssistantHeader={showAssistantHeader}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Profiler, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { ChatMessageList, type ChatMessageTexts } from "@nextclaw/agent-chat-ui";
|
|
2
|
+
import { ChatMessageList, type ChatMessageTexts, type ChatMessageListProps } from "@nextclaw/agent-chat-ui";
|
|
3
3
|
import {
|
|
4
4
|
createChatToolCallStressViewModel,
|
|
5
5
|
DEFAULT_CHAT_TOOL_CALL_STRESS_CONFIG,
|
|
@@ -40,6 +40,8 @@ const STRESS_TEXTS: ChatMessageTexts = {
|
|
|
40
40
|
},
|
|
41
41
|
toolActivityFailedLabel: "failed",
|
|
42
42
|
toolActivityCancelledLabel: "cancelled",
|
|
43
|
+
toolPayloadLoadingLabel: "正在加载详情",
|
|
44
|
+
toolPayloadLoadFailedLabel: "详情加载失败,点击重试",
|
|
43
45
|
};
|
|
44
46
|
|
|
45
47
|
function readBoundedNumber(value: string, fallback: number, maximum: number): number {
|
|
@@ -53,6 +55,18 @@ export function ChatToolCallStressHarness() {
|
|
|
53
55
|
const [config, setConfig] = useState(DEFAULT_CHAT_TOOL_CALL_STRESS_CONFIG);
|
|
54
56
|
const [renderVersion, setRenderVersion] = useState(0);
|
|
55
57
|
const [metric, setMetric] = useState<StressMetric | null>(null);
|
|
58
|
+
const [detailState, setDetailState] = useState<
|
|
59
|
+
ReturnType<NonNullable<ChatMessageListProps["resolveMessageToolPayloadState"]>>
|
|
60
|
+
>("ready");
|
|
61
|
+
const detailTimer = useRef<ReturnType<typeof setTimeout>>();
|
|
62
|
+
const failNextRequest = useRef(false);
|
|
63
|
+
useEffect(() => () => clearTimeout(detailTimer.current), []);
|
|
64
|
+
const resetDetails = (fail: boolean) => {
|
|
65
|
+
clearTimeout(detailTimer.current);
|
|
66
|
+
failNextRequest.current = fail;
|
|
67
|
+
setDetailState("summary");
|
|
68
|
+
setRenderVersion((value) => value + 1);
|
|
69
|
+
};
|
|
56
70
|
const pendingMeasurement = useRef<{ action: string; startedAt: number } | null>(null);
|
|
57
71
|
const viewModel = useMemo(
|
|
58
72
|
() => createChatToolCallStressViewModel(config, `chat-tool-call-stress-message-${renderVersion}`),
|
|
@@ -104,6 +118,12 @@ export function ChatToolCallStressHarness() {
|
|
|
104
118
|
))}
|
|
105
119
|
</div>
|
|
106
120
|
<div className="flex flex-wrap gap-2">
|
|
121
|
+
<button type="button" className="rounded border border-border px-3 py-1.5 text-sm" onClick={() => resetDetails(false)}>
|
|
122
|
+
演练慢加载(5 秒)
|
|
123
|
+
</button>
|
|
124
|
+
<button type="button" className="rounded border border-border px-3 py-1.5 text-sm" onClick={() => resetDetails(true)}>
|
|
125
|
+
演练失败后重试
|
|
126
|
+
</button>
|
|
107
127
|
<button
|
|
108
128
|
type="button"
|
|
109
129
|
className="rounded bg-primary px-3 py-1.5 text-sm text-primary-foreground"
|
|
@@ -165,10 +185,20 @@ export function ChatToolCallStressHarness() {
|
|
|
165
185
|
>
|
|
166
186
|
<section data-stress-message="true" className="rounded-lg border border-border p-4">
|
|
167
187
|
<ChatMessageList
|
|
168
|
-
|
|
188
|
+
layout="flat"
|
|
189
|
+
messages={[{ ...viewModel, processSummary: { label: "已处理 2分钟58秒" } }]}
|
|
169
190
|
isSending={false}
|
|
170
191
|
hasAssistantDraft={false}
|
|
171
192
|
texts={STRESS_TEXTS}
|
|
193
|
+
resolveMessageToolPayloadState={() => detailState}
|
|
194
|
+
onMessageToolPayloadRequest={() => {
|
|
195
|
+
if (detailState === "loading" || detailState === "ready") return;
|
|
196
|
+
setDetailState("loading");
|
|
197
|
+
detailTimer.current = setTimeout(() => {
|
|
198
|
+
setDetailState(failNextRequest.current ? "error" : "ready");
|
|
199
|
+
failNextRequest.current = false;
|
|
200
|
+
}, 5_000);
|
|
201
|
+
}}
|
|
172
202
|
/>
|
|
173
203
|
</section>
|
|
174
204
|
</Profiler>
|
package/src/features/chat/features/message/hooks/__tests__/use-chat-message-virtualizer.test.tsx
CHANGED
|
@@ -207,9 +207,11 @@ describe("useChatMessageVirtualizer", () => {
|
|
|
207
207
|
expect(scrollElement.scrollTop - (anchored?.start ?? 0)).toBe(anchorOffset);
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
-
it.each([11, 20])
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
it.each([-200, 0, 11, 20].flatMap((distance) => [
|
|
211
|
+
[distance, 100], [distance, -105],
|
|
212
|
+
]))(
|
|
213
|
+
"leaves bottom following to the scroll owner at distance %d with height delta %d",
|
|
214
|
+
async (escapeDistance, delta) => {
|
|
213
215
|
const scrollElement = document.createElement("div");
|
|
214
216
|
Object.defineProperties(scrollElement, {
|
|
215
217
|
clientHeight: { configurable: true, value: 800 },
|
|
@@ -248,14 +250,14 @@ describe("useChatMessageVirtualizer", () => {
|
|
|
248
250
|
expect(result.current.virtualizer.scrollOffset).toBe(escapedScrollTop),
|
|
249
251
|
);
|
|
250
252
|
|
|
251
|
-
act(() => result.current.virtualizer.resizeItem(9,
|
|
253
|
+
act(() => result.current.virtualizer.resizeItem(9, 1_000 + delta));
|
|
252
254
|
|
|
253
255
|
expect(scrollElement.scrollTop).toBe(escapedScrollTop);
|
|
254
256
|
expect(
|
|
255
257
|
result.current.virtualizer.getTotalSize() -
|
|
256
258
|
scrollElement.scrollTop -
|
|
257
259
|
scrollElement.clientHeight,
|
|
258
|
-
).toBe(escapeDistance +
|
|
260
|
+
).toBe(escapeDistance + delta);
|
|
259
261
|
},
|
|
260
262
|
);
|
|
261
263
|
});
|
|
@@ -12,14 +12,15 @@ export function useChatMessageVirtualizer(params: {
|
|
|
12
12
|
scrollRef: RefObject<HTMLDivElement | null>;
|
|
13
13
|
activeRowKey?: string | null;
|
|
14
14
|
focusedRowKey?: string | null;
|
|
15
|
+
anchorRowKey?: string | null;
|
|
15
16
|
}) {
|
|
16
|
-
const { activeRowKey, focusedRowKey, rows, scrollRef } = params;
|
|
17
|
+
const { activeRowKey, focusedRowKey, anchorRowKey, rows, scrollRef } = params;
|
|
17
18
|
const pinnedIndexes = useMemo(
|
|
18
19
|
() =>
|
|
19
20
|
rows.flatMap((row, index) =>
|
|
20
|
-
row.key === activeRowKey || row.key === focusedRowKey ? [index] : [],
|
|
21
|
+
row.key === activeRowKey || row.key === focusedRowKey || row.key === anchorRowKey ? [index] : [],
|
|
21
22
|
),
|
|
22
|
-
[activeRowKey, focusedRowKey, rows],
|
|
23
|
+
[activeRowKey, focusedRowKey, anchorRowKey, rows],
|
|
23
24
|
);
|
|
24
25
|
const rangeExtractor = useCallback(
|
|
25
26
|
(range: Parameters<typeof defaultRangeExtractor>[0]) =>
|
|
@@ -35,6 +36,9 @@ export function useChatMessageVirtualizer(params: {
|
|
|
35
36
|
// eslint-disable-next-line react-hooks/incompatible-library
|
|
36
37
|
const virtualizer = useVirtualizer({
|
|
37
38
|
anchorTo: "end",
|
|
39
|
+
// Preserve prepend anchoring, but let useStickyBottomScroll own end following.
|
|
40
|
+
// Virtual content excludes the reserved turn space and cannot detect its bottom.
|
|
41
|
+
scrollEndThreshold: -1,
|
|
38
42
|
count: rows.length,
|
|
39
43
|
directDomUpdates: true,
|
|
40
44
|
estimateSize: () => ESTIMATED_CHAT_MESSAGE_ROW_HEIGHT,
|
|
@@ -31,8 +31,6 @@ const baseAssistantMessage = {
|
|
|
31
31
|
|
|
32
32
|
const summaryLabels = {
|
|
33
33
|
processedLabel: "已处理",
|
|
34
|
-
failedLabel: "处理失败",
|
|
35
|
-
stoppedLabel: "已停止",
|
|
36
34
|
language: "zh" as const,
|
|
37
35
|
};
|
|
38
36
|
|
|
@@ -135,8 +133,8 @@ describe("buildChatMessageProcessSummary", () => {
|
|
|
135
133
|
});
|
|
136
134
|
|
|
137
135
|
it.each([
|
|
138
|
-
["failed", "
|
|
139
|
-
] as const)("
|
|
136
|
+
["failed", "已处理"], ["aborted", "已处理"], ["completed", "已处理"],
|
|
137
|
+
] as const)("keeps the process summary neutral for a %s outcome", (outcome, expected) => {
|
|
140
138
|
const message: NcpMessage = {
|
|
141
139
|
...baseAssistantMessage,
|
|
142
140
|
metadata: { ai_execution: createUnavailableNcpAiExecutionMetadata({
|
|
@@ -146,9 +144,9 @@ describe("buildChatMessageProcessSummary", () => {
|
|
|
146
144
|
expect(buildChatMessageProcessSummary({ ...summaryLabels, message })?.label).toBe(expected);
|
|
147
145
|
});
|
|
148
146
|
|
|
149
|
-
it("
|
|
147
|
+
it("keeps the process summary neutral for an error message", () => {
|
|
150
148
|
expect(buildChatMessageProcessSummary({
|
|
151
149
|
...summaryLabels, message: { ...baseAssistantMessage, status: "error" },
|
|
152
|
-
})?.label).toBe("
|
|
150
|
+
})?.label).toBe("已处理");
|
|
153
151
|
});
|
|
154
152
|
});
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { NcpMessage } from "@nextclaw/ncp";
|
|
2
2
|
import type { ChatMessageProcessSummarySource } from "@/features/chat/types/chat-message.types";
|
|
3
3
|
import { t, type I18nLanguage } from "@/shared/lib/i18n";
|
|
4
4
|
|
|
5
5
|
type BuildChatMessageProcessSummaryParams = {
|
|
6
6
|
message: NcpMessage;
|
|
7
7
|
processedLabel: string;
|
|
8
|
-
failedLabel: string;
|
|
9
|
-
stoppedLabel: string;
|
|
10
8
|
language: I18nLanguage;
|
|
11
9
|
};
|
|
12
10
|
|
|
@@ -78,8 +76,6 @@ function formatLifecycleDuration(message: NcpMessage, language: I18nLanguage): s
|
|
|
78
76
|
* and must not be mixed into this summary.
|
|
79
77
|
*/
|
|
80
78
|
export function buildChatMessageProcessSummary({
|
|
81
|
-
failedLabel,
|
|
82
|
-
stoppedLabel,
|
|
83
79
|
message,
|
|
84
80
|
processedLabel,
|
|
85
81
|
language,
|
|
@@ -88,11 +84,7 @@ export function buildChatMessageProcessSummary({
|
|
|
88
84
|
return undefined;
|
|
89
85
|
}
|
|
90
86
|
const duration = formatLifecycleDuration(message, language);
|
|
91
|
-
const outcome = readNcpAiExecutionMetadata(message.metadata)?.outcome;
|
|
92
|
-
const statusLabel = message.status === "error" || outcome === "failed"
|
|
93
|
-
? failedLabel
|
|
94
|
-
: outcome === "aborted" ? stoppedLabel : processedLabel;
|
|
95
87
|
return {
|
|
96
|
-
label: duration ? `${
|
|
88
|
+
label: duration ? `${processedLabel} ${duration}` : processedLabel,
|
|
97
89
|
};
|
|
98
90
|
}
|
|
@@ -189,6 +189,7 @@ export function buildChatMessageTexts(
|
|
|
189
189
|
copiedMessageLabel: t("chatMessageCopied"),
|
|
190
190
|
excerptCharacterCountTemplate: t("chatWorkspaceExcerptCharacterCount"),
|
|
191
191
|
mermaidDiagramLabel: t("chatMermaidDiagram"),
|
|
192
|
+
frontmatterLabel: t("chatMarkdownFrontmatter"),
|
|
192
193
|
mermaidExpandLabel: t("chatMermaidExpand"),
|
|
193
194
|
mermaidLoadingLabel: t("chatMermaidLoading"),
|
|
194
195
|
mermaidRenderErrorLabel: t("chatMermaidRenderError"),
|
package/src/features/chat/features/session/components/__tests__/chat-sidebar-project-groups.test.tsx
CHANGED
|
@@ -113,7 +113,7 @@ describe("ChatSidebarProjectGroups", () => {
|
|
|
113
113
|
const projectRow = projectLink.parentElement?.parentElement;
|
|
114
114
|
expect(container.firstElementChild?.className).toBe("space-y-0.5");
|
|
115
115
|
expect(projectRow?.className).toContain("h-8");
|
|
116
|
-
expect(projectRow?.className).toContain("hover:bg-[
|
|
116
|
+
expect(projectRow?.className).toContain("hover:bg-[var(--sidebar-interaction-hover)]");
|
|
117
117
|
const projectContent = projectLink.parentElement;
|
|
118
118
|
expect(projectContent?.classList.contains("pr-14")).toBe(false);
|
|
119
119
|
expect(projectContent?.className).toContain("group-hover/project:pr-20");
|
|
@@ -28,7 +28,7 @@ export function ChatMobileSessionRow(props: ChatSidebarSessionItemProps) {
|
|
|
28
28
|
onStartEditing, onTogglePinned, onDelete } = props;
|
|
29
29
|
return (
|
|
30
30
|
<div className="relative flex min-w-0 items-center">
|
|
31
|
-
<button type="button" onClick={onSelect} className="flex min-h-[76px] min-w-0 flex-1 items-center gap-3 py-3 pl-4 pr-1 text-left active:bg-
|
|
31
|
+
<button type="button" onClick={onSelect} className="flex min-h-[76px] min-w-0 flex-1 items-center gap-3 py-3 pl-4 pr-1 text-left active:bg-[var(--interaction-hover)]">
|
|
32
32
|
<span className="relative shrink-0">
|
|
33
33
|
{agentAvatarUrl || (agentId && agentId.toLowerCase() !== 'main') ? (
|
|
34
34
|
<AgentAvatar agentId={agentId || 'main'} displayName={agentLabel} avatarUrl={agentAvatarUrl} className="h-12 w-12 rounded-xl text-lg" />
|
|
@@ -30,7 +30,7 @@ export function ChatSidebarContextCard({
|
|
|
30
30
|
align="start"
|
|
31
31
|
sideOffset={10}
|
|
32
32
|
collisionPadding={12}
|
|
33
|
-
className="pointer-events-none w-64
|
|
33
|
+
className="pointer-events-none w-64 p-3"
|
|
34
34
|
>
|
|
35
35
|
<div className="truncate text-sm font-semibold leading-5">
|
|
36
36
|
{title}
|
|
@@ -175,6 +175,7 @@ export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
|
|
|
175
175
|
{group.projectId ? (
|
|
176
176
|
<Link
|
|
177
177
|
to={`/projects/${encodeURIComponent(group.projectId)}/overview`}
|
|
178
|
+
state={{ returnTo: `${location.pathname}${location.search}` }}
|
|
178
179
|
className="flex h-full min-w-0 flex-1 items-center gap-1.5 rounded-md px-1 text-left focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
179
180
|
aria-current={isSelected ? "page" : undefined}
|
|
180
181
|
title={group.projectRoot}
|
|
@@ -233,7 +234,7 @@ export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
|
|
|
233
234
|
/>
|
|
234
235
|
}
|
|
235
236
|
label={pinLabel}
|
|
236
|
-
tooltipSide="
|
|
237
|
+
tooltipSide="top"
|
|
237
238
|
onClick={() =>
|
|
238
239
|
presenter.chatSessionListManager.toggleProjectPinned(
|
|
239
240
|
group.projectRoot,
|
|
@@ -73,6 +73,7 @@ function ChatSidebarSessionEditingView({
|
|
|
73
73
|
return (
|
|
74
74
|
<div className="space-y-2">
|
|
75
75
|
<Input
|
|
76
|
+
density="compact"
|
|
76
77
|
value={draftLabel}
|
|
77
78
|
onChange={(event) => onDraftLabelChange(event.target.value)}
|
|
78
79
|
onKeyDown={(event) => {
|
|
@@ -85,7 +86,7 @@ function ChatSidebarSessionEditingView({
|
|
|
85
86
|
}
|
|
86
87
|
}}
|
|
87
88
|
placeholder={t("sessionsLabelPlaceholder")}
|
|
88
|
-
className="h-8 rounded-lg border-border bg-background
|
|
89
|
+
className="h-8 rounded-lg border-border bg-background"
|
|
89
90
|
autoFocus
|
|
90
91
|
disabled={isSaving}
|
|
91
92
|
/>
|
|
@@ -248,7 +249,7 @@ function ChatSidebarSessionDisplayView({
|
|
|
248
249
|
label={t(
|
|
249
250
|
isPinned ? "chatSidebarUnpinSession" : "chatSidebarPinSession",
|
|
250
251
|
)}
|
|
251
|
-
tooltipSide="
|
|
252
|
+
tooltipSide="top"
|
|
252
253
|
onClick={(event) => {
|
|
253
254
|
event.stopPropagation();
|
|
254
255
|
onTogglePinned();
|
|
@@ -259,7 +260,7 @@ function ChatSidebarSessionDisplayView({
|
|
|
259
260
|
tone="strong"
|
|
260
261
|
icon={<Pencil className="h-3.5 w-3.5" />}
|
|
261
262
|
label={t("edit")}
|
|
262
|
-
tooltipSide="
|
|
263
|
+
tooltipSide="top"
|
|
263
264
|
onClick={(event) => {
|
|
264
265
|
event.stopPropagation();
|
|
265
266
|
onStartEditing();
|
|
@@ -270,6 +271,7 @@ function ChatSidebarSessionDisplayView({
|
|
|
270
271
|
sessionTitle={title}
|
|
271
272
|
triggerSize="sm"
|
|
272
273
|
triggerTone="strong"
|
|
274
|
+
triggerTooltipSide="top"
|
|
273
275
|
>
|
|
274
276
|
<ChatSessionHeaderMenuItem
|
|
275
277
|
icon={Trash2}
|
|
@@ -319,8 +321,8 @@ export function ChatSidebarSessionItem(props: ChatSidebarSessionItemProps) {
|
|
|
319
321
|
? 'w-full border-b border-border/50 bg-background text-left'
|
|
320
322
|
: cn("w-full px-3 py-2 text-left text-[13px]", SIDEBAR_ITEM_SURFACE_CLASS, active && SIDEBAR_ITEM_ACTIVE_SURFACE_CLASS),
|
|
321
323
|
variant === 'mobile' && (active
|
|
322
|
-
? "bg-
|
|
323
|
-
: "text-foreground/80 hover:bg-
|
|
324
|
+
? "bg-[var(--session-interaction-selection)] font-medium text-foreground"
|
|
325
|
+
: "text-foreground/80 hover:bg-[var(--session-interaction-hover)] hover:text-foreground"),
|
|
324
326
|
active && "font-medium",
|
|
325
327
|
)}
|
|
326
328
|
>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import {
|
|
1
|
+
import { useState, type ReactNode } from "react";
|
|
2
|
+
import { ChatCollapsibleContent } from "@nextclaw/agent-chat-ui";
|
|
3
|
+
import { ChevronRight, MessageSquareText } from "lucide-react";
|
|
3
4
|
import type { NcpSessionListItemView } from "@/features/chat/features/ncp/hooks/use-ncp-session-list-view";
|
|
4
5
|
import type {
|
|
5
6
|
ChatSidebarDateGroup,
|
|
@@ -8,6 +9,7 @@ import type {
|
|
|
8
9
|
import { ChatSidebarProjectGroups } from "@/features/chat/features/session/components/chat-sidebar-project-groups";
|
|
9
10
|
import type { ChatSessionTypeOption } from "@/features/chat/features/session-type/utils/chat-session-type.utils";
|
|
10
11
|
import { t } from "@/shared/lib/i18n";
|
|
12
|
+
import { cn } from "@/shared/lib/utils";
|
|
11
13
|
|
|
12
14
|
type SessionTypeOption = ChatSessionTypeOption;
|
|
13
15
|
|
|
@@ -32,6 +34,41 @@ function ChatSidebarEmptyState({ label }: { label: string }) {
|
|
|
32
34
|
);
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
function ChatSidebarSessionGroup({ group, variant, renderSessionItem }: {
|
|
38
|
+
group: ChatSidebarDateGroup;
|
|
39
|
+
variant: 'desktop' | 'mobile';
|
|
40
|
+
renderSessionItem: ChatSidebarSessionListProps['renderSessionItem'];
|
|
41
|
+
}) {
|
|
42
|
+
const [open, setOpen] = useState(true);
|
|
43
|
+
return (
|
|
44
|
+
<div>
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
aria-expanded={open}
|
|
48
|
+
onClick={() => setOpen((value) => !value)}
|
|
49
|
+
className={cn(
|
|
50
|
+
'group/session-heading flex w-full cursor-pointer items-center justify-between gap-2 rounded-xl text-left text-xs font-medium text-muted-foreground outline-none transition-colors duration-base hover:bg-[var(--interaction-hover)] hover:text-foreground',
|
|
51
|
+
variant === 'mobile' ? 'min-h-9 px-4' : 'min-h-7 px-2',
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
<span className="truncate">{group.label}</span>
|
|
55
|
+
<ChevronRight
|
|
56
|
+
aria-hidden="true"
|
|
57
|
+
className={cn(
|
|
58
|
+
'h-3 w-3 shrink-0 opacity-0 transition-[opacity,transform] duration-200 group-hover/session-heading:opacity-100 motion-reduce:transition-none',
|
|
59
|
+
open && 'rotate-90',
|
|
60
|
+
)}
|
|
61
|
+
/>
|
|
62
|
+
</button>
|
|
63
|
+
<ChatCollapsibleContent open={open}>{() => (
|
|
64
|
+
<div className={variant === 'mobile' ? undefined : 'space-y-0.5'}>
|
|
65
|
+
{group.items.map(renderSessionItem)}
|
|
66
|
+
</div>
|
|
67
|
+
)}</ChatCollapsibleContent>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
35
72
|
export function ChatSidebarSessionList({
|
|
36
73
|
variant = 'desktop',
|
|
37
74
|
defaultSessionType,
|
|
@@ -72,14 +109,12 @@ export function ChatSidebarSessionList({
|
|
|
72
109
|
return (
|
|
73
110
|
<div className={variant === 'mobile' ? undefined : 'space-y-2'}>
|
|
74
111
|
{groups.map((group) => (
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
112
|
+
<ChatSidebarSessionGroup
|
|
113
|
+
key={group.label}
|
|
114
|
+
group={group}
|
|
115
|
+
variant={variant}
|
|
116
|
+
renderSessionItem={renderSessionItem}
|
|
117
|
+
/>
|
|
83
118
|
))}
|
|
84
119
|
</div>
|
|
85
120
|
);
|