agent-team/web/src/components/AgentsPage.tsx
scorpio de773586c7 feat: implement full agent-team platform
Go backend:
- LLM client with DeepSeek/Kimi/Ollama/OpenAI support (OpenAI-compat)
- Agent loader: AGENT.md frontmatter, SOUL.md, memory read/write
- Skill system following agentskills.io standard
- Room orchestration: master assign→execute→review loop with streaming
- Hub: GitHub repo clone and team package install
- Echo HTTP server with WebSocket and full REST API

React frontend:
- Discord-style 3-panel layout with Tailwind v4
- Zustand store with WebSocket streaming message handling
- Chat view: streaming messages, role styles, right panel, drawer buttons
- Agent MD editor with Monaco Editor (AGENT.md + SOUL.md)
- Market page for GitHub team install/publish

Docs:
- plan.md with full progress tracking and next steps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 21:57:46 +08:00

88 lines
3.6 KiB
TypeScript

import { useEffect, useState } from 'react'
import Editor from '@monaco-editor/react'
import { useStore } from '../store'
const API = '/api'
export function AgentsPage() {
const { agents, fetchAgents } = useStore()
const [selected, setSelected] = useState<string | null>(null)
const [tab, setTab] = useState<'AGENT.md' | 'SOUL.md'>('AGENT.md')
const [content, setContent] = useState('')
const [newName, setNewName] = useState('')
useEffect(() => { fetchAgents() }, [])
useEffect(() => {
if (!selected) return
fetch(`${API}/agents/${selected}/files/${tab}`).then(r => r.json()).then(d => setContent(d.content || ''))
}, [selected, tab])
const save = async () => {
if (!selected) return
await fetch(`${API}/agents/${selected}/files/${tab}`, {
method: 'PUT', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content })
})
}
const create = async () => {
if (!newName.trim()) return
await fetch(`${API}/agents`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newName.trim() }) })
setNewName('')
fetchAgents()
}
const del = async (name: string) => {
await fetch(`${API}/agents/${name}`, { method: 'DELETE' })
if (selected === name) setSelected(null)
fetchAgents()
}
return (
<div className="flex flex-1 overflow-hidden">
{/* Agent list */}
<div className="w-48 border-r border-gray-700 flex flex-col">
<div className="p-3 border-b border-gray-700">
<div className="flex gap-1">
<input className="flex-1 bg-gray-700 rounded px-2 py-1 text-xs" placeholder="新 agent 名" value={newName} onChange={e => setNewName(e.target.value)} onKeyDown={e => e.key === 'Enter' && create()} />
<button onClick={create} className="bg-indigo-600 hover:bg-indigo-500 px-2 py-1 rounded text-xs">+</button>
</div>
</div>
<div className="flex-1 overflow-y-auto">
{agents.map(a => (
<div key={a.name} onClick={() => setSelected(a.name)}
className={`flex items-center justify-between px-3 py-2 cursor-pointer text-sm hover:bg-gray-700 ${selected === a.name ? 'bg-gray-700' : ''}`}>
<span className="truncate">{a.name}</span>
<button onClick={e => { e.stopPropagation(); del(a.name) }} className="text-gray-500 hover:text-red-400 text-xs"></button>
</div>
))}
</div>
</div>
{/* Editor */}
<div className="flex-1 flex flex-col overflow-hidden">
{selected ? (
<>
<div className="flex items-center gap-2 px-4 py-2 border-b border-gray-700">
<span className="font-semibold text-sm">{selected}</span>
<div className="flex gap-1 ml-2">
{(['AGENT.md', 'SOUL.md'] as const).map(t => (
<button key={t} onClick={() => setTab(t)}
className={`text-xs px-3 py-1 rounded ${tab === t ? 'bg-indigo-600' : 'bg-gray-700 hover:bg-gray-600'}`}>{t}</button>
))}
</div>
<button onClick={save} className="ml-auto bg-green-700 hover:bg-green-600 px-3 py-1 rounded text-xs"></button>
</div>
<div className="flex-1">
<Editor height="100%" defaultLanguage="markdown" theme="vs-dark" value={content} onChange={v => setContent(v || '')} options={{ minimap: { enabled: false }, wordWrap: 'on', fontSize: 13 }} />
</div>
</>
) : (
<div className="flex-1 flex items-center justify-center text-gray-400 text-sm"> agent </div>
)}
</div>
</div>
)
}