agent-team/web/src/components/RightSidebar.tsx
2026-03-06 10:07:16 +08:00

119 lines
5.0 KiB
TypeScript

import { useState } from 'react'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { ListTodo, FileText, X, ExternalLink } from 'lucide-react'
import { useStore } from '../store'
const API = '/api'
export function RightSidebar() {
const { activeRoomId, tasks, workspace } = useStore()
const [previewFile, setPreviewFile] = useState<{ name: string; content: string } | null>(null)
const tasksMd = activeRoomId ? (tasks[activeRoomId] || '') : ''
const files = activeRoomId ? (workspace[activeRoomId] || []) : []
const openFile = async (filename: string) => {
if (!activeRoomId) return
const d = await fetch(`${API}/rooms/${activeRoomId}/workspace/${filename}`).then(r => r.json())
setPreviewFile({ name: filename, content: d.content || '' })
}
if (!activeRoomId) {
return (
<div className="w-[280px] bg-[var(--bg-secondary)] border-l border-[var(--border)] flex flex-col">
<div className="h-12 px-4 border-b border-[var(--border)] flex items-center">
<span className="text-sm font-semibold text-[var(--text-muted)]"></span>
</div>
<div className="flex-1 flex items-center justify-center text-[var(--text-muted)] text-xs">
</div>
</div>
)
}
return (
<>
<div className="w-[280px] bg-[var(--bg-secondary)] border-l border-[var(--border)] flex flex-col overflow-hidden">
<div className="h-12 px-4 border-b border-[var(--border)] flex items-center">
<span className="text-sm font-semibold text-[var(--text-muted)]"></span>
</div>
<div className="flex-1 overflow-y-auto scrollbar-thin">
{/* TodoList */}
<section className="border-b border-[var(--border)]">
<div className="px-4 py-3 flex items-center gap-2 text-xs font-semibold text-[var(--text-muted)] uppercase">
<ListTodo className="w-3.5 h-3.5" />
</div>
<div className="px-4 pb-4">
{tasksMd ? (
<div className="text-sm prose prose-sm max-w-none dark:prose-invert">
<ReactMarkdown remarkPlugins={[remarkGfm]}>{tasksMd}</ReactMarkdown>
</div>
) : (
<p className="text-xs text-[var(--text-muted)]"></p>
)}
</div>
</section>
{/* Modified Files */}
<section>
<div className="px-4 py-3 flex items-center gap-2 text-xs font-semibold text-[var(--text-muted)] uppercase">
<FileText className="w-3.5 h-3.5" />
({files.length})
</div>
<div className="px-4 pb-4">
{files.length > 0 ? (
<div className="space-y-1">
{files.map(f => (
<button
key={f}
onClick={() => openFile(f)}
className="w-full flex items-center gap-2 text-sm text-[var(--accent)] hover:text-[var(--accent-hover)] py-1.5 px-2 rounded hover:bg-[var(--bg-hover)] transition-colors text-left"
>
<FileText className="w-4 h-4 flex-shrink-0" />
<span className="truncate">{f}</span>
<ExternalLink className="w-3 h-3 flex-shrink-0 ml-auto opacity-0 group-hover:opacity-100" />
</button>
))}
</div>
) : (
<p className="text-xs text-[var(--text-muted)]"></p>
)}
</div>
</section>
</div>
</div>
{/* File preview modal */}
{previewFile && (
<div
className="fixed inset-0 bg-black/60 flex items-center justify-center z-50 p-4"
onClick={() => setPreviewFile(null)}
>
<div
className="bg-[var(--bg-secondary)] rounded-lg w-full max-w-3xl max-h-[80vh] flex flex-col shadow-2xl"
onClick={e => e.stopPropagation()}
>
<div className="flex items-center justify-between px-4 py-3 border-b border-[var(--border)]">
<div className="flex items-center gap-2">
<FileText className="w-5 h-5 text-[var(--accent)]" />
<span className="font-semibold">{previewFile.name}</span>
</div>
<button
onClick={() => setPreviewFile(null)}
className="w-8 h-8 rounded flex items-center justify-center text-[var(--text-muted)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-primary)] transition-colors"
>
<X className="w-5 h-5" />
</button>
</div>
<div className="overflow-y-auto p-4 prose dark:prose-invert max-w-none text-sm">
<ReactMarkdown remarkPlugins={[remarkGfm]}>{previewFile.content}</ReactMarkdown>
</div>
</div>
</div>
)}
</>
)
}