@nextclaw/ui 0.20.0 → 0.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/dist/assets/{api-C2iYhdTe.js → api-DvpQWjTS.js} +3 -3
- package/dist/assets/{appearance-settings-page-BwQDQr0n.js → appearance-settings-page-U0m0ktu3.js} +1 -1
- package/dist/assets/{book-open-C0IlT9zs.js → book-open-DRofSTWr.js} +1 -1
- package/dist/assets/channels-list-page-BURQarD0.js +8 -0
- package/dist/assets/{config-split-page-VDTf3zZb.js → config-split-page-BrAQYTIl.js} +1 -1
- package/dist/assets/{confirm-dialog-BI_Vt2s_.js → confirm-dialog-DDJomLnO.js} +1 -1
- package/dist/assets/{createLucideIcon-Bydi9Gc5.js → createLucideIcon-DzkNHr5Z.js} +1 -1
- package/dist/assets/{desktop-update-config-DVoD6NxG.js → desktop-update-config-BzZFhMv9.js} +1 -1
- package/dist/assets/{dist-CmHtkFbs.js → dist-DXbLo2b1.js} +1 -1
- package/dist/assets/{dist-A5btQciA.js → dist-NskPB_yW.js} +1 -1
- package/dist/assets/doc-browser-DB22tQKU.js +1 -0
- package/dist/assets/{doc-browser-context-Bxw0ora-.js → doc-browser-context-CnKV4gJy.js} +1 -1
- package/dist/assets/doc-browser-sJjw9fl5.js +1 -0
- package/dist/assets/{extensions-CG4voDCh.js → extensions-DvlFMpAs.js} +1 -1
- package/dist/assets/{form-actions-lv47uGIB.js → form-actions-UnPEZBkx.js} +1 -1
- package/dist/assets/index-Dc_ZMkmo.css +1 -0
- package/dist/assets/index-N1HnesBp.js +109 -0
- package/dist/assets/loader-circle-BlPXxgDB.js +1 -0
- package/dist/assets/mcp-marketplace-page-BIgFTbMq.js +9 -0
- package/dist/assets/mcp-marketplace-page-CZN4ovdK.js +1 -0
- package/dist/assets/model-config-page-CnSm7kfa.js +1 -0
- package/dist/assets/{navigation-link-D0agTj5t.js → navigation-link-NjUdG6Vp.js} +1 -1
- package/dist/assets/plus-DVXol9Q5.js +1 -0
- package/dist/assets/privacy-settings-page-DVn2HU7W.js +1 -0
- package/dist/assets/providers-config-page-CzHJS3eg.js +1 -0
- package/dist/assets/react-DCVbGroo.js +8 -0
- package/dist/assets/{refresh-cw-DjgY2Eju.js → refresh-cw-CkeninE0.js} +1 -1
- package/dist/assets/remote-B2lsJF1c.js +1 -0
- package/dist/assets/{rotate-cw-Dot4PVh0.js → rotate-cw-DP33PuXU.js} +1 -1
- package/dist/assets/runtime-config-page-D69buyGp.js +1 -0
- package/dist/assets/{save-B-jdTe_c.js → save-Cw2Xz-pB.js} +1 -1
- package/dist/assets/{search-CEp2AIiV.js → search-C2CFAVvd.js} +1 -1
- package/dist/assets/{search-config-page-Dp5guS5O.js → search-config-page-dqNT0Swf.js} +1 -1
- package/dist/assets/{secrets-config-page-C7nRMjiv.js → secrets-config-page-De6J3wVx.js} +2 -2
- package/dist/assets/security-config-Dcu9C4OL.js +1 -0
- package/dist/assets/{select-BNA3cma8.js → select-DSuMhwVP.js} +1 -1
- package/dist/assets/skeleton-QoWydNLp.js +1 -0
- package/dist/assets/{tag-chip-DmCNi4-9.js → tag-chip-DFBLgsGf.js} +1 -1
- package/dist/assets/x-BXvAzpjb.js +1 -0
- package/dist/index.html +21 -21
- package/package.json +8 -8
- package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +4 -3
- package/src/features/chat/components/layout/__tests__/chat-sidebar-session-area.test.tsx +11 -4
- package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +17 -17
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +17 -5
- package/src/features/chat/components/layout/chat-sidebar.tsx +28 -21
- package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-controller.test.tsx +41 -1
- package/src/features/chat/features/conversation/hooks/use-session-pending-input-actions.ts +22 -4
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +33 -4
- package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts +186 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-texts.utils.test.ts +9 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-timeline.utils.test.ts +71 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-trigger-details.utils.test.ts +147 -0
- package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +36 -0
- package/src/features/chat/features/message/utils/chat-message-trigger-details.utils.ts +118 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime-queue.test.tsx +50 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime.test.tsx +90 -1
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-session-list-view.test.tsx +24 -3
- package/src/features/chat/features/ncp/hooks/use-ncp-session-list-view.ts +35 -19
- package/src/features/chat/features/session/components/__tests__/chat-sidebar-project-groups.test.tsx +26 -2
- package/src/features/chat/features/session/components/__tests__/chat-sidebar-session-item.test.tsx +55 -27
- package/src/features/chat/features/session/components/chat-sidebar-context-card.tsx +60 -0
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +95 -54
- package/src/features/chat/features/session/components/chat-sidebar-session-entry.tsx +9 -8
- package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +141 -95
- package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +11 -2
- package/src/features/chat/features/session/hooks/use-chat-sidebar-context-counts.ts +40 -0
- package/src/features/chat/features/session/utils/ncp-session-context-metadata.utils.ts +5 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +16 -9
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +5 -5
- package/src/features/chat/features/workspace/components/__tests__/workspace-markdown-outline.test.tsx +128 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +3 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +20 -2
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +3 -40
- package/src/features/chat/features/workspace/components/workspace-markdown-outline.tsx +156 -0
- package/src/features/chat/managers/__tests__/chat-completion-notification.manager.test.ts +70 -0
- package/src/features/chat/managers/chat-completion-notification.manager.ts +23 -6
- package/src/features/settings/pages/__tests__/privacy-settings-page.test.tsx +14 -5
- package/src/features/settings/pages/privacy-settings-page.tsx +38 -2
- package/src/shared/components/ui/actions/icon-action-button.tsx +1 -8
- package/src/shared/components/ui/tooltip.tsx +16 -1
- package/src/shared/hooks/use-config.ts +11 -0
- package/src/shared/lib/api/types.ts +8 -0
- package/src/shared/lib/api/utils/config.utils.ts +5 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +28 -0
- package/src/shared/lib/i18n/locales/en-US/core.json +17 -9
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +28 -0
- package/src/shared/lib/i18n/locales/zh-CN/core.json +17 -9
- package/dist/assets/channels-list-page-B0z4bh8e.js +0 -8
- package/dist/assets/doc-browser-Cm_tMZ_C.js +0 -1
- package/dist/assets/doc-browser-Dto54tCf.js +0 -1
- package/dist/assets/index-BTSwNDVi.js +0 -109
- package/dist/assets/index-QrdUWHfm.css +0 -1
- package/dist/assets/loader-circle-CkaqeEne.js +0 -1
- package/dist/assets/mcp-marketplace-page-BSk0qUOX.js +0 -9
- package/dist/assets/mcp-marketplace-page-BgZGc-S_.js +0 -1
- package/dist/assets/model-config-page-c8RnUGoH.js +0 -1
- package/dist/assets/plus-BiMjkv3R.js +0 -1
- package/dist/assets/privacy-settings-page-CPkyTvEE.js +0 -1
- package/dist/assets/providers-config-page-CGUEb5Dx.js +0 -1
- package/dist/assets/react-BMYFkaGi.js +0 -8
- package/dist/assets/remote-1LxkpRED.js +0 -1
- package/dist/assets/runtime-config-page-C6nzqTHg.js +0 -1
- package/dist/assets/security-config-DvHUDxSg.js +0 -1
- package/dist/assets/skeleton-CbroEO8Q.js +0 -1
- package/dist/assets/x-BTo5-A8h.js +0 -1
|
@@ -1,14 +1,23 @@
|
|
|
1
|
-
import { AgentAvatar } from
|
|
2
|
-
import { SessionContextIconNode } from
|
|
3
|
-
import { SessionRunBadge } from
|
|
4
|
-
import { ChatSessionMoreActionsMenu } from
|
|
5
|
-
import { IconActionButton } from
|
|
6
|
-
import { Input } from
|
|
7
|
-
import { type SessionContextView } from
|
|
8
|
-
import type { SessionRunStatus } from
|
|
9
|
-
import { cn } from
|
|
10
|
-
import { t } from
|
|
11
|
-
import {
|
|
1
|
+
import { AgentAvatar } from "@/shared/components/common/agent-avatar";
|
|
2
|
+
import { SessionContextIconNode } from "@/features/chat/features/session/components/session-context-icon";
|
|
3
|
+
import { SessionRunBadge } from "@/features/chat/features/session/components/session-run-badge";
|
|
4
|
+
import { ChatSessionMoreActionsMenu } from "@/features/chat/features/session/components/session-header/chat-session-more-actions-menu";
|
|
5
|
+
import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
|
|
6
|
+
import { Input } from "@/shared/components/ui/input";
|
|
7
|
+
import { type SessionContextView } from "@/features/chat/features/session/utils/session-context.utils";
|
|
8
|
+
import type { SessionRunStatus } from "@/features/chat/types/session-run-status.types";
|
|
9
|
+
import { cn } from "@/shared/lib/utils";
|
|
10
|
+
import { t } from "@/shared/lib/i18n";
|
|
11
|
+
import {
|
|
12
|
+
AlarmClock,
|
|
13
|
+
Check,
|
|
14
|
+
Folder,
|
|
15
|
+
GitBranch,
|
|
16
|
+
Pencil,
|
|
17
|
+
Pin,
|
|
18
|
+
X,
|
|
19
|
+
} from "lucide-react";
|
|
20
|
+
import { ChatSidebarContextCard } from "@/features/chat/features/session/components/chat-sidebar-context-card";
|
|
12
21
|
|
|
13
22
|
type ChatSidebarSessionItemProps = {
|
|
14
23
|
sessionKey: string;
|
|
@@ -24,11 +33,12 @@ type ChatSidebarSessionItemProps = {
|
|
|
24
33
|
agentLabel?: string | null;
|
|
25
34
|
agentAvatarUrl?: string | null;
|
|
26
35
|
childSessionCount?: number;
|
|
36
|
+
cronJobCount?: number;
|
|
37
|
+
projectName?: string | null;
|
|
27
38
|
isEditing: boolean;
|
|
28
39
|
draftLabel: string;
|
|
29
40
|
isSaving: boolean;
|
|
30
41
|
onSelect: () => void;
|
|
31
|
-
onOpenChildSessions?: () => void;
|
|
32
42
|
onStartEditing: () => void;
|
|
33
43
|
onDraftLabelChange: (value: string) => void;
|
|
34
44
|
onSave: () => void | Promise<void>;
|
|
@@ -38,7 +48,12 @@ type ChatSidebarSessionItemProps = {
|
|
|
38
48
|
|
|
39
49
|
type ChatSidebarSessionEditingViewProps = Pick<
|
|
40
50
|
ChatSidebarSessionItemProps,
|
|
41
|
-
|
|
51
|
+
| "sessionKey"
|
|
52
|
+
| "draftLabel"
|
|
53
|
+
| "isSaving"
|
|
54
|
+
| "onDraftLabelChange"
|
|
55
|
+
| "onSave"
|
|
56
|
+
| "onCancel"
|
|
42
57
|
>;
|
|
43
58
|
|
|
44
59
|
function ChatSidebarSessionEditingView({
|
|
@@ -47,7 +62,7 @@ function ChatSidebarSessionEditingView({
|
|
|
47
62
|
isSaving,
|
|
48
63
|
onDraftLabelChange,
|
|
49
64
|
onSave,
|
|
50
|
-
onCancel
|
|
65
|
+
onCancel,
|
|
51
66
|
}: ChatSidebarSessionEditingViewProps) {
|
|
52
67
|
return (
|
|
53
68
|
<div className="space-y-2">
|
|
@@ -55,32 +70,34 @@ function ChatSidebarSessionEditingView({
|
|
|
55
70
|
value={draftLabel}
|
|
56
71
|
onChange={(event) => onDraftLabelChange(event.target.value)}
|
|
57
72
|
onKeyDown={(event) => {
|
|
58
|
-
if (event.key ===
|
|
73
|
+
if (event.key === "Enter") {
|
|
59
74
|
event.preventDefault();
|
|
60
75
|
void onSave();
|
|
61
|
-
} else if (event.key ===
|
|
76
|
+
} else if (event.key === "Escape") {
|
|
62
77
|
event.preventDefault();
|
|
63
78
|
onCancel();
|
|
64
79
|
}
|
|
65
80
|
}}
|
|
66
|
-
placeholder={t(
|
|
81
|
+
placeholder={t("sessionsLabelPlaceholder")}
|
|
67
82
|
className="h-8 rounded-lg border-border bg-background text-xs"
|
|
68
83
|
autoFocus
|
|
69
84
|
disabled={isSaving}
|
|
70
85
|
/>
|
|
71
86
|
<div className="flex items-center justify-between gap-2">
|
|
72
|
-
<div className="min-w-0 truncate text-[11px] text-muted-foreground/65">
|
|
87
|
+
<div className="min-w-0 truncate text-[11px] text-muted-foreground/65">
|
|
88
|
+
{sessionKey}
|
|
89
|
+
</div>
|
|
73
90
|
<div className="flex items-center gap-1">
|
|
74
91
|
<IconActionButton
|
|
75
92
|
icon={<Check className="h-3.5 w-3.5" />}
|
|
76
|
-
label={t(
|
|
93
|
+
label={t("save")}
|
|
77
94
|
tooltip={false}
|
|
78
95
|
onClick={() => void onSave()}
|
|
79
96
|
disabled={isSaving}
|
|
80
97
|
/>
|
|
81
98
|
<IconActionButton
|
|
82
99
|
icon={<X className="h-3.5 w-3.5" />}
|
|
83
|
-
label={t(
|
|
100
|
+
label={t("cancel")}
|
|
84
101
|
tooltip={false}
|
|
85
102
|
onClick={onCancel}
|
|
86
103
|
disabled={isSaving}
|
|
@@ -93,8 +110,15 @@ function ChatSidebarSessionEditingView({
|
|
|
93
110
|
|
|
94
111
|
type ChatSidebarSessionDisplayViewProps = Omit<
|
|
95
112
|
ChatSidebarSessionItemProps,
|
|
96
|
-
|
|
97
|
-
|
|
113
|
+
| "sessionKey"
|
|
114
|
+
| "isEditing"
|
|
115
|
+
| "draftLabel"
|
|
116
|
+
| "isSaving"
|
|
117
|
+
| "onDraftLabelChange"
|
|
118
|
+
| "onSave"
|
|
119
|
+
| "onCancel"
|
|
120
|
+
> &
|
|
121
|
+
Pick<ChatSidebarSessionItemProps, "sessionKey">;
|
|
98
122
|
|
|
99
123
|
function ChatSidebarSessionDisplayView({
|
|
100
124
|
sessionKey,
|
|
@@ -110,93 +134,113 @@ function ChatSidebarSessionDisplayView({
|
|
|
110
134
|
agentLabel,
|
|
111
135
|
agentAvatarUrl,
|
|
112
136
|
childSessionCount = 0,
|
|
137
|
+
cronJobCount = 0,
|
|
138
|
+
projectName,
|
|
113
139
|
onSelect,
|
|
114
|
-
onOpenChildSessions,
|
|
115
140
|
onStartEditing,
|
|
116
|
-
onTogglePinned
|
|
141
|
+
onTogglePinned,
|
|
117
142
|
}: ChatSidebarSessionDisplayViewProps) {
|
|
118
|
-
const trailingControlsClassName = childSessionCount > 0 && onOpenChildSessions ? 'pr-32' : 'pr-20';
|
|
119
|
-
|
|
120
143
|
return (
|
|
121
144
|
<div className="group/session relative">
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
145
|
+
<ChatSidebarContextCard
|
|
146
|
+
title={title}
|
|
147
|
+
metrics={[
|
|
148
|
+
{
|
|
149
|
+
icon: <Folder className="h-3.5 w-3.5" />,
|
|
150
|
+
label: t("chatSidebarContextProject"),
|
|
151
|
+
value: projectName,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
icon: <GitBranch className="h-3.5 w-3.5" />,
|
|
155
|
+
label: t("chatSidebarContextChildSessions"),
|
|
156
|
+
value: childSessionCount,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
icon: <AlarmClock className="h-3.5 w-3.5" />,
|
|
160
|
+
label: t("chatSidebarContextScheduledTasks"),
|
|
161
|
+
value: cronJobCount,
|
|
162
|
+
},
|
|
163
|
+
]}
|
|
164
|
+
>
|
|
165
|
+
<button type="button" onClick={onSelect} className="w-full text-left">
|
|
166
|
+
<div
|
|
167
|
+
className={cn(
|
|
168
|
+
"flex min-h-6 min-w-0 items-center",
|
|
169
|
+
runStatus && "pr-6",
|
|
170
|
+
"group-hover/session:pr-20 group-has-[[data-session-actions]:focus-within]/session:pr-20",
|
|
171
|
+
)}
|
|
172
|
+
>
|
|
173
|
+
<span className="flex min-w-0 items-center gap-1.5">
|
|
174
|
+
{agentId?.trim() && agentId.trim().toLowerCase() !== "main" ? (
|
|
175
|
+
<AgentAvatar
|
|
176
|
+
agentId={agentId}
|
|
177
|
+
displayName={agentLabel}
|
|
178
|
+
avatarUrl={agentAvatarUrl}
|
|
179
|
+
className="h-5 w-5 shrink-0"
|
|
180
|
+
/>
|
|
181
|
+
) : null}
|
|
182
|
+
<span className="truncate leading-6 font-medium">{title}</span>
|
|
183
|
+
{context.label ? (
|
|
184
|
+
<span
|
|
185
|
+
className={cn(
|
|
186
|
+
"shrink-0 rounded-full border px-1.5 py-0.5 text-[10px] font-semibold leading-none",
|
|
187
|
+
active
|
|
188
|
+
? "border-border/70 bg-background/80 text-foreground/75"
|
|
189
|
+
: "border-transparent bg-muted/70 text-muted-foreground",
|
|
190
|
+
)}
|
|
191
|
+
>
|
|
192
|
+
{context.label}
|
|
193
|
+
</span>
|
|
194
|
+
) : null}
|
|
195
|
+
{context.icon ? (
|
|
196
|
+
<span className="inline-flex h-4 w-4 shrink-0 items-center justify-center">
|
|
197
|
+
<SessionContextIconNode
|
|
198
|
+
icon={context.icon}
|
|
199
|
+
className={cn(
|
|
200
|
+
"h-[13px] w-[13px]",
|
|
201
|
+
active ? "text-foreground/75" : "text-muted-foreground",
|
|
202
|
+
)}
|
|
203
|
+
/>
|
|
204
|
+
</span>
|
|
205
|
+
) : null}
|
|
206
|
+
</span>
|
|
207
|
+
</div>
|
|
208
|
+
<div className="mt-0.5 flex items-center gap-2 text-[11px] text-muted-foreground/65">
|
|
209
|
+
<span className="min-w-0 truncate">{previewText}</span>
|
|
210
|
+
{showUnreadDot ? (
|
|
135
211
|
<span
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
{context.label}
|
|
144
|
-
</span>
|
|
145
|
-
) : null}
|
|
146
|
-
{context.icon ? (
|
|
147
|
-
<span className="inline-flex h-4 w-4 shrink-0 items-center justify-center">
|
|
148
|
-
<SessionContextIconNode icon={context.icon} className={cn('h-[13px] w-[13px]', active ? 'text-foreground/75' : 'text-muted-foreground')} />
|
|
149
|
-
</span>
|
|
150
|
-
) : null}
|
|
151
|
-
</span>
|
|
152
|
-
</div>
|
|
153
|
-
<div className="mt-0.5 flex items-center gap-2 text-[11px] text-muted-foreground/65">
|
|
154
|
-
<span className="min-w-0 truncate">
|
|
155
|
-
{previewText}
|
|
156
|
-
</span>
|
|
157
|
-
{showUnreadDot ? (
|
|
158
|
-
<span
|
|
159
|
-
aria-label={t('chatSessionUnread')}
|
|
160
|
-
className="ml-auto h-2 w-2 shrink-0 rounded-full bg-primary"
|
|
161
|
-
/>
|
|
162
|
-
) : (
|
|
163
|
-
<span className="ml-auto shrink-0">{trailingText}</span>
|
|
164
|
-
)}
|
|
165
|
-
</div>
|
|
166
|
-
</button>
|
|
167
|
-
{childSessionCount > 0 && onOpenChildSessions ? (
|
|
168
|
-
<button
|
|
169
|
-
type="button"
|
|
170
|
-
onClick={(event) => {
|
|
171
|
-
event.stopPropagation();
|
|
172
|
-
onOpenChildSessions();
|
|
173
|
-
}}
|
|
174
|
-
className="pointer-events-none absolute right-12 top-0 inline-flex h-6 items-center gap-1 rounded-md px-1.5 text-[10px] font-medium text-muted-foreground opacity-0 transition-colors hover:bg-background/80 hover:text-foreground group-hover/session:pointer-events-auto group-hover/session:opacity-100 focus-visible:pointer-events-auto focus-visible:opacity-100"
|
|
175
|
-
aria-label={t('chatSessionOpenChildSessions')}
|
|
176
|
-
title={t('chatSessionOpenChildSessions')}
|
|
177
|
-
>
|
|
178
|
-
<GitBranch className="h-3.5 w-3.5" />
|
|
179
|
-
<span>{childSessionCount}</span>
|
|
212
|
+
aria-label={t("chatSessionUnread")}
|
|
213
|
+
className="ml-auto h-2 w-2 shrink-0 rounded-full bg-primary"
|
|
214
|
+
/>
|
|
215
|
+
) : (
|
|
216
|
+
<span className="ml-auto shrink-0">{trailingText}</span>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
180
219
|
</button>
|
|
181
|
-
|
|
220
|
+
</ChatSidebarContextCard>
|
|
182
221
|
{runStatus ? (
|
|
183
222
|
<span className="absolute right-0 top-0 inline-flex h-6 w-6 items-center justify-center transition-opacity group-hover/session:opacity-0">
|
|
184
223
|
<SessionRunBadge status={runStatus} />
|
|
185
224
|
</span>
|
|
186
225
|
) : null}
|
|
187
|
-
<div
|
|
226
|
+
<div
|
|
227
|
+
data-session-actions
|
|
228
|
+
className="pointer-events-none absolute right-0 top-0 flex h-6 items-center gap-0.5 opacity-0 transition-opacity group-hover/session:pointer-events-auto group-hover/session:opacity-100 focus-within:pointer-events-auto focus-within:opacity-100"
|
|
229
|
+
>
|
|
188
230
|
<IconActionButton
|
|
189
231
|
size="sm"
|
|
190
232
|
tone="strong"
|
|
191
233
|
icon={
|
|
192
234
|
<Pin
|
|
193
235
|
className={cn(
|
|
194
|
-
|
|
195
|
-
isPinned &&
|
|
236
|
+
"h-3.5 w-3.5",
|
|
237
|
+
isPinned && "fill-current text-foreground",
|
|
196
238
|
)}
|
|
197
239
|
/>
|
|
198
240
|
}
|
|
199
|
-
label={t(
|
|
241
|
+
label={t(
|
|
242
|
+
isPinned ? "chatSidebarUnpinSession" : "chatSidebarPinSession",
|
|
243
|
+
)}
|
|
200
244
|
tooltipSide="right"
|
|
201
245
|
onClick={(event) => {
|
|
202
246
|
event.stopPropagation();
|
|
@@ -207,7 +251,7 @@ function ChatSidebarSessionDisplayView({
|
|
|
207
251
|
size="sm"
|
|
208
252
|
tone="strong"
|
|
209
253
|
icon={<Pencil className="h-3.5 w-3.5" />}
|
|
210
|
-
label={t(
|
|
254
|
+
label={t("edit")}
|
|
211
255
|
tooltipSide="right"
|
|
212
256
|
onClick={(event) => {
|
|
213
257
|
event.stopPropagation();
|
|
@@ -238,24 +282,25 @@ export function ChatSidebarSessionItem({
|
|
|
238
282
|
agentLabel,
|
|
239
283
|
agentAvatarUrl,
|
|
240
284
|
childSessionCount,
|
|
285
|
+
cronJobCount,
|
|
286
|
+
projectName,
|
|
241
287
|
isEditing,
|
|
242
288
|
draftLabel,
|
|
243
289
|
isSaving,
|
|
244
290
|
onSelect,
|
|
245
|
-
onOpenChildSessions,
|
|
246
291
|
onStartEditing,
|
|
247
292
|
onDraftLabelChange,
|
|
248
293
|
onSave,
|
|
249
294
|
onCancel,
|
|
250
|
-
onTogglePinned
|
|
295
|
+
onTogglePinned,
|
|
251
296
|
}: ChatSidebarSessionItemProps) {
|
|
252
297
|
return (
|
|
253
298
|
<div
|
|
254
299
|
className={cn(
|
|
255
|
-
|
|
300
|
+
"w-full rounded-lg px-2.5 py-2 text-left text-[13px] transition-colors",
|
|
256
301
|
active
|
|
257
|
-
?
|
|
258
|
-
:
|
|
302
|
+
? "bg-background/90 font-medium text-foreground"
|
|
303
|
+
: "text-foreground/80 hover:bg-background/65 hover:text-foreground",
|
|
259
304
|
)}
|
|
260
305
|
>
|
|
261
306
|
{isEditing ? (
|
|
@@ -283,7 +328,8 @@ export function ChatSidebarSessionItem({
|
|
|
283
328
|
agentAvatarUrl={agentAvatarUrl}
|
|
284
329
|
onSelect={onSelect}
|
|
285
330
|
childSessionCount={childSessionCount}
|
|
286
|
-
|
|
331
|
+
cronJobCount={cronJobCount}
|
|
332
|
+
projectName={projectName}
|
|
287
333
|
onStartEditing={onStartEditing}
|
|
288
334
|
onTogglePinned={onTogglePinned}
|
|
289
335
|
/>
|
|
@@ -16,6 +16,7 @@ type ChatSidebarSessionListProps = {
|
|
|
16
16
|
isProjectFirstView: boolean;
|
|
17
17
|
groups: ChatSidebarDateGroup[];
|
|
18
18
|
projectGroups: ChatSidebarProjectGroup[];
|
|
19
|
+
projectCronJobCountByRoot: ReadonlyMap<string, number>;
|
|
19
20
|
defaultSessionType: string;
|
|
20
21
|
sessionTypeOptions: SessionTypeOption[];
|
|
21
22
|
renderSessionItem: (item: NcpSessionListItemView) => ReactNode;
|
|
@@ -36,11 +37,16 @@ export function ChatSidebarSessionList({
|
|
|
36
37
|
isLoading,
|
|
37
38
|
isProjectFirstView,
|
|
38
39
|
projectGroups,
|
|
40
|
+
projectCronJobCountByRoot,
|
|
39
41
|
renderSessionItem,
|
|
40
42
|
sessionTypeOptions,
|
|
41
43
|
}: ChatSidebarSessionListProps) {
|
|
42
44
|
if (isLoading) {
|
|
43
|
-
return
|
|
45
|
+
return (
|
|
46
|
+
<div className="p-3 text-xs text-muted-foreground">
|
|
47
|
+
{t("sessionsLoading")}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
if (isProjectFirstView) {
|
|
@@ -49,6 +55,7 @@ export function ChatSidebarSessionList({
|
|
|
49
55
|
) : (
|
|
50
56
|
<ChatSidebarProjectGroups
|
|
51
57
|
groups={projectGroups}
|
|
58
|
+
projectCronJobCountByRoot={projectCronJobCountByRoot}
|
|
52
59
|
defaultSessionType={defaultSessionType}
|
|
53
60
|
sessionTypeOptions={sessionTypeOptions}
|
|
54
61
|
renderSessionItem={renderSessionItem}
|
|
@@ -67,7 +74,9 @@ export function ChatSidebarSessionList({
|
|
|
67
74
|
<div className="px-2 pb-1 pt-0.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground/65">
|
|
68
75
|
{group.label}
|
|
69
76
|
</div>
|
|
70
|
-
<div className="space-y-0.5">
|
|
77
|
+
<div className="space-y-0.5">
|
|
78
|
+
{group.items.map(renderSessionItem)}
|
|
79
|
+
</div>
|
|
71
80
|
</div>
|
|
72
81
|
))}
|
|
73
82
|
</div>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useCronJobs } from "@/features/cron";
|
|
3
|
+
import type { NcpSessionListItemView } from "@/features/chat/features/ncp/hooks/use-ncp-session-list-view";
|
|
4
|
+
|
|
5
|
+
export function useChatSidebarContextCounts(
|
|
6
|
+
allItems: readonly NcpSessionListItemView[],
|
|
7
|
+
) {
|
|
8
|
+
const cronQuery = useCronJobs({ all: true });
|
|
9
|
+
|
|
10
|
+
return useMemo(() => {
|
|
11
|
+
const projectRootBySessionKey = new Map(
|
|
12
|
+
allItems.flatMap(({ session }) => {
|
|
13
|
+
const projectRoot = session.projectRoot?.trim();
|
|
14
|
+
return projectRoot ? [[session.key, projectRoot] as const] : [];
|
|
15
|
+
}),
|
|
16
|
+
);
|
|
17
|
+
const cronJobCountBySessionKey = new Map<string, number>();
|
|
18
|
+
const cronJobCountByProjectRoot = new Map<string, number>();
|
|
19
|
+
|
|
20
|
+
for (const job of cronQuery.data?.jobs ?? []) {
|
|
21
|
+
const sessionKey = job.payload.sessionId?.trim();
|
|
22
|
+
if (!sessionKey) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
cronJobCountBySessionKey.set(
|
|
26
|
+
sessionKey,
|
|
27
|
+
(cronJobCountBySessionKey.get(sessionKey) ?? 0) + 1,
|
|
28
|
+
);
|
|
29
|
+
const projectRoot = projectRootBySessionKey.get(sessionKey);
|
|
30
|
+
if (projectRoot) {
|
|
31
|
+
cronJobCountByProjectRoot.set(
|
|
32
|
+
projectRoot,
|
|
33
|
+
(cronJobCountByProjectRoot.get(projectRoot) ?? 0) + 1,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { cronJobCountByProjectRoot, cronJobCountBySessionKey };
|
|
39
|
+
}, [allItems, cronQuery.data?.jobs]);
|
|
40
|
+
}
|
|
@@ -91,7 +91,11 @@ export type ContextCompactionTimelineView = {
|
|
|
91
91
|
|
|
92
92
|
export function readContextCompactionTimeline(message: Pick<NcpMessageView, 'metadata'>): ContextCompactionTimelineView | null {
|
|
93
93
|
const { metadata } = message;
|
|
94
|
-
if (
|
|
94
|
+
if (
|
|
95
|
+
!metadata ||
|
|
96
|
+
readOptionalString(metadata.inherited_from_session_id) ||
|
|
97
|
+
metadata[NEXTCLAW_TIMELINE_KIND_METADATA_KEY] !== CONTEXT_COMPACTION_TIMELINE_KIND
|
|
98
|
+
) {
|
|
95
99
|
return null;
|
|
96
100
|
}
|
|
97
101
|
const rawCheckpoint =
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
2
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
2
3
|
import type { ReactNode } from "react";
|
|
3
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
5
|
import type {
|
|
@@ -123,16 +124,21 @@ function renderWorkspaceFilePreview({
|
|
|
123
124
|
sessionWorkingDir = "/tmp",
|
|
124
125
|
showBreadcrumbs,
|
|
125
126
|
}: RenderWorkspaceFilePreviewOptions = {}) {
|
|
127
|
+
const queryClient = new QueryClient({
|
|
128
|
+
defaultOptions: { queries: { retry: false } },
|
|
129
|
+
});
|
|
126
130
|
const view = render(
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
<QueryClientProvider client={queryClient}>
|
|
132
|
+
<ChatSessionWorkspaceFilePreview
|
|
133
|
+
file={buildWorkspaceFile(file ?? {})}
|
|
134
|
+
sessionProjectRoot={sessionProjectRoot}
|
|
135
|
+
sessionWorkingDir={sessionWorkingDir}
|
|
136
|
+
refreshVersion={refreshVersion}
|
|
137
|
+
showBreadcrumbs={showBreadcrumbs}
|
|
138
|
+
onHtmlContentHeightChange={onHtmlContentHeightChange}
|
|
139
|
+
onFileOpen={onFileOpen}
|
|
140
|
+
/>
|
|
141
|
+
</QueryClientProvider>,
|
|
136
142
|
);
|
|
137
143
|
return { ...view, onFileOpen };
|
|
138
144
|
}
|
|
@@ -580,6 +586,7 @@ describe("ChatSessionWorkspaceFilePreview text rendering", () => {
|
|
|
580
586
|
|
|
581
587
|
expect(screen.queryByTestId("markdown-preview")).toBeNull();
|
|
582
588
|
expect(screen.getByTestId("file-code-surface")).toBeTruthy();
|
|
589
|
+
expect(screen.queryByRole("button", { name: t("chatWorkspaceMarkdownOutline") })).toBeNull();
|
|
583
590
|
});
|
|
584
591
|
|
|
585
592
|
it("keeps HTML files in the source preview when preview viewer is automatic", () => {
|
|
@@ -222,7 +222,7 @@ function createChildTab(): ResolvedChildSessionTab {
|
|
|
222
222
|
};
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
it('shows
|
|
225
|
+
it('shows child session content without a redundant metadata header', () => {
|
|
226
226
|
render(
|
|
227
227
|
<ChatSessionWorkspacePanelContent
|
|
228
228
|
activeSelection={{
|
|
@@ -239,10 +239,10 @@ it('shows compact child session metadata in a full-height column layout', () =>
|
|
|
239
239
|
);
|
|
240
240
|
|
|
241
241
|
expect(screen.queryByText('Child title')).toBeNull();
|
|
242
|
-
expect(screen.
|
|
243
|
-
expect(screen.
|
|
244
|
-
expect(screen.
|
|
245
|
-
expect(screen.
|
|
242
|
+
expect(screen.queryByText('原生')).toBeNull();
|
|
243
|
+
expect(screen.queryByText('minimax/MiniMax-M3')).toBeNull();
|
|
244
|
+
expect(screen.queryByText('nextbot')).toBeNull();
|
|
245
|
+
expect(screen.queryByTitle('/Users/peiwang/Projects/nextbot')).toBeNull();
|
|
246
246
|
const conversationArea = screen.getByTestId('session-conversation-area');
|
|
247
247
|
const selectedContentHost = conversationArea.parentElement?.parentElement;
|
|
248
248
|
expect(Array.from(selectedContentHost?.classList ?? [])).toEqual(
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
2
|
+
import { createRef } from "react";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { WorkspaceMarkdownOutline } from "@/features/chat/features/workspace/components/workspace-markdown-outline";
|
|
5
|
+
import { t } from "@/shared/lib/i18n";
|
|
6
|
+
|
|
7
|
+
describe("WorkspaceMarkdownOutline", () => {
|
|
8
|
+
it("does not render an outline action when the document has no headings", async () => {
|
|
9
|
+
const scrollContainerRef = createRef<HTMLDivElement>();
|
|
10
|
+
render(
|
|
11
|
+
<>
|
|
12
|
+
<WorkspaceMarkdownOutline
|
|
13
|
+
contentKey="plain fixture"
|
|
14
|
+
documentKey="notes.md"
|
|
15
|
+
scrollContainerRef={scrollContainerRef}
|
|
16
|
+
/>
|
|
17
|
+
<div ref={scrollContainerRef}>Plain paragraph</div>
|
|
18
|
+
</>,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
await waitFor(() => {
|
|
22
|
+
expect(
|
|
23
|
+
screen.queryByRole("button", {
|
|
24
|
+
name: t("chatWorkspaceMarkdownOutline"),
|
|
25
|
+
}),
|
|
26
|
+
).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("preserves outline scroll state and instantly jumps to the selected rendered heading", async () => {
|
|
31
|
+
const scrollContainerRef = createRef<HTMLDivElement>();
|
|
32
|
+
const scrollTo = vi.fn();
|
|
33
|
+
const { rerender } = render(
|
|
34
|
+
<>
|
|
35
|
+
<WorkspaceMarkdownOutline
|
|
36
|
+
contentKey="outline fixture"
|
|
37
|
+
documentKey="README.md"
|
|
38
|
+
scrollContainerRef={scrollContainerRef}
|
|
39
|
+
/>
|
|
40
|
+
<div ref={scrollContainerRef}>
|
|
41
|
+
<h1>Overview</h1>
|
|
42
|
+
<h3>
|
|
43
|
+
Install <em>quickly</em>
|
|
44
|
+
</h3>
|
|
45
|
+
<h2>Overview</h2>
|
|
46
|
+
</div>
|
|
47
|
+
</>,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const outlineTrigger = await screen.findByRole("button", {
|
|
51
|
+
name: t("chatWorkspaceMarkdownOutline"),
|
|
52
|
+
});
|
|
53
|
+
const scrollContainer = scrollContainerRef.current!;
|
|
54
|
+
const headings = screen.getAllByRole("heading");
|
|
55
|
+
Object.defineProperty(scrollContainer, "scrollTop", {
|
|
56
|
+
configurable: true,
|
|
57
|
+
value: 40,
|
|
58
|
+
writable: true,
|
|
59
|
+
});
|
|
60
|
+
Object.defineProperty(scrollContainer, "scrollTo", {
|
|
61
|
+
configurable: true,
|
|
62
|
+
value: scrollTo,
|
|
63
|
+
});
|
|
64
|
+
vi.spyOn(scrollContainer, "getBoundingClientRect").mockReturnValue({
|
|
65
|
+
top: 100,
|
|
66
|
+
} as DOMRect);
|
|
67
|
+
vi.spyOn(headings[1]!, "getBoundingClientRect").mockReturnValue({
|
|
68
|
+
top: 420,
|
|
69
|
+
} as DOMRect);
|
|
70
|
+
|
|
71
|
+
fireEvent.click(outlineTrigger);
|
|
72
|
+
|
|
73
|
+
expect(
|
|
74
|
+
screen.getByTestId("workspace-markdown-outline-popover").className,
|
|
75
|
+
).toContain("w-[min(20rem,calc(100vw-1.5rem))]");
|
|
76
|
+
const outlineNavigation = screen.getByRole("navigation");
|
|
77
|
+
expect(outlineNavigation.className).toContain("max-h-80");
|
|
78
|
+
expect(screen.getAllByRole("button", { name: "Overview" })).toHaveLength(2);
|
|
79
|
+
const nestedHeading = screen.getByRole("button", {
|
|
80
|
+
name: "Install quickly",
|
|
81
|
+
});
|
|
82
|
+
expect(nestedHeading.style.paddingInlineStart).toBe("1.25rem");
|
|
83
|
+
|
|
84
|
+
outlineNavigation.scrollTop = 176;
|
|
85
|
+
fireEvent.click(outlineTrigger);
|
|
86
|
+
await waitFor(() => {
|
|
87
|
+
expect(
|
|
88
|
+
screen.queryByTestId("workspace-markdown-outline-popover"),
|
|
89
|
+
).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
fireEvent.click(outlineTrigger);
|
|
92
|
+
expect((await screen.findByRole("navigation")).scrollTop).toBe(176);
|
|
93
|
+
fireEvent.click(outlineTrigger);
|
|
94
|
+
rerender(
|
|
95
|
+
<>
|
|
96
|
+
<WorkspaceMarkdownOutline
|
|
97
|
+
contentKey="outline fixture"
|
|
98
|
+
documentKey="docs/other.md"
|
|
99
|
+
scrollContainerRef={scrollContainerRef}
|
|
100
|
+
/>
|
|
101
|
+
<div ref={scrollContainerRef}>
|
|
102
|
+
<h1>Overview</h1>
|
|
103
|
+
<h3>
|
|
104
|
+
Install <em>quickly</em>
|
|
105
|
+
</h3>
|
|
106
|
+
<h2>Overview</h2>
|
|
107
|
+
</div>
|
|
108
|
+
</>,
|
|
109
|
+
);
|
|
110
|
+
fireEvent.click(outlineTrigger);
|
|
111
|
+
expect((await screen.findByRole("navigation")).scrollTop).toBe(0);
|
|
112
|
+
|
|
113
|
+
fireEvent.click(screen.getByRole("button", { name: "Install quickly" }));
|
|
114
|
+
|
|
115
|
+
expect(scrollTo).toHaveBeenCalledWith({ top: 344 });
|
|
116
|
+
await waitFor(() => {
|
|
117
|
+
expect(
|
|
118
|
+
screen.queryByTestId("workspace-markdown-outline-popover"),
|
|
119
|
+
).toBeNull();
|
|
120
|
+
});
|
|
121
|
+
expect(document.activeElement).toBe(outlineTrigger);
|
|
122
|
+
expect(
|
|
123
|
+
document.querySelector(
|
|
124
|
+
'[role="tooltip"][data-state="delayed-open"], [role="tooltip"][data-state="instant-open"]',
|
|
125
|
+
),
|
|
126
|
+
).toBeNull();
|
|
127
|
+
});
|
|
128
|
+
});
|