feat(phase3): threads backend + thread panel UI

This commit is contained in:
2026-06-30 10:36:18 -04:00
parent bd260183ef
commit f3f03df710
9 changed files with 634 additions and 122 deletions
+69
View File
@@ -0,0 +1,69 @@
import { useEffect, useState } from 'react';
import { useThreadStore, type Thread } from '../stores/thread.ts';
interface ThreadListPanelProps {
channelId: string;
onSelect: (thread: { id: string; name: string }) => void;
onClose: () => void;
}
export function ThreadListPanel({ channelId, onSelect, onClose }: ThreadListPanelProps) {
const threads = useThreadStore((s) => s.threadsByParent[channelId] || []);
const fetchThreads = useThreadStore((s) => s.fetchThreads);
const createThread = useThreadStore((s) => s.createThread);
const [name, setName] = useState('');
const [loading, setLoading] = useState(false);
useEffect(() => {
fetchThreads(channelId);
}, [channelId, fetchThreads]);
const handleCreate = async () => {
if (!name.trim()) return;
setLoading(true);
try {
const t = await createThread(channelId, { name: name.trim() });
onSelect({ id: t.id, name: t.name });
setName('');
} finally {
setLoading(false);
}
};
return (
<div className="bg-gb-bg-s border-b border-gb-bg-t p-3 font-mono text-sm">
<div className="flex items-center justify-between mb-2">
<span className="text-gb-orange text-xs">THREADS</span>
<button onClick={onClose} className="text-xs text-gb-fg-f hover:text-gb-orange">[x]</button>
</div>
<div className="flex gap-2 mb-2">
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="new thread name"
className="terminal-input flex-1 text-xs"
/>
<button
onClick={handleCreate}
disabled={loading || !name.trim()}
className="text-xs bg-gb-orange text-gb-bg px-2 disabled:opacity-50"
>
[+]
</button>
</div>
{threads.length === 0 && <div className="text-gb-fg-f text-xs">[no threads]</div>}
<div className="space-y-1">
{threads.map((t: Thread) => (
<button
key={t.id}
onClick={() => onSelect({ id: t.id, name: t.name })}
className="w-full text-left text-xs text-gb-fg hover:text-gb-orange truncate"
>
{t.name} <span className="text-gb-fg-f">({t.message_count})</span>
</button>
))}
</div>
</div>
);
}