Initial commit
This commit is contained in:
106
frontend/src/services/api.ts
Normal file
106
frontend/src/services/api.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import type {
|
||||
ModelListResponse,
|
||||
StreamAnalyzeRequest,
|
||||
StreamAnalyzeResponse,
|
||||
Step1Result,
|
||||
CausalChain
|
||||
} from '../types';
|
||||
|
||||
// 自動使用當前瀏覽器的 hostname,支援遠端存取
|
||||
const API_BASE_URL = `http://${window.location.hostname}:8000/api`;
|
||||
|
||||
export interface SSECallbacks {
|
||||
onStep1Start?: () => void;
|
||||
onStep1Complete?: (result: Step1Result) => void;
|
||||
onChainStart?: (index: number, total: number) => void;
|
||||
onChainComplete?: (index: number, chain: CausalChain) => void;
|
||||
onChainError?: (index: number, error: string) => void;
|
||||
onDone?: (response: StreamAnalyzeResponse) => void;
|
||||
onError?: (error: string) => void;
|
||||
}
|
||||
|
||||
export async function analyzeAttributesStream(
|
||||
request: StreamAnalyzeRequest,
|
||||
callbacks: SSECallbacks
|
||||
): Promise<void> {
|
||||
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 'step1_start':
|
||||
callbacks.onStep1Start?.();
|
||||
break;
|
||||
case 'step1_complete':
|
||||
callbacks.onStep1Complete?.(eventData.result);
|
||||
break;
|
||||
case 'chain_start':
|
||||
callbacks.onChainStart?.(eventData.index, eventData.total);
|
||||
break;
|
||||
case 'chain_complete':
|
||||
callbacks.onChainComplete?.(eventData.index, eventData.chain);
|
||||
break;
|
||||
case 'chain_error':
|
||||
callbacks.onChainError?.(eventData.index, eventData.error);
|
||||
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<ModelListResponse> {
|
||||
const response = await fetch(`${API_BASE_URL}/models`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
Reference in New Issue
Block a user