Download src/components/layout/LeftToolWindowBar.tsx from Leon4gr45/Velo: direct link, hf CLI and curl.
- Browser
- Download file 5.22 kB
-
https://huggingface.co/spaces/Leon4gr45/Velo/resolve/main/src/components/layout/LeftToolWindowBar.tsx
- Command line
-
hf download hf://spaces/Leon4gr45/Velo/src/components/layout/LeftToolWindowBar.tsx
-
curl -L -o LeftToolWindowBar.tsx https://huggingface.co/spaces/Leon4gr45/Velo/resolve/main/src/components/layout/LeftToolWindowBar.tsx
5.22 kB
| 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> | |
| ) | |
| } | |