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)
This commit is contained in:
2026-05-23 15:09:55 +08:00
parent 9de75d2f25
commit bb6cc6e241
16 changed files with 837 additions and 8 deletions
+7 -2
View File
@@ -70,6 +70,7 @@ async function handleSend(text: string, files: File[]) {
jrxml_length: data.jrxml_length,
error_msg: data.error_msg,
natural_explanation: data.natural_explanation,
consult_answer: data.consult_answer,
retry_count: data.retry_count,
total_duration_ms: data.total_duration_ms,
ocr_extraction_result: data.ocr_extraction_result,
@@ -88,8 +89,12 @@ async function handleSend(text: string, files: File[]) {
type: 'error',
})
} else if (data.intent === 'consult_question') {
if (streamContent) {
chat.addMessage({ role: 'assistant', content: streamContent, type: 'consult' })
// 咨询回答:优先用 streamContent,其次用 consult_answer
const answerText = streamContent || data.consult_answer || ''
if (answerText) {
chat.addMessage({ role: 'assistant', content: answerText, type: 'consult' })
} else {
chat.addMessage({ role: 'assistant', content: '咨询已完成,但未获取到回答内容。', type: 'error' })
}
} else {
if (streamContent) {
+1
View File
@@ -27,6 +27,7 @@ export interface AgentCompleteData {
jrxml_length: number
error_msg: string
natural_explanation: string
consult_answer: string
retry_count: number
total_duration_ms: number
ocr_extraction_result: any
+2 -1
View File
@@ -144,7 +144,7 @@ export const useChatStore = defineStore('chat', () => {
function finishStreaming(data?: {
intent?: string; status?: string; jrxml_length?: number
error_msg?: string; natural_explanation?: string; retry_count?: number
error_msg?: string; natural_explanation?: string; consult_answer?: string; retry_count?: number
total_duration_ms?: number; ocr_extraction_result?: any
}) {
streaming.value = false
@@ -164,6 +164,7 @@ export const useChatStore = defineStore('chat', () => {
jrxml_length: data.jrxml_length || 0,
error_msg: data.error_msg || '',
natural_explanation: data.natural_explanation || '',
consult_answer: data.consult_answer || '',
retry_count: data.retry_count || 0,
}
if (data.ocr_extraction_result) {