Spaces:
No application file
No application file
| <html lang="id"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>AI Orchestration Platform</title> | |
| <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=Syne:wght@400;600;700&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="../style/style.css" /> | |
| </head> | |
| <body> | |
| <div id="app"> | |
| <!-- ββββββββ SIDEBAR ββββββββ --> | |
| <aside class="sidebar"> | |
| <div class="sidebar-header"> | |
| <div class="logo-mark">AI</div> | |
| <div class="logo-text">ORCH<span>.</span>STUDIO</div> | |
| </div> | |
| <!-- GitHub PAT Auth --> | |
| <div class="sidebar-section" style="padding-bottom: 4px;"> | |
| <div class="sidebar-label">GitHub Auth</div> | |
| <div class="auth-card" :class="{ authenticated: github_auth.is_authenticated }"> | |
| <div class="auth-header"> | |
| <!-- GitHub icon --> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="color: var(--text2)"> | |
| <path d="M12 2C6.477 2 2 6.477 2 12c0 4.418 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.009-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.155-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0 0 22 12c0-5.523-4.477-10-10-10z"/> | |
| </svg> | |
| <div> | |
| <div class="auth-title">GitHub Authentication</div> | |
| <div class="auth-sub">Personal Access Token</div> | |
| </div> | |
| </div> | |
| <!-- Not authenticated --> | |
| <div v-if="!github_auth.is_authenticated" class="auth-input-row"> | |
| <input | |
| class="auth-input" | |
| type="password" | |
| v-model="github_auth.pat_input" | |
| placeholder="ghp_xxxxxxxxxxxx" | |
| @keydown.enter="connectGithub" | |
| /> | |
| <button class="auth-connect-btn" @click="connectGithub" :disabled="!github_auth.pat_input.trim()"> | |
| Connect | |
| </button> | |
| </div> | |
| <!-- Authenticated --> | |
| <div v-else class="auth-user-row"> | |
| <div class="auth-dot"></div> | |
| <span class="auth-username">{{ github_auth.username }}</span> | |
| <button class="auth-revoke" @click="revokeGithub">Revoke</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Repo URL --> | |
| <div class="repo-section"> | |
| <label class="repo-label">Repository</label> | |
| <div class="repo-row"> | |
| <input | |
| class="repo-url-input" | |
| type="text" | |
| v-model="repo.url_input" | |
| placeholder="https://github.com/owner/repo" | |
| @keydown.enter="analyseRepo" | |
| /> | |
| <button | |
| class="repo-analyse-btn" | |
| @click="analyseRepo" | |
| :disabled="!repo.url_input.trim() || repo.is_analysing" | |
| > | |
| <svg v-if="!repo.is_analysing" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> | |
| <circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/> | |
| </svg> | |
| <svg v-else width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="animation: spin 1s linear infinite;"> | |
| <path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/> | |
| </svg> | |
| {{ repo.is_analysing ? 'Analysingβ¦' : 'Analyse' }} | |
| </button> | |
| </div> | |
| <!-- Repo info result --> | |
| <div v-if="repo.info" class="repo-info-box"> | |
| <div class="repo-info-name">{{ repo.info.full_name }}</div> | |
| <div class="repo-info-meta">{{ repo.info.language }} Β· β {{ repo.info.stars_count }} Β· {{ repo.info.open_issues_count }} issues</div> | |
| <span class="contributor-badge" :class="repo.info.is_contributor ? 'yes' : 'no'"> | |
| {{ repo.info.is_contributor ? 'β Contributor' : 'β Read-only' }} | |
| </span> | |
| </div> | |
| </div> | |
| <div class="divider" style="margin: 4px 16px;"></div> | |
| <!-- Active Agents --> | |
| <div class="sidebar-section"> | |
| <div class="sidebar-label">Active Agents</div> | |
| <div | |
| v-for="agent in agents" | |
| :key="agent.agent_id" | |
| class="agent-item" | |
| :class="{ active: selected_agent_id === agent.agent_id }" | |
| @click="selected_agent_id = agent.agent_id" | |
| > | |
| <div class="agent-dot" :style="{ background: agent.color }"></div> | |
| <div class="agent-info"> | |
| <div class="agent-name">{{ agent.agent_name }}</div> | |
| <div class="agent-role">{{ agent.agent_role }}</div> | |
| </div> | |
| <span class="agent-status" :class="'status-' + agent.status">{{ agent.status }}</span> | |
| </div> | |
| </div> | |
| <div class="divider" style="margin: 0 16px;"></div> | |
| <!-- Session Stats --> | |
| <div class="sidebar-section"> | |
| <div class="sidebar-label">Session Stats</div> | |
| <div class="stats-row"> | |
| <div class="stat-box"> | |
| <div class="stat-num" style="color: var(--accent)">{{ session_stats.total_tasks }}</div> | |
| <div class="stat-label">Tasks</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-num" style="color: var(--accent2)">{{ session_stats.success_rate }}%</div> | |
| <div class="stat-label">Success</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-num" style="color: var(--warn)">{{ session_stats.avg_latency_ms }}ms</div> | |
| <div class="stat-label">Latency</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="sidebar-bottom"> | |
| <button class="setting-btn"> | |
| <svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"> | |
| <circle cx="12" cy="12" r="3"/> | |
| <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/> | |
| </svg> | |
| Settings | |
| </button> | |
| <button class="setting-btn" style="color: var(--danger)"> | |
| <svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"> | |
| <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/> | |
| <polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/> | |
| </svg> | |
| Logout | |
| </button> | |
| </div> | |
| </aside> | |
| <!-- ββββββββ CHAT AREA ββββββββ --> | |
| <main class="chat-area"> | |
| <!-- Topbar --> | |
| <header class="chat-topbar"> | |
| <div class="topbar-left"> | |
| <div class="topbar-title">AI Orchestration Chat</div> | |
| <span class="pipeline-badge">{{ active_pipeline.pipeline_name }}</span> | |
| </div> | |
| <div class="topbar-right"> | |
| <button class="icon-btn" title="Clear chat" @click="clearChat"> | |
| <svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"> | |
| <polyline points="3 6 5 6 21 6"/> | |
| <path d="M19 6l-1 14H6L5 6"/> | |
| <path d="M10 11v6M14 11v6M9 6V4h6v2"/> | |
| </svg> | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Action Bar --> | |
| <div class="action-bar"> | |
| <!-- Download Reports --> | |
| <button class="action-btn green" @click="downloadReport" :disabled="messages.length === 0"> | |
| <svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> | |
| <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> | |
| <polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/> | |
| </svg> | |
| Download Report | |
| </button> | |
| <div class="action-sep"></div> | |
| <!-- View Commits β hanya muncul kalau ada commit_result di chat --> | |
| <template v-if="last_commit_result"> | |
| <a | |
| class="action-btn purple" | |
| :href="last_commit_result.commits_url" | |
| target="_blank" | |
| rel="noopener" | |
| > | |
| <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <circle cx="12" cy="12" r="3"/><line x1="12" y1="3" x2="12" y2="9"/><line x1="12" y1="15" x2="12" y2="21"/> | |
| <line x1="3" y1="12" x2="9" y2="12"/><line x1="15" y1="12" x2="21" y2="12"/> | |
| </svg> | |
| View Commits | |
| </a> | |
| <!-- Pull Request β hanya contributor --> | |
| <template v-if="repo.info && repo.info.is_contributor"> | |
| <a class="action-btn green" :href="last_commit_result.pr_url" target="_blank" rel="noopener"> | |
| <svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> | |
| <circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/> | |
| <path d="M6 9v6M15.5 6.5l-3 3.5 3 3.5M18 6v9"/> | |
| </svg> | |
| Open Pull Request | |
| </a> | |
| </template> | |
| <div class="action-sep"></div> | |
| </template> | |
| <!-- Non-contributor notice --> | |
| <div v-if="repo.info && !repo.info.is_contributor" class="no-contrib-notice"> | |
| <svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> | |
| <circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/> | |
| </svg> | |
| Read-only β commit & push disabled | |
| </div> | |
| </div> | |
| <!-- Messages --> | |
| <div class="chat-messages" ref="messageContainer"> | |
| <!-- Welcome --> | |
| <div v-if="messages.length === 0" class="welcome"> | |
| <div class="welcome-logo">⬑</div> | |
| <h1>AI Orchestration Studio</h1> | |
| <p>Masukkan link repo GitHub dan token PAT di sidebar, lalu klik Analyse untuk memulai agent.</p> | |
| <div class="suggestion-grid"> | |
| <div | |
| v-for="s in suggestions" :key="s.title" | |
| class="suggestion-card" | |
| @click="useSuggestion(s.prompt)" | |
| > | |
| <div class="sugg-title">{{ s.title }}</div> | |
| <div class="sugg-desc">{{ s.desc }}</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Message list --> | |
| <template v-else> | |
| <div | |
| v-for="msg in messages" :key="msg.message_id" | |
| class="msg-wrapper" | |
| :class="{ user: msg.sender_type === 'user' }" | |
| > | |
| <div | |
| class="msg-avatar" | |
| :class="avatarClass(msg)" | |
| :style="msg.sender_type === 'agent' ? { background: getAgentColor(msg.agent_id) } : {}" | |
| > | |
| {{ msg.sender_type === 'user' ? 'ME' : msg.sender_initials }} | |
| </div> | |
| <div class="msg-body"> | |
| <div class="msg-meta"> | |
| <span class="msg-sender">{{ msg.sender_name }}</span> | |
| <span class="msg-time">{{ msg.created_at }}</span> | |
| </div> | |
| <div v-if="msg.is_typing" class="typing-indicator"> | |
| <div class="typing-dot"></div> | |
| <div class="typing-dot"></div> | |
| <div class="typing-dot"></div> | |
| </div> | |
| <div | |
| v-else | |
| class="msg-bubble" | |
| :class="bubbleClass(msg)" | |
| :style="msg.sender_type === 'agent' ? agentBubbleStyle(msg.agent_id) : {}" | |
| > | |
| {{ msg.message_content }} | |
| <!-- Pipeline trace --> | |
| <div v-if="msg.pipeline_trace && msg.pipeline_trace.length" class="pipeline-trace"> | |
| <div class="trace-label">Pipeline Trace</div> | |
| <div class="trace-steps"> | |
| <div v-for="(step, idx) in msg.pipeline_trace" :key="idx" class="trace-step"> | |
| <div class="trace-dot" :style="{ background: step.step_color }"></div> | |
| <span class="trace-step-name">{{ step.step_agent }}</span> | |
| <span class="trace-arrow">β</span> | |
| <span style="color: var(--text2)">{{ step.step_output }}</span> | |
| <span class="trace-step-time">{{ step.duration_ms }}ms</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Commit result card --> | |
| <div v-if="msg.commit_result" class="commit-card"> | |
| <div class="commit-card-header"> | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" style="color: var(--accent2)"> | |
| <path d="M12 2C6.477 2 2 6.477 2 12c0 4.418 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.009-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.155-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0 0 22 12c0-5.523-4.477-10-10-10z"/> | |
| </svg> | |
| <span class="commit-card-title">Branch Created</span> | |
| </div> | |
| <div class="commit-branch-name">{{ msg.commit_result.branch_name }}</div> | |
| <div class="commit-desc">{{ msg.commit_result.commit_message }}</div> | |
| <div class="commit-actions"> | |
| <a class="commit-btn view-commits" :href="msg.commit_result.commits_url" target="_blank" rel="noopener"> | |
| <svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> | |
| <circle cx="12" cy="12" r="3"/><line x1="12" y1="3" x2="12" y2="9"/><line x1="12" y1="15" x2="12" y2="21"/> | |
| <line x1="3" y1="12" x2="9" y2="12"/><line x1="15" y1="12" x2="21" y2="12"/> | |
| </svg> | |
| View Commits | |
| </a> | |
| <a | |
| v-if="repo.info && repo.info.is_contributor" | |
| class="commit-btn view-pr" | |
| :href="msg.commit_result.pr_url" | |
| target="_blank" | |
| rel="noopener" | |
| > | |
| <svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> | |
| <circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/> | |
| <path d="M6 9v6M15.5 6.5l-3 3.5 3 3.5M18 6v9"/> | |
| </svg> | |
| Open PR | |
| </a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </template> | |
| </div> | |
| <!-- Input area --> | |
| <div class="chat-input-area"> | |
| <div class="pipeline-selector"> | |
| <button | |
| v-for="p in pipelines" :key="p.pipeline_id" | |
| class="pipeline-chip" | |
| :class="{ active: active_pipeline.pipeline_id === p.pipeline_id }" | |
| @click="active_pipeline = p" | |
| > | |
| {{ p.pipeline_name }} | |
| </button> | |
| </div> | |
| <div class="input-box"> | |
| <textarea | |
| v-model="input_text" | |
| placeholder="Chat nonaktif β agent bekerja otomatis saat repo di-Analyse" | |
| disabled | |
| rows="1" | |
| ></textarea> | |
| </div> | |
| <div class="input-hint">Mode otomatis Β· Analyse repo untuk memulai agent</div> | |
| </div> | |
| </main> | |
| <!-- ββββββββ RIGHT PANEL ββββββββ --> | |
| <aside class="right-panel"> | |
| <!-- System status --> | |
| <div class="panel-section"> | |
| <div class="system-status"> | |
| <div class="pulse-dot"></div> | |
| <span class="status-text">System Online</span> | |
| <span class="status-sub">v2.4.1</span> | |
| </div> | |
| </div> | |
| <!-- SSE / AI Response Logs --> | |
| <div class="panel-section"> | |
| <div class="panel-title"> | |
| AI Response Stream | |
| <span class="panel-title-badge">SSE</span> | |
| </div> | |
| <div class="sse-header"> | |
| <div class="sse-status-dot" :class="{ active: sse_stream.is_active }"></div> | |
| <span class="sse-status-label" :class="{ active: sse_stream.is_active }"> | |
| {{ sse_stream.is_active ? 'Streamingβ¦' : 'Idle' }} | |
| </span> | |
| <button class="sse-clear-btn" @click="sse_stream.entries = []">Clear</button> | |
| </div> | |
| <div class="sse-log-area" ref="sseLogArea"> | |
| <div v-if="!sse_stream.entries.length" class="sse-empty-msg"> | |
| Waiting for stream events⦠| |
| </div> | |
| <div v-for="(entry, idx) in sse_stream.entries" :key="idx" class="sse-entry"> | |
| <span class="sse-ts">{{ entry.ts }}</span> | |
| <span class="sse-event-tag" :class="entry.event_type">{{ entry.event_type }}</span> | |
| <span class="sse-data">{{ entry.data }}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Active tasks --> | |
| <div class="panel-section"> | |
| <div class="panel-title"> | |
| Active Tasks | |
| <span class="panel-title-badge">{{ active_tasks.length }}</span> | |
| </div> | |
| <div v-for="task in active_tasks" :key="task.task_id" class="task-card"> | |
| <div class="task-name">{{ task.task_name }}</div> | |
| <div class="task-agent">{{ task.assigned_agent }}</div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" :style="{ width: task.progress_percent + '%', background: task.task_color }"></div> | |
| </div> | |
| </div> | |
| <div v-if="!active_tasks.length" style="font-size: 12px; color: var(--text3); text-align: center; padding: 8px 0;"> | |
| No active tasks | |
| </div> | |
| </div> | |
| <!-- Token usage --> | |
| <div class="panel-section"> | |
| <div class="panel-title">Token Usage</div> | |
| <div v-for="usage in token_usage" :key="usage.model_name"> | |
| <div class="token-row"> | |
| <span class="token-label">{{ usage.model_name }}</span> | |
| <span class="token-val">{{ usage.tokens_used.toLocaleString() }}</span> | |
| </div> | |
| <div class="token-bar"> | |
| <div class="token-fill" :style="{ width: tokenBarWidth(usage) + '%', background: usage.bar_color }"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- System log --> | |
| <div class="panel-section"> | |
| <div class="panel-title">System Log</div> | |
| <div v-for="log in system_logs" :key="log.log_id" class="log-item"> | |
| <span class="log-time">{{ log.log_time }}</span> | |
| <span class="log-msg" :class="log.log_level">{{ log.log_message }}</span> | |
| </div> | |
| </div> | |
| </aside> | |
| <!-- ββββββββ MODAL ββββββββ --> | |
| <div v-if="modal.show" class="modal-overlay" @click.self="modal.show = false"> | |
| <div class="modal"> | |
| <div class="modal-header"> | |
| <div class="modal-title">{{ modal.title }}</div> | |
| <button class="modal-close" @click="modal.show = false"> | |
| <svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> | |
| <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/> | |
| </svg> | |
| </button> | |
| </div> | |
| <div class="modal-body" v-html="modal.body"></div> | |
| <div class="modal-footer"> | |
| <button class="modal-btn secondary" @click="modal.show = false">Close</button> | |
| <button v-if="modal.confirm_label" class="modal-btn" :class="modal.confirm_class" @click="modal.on_confirm"> | |
| {{ modal.confirm_label }} | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <style> | |
| @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } | |
| </style> | |
| <script src="../app.js"></script> | |
| </body> | |
| </html> |