/** * 项目 Claude 会话管理器(底部第三个 tab)。 * * - 上方表单:选 profile(下拉,复用 useIssues 的 ClaudeProfile 列表)+ 首个提示词 * (可选,多行)+「创建」按钮 → 主进程开一个 cc 终端 tab 并捕获会话。 * - 下方列表:每行显示名字(双击行内改名,Enter 提交、Esc 取消)、profile 文件名、 * 创建时间;点击行(非编辑区)→ 主进程新终端 resume;行尾删除按钮(仅移除记录)。 */ import type { ClaudeProfile } from '../hooks/useIssues' import type { ManagedSession, ManagedSessionsData } from '../types' import { Download, Plus, Terminal, Trash2, X } from 'lucide-react' import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' interface ManagedSessionsPanelProps { data: ManagedSessionsData profiles: ClaudeProfile[] onCreate: (profilePath: string, name?: string, prompt?: string) => void onRename: (id: string, name: string) => void onResume: (id: string) => void onDelete: (id: string) => void onCloseTab: (id: string) => void importableSessions: Array<{ id: string, name: string, mtimeMs: number }> importableLoading: boolean onListImportable: () => void onImport: (sessionId: string, name: string) => void } function basename(p?: string): string { if (!p) return '' const idx = Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\')) return idx >= 0 ? p.slice(idx + 1) : p } function formatTime(ts: number): string { if (!ts) return '' try { return new Date(ts).toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', }) } catch { return '' } } export function ManagedSessionsPanel({ data, profiles, onCreate, onRename, onResume, onDelete, onCloseTab, importableSessions, importableLoading, onListImportable, onImport, }: ManagedSessionsPanelProps) { const [selectedProfile, setSelectedProfile] = useState('') const [name, setName] = useState('') const [prompt, setPrompt] = useState('') const [importOpen, setImportOpen] = useState(false) const importWrapRef = useRef(null) // 默认选中官方 profile(profiles 异步到达后;用户手动改选后不覆盖)。 useEffect(() => { if (selectedProfile || profiles.length === 0) return const official = profiles.find(p => p.name.toLowerCase() === 'offical') setSelectedProfile((official ?? profiles[0]).path) }, [profiles, selectedProfile]) // 弹层:外点击 / Esc 关闭 useEffect(() => { if (!importOpen) return function onKey(e: KeyboardEvent): void { if (e.key === 'Escape') setImportOpen(false) } function onPointer(e: MouseEvent): void { const el = importWrapRef.current if (!el) return if (e.target instanceof Node && !el.contains(e.target)) setImportOpen(false) } window.addEventListener('keydown', onKey) window.addEventListener('mousedown', onPointer) return () => { window.removeEventListener('keydown', onKey) window.removeEventListener('mousedown', onPointer) } }, [importOpen]) const sessions = useMemo( () => [...data.sessions].sort((a, b) => b.createdAt - a.createdAt), [data.sessions], ) const canCreate = selectedProfile.trim() !== '' const handleCreate = useCallback((): void => { if (!canCreate) return const trimmedName = name.trim() const trimmedPrompt = prompt.trim() onCreate(selectedProfile, trimmedName || undefined, trimmedPrompt || undefined) setName('') setPrompt('') }, [canCreate, name, prompt, selectedProfile, onCreate]) const openImport = useCallback((): void => { setImportOpen(true) onListImportable() }, [onListImportable]) const pickImport = useCallback((sessionId: string, sessionName: string): void => { onImport(sessionId, sessionName) setImportOpen(false) }, [onImport]) return (
{/* 上方表单 */}
Profile
名字
setName(e.target.value)} placeholder="会话名字(可选)" className="min-w-0 flex-1 rounded border border-[var(--vscode-input-border,var(--vscode-panel-border))] bg-[var(--vscode-input-background)] px-2 py-1 text-xs text-[var(--vscode-input-foreground)] outline-none placeholder:text-[var(--vscode-input-placeholderForeground)] focus:ring-1 focus:ring-inset focus:ring-[var(--vscode-focusBorder)]" />
{importOpen && (
{importableLoading ? (
加载中…
) : importableSessions.length === 0 ? (
没有可导入的命名会话
) : (
    {importableSessions.map(s => (
  • ))}
)}
)}
提示词