From a65e7b236ddf3b6da3c130855bf59e60ed4a3172 Mon Sep 17 00:00:00 2001 From: xiongxiao Date: Thu, 19 Mar 2026 05:02:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E9=80=89=E6=8B=A9=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E5=A4=9A=E9=A1=B9=E7=9B=AE=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=92=8CURL=E7=8A=B6=E6=80=81=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code-graph/components/ProjectDialog.tsx | 774 ++++++++++-------- .../code-graph/components/ProjectPanel.tsx | 42 +- src/pages/code-graph/page.tsx | 13 - src/pages/code-graph/store/index.ts | 28 +- 4 files changed, 512 insertions(+), 345 deletions(-) diff --git a/src/pages/code-graph/components/ProjectDialog.tsx b/src/pages/code-graph/components/ProjectDialog.tsx index 3bd0202..a7ff737 100644 --- a/src/pages/code-graph/components/ProjectDialog.tsx +++ b/src/pages/code-graph/components/ProjectDialog.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { useShallow } from 'zustand/react/shallow'; import { FolderOpenIcon, PlusIcon, Trash2Icon, RefreshCwIcon, PlayCircleIcon, StopCircleIcon, FolderIcon, AlertCircleIcon, CircleOffIcon, DownloadIcon, ListTodoIcon, CheckSquareIcon } from 'lucide-react'; import { toast } from 'sonner'; @@ -7,8 +7,189 @@ import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Card, CardContent } from '@/components/ui/card'; import { useCodeGraphStore } from '../store'; +// 初始化项目弹窗组件 +function ProjectInitDialog({ + open, + onOpenChange, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; +}) { + const { initProject, fetchProjectFiles } = useCodeGraphStore( + useShallow((s) => ({ + initProject: s.initProject, + fetchProjectFiles: s.fetchProjectFiles, + })), + ); + + const [loading, setLoading] = useState(false); + const [files, setFiles] = useState([]); + const [selectedPaths, setSelectedPaths] = useState([]); + const [rootPath, setRootPath] = useState('/workspace/projects'); + + // 加载文件列表 + const loadFiles = async () => { + const data = await fetchProjectFiles(rootPath); + setFiles(data); + }; + + // 打开时加载数据 + useEffect(() => { + if (open) { + loadFiles(); + } + }, [open]); + + // 切换选中状态 + const toggleSelection = (path: string) => { + setSelectedPaths((prev) => + prev.includes(path) ? prev.filter((p) => p !== path) : [...prev, path] + ); + }; + + // 全选 + const selectAll = () => setSelectedPaths([...files]); + + // 取消全选 + const deselectAll = () => setSelectedPaths([]); + + // 确认初始化 + const handleConfirm = async () => { + setLoading(true); + await initProject(selectedPaths.length > 0 ? selectedPaths : undefined); + setLoading(false); + setSelectedPaths([]); + onOpenChange(false); + }; + + // 取消 + const handleCancel = () => { + setSelectedPaths([]); + onOpenChange(false); + }; + + const allSelected = files.length > 0 && selectedPaths.length === files.length; + const isIndeterminate = selectedPaths.length > 0 && selectedPaths.length < files.length; + + return ( + !open && handleCancel()}> + + +
+
+ +
+
+ + 初始化项目 + + + 选择要初始化的项目,选中后点击确认开始初始化 + +
+ + {/* 路径输入 */} +
+
+
+ + setRootPath(e.target.value)} + placeholder='项目根目录' + className='bg-slate-800/80 border-white/10 text-slate-100 placeholder:text-slate-500 h-9 text-sm pl-10 pr-4 focus:border-indigo-500/50 focus:ring-2 focus:ring-indigo-500/20 transition-all' + /> +
+ +
+
+ + {/* 项目列表 */} +
+ + + {files.length === 0 ? ( +
+ + 暂无项目,点击加载获取项目列表 +
+ ) : ( +
+ {files.map((path) => ( +
toggleSelection(path)} + className={`flex items-center gap-3 rounded-lg px-3 py-2 cursor-pointer transition-all ${selectedPaths.includes(path) + ? 'bg-indigo-500/20 border border-indigo-500/50' + : 'hover:bg-white/5 border border-transparent' + }`}> + toggleSelection(path)} + className='border-slate-500 data-[checked]:bg-indigo-500 data-[checked]:border-indigo-500' + /> + + {path} +
+ ))} +
+ )} +
+
+
+ + {/* 操作按钮 */} +
+
+ + + 已选择 {selectedPaths.length}/{files.length} + +
+
+ + +
+
+
+
+ ); +} + export function ProjectDialog() { const { projectDialogOpen, @@ -19,7 +200,6 @@ export function ProjectDialog() { addProject, removeProject, toggleProjectStatus, - initProject, } = useCodeGraphStore( useShallow((s) => ({ projectDialogOpen: s.projectDialogOpen, @@ -30,7 +210,6 @@ export function ProjectDialog() { addProject: s.addProject, removeProject: s.removeProject, toggleProjectStatus: s.toggleProjectStatus, - initProject: s.initProject, })), ); @@ -53,9 +232,8 @@ export function ProjectDialog() { const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [pendingDeleteProject, setPendingDeleteProject] = useState<{ path: string; name?: string } | null>(null); - // 初始化确认弹窗 + // 初始化弹窗 const [initConfirmOpen, setInitConfirmOpen] = useState(false); - const [initLoading, setInitLoading] = useState(false); // 多选模式 const [multiSelectMode, setMultiSelectMode] = useState(false); @@ -175,19 +353,6 @@ export function ProjectDialog() { setPendingDeleteProject(null); }; - // 确认初始化 - const handleConfirmInit = async () => { - setInitLoading(true); - await initProject(); - setInitLoading(false); - setInitConfirmOpen(false); - }; - - // 取消初始化 - const handleCancelInit = () => { - setInitConfirmOpen(false); - }; - return ( @@ -211,293 +376,290 @@ export function ProjectDialog() { {/* 内容区域 - 可滚动 */}
- {/* 新增项目 */} - {showAddProject && ( -
-
-
-

添加新项目

-
-
- {/* 类型选择 */} -
- - -
-
- -
- - setNewPath(e.target.value)} - placeholder={projectType === 'filepath' ? '/workspace/projects' : 'kevisual/cnb'} - className='bg-slate-800/80 border-white/10 text-slate-100 placeholder:text-slate-500 h-9 text-sm pl-10 pr-4 focus:border-indigo-500/50 focus:ring-2 focus:ring-indigo-500/20 transition-all' - /> + {/* 新增项目 */} + {showAddProject && ( +
+
+
+

添加新项目

-
-
- - setNewName(e.target.value)} - placeholder='My Project' - className='bg-slate-800/80 border-white/10 text-slate-100 placeholder:text-slate-500 h-9 text-sm focus:border-indigo-500/50 focus:ring-2 focus:ring-indigo-500/20 transition-all' - /> -
-
- -
- )} - - {/* 项目列表 */} -
-
-
-
-

已注册项目

- - {projects.length} - -
-
- - - - -
setInitConfirmOpen(true)} - className='text-slate-500 hover:text-indigo-400 transition-colors p-1.5 rounded-lg hover:bg-white/5 disabled:opacity-50'> - +
+ {/* 类型选择 */} +
+ + +
+
+ +
+ + setNewPath(e.target.value)} + placeholder={projectType === 'filepath' ? '/workspace/projects' : 'kevisual/cnb'} + className='bg-slate-800/80 border-white/10 text-slate-100 placeholder:text-slate-500 h-9 text-sm pl-10 pr-4 focus:border-indigo-500/50 focus:ring-2 focus:ring-indigo-500/20 transition-all' + />
- - -

初始化 workspace/projects 仓库

-
- - {multiSelectMode ? ( - <> -
- - -
- -
-
- -

全选

-
-
- - -
- -
-
- -

取消全选

-
-
- - - - - -

全部启动 ({selectedProjects.length})

-
-
- - - - - -

全部关闭 ({selectedProjects.length})

-
-
+
+
+ + setNewName(e.target.value)} + placeholder='My Project' + className='bg-slate-800/80 border-white/10 text-slate-100 placeholder:text-slate-500 h-9 text-sm focus:border-indigo-500/50 focus:ring-2 focus:ring-indigo-500/20 transition-all' + /> +
+
+ +
+ )} + + {/* 项目列表 */} +
+
+
+
+

已注册项目

+ + {projects.length} + +
+
+ + + + +
setInitConfirmOpen(true)} + className='text-slate-500 hover:text-indigo-400 transition-colors p-1.5 rounded-lg hover:bg-white/5 disabled:opacity-50'> + +
+
+ +

初始化 workspace/projects 仓库

+
+
+ {multiSelectMode ? ( + <> +
+ + +
+ +
+
+ +

全选

+
+
+ + +
+ +
+
+ +

取消全选

+
+
+ + + + + +

全部启动 ({selectedProjects.length})

+
+
+ + + + + +

全部关闭 ({selectedProjects.length})

+
+
+ + +
+ +
+
+ +

退出多选

+
+
+ + ) : (
+ title='多选'>
-

退出多选

+

多选

- - ) : ( - - -
- + )} +
+
+ + {projectsLoading ? ( +
+ + + 加载中… + +
+ ) : projects.length === 0 ? ( +
+ + 暂无项目 +
+ ) : ( +
    + {projects.map((p) => ( +
  • + {/* 多选框 */} + {multiSelectMode && ( + + )} + {/* 项目图标 */} +
    +
    - - -

    多选

    -
    - - )} -
-
- {projectsLoading ? ( -
- - - 加载中… - -
- ) : projects.length === 0 ? ( -
- - 暂无项目 -
- ) : ( -
    - {projects.map((p) => ( -
  • - {/* 多选框 */} - {multiSelectMode && ( - - )} - {/* 项目图标 */} -
    - -
    +
    +

    {p.name ?? p.path.split('/').pop()}

    +

    {p.path}

    +
    -
    -

    {p.name ?? p.path.split('/').pop()}

    -

    {p.path}

    -
    - - {/* 状态切换按钮 */} - {p.status !== undefined && ( - - )} + }`}> + {p.status === 'active' ? ( + <> + + 监听中 + + ) : p.status === 'unlive' ? ( + <> + + 未启动 + + ) : ( + <> + + 已停止 + + )} + + )} - {/* 删除按钮 */} - -
  • - ))} -
- )} -
+ {/* 删除按钮 */} + + + ))} + + )} +
@@ -531,8 +693,8 @@ export function ProjectDialog() { @@ -570,44 +732,8 @@ export function ProjectDialog() {
- {/* 初始化确认弹窗 */} - !open && handleCancelInit()}> - - -
- -
- - 初始化项目 - - - 确定要初始化 workspace/projects 仓库吗?初始化过程可能需要一些时间。 - -
- - - - -
-
+ {/* 初始化弹窗 */} + ); } diff --git a/src/pages/code-graph/components/ProjectPanel.tsx b/src/pages/code-graph/components/ProjectPanel.tsx index 230eb0e..beb459b 100644 --- a/src/pages/code-graph/components/ProjectPanel.tsx +++ b/src/pages/code-graph/components/ProjectPanel.tsx @@ -8,17 +8,16 @@ import { useBotHelperStore } from '../store/bot-helper'; interface ProjectPanelProps { onProjectClick?: (projectPath: string, files: FileProjectData[]) => void; - onOpenCodePod?: (projectPath: string) => void; onStopProject?: (projectPath: string) => void; } export function ProjectPanel({ onProjectClick, - onOpenCodePod, onStopProject, }: ProjectPanelProps) { const [isDragging, setIsDragging] = useState(false); const [position, setPosition] = useState({ x: 20, y: 100 }); + const [selectedProject, setSelectedProject] = useState(null); const dragOffset = useRef({ x: 0, y: 0 }); const panelRef = useRef(null); @@ -83,16 +82,47 @@ export function ProjectPanel({ const handleProjectClick = useCallback( (projectPath: string) => { + // 如果点击的是已选中的项目,则取消选中 + if (selectedProject === projectPath) { + setSelectedProject(null); + window.location.hash = ''; + useCodeGraphStore.getState().fetchProjects(); + return; + } + + // 从 projects 列表中查找对应项目的 repo const project = projects.find((p) => p.path === projectPath); + const repo = project?.repo; + + if (repo) { + // 设置 hash 并刷新 + window.location.hash = `repo=${repo}`; + useCodeGraphStore.getState().fetchProjects(); + } + + setSelectedProject(projectPath); + if (project && onProjectClick) { onProjectClick(projectPath, files); } }, - [projects, files, onProjectClick], + [projects, files, onProjectClick, selectedProject], ); const [isLargeScreen, setIsLargeScreen] = useState(false); + // 初始化选中状态 + useEffect(() => { + const hashParams = new URLSearchParams(window.location.hash.slice(1)); + const repo = hashParams.get('repo'); + if (repo && projects.length > 0) { + const projectItem = projects.find((p) => p.repo === repo); + if (projectItem) { + setSelectedProject(projectItem.path); + } + } + }, [projects]); + useEffect(() => { const checkScreen = () => { setIsLargeScreen(window.innerWidth >= 1024); @@ -143,7 +173,11 @@ export function ProjectPanel({ return (