This commit is contained in:
cruldra committed 2026-06-23 05:02:15 +08:00
commit e6f1776d4f
264 files changed
+54215

No files matched your search

@@ -0,0 +1,181 @@
/**
* 底部 panel:左侧垂直 tab 切换两个 property grid。
*
* - tab 1 「工单」:现有 IssueDetailPanel(基于 PropertyGrid)
* - tab 2 「Profile」:工作区 KV 表 ProfileGrid
*
* tab 状态用 useState,无持久化;激活态加左侧 2px 高亮条。
*/
import type { ClaudeProfile } from '../hooks/useIssues'
import type { ProfilesData } from '../lib/messages'
import type { Issue, ManagedSessionsData } from '../types'
import { ClipboardList, GitCommitHorizontal, Layers, MessagesSquare } from 'lucide-react'
import { useState } from 'react'
import { IssueDetailPanel } from './IssueDetailPanel'
import { ManagedSessionsPanel } from './ManagedSessionsPanel'
import { PrCommitsPanel } from './PrCommitsPanel'
import { ProfileGrid } from './ProfileGrid'
type TabKey = 'issue' | 'profile' | 'sessions' | 'commits'
interface BottomTabsProps {
// Issue tab props (pass-through to IssueDetailPanel)
issue: Issue | null
allIssues: Issue[]
globalAutoReview: boolean
onOpenInBrowser: (url: string) => void
onResumeSession: (sessionId: string, profilePath?: string, cwd?: string, issueNumber?: number) => void
onResumeReviewSession: (sessionId: string, issueNumber: number, cwd?: string) => void
onStartTestSession: (issueNumber: number) => void
onResumeTestSession: (sessionId: string, issueNumber: number, cwd?: string) => void
onOpenFile: (path: string) => void
onImplement: (issueNumber: number, planFile: string, profilePath?: string, sessionId?: string) => void
onOpenPr: (pr: string) => void
onGeneratePrDiffSummary: (issueNumber: number) => void
isPrDiffSummaryRunning: (issueNumber: number) => boolean
onOpenWorktree: (path: string) => void
onDeleteWorktree: (issueNumber: number, path: string) => void
onMergeBranch: (issueNumber: number, branch: string) => void
onDeleteIssue?: (issueNumber: number) => void
onCloseIssue: (issueNumber: number) => void
onCloseSessionTab: (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test') => void
onStartBrainstormSession: (issueNumber: number) => void
onUpdateAutoReview: (issueNumber: number, value: boolean) => void
onUpdateProfilePath: (issueNumber: number, profilePath: string) => void
onUpdateTestProfilePath: (issueNumber: number, testProfilePath: string) => void
onOpenLogs: () => void
// Profile tab props
profileData: ProfilesData
onProfileSave: (next: ProfilesData) => void
onProfileOpen: (value: string) => void
// 会话管理 tab props
managedSessions: ManagedSessionsData
profiles: ClaudeProfile[]
onManagedSessionCreate: (profilePath: string, name?: string, prompt?: string) => void
onManagedSessionRename: (id: string, name: string) => void
onManagedSessionResume: (id: string) => void
onManagedSessionDelete: (id: string) => void
onManagedSessionCloseTab: (id: string) => void
}
export function BottomTabs(props: BottomTabsProps) {
const [tab, setTab] = useState<TabKey>('issue')
return (
<div className="flex h-full w-full bg-[var(--vscode-editor-background)]">
{/* 左侧垂直 tab 栏 */}
<div className="flex w-9 shrink-0 flex-col items-stretch border-r border-[var(--vscode-panel-border)] bg-[var(--vscode-sideBar-background,var(--vscode-editor-background))]">
<TabButton
active={tab === 'issue'}
onClick={() => setTab('issue')}
icon={<ClipboardList className="size-4" />}
title="工单"
/>
<TabButton
active={tab === 'profile'}
onClick={() => setTab('profile')}
icon={<Layers className="size-4" />}
title="Profile"
/>
<TabButton
active={tab === 'sessions'}
onClick={() => setTab('sessions')}
icon={<MessagesSquare className="size-4" />}
title="会话"
/>
<TabButton
active={tab === 'commits'}
onClick={() => setTab('commits')}
icon={<GitCommitHorizontal className="size-4" />}
title="提交"
/>
</div>
{/* 右侧内容 */}
<div className="min-w-0 flex-1 overflow-hidden">
{tab === 'issue' && (
<IssueDetailPanel
issue={props.issue}
allIssues={props.allIssues}
globalAutoReview={props.globalAutoReview}
onOpenInBrowser={props.onOpenInBrowser}
onResumeSession={props.onResumeSession}
onResumeReviewSession={props.onResumeReviewSession}
onStartTestSession={props.onStartTestSession}
onResumeTestSession={props.onResumeTestSession}
onOpenFile={props.onOpenFile}
onImplement={props.onImplement}
onOpenPr={props.onOpenPr}
onGeneratePrDiffSummary={props.onGeneratePrDiffSummary}
isPrDiffSummaryRunning={props.isPrDiffSummaryRunning}
onOpenWorktree={props.onOpenWorktree}
onDeleteWorktree={props.onDeleteWorktree}
onMergeBranch={props.onMergeBranch}
onDeleteIssue={props.onDeleteIssue}
onCloseIssue={props.onCloseIssue}
onCloseSessionTab={props.onCloseSessionTab}
onStartBrainstormSession={props.onStartBrainstormSession}
onUpdateAutoReview={props.onUpdateAutoReview}
onUpdateProfilePath={props.onUpdateProfilePath}
onUpdateTestProfilePath={props.onUpdateTestProfilePath}
profiles={props.profiles}
onOpenLogs={props.onOpenLogs}
/>
)}
{tab === 'profile' && (
<ProfileGrid
data={props.profileData}
onSave={props.onProfileSave}
onOpen={props.onProfileOpen}
/>
)}
{tab === 'sessions' && (
<ManagedSessionsPanel
data={props.managedSessions}
profiles={props.profiles}
onCreate={props.onManagedSessionCreate}
onRename={props.onManagedSessionRename}
onResume={props.onManagedSessionResume}
onDelete={props.onManagedSessionDelete}
onCloseTab={props.onManagedSessionCloseTab}
/>
)}
{tab === 'commits' && (
<PrCommitsPanel issue={props.issue} />
)}
</div>
</div>
)
}
interface TabButtonProps {
active: boolean
onClick: () => void
icon: React.ReactNode
title: string
}
function TabButton({ active, onClick, icon, title }: TabButtonProps) {
return (
<button
type="button"
onClick={onClick}
title={title}
aria-label={title}
aria-pressed={active}
className={`relative flex h-9 w-full items-center justify-center transition-colors ${
active
? 'text-[var(--vscode-foreground)]'
: 'text-[var(--vscode-descriptionForeground)] hover:text-[var(--vscode-foreground)]'
}`}
>
{active && (
<span className="absolute left-0 top-0 h-full w-[2px] bg-[var(--vscode-focusBorder)]" />
)}
{icon}
</button>
)
}