File size: 5,220 Bytes
55847d9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | import {
FolderOpen, BookOpen, Users,
Home, Zap, ScrollText, Cpu,
} from 'lucide-react'
import { useLayoutStore, type SidebarView, type BottomTab } from '../../stores/layout-store'
import { useWorkflowStore } from '../../stores/workflow-store'
import { useTranslation } from 'react-i18next'
/** 左侧侧边栏视图按钮配置(不含 Home,它单独渲染) */
// Note: These arrays are now inside the component to support i18n
/**
* 左侧工具窗口栏(LeftToolWindowBar)
* JetBrains 风格:36px 宽,全高
*/
export default function LeftToolWindowBar() {
const { t } = useTranslation('layout')
const sidebarView = useLayoutStore(s => s.sidebarView)
const sidebarOpen = useLayoutStore(s => s.sidebarOpen)
const setSidebarView = useLayoutStore(s => s.setSidebarView)
const bottomTab = useLayoutStore(s => s.bottomTab)
const bottomPanelOpen = useLayoutStore(s => s.bottomPanelOpen)
const setBottomTab = useLayoutStore(s => s.setBottomTab)
const currentRun = useWorkflowStore(s => s.currentRun)
// Side bar activities (Home excluded - rendered separately)
const sidebarActivities: Array<{ id: SidebarView; icon: typeof FolderOpen; label: string }> = [
{ id: 'project', icon: FolderOpen, label: t('activityBar.project') },
{ id: 'knowledge', icon: BookOpen, label: t('activityBar.knowledge') },
{ id: 'characters', icon: Users, label: t('activityBar.characters') },
]
// Bottom panel tabs
const bottomTabs: Array<{ id: BottomTab; icon: typeof Zap; label: string }> = [
{ id: 'tasks', icon: Zap, label: t('statusBar.tasksLabel') },
{ id: 'log', icon: ScrollText, label: t('bottomPanel.tabs.log') },
{ id: 'models', icon: Cpu, label: t('bottomPanel.tabs.models') },
]
/** Home 按钮是否激活 */
const homeActive = sidebarOpen && sidebarView === 'home'
return (
<div
className="no-select flex flex-col h-full"
style={{
width: 'var(--width-left-bar)', /* 36px */
backgroundColor: 'var(--color-activity-bar)',
borderRight: '1px solid var(--color-border)',
flexShrink: 0,
}}
>
{/* ===== 顶部:Home + 侧边栏视图切换 ===== */}
<div className="flex flex-col items-center w-full pt-0.5">
{/* Home 按钮 — 点击切换到主页视图 */}
<button
onClick={() => setSidebarView('home')}
title={t('activityBar.home')}
className="tool-btn"
style={{
height: 30,
boxShadow: homeActive ? 'inset 2px 0 0 var(--color-activity-indicator)' : 'none',
color: homeActive ? 'var(--color-activity-icon-active)' : undefined,
}}
>
<Home size={16} strokeWidth={homeActive ? 2 : 1.5} />
</button>
{/* 分割线 */}
<div className="w-4 my-0.5" style={{ height: 1, backgroundColor: 'var(--color-border)' }} />
{/* 侧边栏视图按钮 */}
{sidebarActivities.map(({ id, icon: Icon, label }) => {
const isActive = sidebarOpen && sidebarView === id
return (
<button
key={id}
onClick={() => setSidebarView(id)}
title={label}
className="tool-btn"
style={{
boxShadow: isActive ? 'inset 2px 0 0 var(--color-activity-indicator)' : 'none',
color: isActive ? 'var(--color-activity-icon-active)' : 'var(--color-activity-icon)',
}}
>
<Icon size={17} strokeWidth={isActive ? 2 : 1.5} />
</button>
)
})}
</div>
{/* 弹性间隔 */}
<div className="flex-1" />
{/* ===== 底部:底部面板 Tab 控制 ===== */}
<div className="flex flex-col items-center w-full pb-1">
<div className="w-4 mb-0.5" style={{ height: 1, backgroundColor: 'var(--color-border)' }} />
{bottomTabs.map(({ id, icon: Icon, label }) => {
const isActive = bottomPanelOpen && bottomTab === id
const showPulse = id === 'tasks' && currentRun &&
(currentRun.status === 'running' || currentRun.status === 'waiting')
return (
<div key={id} className="relative w-full">
<button
onClick={() => setBottomTab(id)}
title={label}
className="tool-btn"
style={{
boxShadow: isActive ? 'inset 2px 0 0 var(--color-activity-indicator)' : 'none',
color: isActive ? 'var(--color-activity-icon-active)' : 'var(--color-activity-icon)',
}}
>
<Icon size={15} strokeWidth={isActive ? 2 : 1.5} />
</button>
{showPulse && (
<span
className="absolute top-[4px] right-[4px] w-[5px] h-[5px] rounded-full animate-pulse pointer-events-none"
style={{
backgroundColor: currentRun.status === 'waiting'
? 'var(--color-warning)'
: 'var(--color-accent)',
}}
/>
)}
</div>
)
})}
</div>
</div>
)
}
|