From 37aa9c0a33c1e543e6e32f75753cd822fb7167f0 Mon Sep 17 00:00:00 2001 From: wonder Date: Sat, 20 Jun 2026 22:15:00 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BB=93=E6=9E=84=E5=8C=96=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E5=AE=A1=E6=9F=A5=E6=80=BB=E7=BB=93=EF=BC=8C=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2=E5=8E=9F=E5=A7=8BJSON=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端发送结构化数据(score/overall/findings/recommendations)而非格式化文本 - 增加灵活JSON解析,兼容LLM返回recommendations为数组的情况 - 前端渲染带样式的审查总结:评分徽章(颜色分级)、分区展示 - 两个页面统一处理:repo页面的内联审查和review页面的完整审查 --- services/review.go | 58 ++++++++++++++++++++++++++++--------- templates/pages/repo.html | 35 +++++++++++++++++++++- templates/pages/review.html | 43 ++++++++++++++++++++++----- 3 files changed, 114 insertions(+), 22 deletions(-) diff --git a/services/review.go b/services/review.go index 8063075..b2c7cba 100644 --- a/services/review.go +++ b/services/review.go @@ -287,9 +287,9 @@ func GenerateReview(db *sql.DB, repoPath, base, head string, topN, concurrency i "score": 7, "overall": "总体评价(2-3 句话)", "findings": "按严重程度排序的主要发现汇总", - "recommendations": "改进建议优先级列表" + "recommendations": "改进建议,用换行分隔多条建议" } -请用中文回复。`, strings.Join(reviewParts, "\n\n")) +注意:所有字段必须是字符串类型,不要使用数组。请用中文回复。`, strings.Join(reviewParts, "\n\n")) messages := []goopenai.ChatCompletionMessage{ {Role: goopenai.ChatMessageRoleUser, Content: summaryPrompt}, @@ -299,20 +299,52 @@ func GenerateReview(db *sql.DB, repoPath, base, head string, topN, concurrency i if err == nil { jsonStr := extractJSON(summaryResponse) if json.Unmarshal([]byte(jsonStr), &summary) == nil { - summaryText := fmt.Sprintf("整体评分: %d/10\n\n%s", summary.Score, summary.Overall) - if summary.Findings != "" { - summaryText += "\n\n**主要发现:**\n" + summary.Findings - } - if summary.Recommendations != "" { - summaryText += "\n\n**改进建议:**\n" + summary.Recommendations - } if callback != nil { - callback("summary", map[string]interface{}{"content": summaryText}) + callback("summary", map[string]interface{}{ + "score": summary.Score, + "overall": summary.Overall, + "findings": summary.Findings, + "recommendations": summary.Recommendations, + }) } } else { - // If parsing fails, send raw response as summary - if callback != nil { - callback("summary", map[string]interface{}{"content": summaryResponse}) + // Try flexible parsing (handle recommendations as array) + var raw map[string]json.RawMessage + if json.Unmarshal([]byte(jsonStr), &raw) == nil { + var flexible struct { + Score int `json:"score"` + Overall string `json:"overall"` + Findings string `json:"findings"` + } + json.Unmarshal(raw["score"], &flexible.Score) + json.Unmarshal(raw["overall"], &flexible.Overall) + json.Unmarshal(raw["findings"], &flexible.Findings) + + recommendations := "" + if rec, ok := raw["recommendations"]; ok { + var arr []string + if json.Unmarshal(rec, &arr) == nil { + recommendations = strings.Join(arr, "\n") + } else { + var s string + json.Unmarshal(rec, &s) + recommendations = s + } + } + + if callback != nil { + callback("summary", map[string]interface{}{ + "score": flexible.Score, + "overall": flexible.Overall, + "findings": flexible.Findings, + "recommendations": recommendations, + }) + } + } else { + // If all parsing fails, send raw response as summary + if callback != nil { + callback("summary", map[string]interface{}{"content": summaryResponse}) + } } } } else { diff --git a/templates/pages/repo.html b/templates/pages/repo.html index 966440c..6d0339e 100644 --- a/templates/pages/repo.html +++ b/templates/pages/repo.html @@ -427,6 +427,34 @@ } } + // ── Utilities ──────────────────────────────────────────────── + function escapeHtml(text) { + if (!text) return ''; + return String(text) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + } + + function renderSummaryHtml(data) { + let html = '
'; + const score = data.score || 0; + const scoreColor = score >= 7 ? 'bg-green-100 text-green-800' : score >= 4 ? 'bg-yellow-100 text-yellow-800' : 'bg-red-100 text-red-800'; + html += `
整体评分${score}/10
`; + if (data.overall) { + html += `
总体评价

${escapeHtml(data.overall)}

`; + } + if (data.findings) { + html += `
主要发现

${escapeHtml(data.findings)}

`; + } + if (data.recommendations) { + html += `
改进建议

${escapeHtml(data.recommendations)}

`; + } + html += '
'; + return html; + } + // ── Inline AI Review ──────────────────────────────────────── let inlineReviewSSE = null; @@ -476,7 +504,12 @@ inlineSuggestions.push(data); }, summary(data) { - document.getElementById('review-summary-content').innerHTML = renderInlineMarkdown(data.content || ''); + const el = document.getElementById('review-summary-content'); + if (data.score !== undefined) { + el.innerHTML = renderSummaryHtml(data); + } else { + el.innerHTML = renderInlineMarkdown(data.content || ''); + } document.getElementById('review-progress-bar').style.width = '90%'; }, done() { diff --git a/templates/pages/review.html b/templates/pages/review.html index bdcf956..3165518 100644 --- a/templates/pages/review.html +++ b/templates/pages/review.html @@ -126,6 +126,34 @@ let fileReviews = {}; let currentAnalysisId = null; + // ── Utilities ──────────────────────────────────────────────── + function escapeHtml(text) { + if (!text) return ''; + return String(text) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + } + + function renderSummaryHtml(data) { + let html = '
'; + const score = data.score || 0; + const scoreColor = score >= 7 ? 'bg-green-100 text-green-800' : score >= 4 ? 'bg-yellow-100 text-yellow-800' : 'bg-red-100 text-red-800'; + html += `
整体评分${score}/10
`; + if (data.overall) { + html += `
总体评价

${escapeHtml(data.overall)}

`; + } + if (data.findings) { + html += `
主要发现

${escapeHtml(data.findings)}

`; + } + if (data.recommendations) { + html += `
改进建议

${escapeHtml(data.recommendations)}

`; + } + html += '
'; + return html; + } + // Load refs on page load async function loadRefs() { try { @@ -310,7 +338,12 @@ } }, summary(data) { - document.getElementById('summary-content').innerHTML = renderMarkdown(data.content || ''); + const el = document.getElementById('summary-content'); + if (data.score !== undefined) { + el.innerHTML = renderSummaryHtml(data); + } else { + el.innerHTML = renderMarkdown(data.content || ''); + } document.getElementById('progress-bar').style.width = '90%'; document.getElementById('progress-text').textContent = '生成总结...'; }, @@ -497,13 +530,7 @@ // Render summary const summary = result.summary || {}; - let summaryText = ''; - if (summary.score) summaryText += `整体评分: ${summary.score}/10\n\n`; - if (summary.overall) summaryText += summary.overall; - if (summary.findings) summaryText += '\n\n**主要发现:**\n' + summary.findings; - if (summary.recommendations) summaryText += '\n\n**改进建议:**\n' + summary.recommendations; - - document.getElementById('summary-content').innerHTML = renderMarkdown(summaryText); + document.getElementById('summary-content').innerHTML = renderSummaryHtml(summary); // Clear and rebuild card view document.getElementById('file-reviews').innerHTML = '';