| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || ''; |
|
|
| function apiUrl(path) { |
| return BACKEND_URL ? `${BACKEND_URL}${path}` : path; |
| } |
|
|
| |
| const activeControllers = new Map(); |
|
|
| |
| |
| |
| |
| export function streamChat(sessionId, message, handlers = {}) { |
| const controller = new AbortController(); |
| activeControllers.set(sessionId, controller); |
|
|
| const run = async () => { |
| try { |
| const res = await fetch(apiUrl('/api/v2/chat/stream'), { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| session_id: sessionId, |
| message, |
| permission_mode: 'normal', |
| }), |
| signal: controller.signal, |
| }); |
|
|
| if (!res.ok || !res.body) { |
| handlers.onError?.({ error: `Server returned ${res.status}` }); |
| return; |
| } |
|
|
| const reader = res.body.getReader(); |
| const decoder = new TextDecoder(); |
| let buffer = ''; |
|
|
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
|
|
| buffer += decoder.decode(value, { stream: true }); |
| const parts = buffer.split('\n\n'); |
| buffer = parts.pop() || ''; |
|
|
| for (const part of parts) { |
| if (!part.startsWith('data: ')) continue; |
| let event; |
| try { |
| event = JSON.parse(part.slice(6)); |
| } catch { |
| continue; |
| } |
|
|
| switch (event.type) { |
| case 'text_delta': |
| handlers.onTextDelta?.(event.text); |
| break; |
| case 'tool_start': |
| handlers.onToolStart?.(event); |
| break; |
| case 'tool_result': |
| handlers.onToolResult?.(event); |
| break; |
| case 'approval_needed': |
| handlers.onApprovalNeeded?.(event); |
| break; |
| case 'plan_step': |
| handlers.onPlanStep?.(event); |
| break; |
| case 'terminal_output': |
| handlers.onTerminalOutput?.(event); |
| break; |
| case 'terminal_exit': |
| handlers.onTerminalExit?.(event); |
| break; |
| case 'test_result': |
| handlers.onTestResult?.(event); |
| break; |
| case 'diagnostics': |
| handlers.onDiagnostics?.(event); |
| break; |
| case 'status_change': |
| handlers.onStatusChange?.(event.status, event.message); |
| break; |
| case 'done': |
| handlers.onDone?.(event); |
| break; |
| case 'error': |
| handlers.onError?.(event); |
| break; |
| } |
| } |
| } |
| } catch (err) { |
| if (controller.signal.aborted) { |
| |
| return; |
| } |
| handlers.onError?.({ error: String(err) }); |
| } finally { |
| activeControllers.delete(sessionId); |
| } |
| }; |
|
|
| run(); |
|
|
| return () => { |
| controller.abort(); |
| activeControllers.delete(sessionId); |
| }; |
| } |
|
|
| |
| |
| |
| export function cancelStream(sessionId) { |
| const controller = activeControllers.get(sessionId); |
| if (controller) { |
| controller.abort(); |
| activeControllers.delete(sessionId); |
| } |
| } |
|
|
| |
| |
| |
| export async function respondToApproval(sessionId, requestId, approved, scope = 'once') { |
| try { |
| await fetch(apiUrl('/api/v2/approval/respond'), { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| session_id: sessionId, |
| request_id: requestId, |
| approved, |
| scope, |
| }), |
| }); |
| } catch (err) { |
| console.error('[GitPilot] Approval response failed:', err); |
| } |
| } |
|
|
| |
| |
| |
| |
| export async function checkV2Support() { |
| try { |
| const res = await fetch(apiUrl('/api/status')); |
| if (!res.ok) return false; |
| |
| |
| return true; |
| } catch { |
| return false; |
| } |
| } |
|
|