AP2.0e rotiert Recurring bei maturity_stage reached. Maturity-Strategie priorisiert Routinen. Team zeigt Namen und Agent-Formular. Archetyp-Hints in Kontrolle und Stufe-A-Gruppierung bei Anlage. Co-authored-by: Cursor <cursoragent@cursor.com>
137 lines
4.4 KiB
JavaScript
137 lines
4.4 KiB
JavaScript
import { useEffect, useState } from 'react'
|
|
import { Link } from 'react-router-dom'
|
|
import { getActorWorkload } from '../../api/workspace.js'
|
|
import { createActor, listActors } from '../../api/actors.js'
|
|
import { useSession } from '../../context/SessionContext.jsx'
|
|
import { ModeShell } from '../../components/ModeShell.jsx'
|
|
import { LoadingState } from '../../components/LoadingState.jsx'
|
|
import { ErrorState } from '../../components/ErrorState.jsx'
|
|
import { scopedPath } from '../../utils/routes.js'
|
|
|
|
const ACTOR_TYPE_LABELS = {
|
|
human: 'Person',
|
|
agent: 'Agent / Vibe-Coder',
|
|
working_group: 'Gruppe',
|
|
external_system: 'Externes System',
|
|
}
|
|
|
|
export function TeamPage() {
|
|
const { hasCapability } = useSession()
|
|
const [rows, setRows] = useState(null)
|
|
const [actors, setActors] = useState([])
|
|
const [error, setError] = useState(null)
|
|
const [agentName, setAgentName] = useState('')
|
|
const [busy, setBusy] = useState(false)
|
|
|
|
async function load() {
|
|
setError(null)
|
|
try {
|
|
const [workload, directory] = await Promise.all([
|
|
getActorWorkload(),
|
|
listActors({ include_inactive: false }),
|
|
])
|
|
setRows(workload)
|
|
setActors(directory)
|
|
} catch (err) {
|
|
setError(err.message)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
load()
|
|
}, [])
|
|
|
|
async function handleCreateAgent(e) {
|
|
e.preventDefault()
|
|
const name = agentName.trim()
|
|
if (!name) return
|
|
setBusy(true)
|
|
try {
|
|
await createActor({ name, actor_type: 'agent' })
|
|
setAgentName('')
|
|
await load()
|
|
} catch (err) {
|
|
setError(err.message)
|
|
} finally {
|
|
setBusy(false)
|
|
}
|
|
}
|
|
|
|
const canManage = hasCapability('kairo.actor.manage')
|
|
|
|
return (
|
|
<div className="app-page">
|
|
<ModeShell modeKey="team" />
|
|
{rows === null && !error && <LoadingState message="Lade Team …" />}
|
|
{error && <ErrorState message={error} onRetry={load} />}
|
|
{rows && (
|
|
<>
|
|
<section className="card">
|
|
<h2 className="card-title">Actor-Verzeichnis</h2>
|
|
{actors.length === 0 ? (
|
|
<p className="muted">Keine Actors im Tenant.</p>
|
|
) : (
|
|
<ul className="team-workload-list">
|
|
{actors.map((actor) => (
|
|
<li key={actor.id} className="team-workload-list__item">
|
|
<div>
|
|
<strong>{actor.name}</strong>
|
|
<span className="muted">
|
|
{' '}
|
|
· {ACTOR_TYPE_LABELS[actor.actor_type] || actor.actor_type}
|
|
</span>
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
{canManage && (
|
|
<form className="form inline-form team-agent-form" onSubmit={handleCreateAgent}>
|
|
<label>
|
|
Agent anlegen (Vibe-Coder)
|
|
<input
|
|
value={agentName}
|
|
onChange={(e) => setAgentName(e.target.value)}
|
|
placeholder="z. B. Cursor Agent"
|
|
maxLength={255}
|
|
/>
|
|
</label>
|
|
<button type="submit" className="btn btn-primary btn-sm" disabled={busy}>
|
|
{busy ? 'Anlegen …' : 'Agent anlegen'}
|
|
</button>
|
|
</form>
|
|
)}
|
|
</section>
|
|
|
|
<section className="card">
|
|
<h2 className="card-title">Auslastung nach Actor</h2>
|
|
{rows.length === 0 ? (
|
|
<p className="muted">Keine Zuweisungen im Portfolio.</p>
|
|
) : (
|
|
<ul className="team-workload-list">
|
|
{rows.map((row) => (
|
|
<li key={row.actor_id} className="team-workload-list__item">
|
|
<div>
|
|
<strong>{row.display_name || row.actor_name || row.actor_id}</strong>
|
|
<span className="muted">
|
|
{' '}
|
|
· {row.open_actions ?? 0} offen
|
|
{(row.blocked_actions ?? 0) > 0
|
|
? ` · ${row.blocked_actions} blockiert`
|
|
: ''}
|
|
</span>
|
|
</div>
|
|
<Link to={scopedPath('/work/mine')} className="link-inline">
|
|
Queue ansehen
|
|
</Link>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</section>
|
|
</>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|