import type { ModelListResponse, StreamAnalyzeRequest, Step1Result, Step0Result, CategoryDefinition, DynamicStep1Result, DAGStreamAnalyzeResponse, TransformationRequest, TransformationCategoryResult, ExpertTransformationRequest, ExpertTransformationCategoryResult, ExpertProfile } from '../types'; // 自動使用當前瀏覽器的 hostname,支援遠端存取 const API_BASE_URL = `http://${window.location.hostname}:8000/api`; export interface SSECallbacks { onStep0Start?: () => void; onStep0Complete?: (result: Step0Result) => void; onCategoriesResolved?: (categories: CategoryDefinition[]) => void; onStep1Start?: () => void; onStep1Complete?: (result: Step1Result | DynamicStep1Result) => void; onRelationshipsStart?: () => void; onRelationshipsComplete?: (count: number) => void; onDone?: (response: DAGStreamAnalyzeResponse) => void; onError?: (error: string) => void; } export async function analyzeAttributesStream( request: StreamAnalyzeRequest, callbacks: SSECallbacks ): Promise { const response = await fetch(`${API_BASE_URL}/analyze`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(request), }); if (!response.ok) { throw new Error(`API error: ${response.statusText}`); } const reader = response.body?.getReader(); if (!reader) { throw new Error('No response body'); } const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); // 解析 SSE 事件 const lines = buffer.split('\n\n'); buffer = lines.pop() || ''; // 保留未完成的部分 for (const chunk of lines) { if (!chunk.trim()) continue; const eventMatch = chunk.match(/event: (\w+)/); const dataMatch = chunk.match(/data: (.+)/s); if (eventMatch && dataMatch) { const eventType = eventMatch[1]; try { const eventData = JSON.parse(dataMatch[1]); switch (eventType) { case 'step0_start': callbacks.onStep0Start?.(); break; case 'step0_complete': callbacks.onStep0Complete?.(eventData.result); break; case 'categories_resolved': callbacks.onCategoriesResolved?.(eventData.categories); break; case 'step1_start': callbacks.onStep1Start?.(); break; case 'step1_complete': callbacks.onStep1Complete?.(eventData.result); break; case 'relationships_start': callbacks.onRelationshipsStart?.(); break; case 'relationships_complete': callbacks.onRelationshipsComplete?.(eventData.count); break; case 'done': callbacks.onDone?.(eventData); break; case 'error': callbacks.onError?.(eventData.error); break; } } catch (e) { console.error('Failed to parse SSE event:', e, chunk); } } } } } export async function getModels(): Promise { const response = await fetch(`${API_BASE_URL}/models`); if (!response.ok) { throw new Error(`API error: ${response.statusText}`); } return response.json(); } // ===== Transformation Agent API ===== export interface TransformationSSECallbacks { onKeywordStart?: () => void; onKeywordComplete?: (keywords: string[]) => void; onDescriptionStart?: () => void; onDescriptionComplete?: (count: number) => void; onDone?: (result: TransformationCategoryResult) => void; onError?: (error: string) => void; } export async function transformCategoryStream( request: TransformationRequest, callbacks: TransformationSSECallbacks ): Promise { const response = await fetch(`${API_BASE_URL}/transformation/category`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(request), }); if (!response.ok) { throw new Error(`API error: ${response.statusText}`); } const reader = response.body?.getReader(); if (!reader) { throw new Error('No response body'); } const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); // 解析 SSE 事件 const lines = buffer.split('\n\n'); buffer = lines.pop() || ''; for (const chunk of lines) { if (!chunk.trim()) continue; const eventMatch = chunk.match(/event: (\w+)/); const dataMatch = chunk.match(/data: (.+)/s); if (eventMatch && dataMatch) { const eventType = eventMatch[1]; try { const eventData = JSON.parse(dataMatch[1]); switch (eventType) { case 'keyword_start': callbacks.onKeywordStart?.(); break; case 'keyword_complete': callbacks.onKeywordComplete?.(eventData.keywords); break; case 'description_start': callbacks.onDescriptionStart?.(); break; case 'description_complete': callbacks.onDescriptionComplete?.(eventData.count); break; case 'done': callbacks.onDone?.(eventData.result); break; case 'error': callbacks.onError?.(eventData.error); break; } } catch (e) { console.error('Failed to parse SSE event:', e, chunk); } } } } } // ===== Expert Transformation Agent API ===== export interface ExpertTransformationSSECallbacks { onExpertStart?: () => void; onExpertComplete?: (experts: ExpertProfile[]) => void; onKeywordStart?: () => void; onKeywordProgress?: (data: { attribute: string; count: number }) => void; onKeywordComplete?: (totalKeywords: number) => void; onDescriptionStart?: () => void; onDescriptionComplete?: (count: number) => void; onDone?: (data: { result: ExpertTransformationCategoryResult; experts: ExpertProfile[] }) => void; onError?: (error: string) => void; } export async function expertTransformCategoryStream( request: ExpertTransformationRequest, callbacks: ExpertTransformationSSECallbacks ): Promise { const response = await fetch(`${API_BASE_URL}/expert-transformation/category`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(request), }); if (!response.ok) { throw new Error(`API error: ${response.statusText}`); } const reader = response.body?.getReader(); if (!reader) { throw new Error('No response body'); } const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); // 解析 SSE 事件 const lines = buffer.split('\n\n'); buffer = lines.pop() || ''; for (const chunk of lines) { if (!chunk.trim()) continue; const eventMatch = chunk.match(/event: (\w+)/); const dataMatch = chunk.match(/data: (.+)/s); if (eventMatch && dataMatch) { const eventType = eventMatch[1]; try { const eventData = JSON.parse(dataMatch[1]); switch (eventType) { case 'expert_start': callbacks.onExpertStart?.(); break; case 'expert_complete': callbacks.onExpertComplete?.(eventData.experts); break; case 'keyword_start': callbacks.onKeywordStart?.(); break; case 'keyword_progress': callbacks.onKeywordProgress?.(eventData); break; case 'keyword_complete': callbacks.onKeywordComplete?.(eventData.total_keywords); break; case 'description_start': callbacks.onDescriptionStart?.(); break; case 'description_complete': callbacks.onDescriptionComplete?.(eventData.count); break; case 'done': callbacks.onDone?.(eventData); break; case 'error': callbacks.onError?.(eventData.error); break; } } catch (e) { console.error('Failed to parse SSE event:', e, chunk); } } } } }