/* ════════════════════════════════════════ AI ORCHESTRATION PLATFORM — APP LOGIC ════════════════════════════════════════ */ const { createApp, ref, reactive, computed, nextTick, onMounted } = Vue; /* ════════════════════════════════════════ SEED / STATIC DATA ════════════════════════════════════════ */ const SEED_AGENTS = [ { agent_id: 'agt-001', agent_name: 'Planner', agent_role: 'Task decomposition', status: 'idle', color: '#6ee7b7' }, { agent_id: 'agt-002', agent_name: 'Researcher', agent_role: 'Web & data retrieval', status: 'busy', color: '#818cf8' }, { agent_id: 'agt-003', agent_name: 'Coder', agent_role: 'Code generation', status: 'idle', color: '#f472b6' }, { agent_id: 'agt-004', agent_name: 'Reviewer', agent_role: 'QA & validation', status: 'idle', color: '#fbbf24' }, ]; const SEED_PIPELINES = [ { pipeline_id: 'pip-001', pipeline_name: 'Auto Orchestrate', pipeline_description: 'Pilih agen secara otomatis' }, { pipeline_id: 'pip-002', pipeline_name: 'Research Mode', pipeline_description: 'Fokus riset & analisis' }, { pipeline_id: 'pip-003', pipeline_name: 'Code Generator', pipeline_description: 'Fokus pembuatan kode' }, { pipeline_id: 'pip-004', pipeline_name: 'Review Pipeline', pipeline_description: 'Review & QA saja' }, ]; const SEED_ACTIVE_TASKS = [ { task_id: 'tsk-001', task_name: 'Market analysis', assigned_agent: 'Researcher', progress_percent: 65, task_color: '#818cf8' }, { task_id: 'tsk-002', task_name: 'Code review', assigned_agent: 'Reviewer', progress_percent: 30, task_color: '#fbbf24' }, ]; const SEED_TOKEN_USAGE = [ { model_name: 'claude-3-opus', tokens_used: 48200, token_limit: 100000, bar_color: '#6ee7b7' }, { model_name: 'claude-3-haiku', tokens_used: 12500, token_limit: 100000, bar_color: '#818cf8' }, ]; const SEED_SYSTEM_LOGS = [ { log_id: 'log-001', log_time: '14:02', log_level: 'success', log_message: 'Orchestrator ready' }, { log_id: 'log-002', log_time: '14:03', log_level: '', log_message: 'Researcher agent connected' }, { log_id: 'log-003', log_time: '14:05', log_level: 'warn', log_message: 'High latency detected (>500ms)' }, ]; const SUGGESTIONS = [ { title: '🔍 Research Task', desc: 'Riset topik dan buat ringkasan', prompt: 'Riset perkembangan terbaru machine learning di 2024 dan buat ringkasan eksekutif' }, { title: '💻 Generate Code', desc: 'Buat kode dari deskripsi', prompt: 'Buat REST API sederhana menggunakan Node.js + Express untuk manajemen todo list' }, { title: '📋 Plan a Project', desc: 'Dekomposisi proyek jadi task', prompt: 'Buat rencana proyek untuk membangun platform e-commerce dari nol dalam 3 bulan' }, { title: '✅ Review & QA', desc: 'Review output dari agen lain', prompt: 'Review dan evaluasi kualitas kode Python berikut untuk deteksi anomali data sensor' }, ]; /** * Mock SSE event sequences — simulasi stream dari AI backend. * * CATATAN INTEGRASI (SSE): * Nanti ganti seluruh fungsi mockSSEStream() dengan: * * const es = new EventSource('/api/stream?task_id=...'); * es.addEventListener('message', (e) => pushSSEEntry('msg', e.data)); * es.addEventListener('tool', (e) => pushSSEEntry('tool', e.data)); * es.addEventListener('done', (e) => { pushSSEEntry('done', e.data); es.close(); }); * es.addEventListener('error', (e) => { pushSSEEntry('error', 'Stream error'); es.close(); }); */ const SSE_MOCK_SEQUENCES = [ [ { event_type: 'msg', data: 'Parsing task intent…', delay: 200 }, { event_type: 'msg', data: 'Selecting optimal agent…', delay: 400 }, { event_type: 'tool', data: 'repo_read: fetching README', delay: 600 }, { event_type: 'tool', data: 'repo_read: scanning src/', delay: 500 }, { event_type: 'msg', data: 'Analysing code structure…', delay: 700 }, { event_type: 'stream', data: 'Generating response', delay: 300 }, { event_type: 'done', data: 'Task completed (1.4s)', delay: 500 }, ], [ { event_type: 'msg', data: 'Decomposing task into steps…', delay: 300 }, { event_type: 'tool', data: 'web_search: query sent', delay: 500 }, { event_type: 'tool', data: 'web_search: 12 results found', delay: 400 }, { event_type: 'msg', data: 'Summarising findings…', delay: 800 }, { event_type: 'stream', data: 'Streaming answer tokens', delay: 600 }, { event_type: 'done', data: 'Task completed (2.1s)', delay: 400 }, ], [ { event_type: 'msg', data: 'Loading repository context…', delay: 400 }, { event_type: 'tool', data: 'git_diff: comparing changes', delay: 700 }, { event_type: 'tool', data: 'code_lint: running checks', delay: 600 }, { event_type: 'msg', data: 'Writing fix…', delay: 500 }, { event_type: 'tool', data: 'git_commit: staging files', delay: 400 }, { event_type: 'tool', data: 'git_push: pushing branch', delay: 500 }, { event_type: 'done', data: 'Branch pushed (2.8s)', delay: 300 }, ], ]; /* ════════════════════════════════════════ HELPERS ════════════════════════════════════════ */ const delay = (ms) => new Promise((r) => setTimeout(r, ms)); const rand = (min, max) => Math.floor(Math.random() * (max - min) + min); const pickRand = (arr) => arr[Math.floor(Math.random() * arr.length)]; const nowTime = () => new Date().toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' }); const nowTs = () => new Date().toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); const makeId = (p, n) => `${p}-${String(n).padStart(3, '0')}`; /** * Parse "https://github.com/owner/repo" → { owner, repo_name } * Returns null when invalid. */ function parseGithubUrl(url) { try { const u = new URL(url.trim()); if (u.hostname !== 'github.com') return null; const parts = u.pathname.replace(/^\//, '').replace(/\/$/, '').split('/'); if (parts.length < 2) return null; return { owner: parts[0], repo_name: parts[1] }; } catch { return null; } } /** * Build GitHub API headers from PAT. * CATATAN INTEGRASI: headers ini yang dikirim ke endpoint /api/github/* * atau langsung ke api.github.com. */ function githubHeaders(pat) { return { Authorization: `Bearer ${pat}`, Accept: 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28', }; } /* ════════════════════════════════════════ VUE APP ════════════════════════════════════════ */ createApp({ setup() { /* ── GitHub Auth state ── * Data contract: GitHubAuth * { * is_authenticated : boolean * pat_input : string (cleared after connect) * pat : string (stored in memory only, never localStorage) * username : string * avatar_url : string * } */ const github_auth = reactive({ is_authenticated: false, pat_input: '', pat: '', username: '', avatar_url: '', }); /* ── Repo state ── * Data contract: RepoState * { * url_input : string * is_analysing : boolean * info : RepoInfo | null * } * * RepoInfo: * { * full_name : string (e.g. "owner/repo") * language : string * stars_count : number * open_issues_count : number * default_branch : string * is_contributor : boolean (true if authed user can push) * } */ const repo = reactive({ url_input: '', is_analysing: false, info: null, }); /* ── SSE Stream state ── * CATATAN INTEGRASI: * is_active → true saat EventSource sedang terbuka * entries → array of { ts, event_type, data } * event_type → 'msg' | 'tool' | 'done' | 'error' | 'stream' */ const sse_stream = reactive({ is_active: false, entries: [], }); /* ── Commit result (last) ── * Data contract: CommitResult * { * branch_name : string * commit_sha : string * commit_message : string * commits_url : string (link ke tab commits branch) * pr_url : string (link buat PR) * } */ const last_commit_result = ref(null); /* ── Core state ── */ const agents = ref(SEED_AGENTS.map((a) => ({ ...a }))); const pipelines = ref(SEED_PIPELINES); const active_pipeline = ref(pipelines.value[0]); const selected_agent_id = ref('agt-001'); const messages = ref([]); const session_stats = reactive({ total_tasks: 0, success_rate: 98, avg_latency_ms: 312 }); const active_tasks = ref(SEED_ACTIVE_TASKS.map((t) => ({ ...t }))); const token_usage = ref(SEED_TOKEN_USAGE.map((u) => ({ ...u }))); const system_logs = ref(SEED_SYSTEM_LOGS.map((l) => ({ ...l }))); const suggestions = SUGGESTIONS; const input_text = ref(''); const is_loading = ref(false); const messageContainer = ref(null); const sseLogArea = ref(null); const textarea = ref(null); const modal = reactive({ show: false, title: '', body: '', confirm_label: '', confirm_class: 'primary', on_confirm: () => {}, }); let msg_counter = 0; let log_counter = SEED_SYSTEM_LOGS.length; /* ════════════════════════════════════════ GITHUB AUTH ════════════════════════════════════════ */ /** * connectGithub — verifikasi PAT dan ambil profil user. * * CATATAN INTEGRASI: * Ganti fetch di bawah dengan endpoint backend kamu, misalnya: * POST /api/auth/github { pat } → { username, avatar_url } * * Backend perlu forward ke: GET https://api.github.com/user * dengan header Authorization: Bearer * dan kembalikan { login, avatar_url } ke frontend. */ async function connectGithub() { const pat = github_auth.pat_input.trim(); if (!pat) return; addLog('', 'Verifying GitHub PAT…'); /* --- MOCK: simulasi verifikasi token --- */ await delay(800); const is_valid = pat.startsWith('ghp_') || pat.length > 20; if (!is_valid) { addLog('error', 'GitHub PAT invalid'); showModal('Authentication Failed', '

PAT tidak valid. Pastikan token dimulai dengan ghp_ dan memiliki scope repo.

'); return; } github_auth.pat = pat; github_auth.pat_input = ''; github_auth.is_authenticated = true; github_auth.username = 'dev_user'; // ← dari GET /user response: data.login github_auth.avatar_url = ''; // ← data.avatar_url addLog('success', `GitHub connected as ${github_auth.username}`); } function revokeGithub() { github_auth.is_authenticated = false; github_auth.pat = ''; github_auth.username = ''; repo.info = null; addLog('warn', 'GitHub session revoked'); } /* ════════════════════════════════════════ REPO ANALYSE ════════════════════════════════════════ */ /** * analyseRepo — fetch metadata repo dan cek contributor status. * * CATATAN INTEGRASI (dua endpoint GitHub API): * * 1. GET https://api.github.com/repos/{owner}/{repo} * → ambil full_name, language, stargazers_count, open_issues_count, default_branch * * 2. GET https://api.github.com/repos/{owner}/{repo}/collaborators/{username} * → 204 = contributor, 404 = bukan contributor * (endpoint ini butuh PAT dengan scope repo) * * Kalau user belum auth, is_contributor selalu false. */ async function analyseRepo() { const url_str = repo.url_input.trim(); const parsed = parseGithubUrl(url_str); if (!parsed) { showModal('URL Tidak Valid', '

Format URL harus: https://github.com/owner/repo

'); return; } repo.is_analysing = true; addLog('', `Analysing ${parsed.owner}/${parsed.repo_name}…`); /* --- MOCK: simulasi API response --- */ await delay(1200); const is_authed = github_auth.is_authenticated; const is_contributor = is_authed && Math.random() > 0.4; // simulasi: 60% kemungkinan contributor repo.info = { full_name: `${parsed.owner}/${parsed.repo_name}`, language: pickRand(['TypeScript', 'Python', 'Go', 'Rust', 'JavaScript']), stars_count: rand(10, 4800), open_issues_count: rand(0, 42), default_branch: 'main', is_contributor, }; repo.is_analysing = false; addLog('success', `Repo loaded: ${repo.info.full_name} (contributor: ${is_contributor})`); /* Auto-trigger agent pipeline */ input_text.value = `Analisis repository ${repo.info.full_name} secara menyeluruh — ${repo.info.language}, ${repo.info.stars_count} stars, ${repo.info.open_issues_count} issues`; await sendMessage(); } /* ════════════════════════════════════════ SSE STREAM (MOCK) ════════════════════════════════════════ */ function pushSSEEntry(event_type, data) { sse_stream.entries.push({ ts: nowTs(), event_type, data }); nextTick(() => { if (sseLogArea.value) sseLogArea.value.scrollTop = sseLogArea.value.scrollHeight; }); } /** * mockSSEStream — simulasi event yang diterima dari backend via SSE. * * CATATAN INTEGRASI: * Hapus fungsi ini dan ganti `await mockSSEStream()` dengan: * * const es = new EventSource(`/api/tasks/${task_id}/stream`); * await new Promise((resolve, reject) => { * es.addEventListener('message', (e) => pushSSEEntry('msg', e.data)); * es.addEventListener('tool', (e) => pushSSEEntry('tool', e.data)); * es.addEventListener('stream', (e) => pushSSEEntry('stream', e.data)); * es.addEventListener('done', (e) => { pushSSEEntry('done', e.data); es.close(); resolve(); }); * es.addEventListener('error', (e) => { pushSSEEntry('error', 'Connection error'); es.close(); reject(); }); * }); */ async function mockSSEStream() { sse_stream.is_active = true; const seq = pickRand(SSE_MOCK_SEQUENCES); for (const event of seq) { await delay(event.delay); pushSSEEntry(event.event_type, event.data); } sse_stream.is_active = false; } /* ════════════════════════════════════════ DOWNLOAD REPORT ════════════════════════════════════════ */ /** * downloadReport — export session ke file .txt / .md. * * CATATAN INTEGRASI: * Kalau backend sudah punya endpoint laporan, ganti dengan: * window.open(`/api/sessions/${session_id}/report?format=pdf`, '_blank'); * * Untuk sekarang, kita generate langsung di browser dari state. */ function downloadReport() { const lines = [ '# AI Orchestration — Session Report', `Generated : ${new Date().toLocaleString('id-ID')}`, `Pipeline : ${active_pipeline.value.pipeline_name}`, `Repo : ${repo.info ? repo.info.full_name : '(none)'}`, `Total tasks: ${session_stats.total_tasks}`, `Success : ${session_stats.success_rate}%`, '', '---', '', '## Conversation Log', '', ]; messages.value.forEach((msg) => { if (msg.is_typing) return; lines.push(`### [${msg.created_at}] ${msg.sender_name} (${msg.sender_type})`); lines.push(msg.message_content || ''); if (msg.pipeline_trace && msg.pipeline_trace.length) { lines.push(''); lines.push('**Pipeline Trace:**'); msg.pipeline_trace.forEach((s) => lines.push(` • ${s.step_agent} → ${s.step_output} (${s.duration_ms}ms)`)); } if (msg.commit_result) { lines.push(''); lines.push(`**Branch Created:** ${msg.commit_result.branch_name}`); lines.push(`**Commit:** ${msg.commit_result.commit_message}`); lines.push(`**Commits URL:** ${msg.commit_result.commits_url}`); } lines.push(''); }); if (sse_stream.entries.length) { lines.push('---', '', '## SSE Stream Log', ''); sse_stream.entries.forEach((e) => lines.push(`[${e.ts}] [${e.event_type}] ${e.data}`)); } const blob = new Blob([lines.join('\n')], { type: 'text/markdown;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `orch-report-${Date.now()}.md`; a.click(); URL.revokeObjectURL(url); addLog('success', 'Report downloaded'); } /* ════════════════════════════════════════ MODAL HELPER ════════════════════════════════════════ */ function showModal(title, body, opts = {}) { modal.title = title; modal.body = body; modal.confirm_label = opts.confirm_label || ''; modal.confirm_class = opts.confirm_class || 'primary'; modal.on_confirm = opts.on_confirm || (() => { modal.show = false; }); modal.show = true; } /* ════════════════════════════════════════ CORE UTILITIES ════════════════════════════════════════ */ function getAgentColor(agent_id) { const a = agents.value.find((a) => a.agent_id === agent_id); return a ? a.color : '#888'; } function setAgentStatus(agent_id, status) { const a = agents.value.find((a) => a.agent_id === agent_id); if (a) a.status = status; } function addLog(level, message) { system_logs.value.unshift({ log_id: makeId('log', ++log_counter), log_time: nowTime(), log_level: level, log_message: message, }); if (system_logs.value.length > 30) system_logs.value.pop(); } async function scrollToBottom() { await nextTick(); if (messageContainer.value) messageContainer.value.scrollTop = messageContainer.value.scrollHeight; } function autoResize(e) { const el = e.target; el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 160) + 'px'; } async function useSuggestion(prompt) { input_text.value = prompt; await sendMessage(); } function clearChat() { messages.value = []; last_commit_result.value = null; session_stats.total_tasks = 0; } /* ── Template class/style helpers ── */ function avatarClass(msg) { if (msg.sender_type === 'user') return 'avatar-user'; if (msg.sender_type === 'orchestrator') return 'avatar-orchestrator'; return 'avatar-agent'; } function bubbleClass(msg) { return { 'bubble-user': msg.sender_type === 'user', 'bubble-system': msg.sender_type === 'orchestrator', 'bubble-agent': msg.sender_type === 'agent', }; } function agentBubbleStyle(agent_id) { const color = getAgentColor(agent_id); return { borderColor: color + '55', background: color + '0d' }; } function tokenBarWidth(usage) { return Math.min((usage.tokens_used / usage.token_limit) * 100, 100); } /* ── Message builders ── */ function buildUserMsg(text) { return { message_id: makeId('msg', ++msg_counter), session_id: 'ses-001', sender_type: 'user', sender_name: 'You', sender_initials: 'ME', agent_id: null, message_content: text, pipeline_trace: [], commit_result: null, is_typing: false, created_at: nowTime(), }; } function buildTypingMsg(sender_type, sender_name, sender_initials, agent_id = null) { return { message_id: makeId('msg', ++msg_counter), sender_type, sender_name, sender_initials, agent_id, message_content: '', pipeline_trace: [], commit_result: null, is_typing: true, created_at: nowTime(), }; } /* ════════════════════════════════════════ SEND MESSAGE — MAIN FLOW ════════════════════════════════════════ */ /** * sendMessage — mengirim pesan ke orchestrator. * * CATATAN INTEGRASI: * Ganti blok "simulate agent work" dengan: * const task = await fetch('/api/tasks', { * method: 'POST', * headers: { 'Content-Type': 'application/json' }, * body: JSON.stringify({ * session_id : 'ses-001', * pipeline_id : active_pipeline.value.pipeline_id, * message_content: text, * repo_url : repo.url_input, * github_pat : github_auth.pat, // jangan log/tampilkan ini * }) * }).then(r => r.json()); * * Kemudian buka SSE: * const es = new EventSource(`/api/tasks/${task.task_id}/stream`); * …(lihat mockSSEStream untuk event handling) * * Respons akhir agent dikirim via event 'done' atau endpoint terpisah: * GET /api/tasks/{task_id}/result */ async function sendMessage() { const text = input_text.value.trim(); if (!text || is_loading.value) return; /* 1. User message */ messages.value.push(buildUserMsg(text)); input_text.value = ''; if (textarea.value) textarea.value.style.height = 'auto'; session_stats.total_tasks++; await scrollToBottom(); /* 2. Orchestrator typing */ is_loading.value = true; const orch_typing = buildTypingMsg('orchestrator', 'Orchestrator', '⬡'); messages.value.push(orch_typing); addLog('', `Task dispatched → ${active_pipeline.value.pipeline_name}`); await scrollToBottom(); /* 3. SSE stream (mock) — jalan paralel */ const stream_promise = mockSSEStream(); await delay(rand(900, 1500)); /* 4. Pick agent */ const picked_agent = pickRand(agents.value); setAgentStatus(picked_agent.agent_id, 'busy'); /* 5. Replace orchestrator typing → response */ const orch_idx = messages.value.indexOf(orch_typing); messages.value[orch_idx] = { ...orch_typing, is_typing: false, message_content: `Menerima tugas dari pipeline "${active_pipeline.value.pipeline_name}". Mendistribusikan ke ${picked_agent.agent_name}…`, pipeline_trace: [ { step_agent: 'Orchestrator', step_output: 'Parsed intent', step_color: '#6ee7b7', duration_ms: rand(40, 140) }, { step_agent: picked_agent.agent_name, step_output: 'Assigned', step_color: picked_agent.color, duration_ms: rand(10, 60) }, ], created_at: nowTime(), }; await scrollToBottom(); await delay(rand(700, 1500)); /* 6. Agent typing */ const agent_typing = buildTypingMsg( 'agent', picked_agent.agent_name, picked_agent.agent_name.slice(0, 2).toUpperCase(), picked_agent.agent_id, ); messages.value.push(agent_typing); addLog('', `${picked_agent.agent_name} processing…`); await scrollToBottom(); await delay(rand(1200, 2200)); /* 7. Determine if this is a code task → generate commit result */ const is_code_task = active_pipeline.value.pipeline_id === 'pip-003' || text.toLowerCase().includes('fix') || text.toLowerCase().includes('refactor') || text.toLowerCase().includes('code') || text.toLowerCase().includes('bug'); const is_contributor = repo.info && repo.info.is_contributor; let commit_result = null; if (is_code_task && github_auth.is_authenticated) { if (is_contributor) { /* Contributor: bisa push branch baru */ const branch_name = `ai-fix/${Date.now()}`; const parsed = repo.info ? parseGithubUrl(repo.url_input) : null; const repo_base = parsed ? `https://github.com/${parsed.owner}/${parsed.repo_name}` : 'https://github.com/owner/repo'; commit_result = { branch_name, commit_sha: rand(100000, 999999).toString(16), commit_message: `fix: AI-generated improvements for task "${text.slice(0, 50)}"`, commits_url: `${repo_base}/commits/${branch_name}`, pr_url: `${repo_base}/compare/${branch_name}?expand=1`, }; last_commit_result.value = commit_result; addLog('success', `Branch pushed: ${branch_name}`); } else { /* Bukan contributor: tidak bisa push */ addLog('warn', 'Cannot push: user is not a contributor'); } } /* 8. Agent response texts */ const AGENT_RESPONSES = [ () => `Saya telah menganalisis permintaan "${text.slice(0, 60)}…"\n\nHasil menunjukkan 3 sub-task utama. Rencana eksekusi sudah disiapkan dengan estimasi waktu ~4 menit.`, () => `Permintaan diterima. Ditemukan referensi dari 12 sumber terpercaya. Data menunjukkan tren positif 6 bulan terakhir dengan growth rate 23%.`, () => is_contributor ? `Kode telah di-generate dan diperbaiki. Branch baru sudah di-push ke repository. Silahkan review pull request untuk merge ke main.` : `Kode telah dianalisis dan perbaikan diidentifikasi. Karena kamu bukan contributor, patch tidak dapat di-push otomatis. Salin output di bawah untuk apply manual.`, () => `Review selesai. Ditemukan 2 improvement: (1) optimasi query endpoint utama, (2) tambahkan error handling untuk edge case. Tidak ada critical bug. ✓`, ]; const resp_fn = pickRand(AGENT_RESPONSES); /* 9. Replace agent typing → final response */ const agent_idx = messages.value.indexOf(agent_typing); messages.value[agent_idx] = { ...agent_typing, is_typing: false, message_content: resp_fn(), commit_result, created_at: nowTime(), }; setAgentStatus(picked_agent.agent_id, 'idle'); token_usage.value[0].tokens_used += rand(200, 1000); addLog('success', `${picked_agent.agent_name} completed task`); is_loading.value = false; await Promise.all([scrollToBottom(), stream_promise]); } /* ════════════════════════════════════════ BACKGROUND SIMULATION ════════════════════════════════════════ */ onMounted(() => { setInterval(() => { active_tasks.value.forEach((t) => { t.progress_percent = Math.min(100, t.progress_percent + rand(0, 4)); if (t.progress_percent >= 100) t.progress_percent = 0; }); }, 2000); }); /* ── Expose to template ── */ return { /* state */ github_auth, repo, sse_stream, last_commit_result, agents, pipelines, active_pipeline, selected_agent_id, messages, session_stats, active_tasks, token_usage, system_logs, suggestions, input_text, is_loading, modal, /* refs */ messageContainer, sseLogArea, textarea, /* methods */ connectGithub, revokeGithub, analyseRepo, sendMessage, clearChat, useSuggestion, autoResize, downloadReport, getAgentColor, avatarClass, bubbleClass, agentBubbleStyle, tokenBarWidth, }; }, }).mount('#app');