119 lines
5.0 KiB
TypeScript
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>
|
|
)}
|
|
</>
|
|
)
|
|
} |