Files
agent_jrxml/frontend/src/api/client.ts
T
panda bb6cc6e241 feat: add Java JRXML-to-PNG rendering pipeline with pixel-level SSIM comparison
- 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)
2026-05-23 15:09:55 +08:00

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
}
}
}
}
},
}