@steerable/agent-shell-web 0.6.30 → 0.6.32
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/package.json +4 -4
- package/public/favicon.ico +0 -0
- package/public/favicon.png +0 -0
- package/public/favicon.svg +14 -0
- package/src/assets/logo-steerable.svg +14 -0
- package/src/brand.ts +3 -3
- package/src/components/AgentSidebar.tsx +406 -221
- package/src/components/BrandLockup.tsx +1 -1
- package/src/components/CreateProjectModal.tsx +269 -0
- package/src/components/chat/ApprovalModal.tsx +2 -2
- package/src/components/chat/ChatProjectBadge.tsx +98 -12
- package/src/components/chat/ExecPolicyPicker.tsx +4 -3
- package/src/layouts/AgentLayout.tsx +12 -10
- package/src/lib/exec-policy.ts +2 -2
- package/src/lib/http-bridge.ts +20 -7
- package/src/lib/local-api.ts +9 -3
- package/src/pages/AgentPage.tsx +11 -10
- package/src/assets/logo-generic.png +0 -0
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
* │ ⬡ 智能体管理 │ ← /settings?section=agents(独立页)
|
|
64
64
|
* │ ⬡ Skill 设置 │ ← /settings?section=skills(独立页)
|
|
65
65
|
* │ 🔌 MCP 设置 │ ← /settings?section=mcp(独立页)
|
|
66
|
-
* │
|
|
67
|
-
* │ v 📁 项目A · 3 (hover:
|
|
66
|
+
* │ 会话 v 📁+ │ ← 📁+ 打开新建项目弹窗
|
|
67
|
+
* │ v 📁 项目A · 3 (hover: +··)│ ← + 新建对话;·· 菜单:重命名/换目录/访达/删
|
|
68
68
|
* │ ...(项目内对话) │
|
|
69
69
|
* │ 今天 │
|
|
70
70
|
* │ ...(无项目对话,按日期分组) │ ← 无项目排在项目分组之后
|
|
@@ -73,19 +73,13 @@
|
|
|
73
73
|
* │ ⚙ 设置 │ ← /settings(模型 + 洞察/遥测/用量/搜索/安全)
|
|
74
74
|
* └─────────────────────────────────┘
|
|
75
75
|
*
|
|
76
|
-
* 项目模式:项目 = 名字 +
|
|
77
|
-
*
|
|
78
|
-
* tool-router.ts ToolExecContext.projectRoot);无项目对话不沙箱。
|
|
76
|
+
* 项目模式:项目 = 名字 + 托管家目录(Documents/<应用名>/<项目名>/)+
|
|
77
|
+
* 可选源文件夹。写入/命令围栏在家目录;源文件夹只放宽读取。
|
|
79
78
|
*/
|
|
80
79
|
|
|
81
|
-
import {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
useMemo,
|
|
85
|
-
useRef,
|
|
86
|
-
useState,
|
|
87
|
-
} from 'react';
|
|
88
|
-
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
|
80
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
81
|
+
import { createPortal } from "react-dom";
|
|
82
|
+
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
|
89
83
|
import {
|
|
90
84
|
LuChevronDown,
|
|
91
85
|
LuChevronUp,
|
|
@@ -104,29 +98,32 @@ import {
|
|
|
104
98
|
LuFolderPlus,
|
|
105
99
|
LuPencil,
|
|
106
100
|
LuPlug,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
import {
|
|
110
|
-
import {
|
|
101
|
+
LuEllipsis,
|
|
102
|
+
} from "react-icons/lu";
|
|
103
|
+
import { parseChatTitle } from "@/lib/chat-title";
|
|
104
|
+
import { getDateGroupLabel, getDateGroupPriority } from "@/lib/date-groups";
|
|
105
|
+
import { getElectronBridge, isElectron } from "@/lib/electron-bridge";
|
|
111
106
|
import {
|
|
112
107
|
createProject,
|
|
113
108
|
deleteProject,
|
|
114
109
|
listProjects,
|
|
110
|
+
openLocalPath,
|
|
115
111
|
updateProject,
|
|
116
112
|
type LocalChat,
|
|
117
113
|
type LocalChatAgent,
|
|
118
114
|
type LocalProject,
|
|
119
|
-
} from
|
|
120
|
-
import type { UseChatsAndAgentsResult } from
|
|
121
|
-
import type { PackChatSlotContribution } from
|
|
122
|
-
import type { RightPanelState } from
|
|
123
|
-
import { BrandLockup } from
|
|
115
|
+
} from "@/lib/local-api";
|
|
116
|
+
import type { UseChatsAndAgentsResult } from "@/hooks/useChatsAndAgents";
|
|
117
|
+
import type { PackChatSlotContribution } from "@/packs/registry";
|
|
118
|
+
import type { RightPanelState } from "@/layouts/AgentLayout";
|
|
119
|
+
import { BrandLockup } from "@/components/BrandLockup";
|
|
120
|
+
import { CreateProjectModal } from "@/components/CreateProjectModal";
|
|
124
121
|
|
|
125
|
-
const DEFAULT_DOT_COLOR =
|
|
122
|
+
const DEFAULT_DOT_COLOR = "#7c3aed";
|
|
126
123
|
|
|
127
124
|
function agentInitial(agent: LocalChatAgent | null): string {
|
|
128
|
-
const name = (agent?.name ||
|
|
129
|
-
return name ? name[0].toUpperCase() :
|
|
125
|
+
const name = (agent?.name || "").trim();
|
|
126
|
+
return name ? name[0].toUpperCase() : "A";
|
|
130
127
|
}
|
|
131
128
|
|
|
132
129
|
function AgentDot({
|
|
@@ -147,6 +144,125 @@ function AgentDot({
|
|
|
147
144
|
);
|
|
148
145
|
}
|
|
149
146
|
|
|
147
|
+
function placeProjectMenu(anchor: HTMLElement): { top: number; left: number } {
|
|
148
|
+
const box = anchor.getBoundingClientRect();
|
|
149
|
+
const width = 224;
|
|
150
|
+
const left = Math.min(box.right + 4, window.innerWidth - width - 8);
|
|
151
|
+
return { top: Math.max(8, box.top), left };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function ProjectOverflowMenu({
|
|
155
|
+
project,
|
|
156
|
+
chatCount,
|
|
157
|
+
anchor,
|
|
158
|
+
revealLabel,
|
|
159
|
+
confirmDelete,
|
|
160
|
+
deleting,
|
|
161
|
+
onClose,
|
|
162
|
+
onRename,
|
|
163
|
+
onChangeFolder,
|
|
164
|
+
onReveal,
|
|
165
|
+
onDelete,
|
|
166
|
+
}: {
|
|
167
|
+
project: LocalProject;
|
|
168
|
+
chatCount: number;
|
|
169
|
+
anchor: HTMLElement;
|
|
170
|
+
revealLabel: string;
|
|
171
|
+
confirmDelete: boolean;
|
|
172
|
+
deleting: boolean;
|
|
173
|
+
onClose: () => void;
|
|
174
|
+
onRename: () => void;
|
|
175
|
+
onChangeFolder: () => void;
|
|
176
|
+
onReveal: () => void;
|
|
177
|
+
onDelete: () => void;
|
|
178
|
+
}) {
|
|
179
|
+
const pos = placeProjectMenu(anchor);
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
const onKey = (event: KeyboardEvent) => {
|
|
182
|
+
if (event.key === "Escape") onClose();
|
|
183
|
+
};
|
|
184
|
+
window.addEventListener("keydown", onKey);
|
|
185
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
186
|
+
}, [onClose]);
|
|
187
|
+
return createPortal(
|
|
188
|
+
<>
|
|
189
|
+
<div className="fixed inset-0 z-[90]" onClick={onClose} />
|
|
190
|
+
<div
|
|
191
|
+
role="menu"
|
|
192
|
+
className="fixed z-[91] w-56 overflow-hidden rounded-2xl border border-agent-border bg-agent-canvas p-1 shadow-lg"
|
|
193
|
+
style={pos}
|
|
194
|
+
data-testid="project-overflow-menu"
|
|
195
|
+
>
|
|
196
|
+
<div className="px-2 py-1.5">
|
|
197
|
+
<div className="flex items-center gap-2 text-xs font-medium text-agent-foreground">
|
|
198
|
+
<LuFolder className="h-3.5 w-3.5 shrink-0 text-agent-muted-foreground" />
|
|
199
|
+
<span className="min-w-0 truncate">{project.name}</span>
|
|
200
|
+
</div>
|
|
201
|
+
<div className="mt-0.5 pl-[22px] text-[10px] text-agent-muted-foreground">
|
|
202
|
+
{chatCount} 个会话
|
|
203
|
+
</div>
|
|
204
|
+
<div
|
|
205
|
+
className="mt-0.5 truncate pl-[22px] font-mono text-[10px] text-agent-muted-foreground/70"
|
|
206
|
+
title={project.folderPath}
|
|
207
|
+
>
|
|
208
|
+
{project.folderPath}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
<div className="mx-1 my-1 border-t border-agent-border/60" />
|
|
212
|
+
<button
|
|
213
|
+
type="button"
|
|
214
|
+
role="menuitem"
|
|
215
|
+
title="重命名项目"
|
|
216
|
+
onClick={onRename}
|
|
217
|
+
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-xs text-agent-foreground hover:bg-agent-foreground/5"
|
|
218
|
+
>
|
|
219
|
+
<LuPencil className="h-3.5 w-3.5 shrink-0 text-agent-muted-foreground" />
|
|
220
|
+
重命名
|
|
221
|
+
</button>
|
|
222
|
+
<button
|
|
223
|
+
type="button"
|
|
224
|
+
role="menuitem"
|
|
225
|
+
title="编辑项目"
|
|
226
|
+
onClick={onChangeFolder}
|
|
227
|
+
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-xs text-agent-foreground hover:bg-agent-foreground/5"
|
|
228
|
+
>
|
|
229
|
+
<LuFolderOpen className="h-3.5 w-3.5 shrink-0 text-agent-muted-foreground" />
|
|
230
|
+
编辑项目
|
|
231
|
+
</button>
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
234
|
+
role="menuitem"
|
|
235
|
+
title={revealLabel}
|
|
236
|
+
onClick={onReveal}
|
|
237
|
+
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-xs text-agent-foreground hover:bg-agent-foreground/5"
|
|
238
|
+
>
|
|
239
|
+
<LuFolder className="h-3.5 w-3.5 shrink-0 text-agent-muted-foreground" />
|
|
240
|
+
{revealLabel}
|
|
241
|
+
</button>
|
|
242
|
+
<div className="mx-1 my-1 border-t border-agent-border/60" />
|
|
243
|
+
<button
|
|
244
|
+
type="button"
|
|
245
|
+
role="menuitem"
|
|
246
|
+
disabled={deleting}
|
|
247
|
+
title={
|
|
248
|
+
confirmDelete
|
|
249
|
+
? "再次点击确认删除(会话会保留为无项目对话)"
|
|
250
|
+
: "删除项目(会话保留为无项目对话)"
|
|
251
|
+
}
|
|
252
|
+
onClick={onDelete}
|
|
253
|
+
className={`flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-xs hover:bg-agent-destructive/10 ${
|
|
254
|
+
confirmDelete ? "text-agent-destructive" : "text-agent-foreground"
|
|
255
|
+
}`}
|
|
256
|
+
>
|
|
257
|
+
<LuTrash2 className={`h-3.5 w-3.5 shrink-0 ${deleting ? "animate-pulse" : ""}`} />
|
|
258
|
+
{confirmDelete ? "再次点击确认删除" : "删除项目"}
|
|
259
|
+
</button>
|
|
260
|
+
</div>
|
|
261
|
+
</>,
|
|
262
|
+
document.body,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
150
266
|
interface AgentSidebarProps {
|
|
151
267
|
data: UseChatsAndAgentsResult;
|
|
152
268
|
/** 右侧栏当前打开的面板:null / 'terminal' / 包槽位 id(互斥)。 */
|
|
@@ -170,17 +286,17 @@ export function AgentSidebar({
|
|
|
170
286
|
const location = useLocation();
|
|
171
287
|
const { chatId: currentChatId } = useParams<{ chatId?: string }>();
|
|
172
288
|
const bridge = getElectronBridge();
|
|
173
|
-
const onSettingsPage = location.pathname ===
|
|
289
|
+
const onSettingsPage = location.pathname === "/settings";
|
|
174
290
|
// /settings?section=skills|mcp|agents|(缺省 = 综合设置)—— 各自高亮。
|
|
175
291
|
const settingsSection = useMemo(
|
|
176
|
-
() => new URLSearchParams(location.search).get(
|
|
292
|
+
() => new URLSearchParams(location.search).get("section"),
|
|
177
293
|
[location.search],
|
|
178
294
|
);
|
|
179
295
|
const onGeneralSettings =
|
|
180
296
|
onSettingsPage &&
|
|
181
|
-
settingsSection !==
|
|
182
|
-
settingsSection !==
|
|
183
|
-
settingsSection !==
|
|
297
|
+
settingsSection !== "skills" &&
|
|
298
|
+
settingsSection !== "mcp" &&
|
|
299
|
+
settingsSection !== "agents";
|
|
184
300
|
const onNewChatHome = !currentChatId && !onSettingsPage;
|
|
185
301
|
|
|
186
302
|
const {
|
|
@@ -198,7 +314,7 @@ export function AgentSidebar({
|
|
|
198
314
|
const [isMac, setIsMac] = useState(false);
|
|
199
315
|
|
|
200
316
|
useEffect(() => {
|
|
201
|
-
if (typeof navigator !==
|
|
317
|
+
if (typeof navigator !== "undefined") {
|
|
202
318
|
setIsMac(/Mac|iPod|iPhone|iPad/.test(navigator.platform));
|
|
203
319
|
}
|
|
204
320
|
}, []);
|
|
@@ -211,17 +327,29 @@ export function AgentSidebar({
|
|
|
211
327
|
|
|
212
328
|
// ───── 项目模式 ─────
|
|
213
329
|
// 项目列表从 local-backend 拉取(electron-store 持久化)。会话按
|
|
214
|
-
// projectId
|
|
215
|
-
//
|
|
330
|
+
// projectId 分组:项目分组在上(组头 hover:+ 新建对话 / ·· 菜单),
|
|
331
|
+
// 无项目对话在下(日期分组)。
|
|
216
332
|
const [projects, setProjects] = useState<LocalProject[]>([]);
|
|
217
333
|
const [collapsedProjectIds, setCollapsedProjectIds] = useState<Set<string>>(
|
|
218
334
|
() => new Set(),
|
|
219
335
|
);
|
|
220
|
-
const [renamingProjectId, setRenamingProjectId] = useState<string | null>(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const [
|
|
336
|
+
const [renamingProjectId, setRenamingProjectId] = useState<string | null>(
|
|
337
|
+
null,
|
|
338
|
+
);
|
|
339
|
+
const [renamingValue, setRenamingValue] = useState("");
|
|
340
|
+
const [confirmDeleteProjectId, setConfirmDeleteProjectId] = useState<
|
|
341
|
+
string | null
|
|
342
|
+
>(null);
|
|
343
|
+
const [deletingProjectId, setDeletingProjectId] = useState<string | null>(
|
|
344
|
+
null,
|
|
345
|
+
);
|
|
224
346
|
const [projectError, setProjectError] = useState<string | null>(null);
|
|
347
|
+
const [createProjectOpen, setCreateProjectOpen] = useState(false);
|
|
348
|
+
const [editingProjectId, setEditingProjectId] = useState<string | null>(null);
|
|
349
|
+
const [projectMenu, setProjectMenu] = useState<{
|
|
350
|
+
id: string;
|
|
351
|
+
anchor: HTMLElement;
|
|
352
|
+
} | null>(null);
|
|
225
353
|
|
|
226
354
|
const fetchProjects = useCallback(async () => {
|
|
227
355
|
if (!isElectron()) return;
|
|
@@ -229,7 +357,7 @@ export function AgentSidebar({
|
|
|
229
357
|
const res = await listProjects();
|
|
230
358
|
setProjects(res.projects || []);
|
|
231
359
|
} catch (err) {
|
|
232
|
-
console.error(
|
|
360
|
+
console.error("获取项目列表失败:", err);
|
|
233
361
|
setProjectError(err instanceof Error ? err.message : String(err));
|
|
234
362
|
}
|
|
235
363
|
}, []);
|
|
@@ -238,24 +366,25 @@ export function AgentSidebar({
|
|
|
238
366
|
void fetchProjects();
|
|
239
367
|
}, [fetchProjects]);
|
|
240
368
|
|
|
241
|
-
const handleCreateProject = useCallback(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
369
|
+
const handleCreateProject = useCallback(
|
|
370
|
+
async (input: { name: string; sourceFolders: string[] }) => {
|
|
371
|
+
setProjectError(null);
|
|
372
|
+
try {
|
|
373
|
+
await createProject({
|
|
374
|
+
name: input.name,
|
|
375
|
+
...(input.sourceFolders.length > 0
|
|
376
|
+
? { sourceFolders: input.sourceFolders }
|
|
377
|
+
: {}),
|
|
378
|
+
});
|
|
379
|
+
await fetchProjects();
|
|
380
|
+
} catch (err) {
|
|
381
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
382
|
+
setProjectError(message);
|
|
383
|
+
throw err instanceof Error ? err : new Error(message);
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
[fetchProjects],
|
|
387
|
+
);
|
|
259
388
|
|
|
260
389
|
const handleRenameProject = useCallback(
|
|
261
390
|
async (projectId: string) => {
|
|
@@ -272,48 +401,80 @@ export function AgentSidebar({
|
|
|
272
401
|
[renamingValue, fetchProjects],
|
|
273
402
|
);
|
|
274
403
|
|
|
275
|
-
const
|
|
276
|
-
async (
|
|
277
|
-
|
|
404
|
+
const handleUpdateProject = useCallback(
|
|
405
|
+
async (
|
|
406
|
+
projectId: string,
|
|
407
|
+
input: { name: string; sourceFolders: string[] },
|
|
408
|
+
) => {
|
|
278
409
|
setProjectError(null);
|
|
279
410
|
try {
|
|
280
|
-
|
|
281
|
-
|
|
411
|
+
await updateProject(projectId, {
|
|
412
|
+
name: input.name,
|
|
413
|
+
sourceFolders: input.sourceFolders,
|
|
282
414
|
});
|
|
283
|
-
if (!result || result.canceled || result.filePaths.length === 0) return;
|
|
284
|
-
await updateProject(projectId, { folderPath: result.filePaths[0] });
|
|
285
415
|
await fetchProjects();
|
|
286
416
|
} catch (err) {
|
|
287
|
-
|
|
417
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
418
|
+
setProjectError(message);
|
|
419
|
+
throw err instanceof Error ? err : new Error(message);
|
|
288
420
|
}
|
|
289
421
|
},
|
|
290
|
-
[
|
|
422
|
+
[fetchProjects],
|
|
291
423
|
);
|
|
292
424
|
|
|
293
|
-
const
|
|
425
|
+
const removeProject = useCallback(
|
|
294
426
|
async (projectId: string) => {
|
|
295
427
|
if (deletingProjectId === projectId) return;
|
|
296
|
-
// 与会话删除同款两段确认:第一次点击武装红色按钮,第二次才真删。
|
|
297
|
-
if (confirmDeleteProjectId !== projectId) {
|
|
298
|
-
setConfirmDeleteProjectId(projectId);
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
428
|
try {
|
|
302
429
|
setDeletingProjectId(projectId);
|
|
303
|
-
// 后端会把该项目下的会话降级为无项目对话(不删会话)。
|
|
304
430
|
await deleteProject(projectId);
|
|
305
431
|
setConfirmDeleteProjectId(null);
|
|
432
|
+
setProjectMenu(null);
|
|
433
|
+
setEditingProjectId(null);
|
|
306
434
|
await fetchProjects();
|
|
307
435
|
await data.refreshChats();
|
|
308
436
|
} catch (err) {
|
|
309
|
-
|
|
437
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
438
|
+
setProjectError(message);
|
|
439
|
+
throw err instanceof Error ? err : new Error(message);
|
|
310
440
|
} finally {
|
|
311
441
|
setDeletingProjectId(null);
|
|
312
442
|
}
|
|
313
443
|
},
|
|
314
|
-
[
|
|
444
|
+
[deletingProjectId, fetchProjects, data],
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
const handleDeleteProject = useCallback(
|
|
448
|
+
async (projectId: string) => {
|
|
449
|
+
// 与会话删除同款两段确认:第一次点击武装红色按钮,第二次才真删。
|
|
450
|
+
if (confirmDeleteProjectId !== projectId) {
|
|
451
|
+
setConfirmDeleteProjectId(projectId);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
await removeProject(projectId);
|
|
456
|
+
} catch {
|
|
457
|
+
// 横幅已写 projectError
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
[confirmDeleteProjectId, removeProject],
|
|
315
461
|
);
|
|
316
462
|
|
|
463
|
+
const closeProjectMenu = useCallback(() => {
|
|
464
|
+
setProjectMenu(null);
|
|
465
|
+
setConfirmDeleteProjectId(null);
|
|
466
|
+
}, []);
|
|
467
|
+
|
|
468
|
+
const handleRevealProjectFolder = useCallback(async (folderPath: string) => {
|
|
469
|
+
setProjectError(null);
|
|
470
|
+
try {
|
|
471
|
+
const res = await openLocalPath(folderPath);
|
|
472
|
+
if (!res.success && res.error) setProjectError(res.error);
|
|
473
|
+
} catch (err) {
|
|
474
|
+
setProjectError(err instanceof Error ? err.message : String(err));
|
|
475
|
+
}
|
|
476
|
+
}, []);
|
|
477
|
+
|
|
317
478
|
const toggleProjectCollapsed = useCallback((projectId: string) => {
|
|
318
479
|
setCollapsedProjectIds((prev) => {
|
|
319
480
|
const next = new Set(prev);
|
|
@@ -350,7 +511,7 @@ export function AgentSidebar({
|
|
|
350
511
|
navigate(`/agent?projectId=${encodeURIComponent(projectId)}`);
|
|
351
512
|
return;
|
|
352
513
|
}
|
|
353
|
-
navigate(
|
|
514
|
+
navigate("/agent");
|
|
354
515
|
},
|
|
355
516
|
[navigate],
|
|
356
517
|
);
|
|
@@ -369,7 +530,7 @@ export function AgentSidebar({
|
|
|
369
530
|
const { displayTitle, isAutomation } = parseChatTitle(chat.title);
|
|
370
531
|
return {
|
|
371
532
|
id: chat.id,
|
|
372
|
-
title: displayTitle ||
|
|
533
|
+
title: displayTitle || "新会话",
|
|
373
534
|
isAutomation,
|
|
374
535
|
isPinned: chat.isPinned,
|
|
375
536
|
sortDate,
|
|
@@ -420,7 +581,7 @@ export function AgentSidebar({
|
|
|
420
581
|
// 分组,「5 天前置顶的会话」会排在「今天」的普通会话之后,置顶语义
|
|
421
582
|
// 就只剩组内有效——与用户点图钉时的预期不符。
|
|
422
583
|
noProjectChats.forEach((chat) => {
|
|
423
|
-
const label = chat.isPinned ?
|
|
584
|
+
const label = chat.isPinned ? "置顶" : getDateGroupLabel(chat.sortDate);
|
|
424
585
|
if (!map.has(label)) {
|
|
425
586
|
map.set(label, {
|
|
426
587
|
label,
|
|
@@ -448,7 +609,7 @@ export function AgentSidebar({
|
|
|
448
609
|
const ok = await deleteChat(id);
|
|
449
610
|
if (ok) {
|
|
450
611
|
setConfirmDeleteChatId(null);
|
|
451
|
-
if (id === currentChatId) navigate(
|
|
612
|
+
if (id === currentChatId) navigate("/agent");
|
|
452
613
|
}
|
|
453
614
|
} finally {
|
|
454
615
|
setDeletingChatId(null);
|
|
@@ -477,22 +638,17 @@ export function AgentSidebar({
|
|
|
477
638
|
navigate(`/agent/${chat.id}`);
|
|
478
639
|
}}
|
|
479
640
|
className={[
|
|
480
|
-
|
|
641
|
+
"flex h-7 w-full min-w-0 items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors duration-200",
|
|
481
642
|
isCurrent
|
|
482
|
-
?
|
|
483
|
-
:
|
|
484
|
-
].join(
|
|
643
|
+
? "bg-agent-foreground/10 font-medium text-agent-foreground"
|
|
644
|
+
: "text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground",
|
|
645
|
+
].join(" ")}
|
|
485
646
|
title={
|
|
486
|
-
chat.isAutomation
|
|
487
|
-
? `[由自动化触发] ${chat.title}`
|
|
488
|
-
: chat.title
|
|
647
|
+
chat.isAutomation ? `[由自动化触发] ${chat.title}` : chat.title
|
|
489
648
|
}
|
|
490
649
|
>
|
|
491
650
|
{chat.isPinned && (
|
|
492
|
-
<span
|
|
493
|
-
className="shrink-0 text-[10px]"
|
|
494
|
-
aria-label="已置顶"
|
|
495
|
-
>
|
|
651
|
+
<span className="shrink-0 text-[10px]" aria-label="已置顶">
|
|
496
652
|
📌
|
|
497
653
|
</span>
|
|
498
654
|
)}
|
|
@@ -503,9 +659,7 @@ export function AgentSidebar({
|
|
|
503
659
|
/>
|
|
504
660
|
)}
|
|
505
661
|
<AgentDot agent={chatAgent} size={16} />
|
|
506
|
-
<span className="min-w-0 truncate leading-none">
|
|
507
|
-
{chat.title}
|
|
508
|
-
</span>
|
|
662
|
+
<span className="min-w-0 truncate leading-none">{chat.title}</span>
|
|
509
663
|
</button>
|
|
510
664
|
{/* Gradient mask so the trash button doesn't paint
|
|
511
665
|
over the chat title — fade matches the row's
|
|
@@ -514,15 +668,15 @@ export function AgentSidebar({
|
|
|
514
668
|
<div
|
|
515
669
|
aria-hidden="true"
|
|
516
670
|
className={[
|
|
517
|
-
|
|
671
|
+
"pointer-events-none absolute inset-y-0 right-0 w-12 rounded-r-full transition-opacity duration-200",
|
|
518
672
|
isConfirmingDelete
|
|
519
673
|
? isCurrent
|
|
520
|
-
?
|
|
521
|
-
:
|
|
674
|
+
? "bg-gradient-to-l from-agent-canvas via-agent-canvas/95 to-transparent opacity-100"
|
|
675
|
+
: "bg-gradient-to-l from-agent-muted via-agent-muted/95 to-transparent opacity-100"
|
|
522
676
|
: isCurrent
|
|
523
|
-
?
|
|
524
|
-
:
|
|
525
|
-
].join(
|
|
677
|
+
? "bg-gradient-to-l from-agent-canvas via-agent-canvas/95 to-transparent opacity-0 group-hover/item:opacity-100"
|
|
678
|
+
: "bg-gradient-to-l from-agent-muted via-agent-muted/95 to-transparent opacity-0 group-hover/item:opacity-100",
|
|
679
|
+
].join(" ")}
|
|
526
680
|
/>
|
|
527
681
|
<button
|
|
528
682
|
type="button"
|
|
@@ -532,29 +686,20 @@ export function AgentSidebar({
|
|
|
532
686
|
}}
|
|
533
687
|
disabled={isDeleting}
|
|
534
688
|
className={[
|
|
535
|
-
|
|
689
|
+
"absolute right-1 top-1/2 flex h-7 w-7 -translate-y-1/2 items-center justify-center rounded-full transition-all duration-200",
|
|
536
690
|
isConfirmingDelete
|
|
537
|
-
?
|
|
538
|
-
:
|
|
539
|
-
|
|
540
|
-
].join(
|
|
541
|
-
title={
|
|
542
|
-
|
|
543
|
-
? '再次点击确认删除'
|
|
544
|
-
: '删除会话'
|
|
545
|
-
}
|
|
546
|
-
aria-label={
|
|
547
|
-
isConfirmingDelete
|
|
548
|
-
? '再次点击确认删除'
|
|
549
|
-
: '删除会话'
|
|
550
|
-
}
|
|
691
|
+
? "bg-agent-destructive/10 text-agent-destructive opacity-100 hover:bg-agent-destructive/20"
|
|
692
|
+
: "text-agent-muted-foreground opacity-0 hover:bg-agent-foreground/5 hover:text-agent-destructive group-hover/item:opacity-100 focus:opacity-100",
|
|
693
|
+
"disabled:cursor-not-allowed disabled:opacity-100",
|
|
694
|
+
].join(" ")}
|
|
695
|
+
title={isConfirmingDelete ? "再次点击确认删除" : "删除会话"}
|
|
696
|
+
aria-label={isConfirmingDelete ? "再次点击确认删除" : "删除会话"}
|
|
551
697
|
data-testid="sidebar-chat-delete"
|
|
552
698
|
>
|
|
553
699
|
<LuTrash2
|
|
554
|
-
className={[
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
].join(' ')}
|
|
700
|
+
className={["h-3.5 w-3.5", isDeleting ? "animate-pulse" : ""].join(
|
|
701
|
+
" ",
|
|
702
|
+
)}
|
|
558
703
|
/>
|
|
559
704
|
</button>
|
|
560
705
|
</div>
|
|
@@ -587,10 +732,10 @@ export function AgentSidebar({
|
|
|
587
732
|
container.scrollHeight - 60;
|
|
588
733
|
if (nearBottom) void loadMoreChats();
|
|
589
734
|
};
|
|
590
|
-
container.addEventListener(
|
|
735
|
+
container.addEventListener("scroll", maybeLoadMore, { passive: true });
|
|
591
736
|
const tickId = window.requestAnimationFrame(maybeLoadMore);
|
|
592
737
|
return () => {
|
|
593
|
-
container.removeEventListener(
|
|
738
|
+
container.removeEventListener("scroll", maybeLoadMore);
|
|
594
739
|
window.cancelAnimationFrame(tickId);
|
|
595
740
|
};
|
|
596
741
|
}, [
|
|
@@ -608,7 +753,7 @@ export function AgentSidebar({
|
|
|
608
753
|
useEffect(() => {
|
|
609
754
|
if (!bridge?.onMenuOpenTerminal) return;
|
|
610
755
|
bridge.onMenuOpenTerminal(() => {
|
|
611
|
-
onToggleRightPanel(
|
|
756
|
+
onToggleRightPanel("terminal");
|
|
612
757
|
});
|
|
613
758
|
return () => {
|
|
614
759
|
bridge.offMenuOpenTerminal?.();
|
|
@@ -620,7 +765,7 @@ export function AgentSidebar({
|
|
|
620
765
|
return (
|
|
621
766
|
<div className="flex h-full w-full flex-col border-r border-agent-border/60 bg-agent-muted/70 backdrop-blur-md">
|
|
622
767
|
{/* ───── Brand + actions ───── */}
|
|
623
|
-
<div className="flex h-
|
|
768
|
+
<div className="flex h-11 flex-shrink-0 items-center justify-between px-2.5">
|
|
624
769
|
<BrandLockup onClick={() => handleOpenNewChat()} />
|
|
625
770
|
<div className="flex items-center gap-1">
|
|
626
771
|
<button
|
|
@@ -643,11 +788,11 @@ export function AgentSidebar({
|
|
|
643
788
|
type="button"
|
|
644
789
|
onClick={() => handleOpenNewChat()}
|
|
645
790
|
className={[
|
|
646
|
-
|
|
791
|
+
"flex h-7 w-full items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors",
|
|
647
792
|
onNewChatHome
|
|
648
|
-
?
|
|
649
|
-
:
|
|
650
|
-
].join(
|
|
793
|
+
? "bg-agent-foreground/10 font-medium text-agent-foreground"
|
|
794
|
+
: "text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground",
|
|
795
|
+
].join(" ")}
|
|
651
796
|
title="新建对话"
|
|
652
797
|
data-testid="sidebar-new-chat"
|
|
653
798
|
>
|
|
@@ -656,13 +801,13 @@ export function AgentSidebar({
|
|
|
656
801
|
</button>
|
|
657
802
|
<button
|
|
658
803
|
type="button"
|
|
659
|
-
onClick={() => navigate(
|
|
804
|
+
onClick={() => navigate("/settings?section=agents")}
|
|
660
805
|
className={[
|
|
661
|
-
|
|
662
|
-
onSettingsPage && settingsSection ===
|
|
663
|
-
?
|
|
664
|
-
:
|
|
665
|
-
].join(
|
|
806
|
+
"flex h-7 w-full items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors",
|
|
807
|
+
onSettingsPage && settingsSection === "agents"
|
|
808
|
+
? "bg-agent-foreground/10 font-medium text-agent-foreground"
|
|
809
|
+
: "text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground",
|
|
810
|
+
].join(" ")}
|
|
666
811
|
title="智能体管理"
|
|
667
812
|
data-testid="sidebar-agent-settings"
|
|
668
813
|
>
|
|
@@ -671,13 +816,13 @@ export function AgentSidebar({
|
|
|
671
816
|
</button>
|
|
672
817
|
<button
|
|
673
818
|
type="button"
|
|
674
|
-
onClick={() => navigate(
|
|
819
|
+
onClick={() => navigate("/settings?section=skills")}
|
|
675
820
|
className={[
|
|
676
|
-
|
|
677
|
-
onSettingsPage && settingsSection ===
|
|
678
|
-
?
|
|
679
|
-
:
|
|
680
|
-
].join(
|
|
821
|
+
"flex h-7 w-full items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors",
|
|
822
|
+
onSettingsPage && settingsSection === "skills"
|
|
823
|
+
? "bg-agent-foreground/10 font-medium text-agent-foreground"
|
|
824
|
+
: "text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground",
|
|
825
|
+
].join(" ")}
|
|
681
826
|
title="Skill 设置"
|
|
682
827
|
data-testid="sidebar-skill-settings"
|
|
683
828
|
>
|
|
@@ -686,13 +831,13 @@ export function AgentSidebar({
|
|
|
686
831
|
</button>
|
|
687
832
|
<button
|
|
688
833
|
type="button"
|
|
689
|
-
onClick={() => navigate(
|
|
834
|
+
onClick={() => navigate("/settings?section=mcp")}
|
|
690
835
|
className={[
|
|
691
|
-
|
|
692
|
-
onSettingsPage && settingsSection ===
|
|
693
|
-
?
|
|
694
|
-
:
|
|
695
|
-
].join(
|
|
836
|
+
"flex h-7 w-full items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors",
|
|
837
|
+
onSettingsPage && settingsSection === "mcp"
|
|
838
|
+
? "bg-agent-foreground/10 font-medium text-agent-foreground"
|
|
839
|
+
: "text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground",
|
|
840
|
+
].join(" ")}
|
|
696
841
|
title="MCP 设置"
|
|
697
842
|
data-testid="sidebar-mcp-settings"
|
|
698
843
|
>
|
|
@@ -706,28 +851,22 @@ export function AgentSidebar({
|
|
|
706
851
|
onClick={() => setChatsExpanded((v) => !v)}
|
|
707
852
|
className="flex h-6 items-center rounded-full px-2.5 text-xs font-semibold tracking-wider text-agent-muted-foreground transition-colors hover:bg-agent-foreground/5 hover:text-agent-foreground"
|
|
708
853
|
>
|
|
709
|
-
|
|
854
|
+
会话
|
|
855
|
+
<span className="ml-1">
|
|
710
856
|
{chatsExpanded ? (
|
|
711
857
|
<LuChevronUp className="h-3 w-3" />
|
|
712
858
|
) : (
|
|
713
859
|
<LuChevronDown className="h-3 w-3" />
|
|
714
860
|
)}
|
|
715
861
|
</span>
|
|
716
|
-
会话
|
|
717
|
-
{normalizedChats.length > 0 && (
|
|
718
|
-
<span className="ml-1.5 text-[10px] font-normal text-agent-muted-foreground/70">
|
|
719
|
-
· {normalizedChats.length}
|
|
720
|
-
</span>
|
|
721
|
-
)}
|
|
722
862
|
</button>
|
|
723
863
|
<div className="flex items-center gap-1">
|
|
724
|
-
{/* 新建项目:选文件夹 → 以文件夹名建项目,之后可在组头重命名 */}
|
|
725
864
|
{hasElectron && (
|
|
726
865
|
<button
|
|
727
866
|
type="button"
|
|
728
|
-
onClick={() =>
|
|
867
|
+
onClick={() => setCreateProjectOpen(true)}
|
|
729
868
|
className="flex h-6 w-6 items-center justify-center rounded-full text-agent-muted-foreground transition-colors duration-200 hover:bg-agent-foreground/5 hover:text-agent-foreground"
|
|
730
|
-
title="
|
|
869
|
+
title="新建项目"
|
|
731
870
|
aria-label="新建项目"
|
|
732
871
|
>
|
|
733
872
|
<LuFolderPlus className="h-3.5 w-3.5" />
|
|
@@ -753,7 +892,7 @@ export function AgentSidebar({
|
|
|
753
892
|
</div>
|
|
754
893
|
) : (
|
|
755
894
|
<>
|
|
756
|
-
{/* 项目分组在前:组头可折叠,hover
|
|
895
|
+
{/* 项目分组在前:组头可折叠,hover 出 + 新建对话 / ·· 菜单 */}
|
|
757
896
|
{projectGroups.map(({ project, items }) => (
|
|
758
897
|
<div key={project.id} className="mb-1">
|
|
759
898
|
<div className="group/proj relative">
|
|
@@ -768,10 +907,13 @@ export function AgentSidebar({
|
|
|
768
907
|
<input
|
|
769
908
|
autoFocus
|
|
770
909
|
value={renamingValue}
|
|
771
|
-
onChange={(event) =>
|
|
910
|
+
onChange={(event) =>
|
|
911
|
+
setRenamingValue(event.target.value)
|
|
912
|
+
}
|
|
772
913
|
onBlur={() => void handleRenameProject(project.id)}
|
|
773
914
|
onKeyDown={(event) => {
|
|
774
|
-
if (event.key ===
|
|
915
|
+
if (event.key === "Escape")
|
|
916
|
+
setRenamingProjectId(null);
|
|
775
917
|
}}
|
|
776
918
|
className="h-6 w-full rounded-agent-md border border-agent-border bg-agent-canvas px-2 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
777
919
|
/>
|
|
@@ -784,25 +926,30 @@ export function AgentSidebar({
|
|
|
784
926
|
className="flex w-full min-w-0 items-center px-2.5 pb-0.5 pt-1.5 text-[10px] font-medium uppercase tracking-wider text-agent-muted-foreground/80 transition-colors hover:text-agent-foreground"
|
|
785
927
|
title={`${project.name}\n${project.folderPath}`}
|
|
786
928
|
>
|
|
787
|
-
<
|
|
929
|
+
<LuFolder className="mr-1 h-3 w-3 shrink-0" />
|
|
930
|
+
<span className="min-w-0 truncate normal-case">
|
|
931
|
+
{project.name}
|
|
932
|
+
</span>
|
|
933
|
+
<span className="ml-0.5 shrink-0">
|
|
788
934
|
{collapsedProjectIds.has(project.id) ? (
|
|
789
935
|
<LuChevronDown className="h-3 w-3" />
|
|
790
936
|
) : (
|
|
791
937
|
<LuChevronUp className="h-3 w-3" />
|
|
792
938
|
)}
|
|
793
939
|
</span>
|
|
794
|
-
<LuFolder className="mr-1 h-3 w-3 shrink-0" />
|
|
795
|
-
<span className="min-w-0 truncate normal-case">
|
|
796
|
-
{project.name}
|
|
797
|
-
</span>
|
|
798
940
|
{items.length > 0 && (
|
|
799
941
|
<span className="ml-1 shrink-0 font-normal">
|
|
800
942
|
· {items.length}
|
|
801
943
|
</span>
|
|
802
944
|
)}
|
|
803
945
|
</button>
|
|
804
|
-
|
|
805
|
-
|
|
946
|
+
<div
|
|
947
|
+
className={`absolute right-1 top-1/2 flex -translate-y-1/2 items-center gap-0.5 transition-opacity ${
|
|
948
|
+
projectMenu?.id === project.id
|
|
949
|
+
? "opacity-100"
|
|
950
|
+
: "opacity-0 group-hover/proj:opacity-100"
|
|
951
|
+
}`}
|
|
952
|
+
>
|
|
806
953
|
<button
|
|
807
954
|
type="button"
|
|
808
955
|
onClick={() => handleOpenNewChat(project.id)}
|
|
@@ -813,41 +960,21 @@ export function AgentSidebar({
|
|
|
813
960
|
</button>
|
|
814
961
|
<button
|
|
815
962
|
type="button"
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
963
|
+
aria-label="项目菜单"
|
|
964
|
+
aria-expanded={projectMenu?.id === project.id}
|
|
965
|
+
onClick={(event) => {
|
|
966
|
+
const button = event.currentTarget;
|
|
967
|
+
setConfirmDeleteProjectId(null);
|
|
968
|
+
setProjectMenu((current) =>
|
|
969
|
+
current?.id === project.id
|
|
970
|
+
? null
|
|
971
|
+
: { id: project.id, anchor: button },
|
|
972
|
+
);
|
|
819
973
|
}}
|
|
820
974
|
className="flex h-5 w-5 items-center justify-center rounded-full text-agent-muted-foreground hover:bg-agent-foreground/10 hover:text-agent-foreground"
|
|
821
|
-
title="
|
|
822
|
-
>
|
|
823
|
-
<LuPencil className="h-3 w-3" />
|
|
824
|
-
</button>
|
|
825
|
-
<button
|
|
826
|
-
type="button"
|
|
827
|
-
onClick={() => void handleChangeProjectFolder(project.id)}
|
|
828
|
-
className="flex h-5 w-5 items-center justify-center rounded-full text-agent-muted-foreground hover:bg-agent-foreground/10 hover:text-agent-foreground"
|
|
829
|
-
title="更换绑定文件夹"
|
|
830
|
-
>
|
|
831
|
-
<LuFolderOpen className="h-3 w-3" />
|
|
832
|
-
</button>
|
|
833
|
-
<button
|
|
834
|
-
type="button"
|
|
835
|
-
onClick={() => void handleDeleteProject(project.id)}
|
|
836
|
-
disabled={deletingProjectId === project.id}
|
|
837
|
-
className={`flex h-5 w-5 items-center justify-center rounded-full transition-colors ${
|
|
838
|
-
confirmDeleteProjectId === project.id
|
|
839
|
-
? 'bg-agent-destructive/10 text-agent-destructive hover:bg-agent-destructive/20'
|
|
840
|
-
: 'text-agent-muted-foreground hover:bg-agent-foreground/10 hover:text-agent-destructive'
|
|
841
|
-
}`}
|
|
842
|
-
title={
|
|
843
|
-
confirmDeleteProjectId === project.id
|
|
844
|
-
? '再次点击确认删除(会话会保留为无项目对话)'
|
|
845
|
-
: '删除项目(会话保留为无项目对话)'
|
|
846
|
-
}
|
|
975
|
+
title="项目菜单"
|
|
847
976
|
>
|
|
848
|
-
<
|
|
849
|
-
className={`h-3 w-3 ${deletingProjectId === project.id ? 'animate-pulse' : ''}`}
|
|
850
|
-
/>
|
|
977
|
+
<LuEllipsis className="h-3 w-3" />
|
|
851
978
|
</button>
|
|
852
979
|
</div>
|
|
853
980
|
</>
|
|
@@ -887,7 +1014,7 @@ export function AgentSidebar({
|
|
|
887
1014
|
加载更多...
|
|
888
1015
|
</span>
|
|
889
1016
|
) : hasMoreChats ? (
|
|
890
|
-
|
|
1017
|
+
"继续下滑加载更多"
|
|
891
1018
|
) : (
|
|
892
1019
|
`共 ${normalizedChats.length} 个会话`
|
|
893
1020
|
)}
|
|
@@ -911,20 +1038,20 @@ export function AgentSidebar({
|
|
|
911
1038
|
{chatSlots.length === 0 ? (
|
|
912
1039
|
<button
|
|
913
1040
|
type="button"
|
|
914
|
-
onClick={() => onToggleRightPanel(
|
|
1041
|
+
onClick={() => onToggleRightPanel("terminal")}
|
|
915
1042
|
className={[
|
|
916
|
-
|
|
917
|
-
rightPanel ===
|
|
918
|
-
?
|
|
919
|
-
:
|
|
920
|
-
].join(
|
|
921
|
-
title={`${rightPanel ===
|
|
1043
|
+
"flex h-7 w-full items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors",
|
|
1044
|
+
rightPanel === "terminal"
|
|
1045
|
+
? "bg-agent-foreground/10 font-medium text-agent-foreground"
|
|
1046
|
+
: "text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-foreground",
|
|
1047
|
+
].join(" ")}
|
|
1048
|
+
title={`${rightPanel === "terminal" ? "关闭" : "打开"}终端面板 (${isMac ? "⌘T" : "Ctrl+T"})`}
|
|
922
1049
|
data-testid="sidebar-terminal"
|
|
923
1050
|
>
|
|
924
1051
|
<LuTerminal className="h-3.5 w-3.5" />
|
|
925
1052
|
<span>终端</span>
|
|
926
1053
|
<span className="ml-auto text-[10px] text-agent-muted-foreground/70">
|
|
927
|
-
{isMac ?
|
|
1054
|
+
{isMac ? "⌘T" : "Ctrl+T"}
|
|
928
1055
|
</span>
|
|
929
1056
|
</button>
|
|
930
1057
|
) : (
|
|
@@ -935,14 +1062,14 @@ export function AgentSidebar({
|
|
|
935
1062
|
>
|
|
936
1063
|
<button
|
|
937
1064
|
type="button"
|
|
938
|
-
onClick={() => onToggleRightPanel(
|
|
1065
|
+
onClick={() => onToggleRightPanel("terminal")}
|
|
939
1066
|
className={[
|
|
940
|
-
|
|
941
|
-
rightPanel ===
|
|
942
|
-
?
|
|
943
|
-
:
|
|
944
|
-
].join(
|
|
945
|
-
title={`${rightPanel ===
|
|
1067
|
+
"flex h-7 flex-1 items-center justify-center gap-1.5 rounded-full text-xs font-medium transition-colors",
|
|
1068
|
+
rightPanel === "terminal"
|
|
1069
|
+
? "bg-agent-foreground/10 text-agent-foreground"
|
|
1070
|
+
: "text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground",
|
|
1071
|
+
].join(" ")}
|
|
1072
|
+
title={`${rightPanel === "terminal" ? "关闭" : "打开"}终端面板`}
|
|
946
1073
|
data-testid="sidebar-terminal"
|
|
947
1074
|
>
|
|
948
1075
|
<LuTerminal className="h-3.5 w-3.5" />
|
|
@@ -954,12 +1081,12 @@ export function AgentSidebar({
|
|
|
954
1081
|
type="button"
|
|
955
1082
|
onClick={() => onToggleRightPanel(slot.slotId)}
|
|
956
1083
|
className={[
|
|
957
|
-
|
|
1084
|
+
"flex h-7 flex-1 items-center justify-center gap-1.5 rounded-full text-xs font-medium transition-colors",
|
|
958
1085
|
rightPanel === slot.slotId
|
|
959
|
-
?
|
|
960
|
-
:
|
|
961
|
-
].join(
|
|
962
|
-
title={`${rightPanel === slot.slotId ?
|
|
1086
|
+
? "bg-agent-foreground/10 text-agent-foreground"
|
|
1087
|
+
: "text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground",
|
|
1088
|
+
].join(" ")}
|
|
1089
|
+
title={`${rightPanel === slot.slotId ? "关闭" : "打开"}${slot.title}`}
|
|
963
1090
|
data-testid={`sidebar-slot-${slot.slotId}`}
|
|
964
1091
|
>
|
|
965
1092
|
<slot.Icon className="h-3.5 w-3.5" />
|
|
@@ -970,13 +1097,13 @@ export function AgentSidebar({
|
|
|
970
1097
|
)}
|
|
971
1098
|
<button
|
|
972
1099
|
type="button"
|
|
973
|
-
onClick={() => navigate(
|
|
1100
|
+
onClick={() => navigate("/settings")}
|
|
974
1101
|
className={[
|
|
975
|
-
|
|
1102
|
+
"mt-0.5 flex h-7 w-full items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors",
|
|
976
1103
|
onGeneralSettings
|
|
977
|
-
?
|
|
978
|
-
:
|
|
979
|
-
].join(
|
|
1104
|
+
? "bg-agent-foreground/10 font-medium text-agent-foreground"
|
|
1105
|
+
: "text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground",
|
|
1106
|
+
].join(" ")}
|
|
980
1107
|
title="设置"
|
|
981
1108
|
data-testid="sidebar-llm-settings"
|
|
982
1109
|
>
|
|
@@ -984,6 +1111,64 @@ export function AgentSidebar({
|
|
|
984
1111
|
<span>设置</span>
|
|
985
1112
|
</button>
|
|
986
1113
|
</div>
|
|
1114
|
+
|
|
1115
|
+
{projectMenu &&
|
|
1116
|
+
(() => {
|
|
1117
|
+
const project = projects.find((item) => item.id === projectMenu.id);
|
|
1118
|
+
if (!project) return null;
|
|
1119
|
+
const chatCount =
|
|
1120
|
+
projectGroups.find((group) => group.project.id === project.id)
|
|
1121
|
+
?.items.length ?? 0;
|
|
1122
|
+
return (
|
|
1123
|
+
<ProjectOverflowMenu
|
|
1124
|
+
project={project}
|
|
1125
|
+
chatCount={chatCount}
|
|
1126
|
+
anchor={projectMenu.anchor}
|
|
1127
|
+
revealLabel={isMac ? "在访达中显示" : "在文件管理器中显示"}
|
|
1128
|
+
confirmDelete={confirmDeleteProjectId === project.id}
|
|
1129
|
+
deleting={deletingProjectId === project.id}
|
|
1130
|
+
onClose={closeProjectMenu}
|
|
1131
|
+
onRename={() => {
|
|
1132
|
+
setProjectMenu(null);
|
|
1133
|
+
setRenamingProjectId(project.id);
|
|
1134
|
+
setRenamingValue(project.name);
|
|
1135
|
+
}}
|
|
1136
|
+
onChangeFolder={() => {
|
|
1137
|
+
setProjectMenu(null);
|
|
1138
|
+
setEditingProjectId(project.id);
|
|
1139
|
+
}}
|
|
1140
|
+
onReveal={() => {
|
|
1141
|
+
setProjectMenu(null);
|
|
1142
|
+
void handleRevealProjectFolder(project.folderPath);
|
|
1143
|
+
}}
|
|
1144
|
+
onDelete={() => void handleDeleteProject(project.id)}
|
|
1145
|
+
/>
|
|
1146
|
+
);
|
|
1147
|
+
})()}
|
|
1148
|
+
|
|
1149
|
+
<CreateProjectModal
|
|
1150
|
+
open={createProjectOpen}
|
|
1151
|
+
onClose={() => setCreateProjectOpen(false)}
|
|
1152
|
+
onCreate={handleCreateProject}
|
|
1153
|
+
/>
|
|
1154
|
+
{editingProjectId &&
|
|
1155
|
+
(() => {
|
|
1156
|
+
const project = projects.find((item) => item.id === editingProjectId);
|
|
1157
|
+
if (!project) return null;
|
|
1158
|
+
return (
|
|
1159
|
+
<CreateProjectModal
|
|
1160
|
+
open
|
|
1161
|
+
mode="edit"
|
|
1162
|
+
initial={{
|
|
1163
|
+
name: project.name,
|
|
1164
|
+
sourceFolders: project.sourceFolders ?? [],
|
|
1165
|
+
}}
|
|
1166
|
+
onClose={() => setEditingProjectId(null)}
|
|
1167
|
+
onCreate={(input) => handleUpdateProject(project.id, input)}
|
|
1168
|
+
onDelete={() => removeProject(project.id)}
|
|
1169
|
+
/>
|
|
1170
|
+
);
|
|
1171
|
+
})()}
|
|
987
1172
|
</div>
|
|
988
1173
|
);
|
|
989
1174
|
}
|