import { Dialog, DialogContent, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@/components/ui/tooltip' import { useRepoStore } from '../store' import type { WorkspaceOpen } from '../store' import { Code2, Terminal, MousePointer2, Lock, Radio, Zap, Copy, Check, Square, Link, ExternalLink, Wind, Plane, Rocket } from 'lucide-react' import { useEffect, useMemo, useState } from 'react' import { toast } from 'sonner' import { useShallow } from 'zustand/shallow' import clsx from 'clsx' type LinkItemKey = keyof WorkspaceOpen; interface LinkItem { key: LinkItemKey label: string icon: React.ReactNode order?: number getUrl: (data: Partial) => string | undefined } const LinkItem = ({ label, icon, url }: { label: string; icon: React.ReactNode; url?: string }) => { const [isCopied, setIsCopied] = useState(false) const handleClick = () => { if (url?.startsWith?.('ssh') || url?.startsWith?.('cnb')) { copy() return; } if (url && url.includes(':')) { window.open(url, '_blank') } } const copy = async () => { try { await navigator.clipboard.writeText(url!) setIsCopied(true) toast.success('已复制到剪贴板') setTimeout(() => setIsCopied(false), 2000) } catch (error) { toast.error('复制失败') } } return (
{icon}
{label} {url && (
{ e.stopPropagation() copy() }} role="button" className="w-6 h-6 flex items-center justify-center text-neutral-500 hover:text-neutral-900 hover:bg-neutral-100 rounded transition-colors cursor-pointer" > {isCopied ? : }
)}

{url}

) } // Dev tab 内容 const DevTabContent = ({ linkItems, workspaceLink, stopWorkspace }: { linkItems: LinkItem[] workspaceLink: Partial stopWorkspace: () => void }) => { return ( <>
{linkItems.map((item) => ( ))}
) } // Link tab 内容(暂留空) const LinkTabContent = () => { const store = useRepoStore(useShallow((state) => ({ selectWorkspace: state.selectWorkspace, workspaceSecretLink: state.workspaceSecretLink, }))) const links = store.workspaceSecretLink.map(item => ({ label: item.title, url: item.value })) if (links.length === 0) { return (
暂无链接, 或工作区未启动
) } return (
{links.map(link => ( } url={link.url} /> ))}
) } // Work tab 内容(暂留,需要根据 business_id 做事情) const WorkTabContent = () => { const store = useRepoStore(useShallow((state) => ({ selectWorkspace: state.selectWorkspace, workspaceLink: state.workspaceLink, }))) const businessId = store.selectWorkspace?.business_id; const appList = [ { title: 'Kevisual Assistant Client', key: 'Assistant Client', port: 51515, end: '/root/cli-center/' }, { title: 'OpenCode', key: 'OpenCode', port: 100, end: '' }, { title: 'OpenClaw', key: 'OpenClaw', port: 80, end: '/openclaw' }, { key: 'vscode' as LinkItemKey, title: 'VS Code', icon: , }, { title: 'OpenWebUI', key: 'OpenWebUI', port: 200, end: '' }, ] const links = appList.map(app => { if (app.icon) { return { label: app.title, icon: app.icon, url: store?.workspaceLink?.[app.key as LinkItemKey] as string | undefined } } const url = `https://${businessId}-${app.port}.cnb.run${app.end}` return { label: app.title, icon: , url } }) return (
{links.map(link => ( ))}
) } export function WorkspaceDetailDialog() { const { showWorkspaceDialog, setShowWorkspaceDialog, workspaceLink, stopWorkspace, workspaceTab, setWorkspaceTab, getWorkspaceSecretLink, selectWorkspace, workspaceSecretLink } = useRepoStore(useShallow((state) => ({ showWorkspaceDialog: state.showWorkspaceDialog, setShowWorkspaceDialog: state.setShowWorkspaceDialog, workspaceLink: state.workspaceLink, stopWorkspace: state.stopWorkspace, workspaceTab: state.workspaceTab, setWorkspaceTab: state.setWorkspaceTab, selectWorkspace: state.selectWorkspace, getWorkspaceSecretLink: state.getWorkspaceSecretLink, workspaceSecretLink: state.workspaceSecretLink }))) const linkItems: LinkItem[] = [ { key: 'jumpUrl' as LinkItemKey, label: 'Jump', icon: , order: 1, getUrl: (data) => data.jumpUrl }, { key: 'webide' as LinkItemKey, label: 'Web IDE', icon: , order: 2, getUrl: (data) => data.webide }, { key: 'vscode' as LinkItemKey, label: 'VS Code', icon: , order: 3, getUrl: (data) => data.vscode }, { key: 'cursor' as LinkItemKey, label: 'Cursor', icon: , order: 4, getUrl: (data) => data.cursor }, { key: 'trae-cn' as LinkItemKey, label: 'Trae', icon: , order: 5, getUrl: (data) => data['trae-cn'] }, { key: 'windsurf' as LinkItemKey, label: 'Windsurf', icon: , order: 6, getUrl: (data) => data.windsurf }, { key: 'antigravity' as LinkItemKey, label: 'Antigravity', icon: , order: 7, getUrl: (data) => data.antigravity }, { key: 'ssh' as LinkItemKey, label: 'SSH', icon: , order: 9, getUrl: (data) => data.ssh }, { key: 'remoteSsh' as LinkItemKey, label: 'Remote SSH', icon: , order: 10, getUrl: (data) => data.remoteSsh }, { key: 'codebuddycn' as LinkItemKey, label: 'CodeBuddy', icon: , order: 11, getUrl: (data) => data.codebuddycn }, ].sort((a, b) => (a.order || 0) - (b.order || 0)) useEffect(() => { if (selectWorkspace) { getWorkspaceSecretLink(selectWorkspace) } }, [selectWorkspace]) return ( 工作区 {/* Tab 导航 */}
{/* Tab 内容 */}
{workspaceTab === 'dev' && ( )} {workspaceTab === 'link' && ( )} {workspaceTab === 'work' && ( )}
) }