| <!DOCTYPE html>
|
| <html lang="en" data-theme="dark">
|
| <head>
|
| <meta charset="UTF-8"/>
|
| <meta name="viewport" content="width=device-width,initial-scale=1"/>
|
| <title>PDF AI Orchestrator</title>
|
| <link rel="preconnect" href="https://fonts.googleapis.com">
|
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300..700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
| <style>
|
| :root {
|
| --bg: #0d0d0f;
|
| --surf: #131316;
|
| --surf2: #1a1a1f;
|
| --surf3: #222228;
|
| --border: rgba(255,255,255,0.07);
|
| --border2: rgba(255,255,255,0.12);
|
| --text: #e2e2e8;
|
| --muted: #7a7a8c;
|
| --faint: #3a3a4a;
|
| --primary: #5b7cfa;
|
| --primary2: #3d5ce0;
|
| --teal: #2ec9b7;
|
| --green: #3ecf8e;
|
| --orange: #f59e0b;
|
| --red: #f87171;
|
| --purple: #a78bfa;
|
| --font: 'Inter', sans-serif;
|
| --mono: 'JetBrains Mono', monospace;
|
| --r: 10px;
|
| --r2: 14px;
|
| --trans: 160ms cubic-bezier(0.16,1,0.3,1);
|
| }
|
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
| html { scroll-behavior: smooth; }
|
| body {
|
| font-family: var(--font);
|
| background: var(--bg);
|
| color: var(--text);
|
| min-height: 100vh;
|
| display: flex;
|
| flex-direction: column;
|
| font-size: 14px;
|
| line-height: 1.5;
|
| -webkit-font-smoothing: antialiased;
|
| }
|
| button { font-family: var(--font); cursor: pointer; border: none; background: none; color: inherit; }
|
| input, select, textarea { font-family: var(--font); font-size: 13px; }
|
|
|
|
|
| .topbar {
|
| display: flex; align-items: center; gap: 12px;
|
| padding: 0 24px; height: 52px;
|
| background: var(--surf);
|
| border-bottom: 1px solid var(--border);
|
| position: sticky; top: 0; z-index: 100;
|
| }
|
| .logo {
|
| display: flex; align-items: center; gap: 8px;
|
| font-size: 15px; font-weight: 700; color: var(--text);
|
| text-decoration: none;
|
| }
|
| .logo-icon {
|
| width: 28px; height: 28px; border-radius: 8px;
|
| background: linear-gradient(135deg,#5b7cfa,#2ec9b7);
|
| display: flex; align-items: center; justify-content: center;
|
| font-size: 14px;
|
| }
|
| .topbar-center { flex: 1; display: flex; justify-content: center; gap: 6px; }
|
| .tab-btn {
|
| padding: 5px 14px; border-radius: 7px;
|
| font-size: 13px; font-weight: 500; color: var(--muted);
|
| transition: all var(--trans);
|
| }
|
| .tab-btn.active { background: var(--surf3); color: var(--text); }
|
| .tab-btn:hover:not(.active) { color: var(--text); }
|
| .topbar-right { display: flex; align-items: center; gap: 8px; }
|
| .btn-run {
|
| padding: 7px 20px; border-radius: 8px;
|
| background: var(--primary); color: #fff;
|
| font-size: 13px; font-weight: 600;
|
| transition: all var(--trans);
|
| display: flex; align-items: center; gap: 6px;
|
| }
|
| .btn-run:hover { background: var(--primary2); transform: translateY(-1px); box-shadow: 0 4px 16px rgba(91,124,250,0.3); }
|
| .btn-run:active { transform: translateY(0); }
|
| .btn-ghost {
|
| padding: 6px 14px; border-radius: 8px; font-size: 13px;
|
| border: 1px solid var(--border2); color: var(--muted);
|
| transition: all var(--trans);
|
| }
|
| .btn-ghost:hover { color: var(--text); border-color: var(--border2); background: var(--surf2); }
|
|
|
|
|
| .layout {
|
| display: flex; flex: 1; overflow: hidden; height: calc(100vh - 52px);
|
| }
|
|
|
|
|
| .left-panel {
|
| width: 280px; min-width: 260px;
|
| background: var(--surf);
|
| border-right: 1px solid var(--border);
|
| display: flex; flex-direction: column;
|
| overflow-y: auto;
|
| }
|
| .panel-section { padding: 16px; border-bottom: 1px solid var(--border); }
|
| .panel-label {
|
| font-size: 11px; font-weight: 600; text-transform: uppercase;
|
| letter-spacing: 0.08em; color: var(--muted); margin-bottom: 10px;
|
| }
|
| .file-drop {
|
| border: 1.5px dashed var(--faint);
|
| border-radius: var(--r);
|
| padding: 20px 12px;
|
| text-align: center;
|
| cursor: pointer;
|
| transition: all var(--trans);
|
| position: relative;
|
| }
|
| .file-drop:hover, .file-drop.drag { border-color: var(--primary); background: rgba(91,124,250,0.05); }
|
| .file-drop input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
|
| .file-drop-icon { font-size: 24px; margin-bottom: 6px; }
|
| .file-drop p { font-size: 12px; color: var(--muted); }
|
| .file-drop p b { color: var(--text); }
|
| .file-name {
|
| margin-top: 8px; padding: 7px 10px; border-radius: 7px;
|
| background: var(--surf2); font-size: 12px;
|
| display: flex; align-items: center; gap: 6px;
|
| display: none;
|
| }
|
| .file-name.show { display: flex; }
|
|
|
| .form-row { margin-bottom: 10px; }
|
| .form-row label { display: block; font-size: 11px; color: var(--muted); margin-bottom: 4px; }
|
| .form-row input, .form-row select {
|
| width: 100%; padding: 7px 10px; border-radius: 7px;
|
| background: var(--surf2); border: 1px solid var(--border);
|
| color: var(--text); outline: none;
|
| transition: border-color var(--trans);
|
| }
|
| .form-row input:focus, .form-row select:focus { border-color: var(--primary); }
|
|
|
| .toggle-row {
|
| display: flex; align-items: center; justify-content: space-between;
|
| margin-bottom: 8px;
|
| }
|
| .toggle-row span { font-size: 12px; color: var(--muted); }
|
| .toggle {
|
| width: 36px; height: 20px; border-radius: 999px;
|
| background: var(--faint); cursor: pointer;
|
| position: relative; transition: background var(--trans);
|
| }
|
| .toggle.on { background: var(--primary); }
|
| .toggle::after {
|
| content: ''; position: absolute;
|
| width: 14px; height: 14px; border-radius: 50%;
|
| background: #fff; top: 3px; left: 3px;
|
| transition: transform var(--trans);
|
| }
|
| .toggle.on::after { transform: translateX(16px); }
|
|
|
|
|
| .agent-palette { display: flex; flex-direction: column; gap: 6px; }
|
| .palette-agent {
|
| display: flex; align-items: center; gap: 10px;
|
| padding: 9px 12px; border-radius: var(--r);
|
| background: var(--surf2); border: 1px solid var(--border);
|
| cursor: grab; transition: all var(--trans);
|
| user-select: none;
|
| }
|
| .palette-agent:hover { border-color: var(--border2); background: var(--surf3); transform: translateX(2px); }
|
| .palette-agent:active { cursor: grabbing; }
|
| .agent-avatar {
|
| width: 32px; height: 32px; border-radius: 50%;
|
| display: flex; align-items: center; justify-content: center;
|
| font-size: 15px; flex-shrink: 0;
|
| }
|
| .agent-info { flex: 1; min-width: 0; }
|
| .agent-info .name { font-size: 12px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
| .agent-info .role { font-size: 10px; color: var(--muted); }
|
| .drag-hint { font-size: 10px; color: var(--faint); }
|
|
|
|
|
| .main-canvas {
|
| flex: 1; overflow-y: auto; overflow-x: hidden;
|
| padding: 24px;
|
| display: flex; flex-direction: column; gap: 16px;
|
| }
|
| .canvas-header {
|
| display: flex; align-items: center; justify-content: space-between;
|
| margin-bottom: 4px;
|
| }
|
| .canvas-title { font-size: 16px; font-weight: 700; }
|
| .canvas-sub { font-size: 12px; color: var(--muted); margin-top: 2px; }
|
|
|
|
|
| .pipeline-zone {
|
| min-height: 280px;
|
| border: 1.5px dashed var(--faint);
|
| border-radius: var(--r2);
|
| padding: 16px;
|
| display: flex; flex-direction: column; gap: 10px;
|
| transition: border-color var(--trans), background var(--trans);
|
| }
|
| .pipeline-zone.drag-over {
|
| border-color: var(--primary);
|
| background: rgba(91,124,250,0.04);
|
| }
|
| .pipeline-empty {
|
| display: flex; flex-direction: column; align-items: center;
|
| justify-content: center; gap: 8px; padding: 40px;
|
| color: var(--muted); text-align: center;
|
| }
|
| .pipeline-empty-icon { font-size: 36px; opacity: 0.4; }
|
|
|
|
|
| .pipeline-card {
|
| background: var(--surf2);
|
| border: 1px solid var(--border);
|
| border-radius: var(--r);
|
| padding: 14px 16px;
|
| display: flex; flex-direction: column; gap: 10px;
|
| position: relative;
|
| transition: all var(--trans);
|
| animation: slideIn 0.18s ease;
|
| }
|
| @keyframes slideIn {
|
| from { opacity: 0; transform: translateY(-8px); }
|
| to { opacity: 1; transform: translateY(0); }
|
| }
|
| .pipeline-card:hover { border-color: var(--border2); }
|
| .pipeline-card.running { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary) inset; }
|
| .pipeline-card.done { border-color: var(--green); }
|
| .pipeline-card.error { border-color: var(--red); }
|
| .pipeline-card.skipped { opacity: 0.5; }
|
|
|
| .card-header { display: flex; align-items: center; gap: 10px; }
|
| .card-num {
|
| width: 22px; height: 22px; border-radius: 50%;
|
| background: var(--faint); font-size: 11px; font-weight: 700;
|
| display: flex; align-items: center; justify-content: center;
|
| flex-shrink: 0; color: var(--muted);
|
| }
|
| .pipeline-card.done .card-num { background: var(--green); color: #000; }
|
| .pipeline-card.running .card-num { background: var(--primary); color: #fff; animation: pulse 1s infinite; }
|
| .pipeline-card.error .card-num { background: var(--red); color: #fff; }
|
| @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }
|
|
|
| .view-panel.hidden { display: none; }
|
| .command-studio { display: flex; flex-direction: column; gap: 16px; }
|
| .command-box, .team-summary, .team-card {
|
| background: var(--surf2);
|
| border: 1px solid var(--border);
|
| border-radius: var(--r2);
|
| }
|
| .command-box {
|
| padding: 18px;
|
| display: flex;
|
| flex-direction: column;
|
| gap: 12px;
|
| }
|
| .command-box textarea {
|
| width: 100%;
|
| min-height: 140px;
|
| resize: vertical;
|
| padding: 12px 14px;
|
| border-radius: 10px;
|
| background: var(--bg);
|
| border: 1px solid var(--border);
|
| color: var(--text);
|
| outline: none;
|
| }
|
| .command-box textarea:focus { border-color: var(--primary); }
|
| .command-actions {
|
| display: flex;
|
| align-items: center;
|
| gap: 10px;
|
| flex-wrap: wrap;
|
| }
|
| .command-meta { font-size: 12px; color: var(--muted); }
|
| .team-summary {
|
| padding: 16px 18px;
|
| display: grid;
|
| grid-template-columns: 1.1fr 1fr;
|
| gap: 16px;
|
| }
|
| .summary-block h4 {
|
| font-size: 12px;
|
| color: var(--muted);
|
| text-transform: uppercase;
|
| letter-spacing: 0.08em;
|
| margin-bottom: 8px;
|
| }
|
| .summary-block p, .summary-block li { font-size: 13px; }
|
| .summary-block ul {
|
| margin-left: 18px;
|
| display: flex;
|
| flex-direction: column;
|
| gap: 6px;
|
| }
|
| .team-grid {
|
| display: grid;
|
| grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
| gap: 12px;
|
| }
|
| .team-card {
|
| padding: 14px;
|
| display: flex;
|
| flex-direction: column;
|
| gap: 10px;
|
| }
|
| .team-card-header {
|
| display: flex;
|
| align-items: center;
|
| gap: 10px;
|
| }
|
| .team-avatar {
|
| width: 36px;
|
| height: 36px;
|
| border-radius: 12px;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| font-size: 18px;
|
| flex-shrink: 0;
|
| }
|
| .team-card-title { font-size: 13px; font-weight: 700; }
|
| .team-card-sub { font-size: 11px; color: var(--muted); }
|
| .model-pill {
|
| display: inline-flex;
|
| align-items: center;
|
| gap: 6px;
|
| padding: 4px 8px;
|
| border-radius: 999px;
|
| font-size: 10px;
|
| background: var(--surf3);
|
| border: 1px solid var(--border);
|
| color: var(--muted);
|
| }
|
| .model-line {
|
| font-size: 11px;
|
| color: var(--muted);
|
| word-break: break-all;
|
| }
|
| .team-insight {
|
| display: flex;
|
| flex-direction: column;
|
| gap: 7px;
|
| font-size: 12px;
|
| }
|
| .team-insight div {
|
| padding: 8px 10px;
|
| border-radius: 10px;
|
| background: var(--bg);
|
| border: 1px solid var(--border);
|
| }
|
| .team-insight strong { color: var(--text); }
|
|
|
| .card-title { font-size: 13px; font-weight: 600; flex: 1; }
|
| .card-badge {
|
| font-size: 10px; font-weight: 600; padding: 2px 8px;
|
| border-radius: 999px; text-transform: uppercase; letter-spacing: 0.05em;
|
| }
|
| .badge-blue { background: rgba(91,124,250,0.15); color: var(--primary); }
|
| .badge-teal { background: rgba(46,201,183,0.15); color: var(--teal); }
|
| .badge-green { background: rgba(62,207,142,0.15); color: var(--green); }
|
| .badge-orange { background: rgba(245,158,11,0.15); color: var(--orange); }
|
| .badge-purple { background: rgba(167,139,250,0.15); color: var(--purple); }
|
| .badge-red { background: rgba(248,113,113,0.15); color: var(--red); }
|
| .badge-muted { background: var(--surf3); color: var(--muted); }
|
|
|
| .card-actions {
|
| position: absolute; top: 10px; right: 12px;
|
| display: flex; gap: 4px;
|
| }
|
| .card-btn {
|
| width: 24px; height: 24px; border-radius: 6px;
|
| display: flex; align-items: center; justify-content: center;
|
| font-size: 12px; color: var(--muted);
|
| transition: all var(--trans);
|
| }
|
| .card-btn:hover { background: var(--surf3); color: var(--text); }
|
| .card-btn.del:hover { color: var(--red); }
|
|
|
|
|
| .task-list { display: flex; flex-direction: column; gap: 6px; }
|
| .task-row {
|
| display: flex; align-items: flex-start; gap: 8px;
|
| padding: 8px 10px; border-radius: 8px;
|
| background: var(--surf3); border: 1px solid var(--border);
|
| transition: all var(--trans);
|
| }
|
| .task-row:hover { border-color: var(--border2); }
|
| .task-type {
|
| font-size: 10px; font-weight: 600; padding: 2px 7px;
|
| border-radius: 999px; background: var(--faint); color: var(--muted);
|
| flex-shrink: 0; margin-top: 1px;
|
| }
|
| .task-fields { flex: 1; display: flex; flex-wrap: wrap; gap: 6px; }
|
| .task-fields input {
|
| flex: 1; min-width: 80px; padding: 4px 8px;
|
| border-radius: 6px; background: var(--surf2);
|
| border: 1px solid var(--border); color: var(--text);
|
| font-size: 12px; outline: none;
|
| transition: border-color var(--trans);
|
| }
|
| .task-fields input:focus { border-color: var(--primary); }
|
| .task-del {
|
| color: var(--faint); font-size: 14px; margin-top: 2px;
|
| transition: color var(--trans); cursor: pointer; flex-shrink: 0;
|
| }
|
| .task-del:hover { color: var(--red); }
|
|
|
| .add-task-btn {
|
| padding: 5px 12px; border-radius: 7px; font-size: 11px;
|
| border: 1px dashed var(--faint); color: var(--muted);
|
| display: flex; align-items: center; gap: 4px; width: 100%;
|
| justify-content: center; transition: all var(--trans);
|
| }
|
| .add-task-btn:hover { border-color: var(--primary); color: var(--primary); background: rgba(91,124,250,0.04); }
|
|
|
|
|
| .connector {
|
| display: flex; justify-content: center; align-items: center;
|
| height: 20px; color: var(--faint); font-size: 16px;
|
| position: relative;
|
| }
|
| .connector::before, .connector::after {
|
| content: ''; position: absolute; left: 50%;
|
| transform: translateX(-50%);
|
| width: 1px; height: 8px;
|
| background: var(--faint);
|
| }
|
| .connector::before { top: 0; }
|
| .connector::after { bottom: 0; }
|
|
|
|
|
| .right-panel {
|
| width: 300px; min-width: 280px;
|
| background: var(--surf);
|
| border-left: 1px solid var(--border);
|
| display: flex; flex-direction: column; overflow-y: auto;
|
| }
|
| .log-header {
|
| padding: 14px 16px; border-bottom: 1px solid var(--border);
|
| display: flex; align-items: center; justify-content: space-between;
|
| }
|
| .log-title { font-size: 12px; font-weight: 700; color: var(--text); }
|
| .log-clear { font-size: 11px; color: var(--muted); cursor: pointer; transition: color var(--trans); }
|
| .log-clear:hover { color: var(--red); }
|
| .log-body { flex: 1; padding: 12px; display: flex; flex-direction: column; gap: 4px; overflow-y: auto; }
|
| .log-entry {
|
| font-family: var(--mono); font-size: 11px; line-height: 1.6;
|
| padding: 6px 8px; border-radius: 6px; background: var(--surf2);
|
| border-left: 2px solid var(--faint);
|
| animation: fadeIn 0.15s ease;
|
| }
|
| @keyframes fadeIn { from { opacity: 0; transform: translateX(4px); } to { opacity: 1; } }
|
| .log-entry.info { border-color: var(--primary); }
|
| .log-entry.ok { border-color: var(--green); }
|
| .log-entry.warn { border-color: var(--orange); }
|
| .log-entry.error { border-color: var(--red); }
|
| .log-time { color: var(--muted); }
|
| .log-stage { font-weight: 600; }
|
| .log-entry.info .log-stage { color: var(--primary); }
|
| .log-entry.ok .log-stage { color: var(--green); }
|
| .log-entry.warn .log-stage { color: var(--orange); }
|
| .log-entry.error .log-stage { color: var(--red); }
|
|
|
| .result-section { padding: 12px 16px; border-top: 1px solid var(--border); }
|
| .result-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin-bottom: 10px; }
|
| .result-row { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; }
|
| .result-label { font-size: 11px; color: var(--muted); flex: 1; }
|
| .result-val { font-family: var(--mono); font-size: 11px; color: var(--teal); }
|
| .result-pill {
|
| font-size: 10px; font-weight: 600; padding: 2px 8px;
|
| border-radius: 999px;
|
| }
|
| .pill-ok { background: rgba(62,207,142,0.15); color: var(--green); }
|
| .pill-err { background: rgba(248,113,113,0.15); color: var(--red); }
|
| .pill-run { background: rgba(91,124,250,0.15); color: var(--primary); animation: pulse 1s infinite; }
|
| .pill-idle { background: var(--surf3); color: var(--muted); }
|
|
|
| .download-btn {
|
| width: 100%; padding: 9px; border-radius: 8px;
|
| background: rgba(62,207,142,0.1); border: 1px solid rgba(62,207,142,0.3);
|
| color: var(--green); font-size: 12px; font-weight: 600;
|
| display: flex; align-items: center; justify-content: center; gap: 6px;
|
| margin-top: 10px; cursor: pointer; transition: all var(--trans);
|
| display: none;
|
| }
|
| .download-btn.show { display: flex; }
|
| .download-btn:hover { background: rgba(62,207,142,0.2); }
|
|
|
|
|
| .statusbar {
|
| height: 26px; background: var(--surf);
|
| border-top: 1px solid var(--border);
|
| display: flex; align-items: center; gap: 16px;
|
| padding: 0 16px; font-size: 11px; color: var(--muted);
|
| }
|
| .statusbar span { display: flex; align-items: center; gap: 4px; }
|
| .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--green); }
|
| .dot.busy { background: var(--orange); animation: pulse 1s infinite; }
|
|
|
|
|
| ::-webkit-scrollbar { width: 5px; height: 5px; }
|
| ::-webkit-scrollbar-track { background: transparent; }
|
| ::-webkit-scrollbar-thumb { background: var(--faint); border-radius: 99px; }
|
| ::-webkit-scrollbar-thumb:hover { background: var(--muted); }
|
|
|
|
|
| @media (max-width: 900px) {
|
| .left-panel { width: 220px; }
|
| .right-panel { width: 240px; }
|
| .team-summary { grid-template-columns: 1fr; }
|
| }
|
| </style>
|
| </head>
|
| <body>
|
|
|
|
|
| <header class="topbar">
|
| <a href="#" class="logo">
|
| <div class="logo-icon">β</div>
|
| PDF Orchestrator
|
| </a>
|
| <div class="topbar-center">
|
| <button class="tab-btn active" onclick="switchTab(this,'pipeline')">Pipeline</button>
|
| <button class="tab-btn" onclick="switchTab(this,'config')">Config</button>
|
| <button class="tab-btn" onclick="switchTab(this,'team')">AI Team</button>
|
| </div>
|
| <div class="topbar-right">
|
| <button class="btn-ghost" id="clearBtn" onclick="clearPipeline()">Clear</button>
|
| <button class="btn-run" id="runBtn" onclick="runPipeline()">
|
| <span>βΆ</span> Run Pipeline
|
| </button>
|
| </div>
|
| </header>
|
|
|
|
|
| <div class="layout">
|
|
|
|
|
| <aside class="left-panel">
|
| <div class="panel-section">
|
| <div class="panel-label">Input PDF</div>
|
| <div class="file-drop" id="fileDrop">
|
| <input type="file" accept=".pdf" onchange="handleFile(this)">
|
| <div class="file-drop-icon">π</div>
|
| <p><b>Click or drag</b> a PDF here</p>
|
| </div>
|
| <div class="file-name" id="fileName">
|
| <span>π</span>
|
| <span id="fileNameText">β</span>
|
| </div>
|
| </div>
|
|
|
| <div class="panel-section">
|
| <div class="panel-label">Output Folder</div>
|
| <div class="form-row">
|
| <input type="text" id="outputDir" placeholder="C:\Users\...\pdf-output" style="width:100%;padding:7px 10px;border-radius:7px;background:var(--surf2);border:1px solid var(--border);color:var(--text);outline:none;">
|
| </div>
|
| <div class="toggle-row">
|
| <span>Force OCR</span>
|
| <div class="toggle" id="ocrToggle" onclick="this.classList.toggle('on')"></div>
|
| </div>
|
| <div class="form-row">
|
| <label>OCR Language</label>
|
| <input type="text" id="ocrLang" value="eng" placeholder="eng / est / deu">
|
| </div>
|
| </div>
|
|
|
| <div class="panel-section" style="flex:1">
|
| <div class="panel-label">Available Agents</div>
|
| <div class="panel-label" style="font-size:10px;color:var(--faint);margin-bottom:8px;margin-top:-6px">Drag into pipeline β</div>
|
| <div class="agent-palette" id="agentPalette"></div>
|
| </div>
|
| </aside>
|
|
|
|
|
| <main class="main-canvas" id="mainCanvas">
|
| <section id="pipelineView" class="view-panel">
|
| <div>
|
| <div class="canvas-header">
|
| <div>
|
| <div class="canvas-title">Agent Pipeline</div>
|
| <div class="canvas-sub">Drag agents from the left to build your processing flow. Configure tasks per agent.</div>
|
| </div>
|
| </div>
|
| </div>
|
| <div class="pipeline-zone" id="pipelineZone"
|
| ondragover="onDragOver(event)" ondragleave="onDragLeave(event)" ondrop="onDrop(event)">
|
| <div class="pipeline-empty" id="pipelineEmpty">
|
| <div class="pipeline-empty-icon">⬑</div>
|
| <p style="font-weight:600;color:var(--text)">Pipeline is empty</p>
|
| <p style="font-size:12px">Drag agents from the left panel to start building</p>
|
| </div>
|
| </div>
|
| </section>
|
|
|
| <section id="teamView" class="view-panel hidden">
|
| <div class="canvas-header">
|
| <div>
|
| <div class="canvas-title">AI Team Command Router</div>
|
| <div class="canvas-sub">Sisesta ΓΌks kΓ΅rgema taseme kΓ€sk ja sΓΌsteem jagab selle AI rollidele laiali koos mudelite, mΓ΅ttekΓ€igu ja soovitusliku pipeline'iga.</div>
|
| </div>
|
| </div>
|
|
|
| <div class="command-studio">
|
| <div class="command-box">
|
| <div class="panel-label" style="margin-bottom:0">Command Brief</div>
|
| <textarea id="teamCommandInput" placeholder="NΓ€ide: VΓ΅ta see konto vΓ€ljavΓ΅tte PDF, tee see otsitavaks, sΓ€ilita tabelid, paranda struktuur ja valmista mulle kontrollitud lΓ΅pp-PDF."></textarea>
|
| <div class="command-actions">
|
| <button class="btn-run" type="button" onclick="routeCommand()">
|
| <span>π§ </span> Build Team Plan
|
| </button>
|
| <button class="btn-ghost" type="button" onclick="loadRecommendedPipeline()">Load Recommended Pipeline</button>
|
| <span class="command-meta" id="teamCommandMeta">AI team is ready to reason about your PDF command.</span>
|
| </div>
|
| </div>
|
|
|
| <div class="team-summary" id="teamSummary">
|
| <div class="summary-block">
|
| <h4>Recommended pipeline</h4>
|
| <p id="recommendedPipelineText">No recommendation yet.</p>
|
| </div>
|
| <div class="summary-block">
|
| <h4>Reasoning</h4>
|
| <ul id="teamReasons">
|
| <li>Enter a command to generate a team plan.</li>
|
| </ul>
|
| </div>
|
| </div>
|
|
|
| <div class="team-grid" id="teamGrid"></div>
|
| </div>
|
| </section>
|
| </main>
|
|
|
|
|
| <aside class="right-panel">
|
| <div class="log-header">
|
| <span class="log-title">Live Logs</span>
|
| <span class="log-clear" onclick="clearLogs()">Clear</span>
|
| </div>
|
| <div class="log-body" id="logBody"></div>
|
|
|
| <div class="result-section">
|
| <div class="result-title">Job Status</div>
|
| <div class="result-row">
|
| <span class="result-label">Status</span>
|
| <span class="result-pill pill-idle" id="statusPill">Idle</span>
|
| </div>
|
| <div class="result-row">
|
| <span class="result-label">Pages</span>
|
| <span class="result-val" id="statPages">β</span>
|
| </div>
|
| <div class="result-row">
|
| <span class="result-label">Duration</span>
|
| <span class="result-val" id="statDuration">β</span>
|
| </div>
|
| <div class="result-row">
|
| <span class="result-label">Output</span>
|
| <span class="result-val" id="statOutput" style="font-size:10px;word-break:break-all">β</span>
|
| </div>
|
| <button class="download-btn" id="downloadBtn" onclick="downloadResult()">
|
| β¬ Download Final PDF
|
| </button>
|
| </div>
|
| </aside>
|
| </div>
|
|
|
|
|
| <footer class="statusbar">
|
| <span><div class="dot" id="statusDot"></div> <span id="statusText">Ready β AI team online</span></span>
|
| <span>π§ AI team Β· Local-first routing</span>
|
| <span id="agentCountBar">0 agents in pipeline</span>
|
| </footer>
|
|
|
| <script>
|
|
|
|
|
|
|
| const AGENTS = [
|
| {
|
| id: 'inspector', name: 'Structure Inspector', role: 'Analyzes PDF structure & metadata',
|
| icon: 'π', color: '#5b7cfa', badge: 'badge-blue',
|
| tasks: [
|
| { type: 'inspect', label: 'Inspect PDF', fields: [] }
|
| ],
|
| taskTypes: []
|
| },
|
| {
|
| id: 'ocr', name: 'OCR Agent', role: 'Converts scanned pages to searchable text',
|
| icon: 'π', color: '#2ec9b7', badge: 'badge-teal',
|
| tasks: [],
|
| taskTypes: []
|
| },
|
| {
|
| id: 'editor', name: 'Editing Agent', role: 'Text, region and page operations',
|
| icon: 'βοΈ', color: '#a78bfa', badge: 'badge-purple',
|
| tasks: [],
|
| taskTypes: [
|
| { type: 'insert_text', label: '+ Insert Text', fields: ['page','x','y','text'] },
|
| { type: 'whiteout_region',label: 'β¬ Whiteout Region',fields: ['page','x0','y0','x1','y1','replacement'] },
|
| { type: 'rotate_page', label: 'β» Rotate Page', fields: ['page','degrees'] },
|
| { type: 'delete_page', label: 'β Delete Page', fields: ['page'] },
|
| { type: 'reorder_pages', label: 'β
Reorder Pages', fields: ['new_order (e.g. 0,2,1)'] },
|
| ]
|
| },
|
| {
|
| id: 'annotator', name: 'Annotation Agent', role: 'Highlights, comments, redactions',
|
| icon: 'π', color: '#f59e0b', badge: 'badge-orange',
|
| tasks: [],
|
| taskTypes: [
|
| { type: 'highlight', label: 'π‘ Highlight', fields: ['page','x0','y0','x1','y1'] },
|
| { type: 'comment', label: 'π¬ Comment', fields: ['page','x','y','text'] },
|
| { type: 'rectangle', label: 'β¬ Rectangle', fields: ['page','x0','y0','x1','y1'] },
|
| { type: 'redact', label: 'β¬ Redact', fields: ['page','x0','y0','x1','y1','label'] },
|
| ]
|
| },
|
| {
|
| id: 'validator', name: 'Validation Agent', role: 'Checks integrity & rebuilds if needed',
|
| icon: 'β
', color: '#3ecf8e', badge: 'badge-green',
|
| tasks: [],
|
| taskTypes: []
|
| },
|
| {
|
| id: 'exporter', name: 'Export Agent', role: 'Saves PDF, manifest & JSONL logs',
|
| icon: 'π¦', color: '#f87171', badge: 'badge-red',
|
| tasks: [],
|
| taskTypes: []
|
| },
|
| ];
|
|
|
|
|
|
|
|
|
| let pipeline = [];
|
| let dragAgentId = null;
|
| let selectedFile = null;
|
| let running = false;
|
| let startTime = null;
|
| let resultPath = null;
|
| let activeTab = 'pipeline';
|
| let teamConfig = null;
|
| let teamPlan = null;
|
|
|
|
|
|
|
|
|
| function init() {
|
| const pal = document.getElementById('agentPalette');
|
| AGENTS.forEach(a => {
|
| const el = document.createElement('div');
|
| el.className = 'palette-agent';
|
| el.draggable = true;
|
| el.dataset.agentId = a.id;
|
| el.innerHTML = `
|
| <div class="agent-avatar" style="background:${a.color}22;font-size:18px">${a.icon}</div>
|
| <div class="agent-info">
|
| <div class="name">${a.name}</div>
|
| <div class="role">${a.role}</div>
|
| </div>
|
| <span class="drag-hint">β Ώ</span>`;
|
| el.addEventListener('dragstart', e => { dragAgentId = a.id; e.dataTransfer.effectAllowed = 'copy'; });
|
| el.addEventListener('dragend', () => { dragAgentId = null; });
|
| pal.appendChild(el);
|
| });
|
| loadTeamConfig();
|
| }
|
|
|
|
|
|
|
|
|
| function handleFile(input) {
|
| const f = input.files[0];
|
| if (!f) return;
|
| selectedFile = f;
|
| document.getElementById('fileNameText').textContent = f.name;
|
| document.getElementById('fileName').classList.add('show');
|
| appendLog('info','File','Loaded: ' + f.name);
|
| }
|
|
|
|
|
|
|
|
|
| function onDragOver(e) {
|
| e.preventDefault();
|
| e.currentTarget.classList.add('drag-over');
|
| }
|
| function onDragLeave(e) { e.currentTarget.classList.remove('drag-over'); }
|
| function onDrop(e) {
|
| e.preventDefault();
|
| e.currentTarget.classList.remove('drag-over');
|
| if (!dragAgentId) return;
|
| addAgentToPipeline(dragAgentId);
|
| }
|
|
|
| function addAgentToPipeline(agentId) {
|
| const agentDef = AGENTS.find(a => a.id === agentId);
|
| if (!agentDef) return;
|
| const instanceId = agentId + '_' + Date.now();
|
| const entry = {
|
| agentId,
|
| instanceId,
|
| tasks: agentDef.tasks.map(t => ({...t, values:{}})),
|
| };
|
| pipeline.push(entry);
|
| renderPipeline();
|
| updateStatusBar();
|
| appendLog('info', agentDef.name, 'Added to pipeline');
|
| }
|
|
|
|
|
|
|
|
|
| function renderPipeline() {
|
| const zone = document.getElementById('pipelineZone');
|
| const empty = document.getElementById('pipelineEmpty');
|
| zone.innerHTML = '';
|
| zone.appendChild(empty);
|
| empty.style.display = pipeline.length === 0 ? 'flex' : 'none';
|
|
|
| pipeline.forEach((entry, idx) => {
|
| const agentDef = AGENTS.find(a => a.id === entry.agentId);
|
| if (!agentDef) return;
|
|
|
| if (idx > 0) {
|
| const conn = document.createElement('div');
|
| conn.className = 'connector';
|
| conn.innerHTML = 'β';
|
| zone.appendChild(conn);
|
| }
|
|
|
| const card = document.createElement('div');
|
| card.className = 'pipeline-card';
|
| card.id = 'card_' + entry.instanceId;
|
|
|
|
|
| const taskHTML = entry.tasks.map((task, ti) => renderTaskRow(entry, task, ti)).join('');
|
|
|
|
|
| const taskOpts = agentDef.taskTypes.map(tt =>
|
| `<option value="${tt.type}">${tt.label}</option>`).join('');
|
| const showAddTask = agentDef.taskTypes.length > 0;
|
|
|
| card.innerHTML = `
|
| <div class="card-header">
|
| <div class="card-num">${idx+1}</div>
|
| <div class="agent-avatar" style="background:${agentDef.color}22;width:28px;height:28px;font-size:15px;">${agentDef.icon}</div>
|
| <span class="card-title">${agentDef.name}</span>
|
| <span class="card-badge ${agentDef.badge}">${agentDef.role}</span>
|
| <div class="card-actions">
|
| ${idx>0 ? `<button class="card-btn" title="Move up" onclick="moveCard('${entry.instanceId}',-1)">β</button>` : ''}
|
| ${idx<pipeline.length-1 ? `<button class="card-btn" title="Move down" onclick="moveCard('${entry.instanceId}',1)">β</button>` : ''}
|
| <button class="card-btn del" title="Remove" onclick="removeCard('${entry.instanceId}')">β</button>
|
| </div>
|
| </div>
|
| <div class="task-list" id="tasks_${entry.instanceId}">${taskHTML}</div>
|
| ${showAddTask ? `
|
| <div style="display:flex;gap:6px;align-items:center">
|
| <select id="sel_${entry.instanceId}" style="flex:1;padding:5px 8px;border-radius:7px;background:var(--surf3);border:1px solid var(--border);color:var(--text);font-size:12px;outline:none">
|
| <option value="">β Add a task β</option>
|
| ${taskOpts}
|
| </select>
|
| <button class="add-task-btn" style="width:auto;padding:5px 12px" onclick="addTask('${entry.instanceId}')">+ Add</button>
|
| </div>` : ''}
|
| `;
|
| zone.appendChild(card);
|
| });
|
| updateStatusBar();
|
| }
|
|
|
| function renderTaskRow(entry, task, ti) {
|
| const fieldInputs = (task.fields || []).map(f =>
|
| `<input placeholder="${f}" title="${f}" value="${task.values?.[f]||''}"
|
| onchange="updateTaskField('${entry.instanceId}',${ti},'${f}',this.value)">`
|
| ).join('');
|
| return `
|
| <div class="task-row" id="task_${entry.instanceId}_${ti}">
|
| <span class="task-type">${task.type}</span>
|
| <div class="task-fields">${fieldInputs}</div>
|
| <span class="task-del" onclick="removeTask('${entry.instanceId}',${ti})">β</span>
|
| </div>`;
|
| }
|
|
|
| function addTask(instanceId) {
|
| const sel = document.getElementById('sel_' + instanceId);
|
| if (!sel || !sel.value) return;
|
| const entry = pipeline.find(e => e.instanceId === instanceId);
|
| const agentDef = AGENTS.find(a => a.id === entry.agentId);
|
| const tt = agentDef.taskTypes.find(t => t.type === sel.value);
|
| if (!tt) return;
|
| entry.tasks.push({ type: tt.type, fields: tt.fields, values: {} });
|
| renderPipeline();
|
| }
|
|
|
| function removeTask(instanceId, ti) {
|
| const entry = pipeline.find(e => e.instanceId === instanceId);
|
| entry.tasks.splice(ti, 1);
|
| renderPipeline();
|
| }
|
|
|
| function updateTaskField(instanceId, ti, field, val) {
|
| const entry = pipeline.find(e => e.instanceId === instanceId);
|
| if (!entry.tasks[ti].values) entry.tasks[ti].values = {};
|
| entry.tasks[ti].values[field] = val;
|
| }
|
|
|
| function removeCard(instanceId) {
|
| pipeline = pipeline.filter(e => e.instanceId !== instanceId);
|
| renderPipeline();
|
| }
|
|
|
| function moveCard(instanceId, dir) {
|
| const idx = pipeline.findIndex(e => e.instanceId === instanceId);
|
| const newIdx = idx + dir;
|
| if (newIdx < 0 || newIdx >= pipeline.length) return;
|
| [pipeline[idx], pipeline[newIdx]] = [pipeline[newIdx], pipeline[idx]];
|
| renderPipeline();
|
| }
|
|
|
| function clearPipeline() {
|
| pipeline = [];
|
| renderPipeline();
|
| clearLogs();
|
| document.getElementById('statusPill').className = 'result-pill pill-idle';
|
| document.getElementById('statusPill').textContent = 'Idle';
|
| document.getElementById('downloadBtn').classList.remove('show');
|
| appendLog('info','System','Pipeline cleared');
|
| }
|
|
|
| function clearTeamPlan() {
|
| teamPlan = null;
|
| document.getElementById('teamCommandInput').value = '';
|
| document.getElementById('teamCommandMeta').textContent = 'AI team is ready to reason about your PDF command.';
|
| document.getElementById('recommendedPipelineText').textContent = 'No recommendation yet.';
|
| document.getElementById('teamReasons').innerHTML = '<li>Enter a command to generate a team plan.</li>';
|
| renderTeamCards(teamConfig?.team || [], []);
|
| appendLog('info','AI Team','Team plan cleared');
|
| }
|
|
|
|
|
|
|
| async function runViaHTTP(config) {
|
| const r = await fetch('/run', {
|
| method: 'POST',
|
| headers: {'Content-Type':'application/json'},
|
| body: JSON.stringify(config)
|
| });
|
| return await r.json();
|
| }
|
|
|
| async function fetchTeamConfig() {
|
| const r = await fetch('/team-config');
|
| return await r.json();
|
| }
|
|
|
| async function buildTeamPlan(payload) {
|
| const r = await fetch('/team-plan', {
|
| method: 'POST',
|
| headers: {'Content-Type':'application/json'},
|
| body: JSON.stringify(payload)
|
| });
|
| return await r.json();
|
| }
|
|
|
|
|
|
|
|
|
| async function runPipeline() {
|
| if (running) return;
|
| if (pipeline.length === 0) { appendLog('warn','System','Add at least one agent first'); return; }
|
|
|
| running = true;
|
| startTime = Date.now();
|
| document.getElementById('runBtn').innerHTML = '<span style="animation:pulse 1s infinite;display:inline-block">β³</span> Runningβ¦';
|
| document.getElementById('statusPill').className = 'result-pill pill-run';
|
| document.getElementById('statusPill').textContent = 'Running';
|
| document.getElementById('statusDot').classList.add('busy');
|
| document.getElementById('statusText').textContent = 'Pipeline runningβ¦';
|
| document.getElementById('downloadBtn').classList.remove('show');
|
|
|
| const config = buildConfig();
|
| appendLog('info','Orchestrator','Job started id=' + config.job_id);
|
|
|
|
|
|
|
| let result = null;
|
| try {
|
| try { result = await runViaHTTP(config); } catch(e) { result = await simulatePipeline(config); }
|
| } catch(e) {
|
| result = await simulatePipeline(config);
|
| }
|
|
|
| finishRun(result);
|
| }
|
|
|
| function buildConfig() {
|
| const forceOcr = document.getElementById('ocrToggle').classList.contains('on');
|
| const lang = document.getElementById('ocrLang').value.trim() || 'eng';
|
| const outDir = document.getElementById('outputDir').value.trim() || (selectedFile ? '' : 'C:\\pdf-output');
|
| const ops = [];
|
| pipeline.forEach(entry => {
|
| entry.tasks.forEach(task => {
|
| if (!task.type) return;
|
| const op = { type: task.type };
|
| (task.fields||[]).forEach(f => {
|
| const v = task.values?.[f];
|
| if (v !== undefined && v !== '') {
|
| const num = Number(v);
|
| op[f] = isNaN(num) ? v : num;
|
| }
|
| });
|
| ops.push(op);
|
| });
|
| });
|
| return {
|
| job_id: Math.random().toString(36).slice(2,10),
|
| input_pdf: selectedFile ? selectedFile.name : '',
|
| output_dir: outDir,
|
| force_ocr: forceOcr,
|
| ocr_language: lang,
|
| pipeline: pipeline.map(e => e.agentId),
|
| operations: ops,
|
| };
|
| }
|
|
|
| async function simulatePipeline(config) {
|
| for (let i = 0; i < pipeline.length; i++) {
|
| const entry = pipeline[i];
|
| const agentDef = AGENTS.find(a => a.id === entry.agentId);
|
| const card = document.getElementById('card_' + entry.instanceId);
|
| if (card) card.classList.add('running');
|
| appendLog('info', agentDef.name, 'Startingβ¦');
|
| await sleep(600 + Math.random()*600);
|
| if (card) { card.classList.remove('running'); card.classList.add('done'); }
|
| const numEl = card?.querySelector('.card-num');
|
| if (numEl) numEl.textContent = 'β';
|
| appendLog('ok', agentDef.name, 'Completed β');
|
| }
|
| return {
|
| success: true,
|
| job_id: config.job_id,
|
| final_pdf: (config.output_dir || 'output') + '\\result-final.pdf',
|
| page_count: 4,
|
| };
|
| }
|
|
|
| function finishRun(result) {
|
| running = false;
|
| const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
| document.getElementById('runBtn').innerHTML = '<span>βΆ</span> Run Pipeline';
|
| document.getElementById('statusDot').classList.remove('busy');
|
|
|
| if (result.success) {
|
| document.getElementById('statusPill').className = 'result-pill pill-ok';
|
| document.getElementById('statusPill').textContent = 'Done';
|
| document.getElementById('statusText').textContent = 'Pipeline complete β';
|
| document.getElementById('statPages').textContent = result.page_count || 'β';
|
| document.getElementById('statDuration').textContent = elapsed + 's';
|
| document.getElementById('statOutput').textContent = result.final_pdf || 'β';
|
| document.getElementById('downloadBtn').classList.add('show');
|
| resultPath = result.final_pdf;
|
| appendLog('ok','Orchestrator','Job finished in ' + elapsed + 's');
|
| } else {
|
| document.getElementById('statusPill').className = 'result-pill pill-err';
|
| document.getElementById('statusPill').textContent = 'Failed';
|
| document.getElementById('statusText').textContent = 'Pipeline failed';
|
| appendLog('error','Orchestrator','Job failed: ' + (result.error || 'unknown error'));
|
| }
|
| }
|
|
|
| function downloadResult() {
|
| if (!resultPath) return;
|
|
|
| if (window.pybridge && window.pybridge.reveal_file) {
|
| window.pybridge.reveal_file(resultPath);
|
| } else {
|
| appendLog('info','Export','File path: ' + resultPath);
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| function appendLog(level, stage, msg) {
|
| const body = document.getElementById('logBody');
|
| const now = new Date().toTimeString().slice(0,8);
|
| const el = document.createElement('div');
|
| el.className = 'log-entry ' + level;
|
| el.innerHTML = `<span class="log-time">${now}</span> <span class="log-stage">[${stage}]</span> ${msg}`;
|
| body.appendChild(el);
|
| body.scrollTop = body.scrollHeight;
|
| }
|
|
|
| function clearLogs() {
|
| document.getElementById('logBody').innerHTML = '';
|
| }
|
|
|
|
|
|
|
|
|
| function updateStatusBar() {
|
| document.getElementById('agentCountBar').textContent = pipeline.length + ' agent' + (pipeline.length !== 1 ? 's' : '') + ' in pipeline';
|
| }
|
|
|
| function switchTab(btn, tab) {
|
| activeTab = tab;
|
| document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
| btn.classList.add('active');
|
| document.getElementById('pipelineView').classList.toggle('hidden', tab !== 'pipeline' && tab !== 'config');
|
| document.getElementById('teamView').classList.toggle('hidden', tab !== 'team');
|
| const clearBtn = document.getElementById('clearBtn');
|
| if (tab === 'team') {
|
| clearBtn.textContent = 'Clear Team';
|
| clearBtn.onclick = clearTeamPlan;
|
| } else {
|
| clearBtn.textContent = 'Clear';
|
| clearBtn.onclick = clearPipeline;
|
| }
|
| }
|
|
|
| function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
|
| async function loadTeamConfig() {
|
| try {
|
| const res = await fetchTeamConfig();
|
| if (!res.success) return;
|
| teamConfig = res;
|
| renderTeamCards(res.team, []);
|
| } catch (error) {
|
| appendLog('warn','AI Team','Could not load team config: ' + error.message);
|
| }
|
| }
|
|
|
| function renderTeamCards(team, insights) {
|
| const grid = document.getElementById('teamGrid');
|
| const insightMap = Object.fromEntries((insights || []).map(i => [i.agent_key, i]));
|
| grid.innerHTML = '';
|
| (team || []).forEach(member => {
|
| const insight = insightMap[member.agent_key];
|
| const card = document.createElement('div');
|
| card.className = 'team-card';
|
| card.innerHTML = `
|
| <div class="team-card-header">
|
| <div class="team-avatar" style="background:${member.color}22;color:${member.color}">${member.icon}</div>
|
| <div style="min-width:0">
|
| <div class="team-card-title">${member.name}</div>
|
| <div class="team-card-sub">${member.title}</div>
|
| </div>
|
| </div>
|
| <div class="model-pill">requested=${member.requested_provider || member.provider} Β· active=${member.provider}</div>
|
| <div class="model-line">${member.model || 'β'}<br>${member.base_url || 'β'}</div>
|
| <div class="team-insight">
|
| <div><strong>Focus:</strong> ${insight?.focus || 'Waiting for a command...'}</div>
|
| <div><strong>Action:</strong> ${insight?.action || 'No action yet.'}</div>
|
| <div><strong>Handoff:</strong> ${insight?.handoff || 'No handoff yet.'}</div>
|
| </div>
|
| `;
|
| grid.appendChild(card);
|
| });
|
| }
|
|
|
| function renderRecommendation(recommendation) {
|
| document.getElementById('recommendedPipelineText').textContent =
|
| recommendation ? `${recommendation.pipeline_name} β ${recommendation.recommended_agents.join(' -> ')}` : 'No recommendation yet.';
|
|
|
| const reasons = document.getElementById('teamReasons');
|
| reasons.innerHTML = '';
|
| const items = recommendation?.reasons?.length ? recommendation.reasons : ['No reasoning available yet.'];
|
| items.forEach(reason => {
|
| const li = document.createElement('li');
|
| li.textContent = reason;
|
| reasons.appendChild(li);
|
| });
|
| }
|
|
|
| function buildCommandPayload() {
|
| return {
|
| brief: document.getElementById('teamCommandInput').value.trim(),
|
| input_pdf: selectedFile ? selectedFile.name : '',
|
| output_dir: document.getElementById('outputDir').value.trim() || '',
|
| };
|
| }
|
|
|
| async function routeCommand() {
|
| const payload = buildCommandPayload();
|
| if (!payload.brief) {
|
| appendLog('warn','AI Team','Kirjelda kΓ΅igepealt, mida PDF-iga teha tahad');
|
| return;
|
| }
|
|
|
| document.getElementById('teamCommandMeta').textContent = 'Thinking across the teamβ¦';
|
| appendLog('info','AI Team','Distributing command across the AI team');
|
|
|
| try {
|
| const res = await buildTeamPlan(payload);
|
| if (!res.success) {
|
| appendLog('error','AI Team','Team planning failed: ' + (res.error || 'unknown error'));
|
| document.getElementById('teamCommandMeta').textContent = 'Team planning failed.';
|
| return;
|
| }
|
| teamPlan = res;
|
| renderTeamCards(res.team, res.insights);
|
| renderRecommendation(res.recommendation);
|
| document.getElementById('teamCommandMeta').textContent =
|
| `Team plan ready. Recommended pipeline: ${res.recommendation.pipeline_name}`;
|
| appendLog('ok','AI Team','Team plan is ready');
|
| } catch (error) {
|
| appendLog('error','AI Team','Team planning failed: ' + error.message);
|
| document.getElementById('teamCommandMeta').textContent = 'Team planning failed.';
|
| }
|
| }
|
|
|
| function loadRecommendedPipeline() {
|
| const recommendation = teamPlan?.recommendation;
|
| if (!recommendation) {
|
| appendLog('warn','AI Team','No recommended pipeline available yet');
|
| return;
|
| }
|
| pipeline = [];
|
| recommendation.recommended_agents.forEach(agentId => addAgentToPipeline(agentId));
|
| switchTab(document.querySelectorAll('.tab-btn')[0], 'pipeline');
|
| appendLog('ok','AI Team','Loaded recommended pipeline: ' + recommendation.pipeline_name);
|
| }
|
|
|
|
|
|
|
|
|
| const drop = document.getElementById('fileDrop');
|
| drop.addEventListener('dragover', e => { e.preventDefault(); drop.classList.add('drag'); });
|
| drop.addEventListener('dragleave', () => drop.classList.remove('drag'));
|
| drop.addEventListener('drop', e => {
|
| e.preventDefault(); drop.classList.remove('drag');
|
| const f = e.dataTransfer.files[0];
|
| if (f && f.name.endsWith('.pdf')) {
|
| selectedFile = f;
|
| document.getElementById('fileNameText').textContent = f.name;
|
| document.getElementById('fileName').classList.add('show');
|
| appendLog('info','File','Dropped: ' + f.name);
|
| }
|
| });
|
|
|
|
|
|
|
|
|
| init();
|
| appendLog('info','System','Orchestrator ready β AI team enabled');
|
| appendLog('info','System','Drag agents from the left panel or switch to AI Team for command routing');
|
| </script>
|
| </body>
|
| </html>
|
|
|