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).
232 lines
11 KiB
HTML
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>
|