codeGuard / view /index.html
aIkal1n's picture
initial interface
32ff0e3
Raw
History Blame Contribute Delete
20.9 kB
<!DOCTYPE html>
<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 &amp; 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>