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

232 lines
11 KiB
HTML

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Agent Orchestrator — Admin</title>
<link rel="stylesheet" href="../../colors_and_type.css">
<style>
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
font-family: var(--font-sans);
background: var(--bg-app, #f8fafc);
color: var(--fg1, #0f172a);
font-size: 13px;
-webkit-font-smoothing: antialiased;
}
@keyframes ao-spin { from { transform: rotate(0) } to { transform: rotate(360deg) } }
@keyframes ao-pulse { 0%, 100% { opacity: 1 } 50% { opacity: 0.35 } }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border: 2px solid #f8fafc; border-radius: 10px; }
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
</style>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
<script type="text/babel" src="./Primitives.jsx"></script>
<script type="text/babel" src="./TopBar.jsx"></script>
<script type="text/babel" src="./TaskList.jsx"></script>
<script type="text/babel" src="./ChatPane.jsx"></script>
<script type="text/babel" src="./DetailPanel.jsx"></script>
<script type="text/babel">
const MIN = 60 * 1000;
const H = 60 * MIN;
const now = Date.now();
const SAMPLE_TASKS = [
{
id: 412, title: 'Xの朝のAIダイジェスト生成',
body: '毎朝 7:00 JST にフォロー中のAI関連アカウントの過去24hをサマリし、DMで送信する。Twitter CLIを使用し、1スレッドにまとめること。',
status: 'running', piece: 'x-ai-digest', worker: 'worker-03', attempts: 1,
assignee: '@daichi', repo: 'gitea:daichi/agent-orchestrator', branch: 'task/412-morning-digest',
createdAt: now - 2*H, updatedAt: now - 4*MIN,
events: [
{ kind: 'info', label: '/brainstorm 完了', meta: '12個のアイデアを生成', time: '10:42' },
{ kind: 'info', label: '/plan 完了', meta: '12ステップ · 推定 4分', time: '10:43' },
{ kind: 'info', label: '/implement 実行中', meta: 'ステップ 8 / 12', time: '10:45' },
],
},
{
id: 411, title: 'Brave Search の CAPTCHA 回避',
body: 'noVNC経由でBraveに繰り返しCAPTCHAが発生。ユーザーの介入が必要。',
status: 'waiting_human', piece: 'general', worker: 'worker-01', attempts: 2,
assignee: '@daichi', repo: 'gitea:daichi/agent-orchestrator', branch: 'task/411-brave-captcha',
createdAt: now - 3*H, updatedAt: now - 18*MIN,
events: [
{ kind: 'info', label: '/brainstorm 完了', meta: '', time: '08:10' },
{ kind: 'error', label: 'ASK が発行されました', meta: 'CAPTCHAの解決を依頼', time: '08:22' },
],
},
{
id: 410, title: 'GitHub Issue #284 の対応',
body: 'scheduler.ts のタイムアウト処理リファクタ。worker-manager.test.ts を更新。',
status: 'succeeded', piece: 'general', worker: 'worker-02', attempts: 1,
assignee: '@daichi', repo: 'gitea:daichi/agent-orchestrator', branch: 'task/410-sched-timeout',
createdAt: now - 8*H, updatedAt: now - 2*H,
events: [
{ kind: 'info', label: '/plan 完了', meta: '7ステップ', time: '02:11' },
{ kind: 'ok', label: 'PR 作成', meta: '#284 テスト通過', time: '04:08' },
],
},
{
id: 409, title: 'ブレスト: 社内AIエージェント活用事例',
body: '営業部向け、週次の活用アイデアを10件ブレストし、優先順位をつけて提出。',
status: 'queued', piece: 'brainstorming', worker: null, attempts: 0,
assignee: '@tomoko', repo: 'gitea:corp/ops', branch: '—',
createdAt: now - 30*MIN, updatedAt: now - 25*MIN,
events: [],
},
{
id: 408, title: '四半期データの集計とグラフ化',
body: 'Q3のSNSエンゲージメントを集計し、CSVとPNGで出力。',
status: 'waiting_subtasks', piece: 'data-process', worker: 'worker-05', attempts: 1,
assignee: '@kenta', repo: 'gitea:corp/analytics', branch: 'task/408-q3-roundup',
createdAt: now - 5*H, updatedAt: now - 45*MIN,
events: [
{ kind: 'info', label: 'サブタスク3件を発行', meta: '#408-1, #408-2, #408-3', time: '09:30' },
],
},
{
id: 407, title: '競合サービスのリサーチ',
body: 'エージェント型ワーカー系SaaSを3社分析し、比較表を作成する。',
status: 'failed', piece: 'research', worker: 'worker-04', attempts: 3,
assignee: '@tomoko', repo: 'gitea:corp/research', branch: 'task/407-competitors',
createdAt: now - 26*H, updatedAt: now - 10*H,
events: [
{ kind: 'error', label: 'タイムアウト', meta: '3回連続で失敗', time: 'yesterday' },
],
},
{
id: 406, title: 'ゲーム実況の告知ツイート生成',
body: '今夜のストリーム用の告知ツイートを3案作成。ハッシュタグ付き。',
status: 'retry', piece: 'game-tweet-generator', worker: 'worker-06', attempts: 2,
assignee: '@daichi', repo: 'gitea:daichi/stream', branch: 'task/406-tweet-gen',
createdAt: now - 40*MIN, updatedAt: now - 8*MIN,
events: [
{ kind: 'error', label: 'レート制限', meta: '60秒後に再試行', time: '10:35' },
],
},
{
id: 405, title: '経費申請書のOCRと仕分け',
body: '添付PDFをOCRし、勘定科目ごとに仕分け。',
status: 'cancelled', piece: 'office-process', worker: null, attempts: 1,
assignee: '@kenta', repo: 'gitea:corp/ops', branch: '—',
createdAt: now - 48*H, updatedAt: now - 20*H,
events: [],
},
];
const INITIAL_MESSAGES = {
412: [
{ role: 'user', content: '毎朝7:00にAI関連のXアカウントの過去24hをまとめて、DMで送ってほしい。1スレッドで。', footer: '10:41 · @daichi' },
{ role: 'assistant', content: '了解。x-ai-digest ピースを使用します。対象アカウント、まとめる観点、文字数制限を確認させてください。' },
{ role: 'ask', content: '❓ 以下を確認させてください:\n\n1. 対象アカウントリストはこのリポジトリの accounts.txt で良いですか?\n2. 1ツイートあたりの上限文字数は280でOK?\n3. 日本語メインの要約で良いですか?' },
{ role: 'user', content: '1. OK\n2. OK\n3. 日本語で。でも原文が英語なら簡潔な英語の引用も残して。' },
{ role: 'progress', content: '/implement — ステップ 8 / 12 (Twitter CLIでタイムライン取得中)' },
],
411: [
{ role: 'user', content: 'Brave Searchで検索結果が取れない。何度もCAPTCHAが出てるっぽい。' },
{ role: 'progress', content: 'noVNCセッションを開いて確認中...' },
{ role: 'ask', content: '❓ CAPTCHAの解決が必要です。noVNCで手動で解決していただけますか?\n\nsession: https://novnc.internal/412\n\n解決後 `/resume 411` と返信してください。' },
],
410: [
{ role: 'user', content: 'Issue #284 の対応お願い。scheduler.tsのタイムアウト処理が不安定。' },
{ role: 'assistant', content: '了解。/brainstorm から始めます。' },
{ role: 'result', content: '✅ 完了しました。\n\n- PR: gitea:daichi/agent-orchestrator#291\n- 変更: scheduler.ts, worker-manager.test.ts, worker.test.ts\n- テスト: 42 passed\n\nレビューお願いします。' },
],
409: [
{ role: 'user', content: '営業部向けに今週のエージェント活用ネタを10個ブレストしてほしい。' },
{ role: 'assistant', content: '了解。キューに入りました。ワーカーの空きが出次第処理します。' },
],
408: [
{ role: 'user', content: 'Q3のSNSエンゲージメントまとめて、折れ線グラフと棒グラフのPNGで。' },
{ role: 'progress', content: 'サブタスク3件の完了を待っています (#408-1, #408-2, #408-3)' },
],
407: [
{ role: 'user', content: '競合3社のリサーチと比較表を作成。' },
{ role: 'assistant', content: '3回試行しましたが、外部サイトの読み込みタイムアウトが続いています。' },
],
406: [
{ role: 'user', content: '今夜のストリーム告知を3案、ハッシュタグ付きで。' },
{ role: 'progress', content: 'レート制限中 — 60秒後に再試行します' },
],
405: [
{ role: 'user', content: '経費PDFのOCRと仕分け。' },
{ role: 'assistant', content: 'キャンセルされました。' },
],
};
function App() {
const [tasks, setTasks] = React.useState(SAMPLE_TASKS);
const [activeId, setActiveId] = React.useState(412);
const [detailOpen, setDetailOpen] = React.useState(true);
const [messages, setMessages] = React.useState(INITIAL_MESSAGES);
const [filters, setFilters] = React.useState({ status: 'all', search: '', sort: 'updated' });
const [page, setPage] = React.useState('tasks');
const active = tasks.find(t => t.id === activeId) || tasks[0];
const counts = {
total: tasks.length,
running: tasks.filter(t => t.status === 'running').length,
waiting: tasks.filter(t => t.status === 'waiting_human' || t.status === 'waiting_subtasks').length,
failed: tasks.filter(t => t.status === 'failed').length,
};
const onSend = (text) => {
setMessages(m => ({
...m,
[activeId]: [...(m[activeId] || []), { role: 'user', content: text, footer: 'たった今 · @daichi' }],
}));
// fake echo after small delay
setTimeout(() => {
setMessages(m => ({
...m,
[activeId]: [...(m[activeId] || []), { role: 'progress', content: 'エージェントが応答を生成中...' }],
}));
}, 400);
};
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
<TopBar
page={page} onNavigate={setPage}
counts={counts}
onOpenCreate={() => alert('新しい依頼 (モック)')}
user={{ name: 'Daichi' }}
/>
<div style={{
flex: 1, minHeight: 0, display: 'grid',
gridTemplateColumns: detailOpen ? '320px 1fr 380px' : '320px 1fr',
background: '#f1f5f9', gap: 1,
}}>
<div style={{ background: '#fff', padding: 12, minWidth: 0, display: 'flex', flexDirection: 'column' }}>
<TaskList
tasks={tasks} activeId={activeId} onSelect={setActiveId}
filters={filters} setFilters={setFilters}
/>
</div>
<ChatPane
task={active}
messages={messages[active.id] || []}
onSend={onSend}
onOpenDetail={() => setDetailOpen(v => !v)}
detailOpen={detailOpen}
/>
{detailOpen && <DetailPanel task={active} onClose={() => setDetailOpen(false)} />}
</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>