clade 7049a874f3 feat: initial public release (MAESTRO v0.1.0)
Open-source release of MAESTRO, an agent orchestration platform that runs
LLM-driven tasks through sandboxed tools, with a web UI. Apache-2.0.
See README.md and docs/ (getting-started, configuration, architecture).
2026-06-03 04:01:14 +00:00

135 lines
6.5 KiB
JavaScript

// DetailPanel — tabs: overview, progress (activity + log surface)
function Tabs({ tab, onTab }) {
const items = [
{ id: 'overview', label: '概要' },
{ id: 'progress', label: '進捗' },
{ id: 'subtasks', label: 'サブタスク' },
];
return (
<div style={{ display: 'flex', gap: 4, padding: '8px 12px', borderBottom: '1px solid #e2e8f0', background: '#fff' }}>
{items.map(it => (
<button key={it.id} onClick={() => onTab(it.id)} style={{
padding: '6px 12px', borderRadius: 8, fontSize: 12, fontWeight: 600,
border: 'none', cursor: 'pointer', fontFamily: 'inherit',
background: tab === it.id ? '#eff6ff' : 'transparent',
color: tab === it.id ? '#1d4ed8' : '#64748b',
}}>{it.label}</button>
))}
</div>
);
}
function OverviewTab({ task }) {
const Row = ({ label, value }) => (
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, padding: '8px 0', borderBottom: '1px solid #f1f5f9', fontSize: 12 }}>
<span style={{ color: '#64748b' }}>{label}</span>
<span style={{ color: '#0f172a', fontWeight: 600, textAlign: 'right' }}>{value}</span>
</div>
);
return (
<div style={{ padding: 16, overflowY: 'auto', fontSize: 13, color: '#0f172a' }}>
<div style={{ fontSize: 10, fontFamily: 'IBM Plex Mono, monospace', color: '#94a3b8', letterSpacing: '.08em' }}>DESCRIPTION</div>
<div style={{ marginTop: 6, color: '#334155', lineHeight: 1.6, fontSize: 13 }}>{task.body}</div>
<div style={{ marginTop: 16, display: 'flex', gap: 8, flexWrap: 'wrap' }}>
<StatChip label="試行" value={`${task.attempts}/3`} />
<StatChip label="ピース" value={task.piece} color="#2563eb" />
<StatChip label="ワーカー" value={task.worker || '—'} />
</div>
<div style={{ marginTop: 16 }}>
<Row label="リポジトリ" value={<span style={{ fontFamily: 'IBM Plex Mono, monospace', fontSize: 11 }}>{task.repo}</span>} />
<Row label="ブランチ" value={<span style={{ fontFamily: 'IBM Plex Mono, monospace', fontSize: 11 }}>{task.branch}</span>} />
<Row label="作成日時" value={new Date(task.createdAt).toLocaleString('ja-JP')} />
<Row label="更新日時" value={relativeTime(task.updatedAt)} />
<Row label="担当" value={task.assignee} />
</div>
<div style={{ marginTop: 16, display: 'flex', gap: 8 }}>
<button style={{
padding: '8px 14px', borderRadius: 10, border: '1px solid #e2e8f0',
background: '#fff', color: '#475569', fontSize: 12, fontWeight: 600,
cursor: 'pointer', fontFamily: 'inherit', whiteSpace: 'nowrap',
}}>再試行</button>
<button style={{
padding: '8px 14px', borderRadius: 10, border: '1px solid #fecaca',
background: '#fff', color: '#b91c1c', fontSize: 12, fontWeight: 600,
cursor: 'pointer', fontFamily: 'inherit', whiteSpace: 'nowrap',
}}>キャンセル</button>
</div>
</div>
);
}
function ProgressTab({ task }) {
const events = task.events || [];
return (
<div style={{ padding: 16, overflowY: 'auto' }}>
<div style={{ fontSize: 10, fontFamily: 'IBM Plex Mono, monospace', color: '#94a3b8', letterSpacing: '.08em', marginBottom: 8 }}>ACTIVITY</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 20 }}>
{events.map((e, i) => (
<div key={i} style={{ display: 'flex', gap: 10, fontSize: 12 }}>
<div style={{ flexShrink: 0, marginTop: 3 }}>
<div style={{ width: 8, height: 8, borderRadius: 9999, background: e.kind === 'error' ? '#dc2626' : e.kind === 'ok' ? '#16a34a' : '#3b82f6' }} />
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ color: '#0f172a', fontWeight: 600 }}>{e.label}</div>
<div style={{ color: '#64748b', fontSize: 11 }}>{e.meta}</div>
</div>
<div style={{ fontSize: 10, color: '#94a3b8', fontFamily: 'IBM Plex Mono, monospace' }}>{e.time}</div>
</div>
))}
</div>
<div style={{ fontSize: 10, fontFamily: 'IBM Plex Mono, monospace', color: '#94a3b8', letterSpacing: '.08em', marginBottom: 6 }}>ACTIVITY.LOG</div>
<div style={{
background: '#0f172a', color: '#e2e8f0',
fontFamily: 'IBM Plex Mono, monospace', fontSize: 11, lineHeight: 1.6,
padding: 12, borderRadius: 8, whiteSpace: 'pre', overflowX: 'auto',
}}>
{`[10:42:18] ` + String.fromCharCode(9432) + ` starting worker for task #` + task.id + `
[10:42:19] ` + String.fromCharCode(9432) + ` branch: ` + task.branch + `
[10:42:21] ` + String.fromCharCode(9432) + ` piece: ` + task.piece + `
[10:42:22] ` + String.fromCharCode(9655) + ` /brainstorm
[10:43:04] ` + String.fromCharCode(10003) + ` /plan (12 steps)
[10:43:05] ` + String.fromCharCode(9655) + ` /implement
[10:44:58] ` + String.fromCharCode(10003) + ` tests passed`}
</div>
</div>
);
}
function DetailPanel({ task, onClose }) {
const [tab, setTab] = React.useState('overview');
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', background: '#f8fafc', borderLeft: '1px solid #e2e8f0' }}>
<div style={{
flexShrink: 0, padding: '12px 16px', borderBottom: '1px solid #e2e8f0', background: '#fff',
display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8,
}}>
<div style={{ minWidth: 0 }}>
<div style={{ fontSize: 10, fontFamily: 'IBM Plex Mono, monospace', color: '#94a3b8', letterSpacing: '.08em' }}>DETAIL</div>
<div style={{ fontSize: 13, fontWeight: 700, color: '#0f172a', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>#{task.id} {task.title}</div>
</div>
<button onClick={onClose} style={{
width: 28, height: 28, borderRadius: 8, border: '1px solid #e2e8f0',
background: '#fff', color: '#64748b', cursor: 'pointer', display: 'inline-flex',
alignItems: 'center', justifyContent: 'center',
}}><IconClose width={12} height={12} /></button>
</div>
<Tabs tab={tab} onTab={setTab} />
<div style={{ flex: 1, minHeight: 0, overflow: 'hidden' }}>
{tab === 'overview' && <OverviewTab task={task} />}
{tab === 'progress' && <ProgressTab task={task} />}
{tab === 'subtasks' && (
<div style={{ padding: 16, fontSize: 13, color: '#64748b' }}>
サブタスクはこのタスクにありません
</div>
)}
</div>
</div>
);
}
window.DetailPanel = DetailPanel;