@nextclaw/ui 0.26.3 → 0.26.4
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 +24 -0
- package/dist/.vite/manifest.json +329 -329
- package/dist/assets/{account-panel-DIdHjKG-.js → account-panel-DVi_jhkG.js} +1 -1
- package/dist/assets/{api-DkLJrSCd.js → api-BZvNpFOS.js} +1 -1
- package/dist/assets/app-presenter-provider-BcYL-VNe.js +124 -0
- package/dist/assets/{appearance-settings-page-DI8aDY2G.js → appearance-settings-page-AZNBzLmT.js} +1 -1
- package/dist/assets/{book-open-BPMkKSci.js → book-open-CmRdT3N1.js} +1 -1
- package/dist/assets/card-CdRBL1E0.js +1 -0
- package/dist/assets/{channels-list-page-CCIaTQu7.js → channels-list-page-eZN74FN3.js} +2 -2
- package/dist/assets/{circle-dashed-B48a0t6p.js → circle-dashed-HOgt3gIJ.js} +1 -1
- package/dist/assets/{client-imrVphQb.js → client-DKKbdJ6Q.js} +1 -1
- package/dist/assets/config-split-page-BA3V6b3Q.js +1 -0
- package/dist/assets/{confirm-dialog-10Qb3dpZ.js → confirm-dialog-8vqYZ0wy.js} +1 -1
- package/dist/assets/{context-menu-CRqH2SEk.js → context-menu-DHVVdHzN.js} +1 -1
- package/dist/assets/{createLucideIcon-B4xj2I2g.js → createLucideIcon-DE0sB9Nu.js} +1 -1
- package/dist/assets/{desktop-capabilities-page-CFcT2g8-.js → desktop-capabilities-page-B9-B9k1A.js} +1 -1
- package/dist/assets/{desktop-update-config-CRJd_An1.js → desktop-update-config-Cfr3IRUe.js} +1 -1
- package/dist/assets/{dialog-BgL3vKJQ.js → dialog-BUfWO8hr.js} +1 -1
- package/dist/assets/{doc-browser-context-DH4aWvkS.js → doc-browser-context-ClLgMbxd.js} +1 -1
- package/dist/assets/doc-browser-w8q1j0jL.js +1 -0
- package/dist/assets/{download-Cr337h4W.js → download-CKqS1Ksj.js} +1 -1
- package/dist/assets/{es2015-D-CRf3_Z.js → es2015-BiB05Sav.js} +1 -1
- package/dist/assets/{extensions-CsmJPz7g.js → extensions-vPD4dGqt.js} +1 -1
- package/dist/assets/{eye-DrQRSAfJ.js → eye-bKzylu3p.js} +1 -1
- package/dist/assets/{form-actions-BEeWTGUZ.js → form-actions-CCoxz33Q.js} +1 -1
- package/dist/assets/{keyboard-shortcuts-settings-page-DQU56SrW.js → keyboard-shortcuts-settings-page-D8rosOVh.js} +1 -1
- package/dist/assets/loader-circle-Clq1Gtb1.js +1 -0
- package/dist/assets/{login-page-sgivRYG1.js → login-page-BT3GV3Sb.js} +1 -1
- package/dist/assets/login-page-DKId3By8.js +1 -0
- package/dist/assets/{main-B-oxUq-o.js → main-bEWCGDHr.js} +2 -2
- package/dist/assets/main-kqMRTwOs.css +1 -0
- package/dist/assets/mcp-marketplace-page-CZIP6j3L.js +1 -0
- package/dist/assets/{model-config-page-CDQBhmVr.js → model-config-page-D21a-BO0.js} +1 -1
- package/dist/assets/{navigation-link-D3pzNHeA.js → navigation-link-DC-Pku0Y.js} +1 -1
- package/dist/assets/panelStandalone-BRg7B1EI.css +1 -0
- package/dist/assets/{panelStandalone-CGkYeMK5.js → panelStandalone-CX8X2hjh.js} +2 -2
- package/dist/assets/{privacy-settings-page-CjoAkoDt.js → privacy-settings-page-e0LFHZJm.js} +1 -1
- package/dist/assets/{projects-C7Zxpcoi.js → projects-Rki7Fmth.js} +1 -1
- package/dist/assets/provider-models-DFQgF4hS.js +1 -0
- package/dist/assets/provider-scoped-model-input-DaONL0j_.js +1 -0
- package/dist/assets/providers-config-page-CEOhQlMe.js +1 -0
- package/dist/assets/{puzzle--RLCeYML.js → puzzle-D_TiFXOv.js} +1 -1
- package/dist/assets/{pwa-DcizGowz.js → pwa-C9ac_1dg.js} +1 -1
- package/dist/assets/{refresh-cw-syCVj_nw.js → refresh-cw-BG8VfcdA.js} +1 -1
- package/dist/assets/remote-DkSx5x4R.js +1 -0
- package/dist/assets/{rotate-cw-BlH9El3x.js → rotate-cw-DPEegUU2.js} +1 -1
- package/dist/assets/{runtime-config-page-Cco9dS73.js → runtime-config-page-DDT1dVLt.js} +1 -1
- package/dist/assets/{save-BWynuRQ2.js → save-CPlctBjT.js} +1 -1
- package/dist/assets/{search-config-page-Cc74_SAU.js → search-config-page-CDwmd6eX.js} +1 -1
- package/dist/assets/{secrets-config-page-raYDzlFC.js → secrets-config-page-1OvgmwPv.js} +1 -1
- package/dist/assets/security-config-Pv4CM-kw.js +1 -0
- package/dist/assets/{select-6Uq4chZW.js → select-Y9IEbx9x.js} +1 -1
- package/dist/assets/service-apps-panel-DPrLeF3N.js +1 -0
- package/dist/assets/{settings-2-COvVf1XX.js → settings-2-CrGx9_De.js} +1 -1
- package/dist/assets/{status-dot-DZlxa5zW.js → status-dot-Cb8ewlUr.js} +1 -1
- package/dist/assets/{tag-chip-DXHtZ8AS.js → tag-chip-D-6aPuuh.js} +1 -1
- package/dist/assets/{theme-provider-C5PCDBPB.js → theme-provider-BjF1Eowc.js} +1 -1
- package/dist/assets/{trash-2-DazgBvmJ.js → trash-2-DER2EJKK.js} +1 -1
- package/dist/assets/{use-config-DRRGF9Zb.js → use-config-BM5jC65L.js} +1 -1
- package/dist/assets/{use-desktop-capabilities-CdfOUjdE.js → use-desktop-capabilities-ezKJwKqg.js} +1 -1
- package/dist/assets/{use-viewport-layout-DdxVnCYc.js → use-viewport-layout-vmbQUNT6.js} +1 -1
- package/dist/assets/x-DSz9cGhy.js +1 -0
- package/dist/index.html +33 -33
- package/dist/panel-standalone.html +10 -10
- package/package.json +4 -4
- 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/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 -1
- 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/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 +1 -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 +2 -2
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.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.tsx +1 -1
- 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/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/mobile-bottom-nav.tsx +3 -3
- 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 +3 -3
- package/src/shared/components/ui/tabs.tsx +1 -1
- 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/mcp-marketplace-page-DSLRpJEB.js +0 -1
- package/dist/assets/panelStandalone-D-mhAGNa.css +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,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
|
|
|
@@ -231,6 +232,10 @@ function isAwaitingAssistantOutputRow(
|
|
|
231
232
|
return item.kind === "typing" || item.key === activeRowKey;
|
|
232
233
|
}
|
|
233
234
|
|
|
235
|
+
function resolveTurnAnchor(item: ChatTimelineItem, messageId: string | undefined, ref?: RefObject<HTMLDivElement>) {
|
|
236
|
+
return item.kind === "message" && item.message.id === messageId ? ref : undefined;
|
|
237
|
+
}
|
|
238
|
+
|
|
234
239
|
export function ChatMessageListContainer({
|
|
235
240
|
canContinue = false,
|
|
236
241
|
messages: rawMessages,
|
|
@@ -241,6 +246,7 @@ export function ChatMessageListContainer({
|
|
|
241
246
|
onEditMessage,
|
|
242
247
|
onLoadMessageDetails,
|
|
243
248
|
scrollRef,
|
|
249
|
+
turnAnchorRef,
|
|
244
250
|
sessionKey,
|
|
245
251
|
className,
|
|
246
252
|
}: ChatMessageListContainerProps) {
|
|
@@ -386,11 +392,13 @@ export function ChatMessageListContainer({
|
|
|
386
392
|
);
|
|
387
393
|
const [focusedRowKey, setFocusedRowKey] = useState<string | null>(null);
|
|
388
394
|
const activeRowKey = activeAssistantMessage ? `message:${activeAssistantMessage.id}` : null;
|
|
395
|
+
const turnMessageId = messages.findLast((message) => message.role === "user")?.id;
|
|
389
396
|
const { containerRef, virtualizer } = useChatMessageVirtualizer({
|
|
390
397
|
rows: virtualRows,
|
|
391
398
|
scrollRef,
|
|
392
399
|
activeRowKey,
|
|
393
400
|
focusedRowKey,
|
|
401
|
+
anchorRowKey: turnAnchorRef && turnMessageId ? `message:${turnMessageId}` : null,
|
|
394
402
|
});
|
|
395
403
|
const { handleAttachmentOpen, handleInlineTokenClick } =
|
|
396
404
|
useChatInlineTokenActions({ selectedSession, sessionKey });
|
|
@@ -448,7 +456,10 @@ export function ChatMessageListContainer({
|
|
|
448
456
|
) : item.kind === "observation-event" ? (
|
|
449
457
|
<ChatMessageObservationEvent event={item.event} />
|
|
450
458
|
) : (
|
|
451
|
-
<div
|
|
459
|
+
<div
|
|
460
|
+
className={item.kind === "message" ? "pb-5" : undefined}
|
|
461
|
+
ref={resolveTurnAnchor(item, turnMessageId, turnAnchorRef)}
|
|
462
|
+
>
|
|
452
463
|
<ChatMessageList
|
|
453
464
|
assistantAvatarIcon={assistantAvatarIcon}
|
|
454
465
|
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,
|
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}
|
|
@@ -233,7 +233,7 @@ export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
|
|
|
233
233
|
/>
|
|
234
234
|
}
|
|
235
235
|
label={pinLabel}
|
|
236
|
-
tooltipSide="
|
|
236
|
+
tooltipSide="top"
|
|
237
237
|
onClick={() =>
|
|
238
238
|
presenter.chatSessionListManager.toggleProjectPinned(
|
|
239
239
|
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
|
);
|
|
@@ -22,6 +22,7 @@ type ChatSessionMoreActionsMenuProps = {
|
|
|
22
22
|
disabled?: boolean;
|
|
23
23
|
triggerSize?: IconActionButtonSize;
|
|
24
24
|
triggerTone?: IconActionButtonTone;
|
|
25
|
+
triggerTooltipSide?: 'top' | 'bottom';
|
|
25
26
|
className?: string;
|
|
26
27
|
};
|
|
27
28
|
|
|
@@ -39,6 +40,7 @@ export function ChatSessionMoreActionsMenu({
|
|
|
39
40
|
disabled = false,
|
|
40
41
|
triggerSize = 'md',
|
|
41
42
|
triggerTone = 'default',
|
|
43
|
+
triggerTooltipSide = 'bottom',
|
|
42
44
|
className,
|
|
43
45
|
}: ChatSessionMoreActionsMenuProps) {
|
|
44
46
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -59,6 +61,7 @@ export function ChatSessionMoreActionsMenu({
|
|
|
59
61
|
label={t('chatSessionMoreActions')}
|
|
60
62
|
size={triggerSize}
|
|
61
63
|
tone={triggerTone}
|
|
64
|
+
tooltipSide={triggerTooltipSide}
|
|
62
65
|
className={className}
|
|
63
66
|
disabled={disabled}
|
|
64
67
|
onClick={(event) => event.stopPropagation()}
|
|
@@ -66,9 +66,9 @@ function ChatSessionSwitchItem({
|
|
|
66
66
|
type="button"
|
|
67
67
|
aria-current={active ? "page" : undefined}
|
|
68
68
|
className={cn(
|
|
69
|
-
"flex w-full min-w-0 items-start gap-2 rounded-lg px-2.5 py-
|
|
69
|
+
"flex w-full min-w-0 items-start gap-2 rounded-lg px-2.5 py-1.5 text-left transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
|
|
70
70
|
active
|
|
71
|
-
? "bg-accent text-accent-foreground"
|
|
71
|
+
? "bg-accent/60 text-accent-foreground"
|
|
72
72
|
: "text-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground",
|
|
73
73
|
)}
|
|
74
74
|
onClick={() => onSelect(session.key)}
|
|
@@ -101,7 +101,7 @@ function ChatSessionSwitchItem({
|
|
|
101
101
|
</span>
|
|
102
102
|
) : null}
|
|
103
103
|
</span>
|
|
104
|
-
<span className="mt-0.5 flex min-w-0 items-center gap-2 text-[11px] text-muted-foreground">
|
|
104
|
+
<span className="mt-0.5 flex min-w-0 items-center gap-2 text-[11px] text-muted-foreground/75">
|
|
105
105
|
<span className="min-w-0 truncate">{previewText}</span>
|
|
106
106
|
<span className="shrink-0">{trailingText}</span>
|
|
107
107
|
</span>
|
|
@@ -177,12 +177,12 @@ function ChatSessionSwitchProjectGroup({
|
|
|
177
177
|
aria-controls={contentId}
|
|
178
178
|
aria-expanded={!isCollapsed}
|
|
179
179
|
aria-label={actionLabel}
|
|
180
|
-
className="group flex h-
|
|
180
|
+
className="group flex h-8 w-full min-w-0 items-center gap-1.5 rounded-lg px-2 text-left text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
181
181
|
onClick={() => onToggleCollapsed(group.projectRoot)}
|
|
182
182
|
>
|
|
183
183
|
<Folder className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
|
|
184
184
|
<span
|
|
185
|
-
className="min-w-0 flex-1 truncate text-[
|
|
185
|
+
className="min-w-0 flex-1 truncate text-[13px] font-medium"
|
|
186
186
|
title={group.projectRoot}
|
|
187
187
|
>
|
|
188
188
|
{group.projectName}
|
|
@@ -222,7 +222,7 @@ export function ChatSessionSwitcherGroups({
|
|
|
222
222
|
projectGroups: ChatSidebarProjectGroup[];
|
|
223
223
|
}) {
|
|
224
224
|
return (
|
|
225
|
-
<div className="space-y-
|
|
225
|
+
<div className="space-y-0.5">
|
|
226
226
|
{isProjectFirstView
|
|
227
227
|
? projectGroups.map((group) => (
|
|
228
228
|
<ChatSessionSwitchProjectGroup
|
package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx
CHANGED
|
@@ -165,9 +165,9 @@ function ChatSessionTitleSwitcherPopover({
|
|
|
165
165
|
</PopoverTrigger>
|
|
166
166
|
<ChatPopoverContent
|
|
167
167
|
align="start"
|
|
168
|
-
className="w-[22rem] max-w-[calc(100vw-2rem)] p-0"
|
|
168
|
+
className="flex w-[22rem] max-w-[calc(100vw-2rem)] flex-col overflow-hidden p-0"
|
|
169
169
|
>
|
|
170
|
-
<div className="space-y-2 border-b border-border px-3 py-2">
|
|
170
|
+
<div className="shrink-0 space-y-2 border-b border-border/40 px-3 py-2">
|
|
171
171
|
<div className="flex items-center justify-between gap-3">
|
|
172
172
|
<div className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground/75">
|
|
173
173
|
{t("chatSessionSwitcherTitle")}
|
|
@@ -180,16 +180,17 @@ function ChatSessionTitleSwitcherPopover({
|
|
|
180
180
|
<div className="relative">
|
|
181
181
|
<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/65" />
|
|
182
182
|
<Input
|
|
183
|
+
density="compact"
|
|
183
184
|
value={searchQuery}
|
|
184
185
|
onChange={(event) => setSearchQuery(event.target.value)}
|
|
185
186
|
placeholder={t("chatSearchSessionPlaceholder")}
|
|
186
187
|
aria-label={t("chatSearchSessionPlaceholder")}
|
|
187
|
-
className="h-8 rounded-lg bg-
|
|
188
|
+
className="h-8 rounded-lg border-0 bg-muted/50 pl-8 pr-2"
|
|
188
189
|
autoFocus
|
|
189
190
|
/>
|
|
190
191
|
</div>
|
|
191
192
|
</div>
|
|
192
|
-
<div className="max-h-80 overflow-y-auto p-1.5">
|
|
193
|
+
<div className="min-h-0 max-h-80 overflow-y-auto overscroll-contain p-1.5">
|
|
193
194
|
{isLoading ? (
|
|
194
195
|
<ChatSessionTitleSwitcherLoadingState />
|
|
195
196
|
) : hasVisibleGroups ? (
|
package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx
CHANGED
|
@@ -27,7 +27,7 @@ export function ChatSessionTypeOptionItem(props: {
|
|
|
27
27
|
onClick={onSelect}
|
|
28
28
|
className={cn(
|
|
29
29
|
"w-full rounded-2xl px-3 py-2.5 text-left transition-colors",
|
|
30
|
-
selected ? "bg-
|
|
30
|
+
selected ? "bg-[var(--interaction-selection)]" : "hover:bg-[var(--interaction-hover)]",
|
|
31
31
|
disabled ? "cursor-not-allowed opacity-70" : null,
|
|
32
32
|
)}
|
|
33
33
|
>
|
|
@@ -55,7 +55,7 @@ export function ChatWelcomeProjectPicker({
|
|
|
55
55
|
</PopoverTrigger>
|
|
56
56
|
<ChatPopoverContent
|
|
57
57
|
align="start"
|
|
58
|
-
className="flex w-[min(20rem,calc(100vw-1rem))] flex-col overflow-hidden
|
|
58
|
+
className="flex w-[min(20rem,calc(100vw-1rem))] flex-col overflow-hidden p-0"
|
|
59
59
|
style={{ maxHeight: PROJECT_PICKER_MAX_HEIGHT }}
|
|
60
60
|
>
|
|
61
61
|
<div className="shrink-0 px-3 pb-2 pt-3 text-[10px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
|
|
@@ -27,7 +27,7 @@ export function ChatWelcomePromptSuggestions({
|
|
|
27
27
|
key={suggestion.labelKey}
|
|
28
28
|
type="button"
|
|
29
29
|
onClick={() => onSelectPrompt(t(suggestion.promptKey))}
|
|
30
|
-
className="rounded-full bg-muted/55 px-3.5 py-2 text-xs font-medium text-muted-foreground transition-colors hover:bg-
|
|
30
|
+
className="rounded-full bg-muted/55 px-3.5 py-2 text-xs font-medium text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
31
31
|
>
|
|
32
32
|
{t(suggestion.labelKey)}
|
|
33
33
|
</button>
|