bb6cc6e241
- lib/java/: Java renderer (JrxmlRenderer) using JasperReports 6.21.0 - JrxmlDebug for diagnostics, JrxmlGen for format reference - download_jars.sh for one-time dependency setup - agent/nodes.py: _render_jrxml_to_png() and _compute_pixel_similarity() - Pixel comparison integrates into validate node (SSIM < 0.4 fails) - Pixel fidelity context injected into correct_jrxml for targeted fixes - tests/test_pixel_comparison.py: 15 unit tests (render, SSIM, integration) - .gitignore: exclude lib/java/*.jar, lib/java/*.class, tmp/ - CLAUDE.md: v11 changelog documenting the rendering pipeline - All non-LLM tests pass (97/97)
148 lines
3.9 KiB
TypeScript
148 lines
3.9 KiB
TypeScript
/** JSON fetch wrapper + SSE streaming helper. */
|
|
|
|
const BASE = '/api'
|
|
|
|
export interface SessionSummary {
|
|
session_id: string
|
|
session_name: string
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
export interface SessionData extends SessionSummary {
|
|
agent_state: Record<string, any>
|
|
}
|
|
|
|
export interface FileInfo {
|
|
file_id: string
|
|
filename: string
|
|
content_type: string
|
|
size: number
|
|
}
|
|
|
|
export interface AgentCompleteData {
|
|
reason: string
|
|
intent: string
|
|
status: string
|
|
jrxml_length: number
|
|
error_msg: string
|
|
natural_explanation: string
|
|
consult_answer: string
|
|
retry_count: number
|
|
total_duration_ms: number
|
|
ocr_extraction_result: any
|
|
}
|
|
|
|
export interface SSECallbacks {
|
|
onNodeStart?: (data: { node: string; label: string; step_index: number }) => void
|
|
onNodeComplete?: (data: { node: string; label: string; detail: string }) => void
|
|
onStreamToken?: (data: { text: string; type: string }) => void
|
|
onAgentComplete?: (data: AgentCompleteData) => void
|
|
onAgentError?: (data: { error: string; traceback?: string }) => void
|
|
}
|
|
|
|
export const api = {
|
|
// ── Health ──
|
|
async health() {
|
|
const r = await fetch(`${BASE}/health`)
|
|
return r.json()
|
|
},
|
|
|
|
async config() {
|
|
const r = await fetch(`${BASE}/config`)
|
|
return r.json()
|
|
},
|
|
|
|
// ── Sessions ──
|
|
async createSession(): Promise<SessionSummary> {
|
|
const r = await fetch(`${BASE}/sessions`, { method: 'POST' })
|
|
return r.json()
|
|
},
|
|
|
|
async listSessions(): Promise<SessionSummary[]> {
|
|
const r = await fetch(`${BASE}/sessions`)
|
|
const data = await r.json()
|
|
return data.sessions
|
|
},
|
|
|
|
async getSession(sessionId: string): Promise<SessionData> {
|
|
const r = await fetch(`${BASE}/sessions/${sessionId}`)
|
|
if (!r.ok) throw new Error('会话不存在')
|
|
return r.json()
|
|
},
|
|
|
|
async deleteSession(sessionId: string): Promise<void> {
|
|
await fetch(`${BASE}/sessions/${sessionId}`, { method: 'DELETE' })
|
|
},
|
|
|
|
// ── Upload ──
|
|
async uploadFile(file: File, sessionId: string): Promise<FileInfo> {
|
|
const form = new FormData()
|
|
form.append('file', file)
|
|
const r = await fetch(`${BASE}/upload?session_id=${encodeURIComponent(sessionId)}`, {
|
|
method: 'POST',
|
|
body: form,
|
|
})
|
|
if (!r.ok) throw new Error('上传失败')
|
|
return r.json()
|
|
},
|
|
|
|
// ── Chat (SSE) ──
|
|
async chat(
|
|
sessionId: string,
|
|
text: string,
|
|
fileIds: string[],
|
|
callbacks: SSECallbacks,
|
|
): Promise<void> {
|
|
const r = await fetch(`${BASE}/sessions/${sessionId}/chat`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ text, file_ids: fileIds }),
|
|
})
|
|
|
|
if (!r.ok) {
|
|
const err = await r.json().catch(() => ({ detail: r.statusText }))
|
|
throw new Error(err.detail || '请求失败')
|
|
}
|
|
|
|
const reader = r.body!.getReader()
|
|
const decoder = new TextDecoder()
|
|
let buffer = ''
|
|
let currentEvent = ''
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
|
|
buffer += decoder.decode(value, { stream: true })
|
|
const lines = buffer.split('\n')
|
|
buffer = lines.pop() || ''
|
|
|
|
for (const line of lines) {
|
|
if (line.startsWith('event: ')) {
|
|
currentEvent = line.slice(7).trim()
|
|
} else if (line.startsWith('data: ')) {
|
|
const payload = JSON.parse(line.slice(6))
|
|
switch (currentEvent) {
|
|
case 'node_start':
|
|
callbacks.onNodeStart?.(payload)
|
|
break
|
|
case 'node_complete':
|
|
callbacks.onNodeComplete?.(payload)
|
|
break
|
|
case 'stream_token':
|
|
callbacks.onStreamToken?.(payload)
|
|
break
|
|
case 'agent_complete':
|
|
callbacks.onAgentComplete?.(payload)
|
|
break
|
|
case 'agent_error':
|
|
callbacks.onAgentError?.(payload)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}
|