服务端: - prompt 改为要求直接输出 Markdown,不再输出 JSON - GeneratePR 返回 string 而非 PRDescription 结构体 - 移除 JSON 解析、extractJSON、fixInlineCode 等逻辑 - ChatStream 的 content 事件直接携带 Markdown 片段流式传输 前端: - generate() 仅处理 content + done + error 三个事件 - content 事件累积 Markdown 文本,实时渲染到两栏 - 移除 parsePartialJSON、buildMarkdown 等中间层 - 代码量从 ~100 行减至 ~50 行
This commit is contained in:
@@ -184,85 +184,27 @@
|
||||
indicator.classList.remove('hidden');
|
||||
|
||||
// Clear previous results
|
||||
document.getElementById('pr-rendered').innerHTML = '<span class="animate-pulse text-gray-400">正在生成...</span>';
|
||||
document.getElementById('pr-rendered').innerHTML = '';
|
||||
document.getElementById('pr-markdown').textContent = '';
|
||||
markdownContent = '';
|
||||
|
||||
const fields = { title: '', type: '', summary: '', details: '', impact: '' };
|
||||
let rawText = '';
|
||||
let done = false;
|
||||
|
||||
function updateColumns() {
|
||||
markdownContent = buildMarkdown(fields);
|
||||
document.getElementById('pr-rendered').innerHTML = renderMarkdown(markdownContent);
|
||||
document.getElementById('pr-markdown').textContent = markdownContent;
|
||||
}
|
||||
|
||||
// Try to extract completed fields from partial JSON
|
||||
function parsePartialJSON(text) {
|
||||
const result = {};
|
||||
for (const key of ['title', 'type', 'summary', 'details', 'impact']) {
|
||||
// Match "key": "value" where value's closing quote exists
|
||||
const re = new RegExp('"' + key + '"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"');
|
||||
const m = text.match(re);
|
||||
if (m) result[key] = m[1].replace(/\\"/g, '"').replace(/\\n/g, '\n').replace(/\\\\/g, '\\');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// LLM outputs markdown directly — just accumulate and render
|
||||
SSE.post(`/api/repos/${repoId}/generate`, {
|
||||
base: baseRef,
|
||||
head: headRef,
|
||||
}, {
|
||||
content(data) {
|
||||
if (done) return;
|
||||
rawText += data.content || '';
|
||||
// Parse partial JSON to extract fields progressively
|
||||
const partial = parsePartialJSON(rawText);
|
||||
let changed = false;
|
||||
for (const key of ['title', 'type', 'summary', 'details', 'impact']) {
|
||||
if (partial[key] && partial[key] !== fields[key]) {
|
||||
fields[key] = partial[key];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) updateColumns();
|
||||
},
|
||||
title(data) {
|
||||
fields.title = data.content || fields.title;
|
||||
updateColumns();
|
||||
document.getElementById('streaming-status').textContent = '解析标题...';
|
||||
},
|
||||
type(data) {
|
||||
fields.type = data.content || fields.type;
|
||||
updateColumns();
|
||||
document.getElementById('streaming-status').textContent = '解析类型...';
|
||||
},
|
||||
summary(data) {
|
||||
fields.summary = data.content || fields.summary;
|
||||
updateColumns();
|
||||
document.getElementById('streaming-status').textContent = '解析摘要...';
|
||||
},
|
||||
detail(data) {
|
||||
fields.details = data.content || fields.details;
|
||||
updateColumns();
|
||||
document.getElementById('streaming-status').textContent = '解析详细说明...';
|
||||
},
|
||||
impact(data) {
|
||||
fields.impact = data.content || fields.impact;
|
||||
updateColumns();
|
||||
document.getElementById('streaming-status').textContent = '解析影响范围...';
|
||||
markdownContent += data.content || '';
|
||||
document.getElementById('pr-rendered').innerHTML = Markdown.render(markdownContent);
|
||||
document.getElementById('pr-markdown').textContent = markdownContent;
|
||||
},
|
||||
done() {
|
||||
done = true;
|
||||
updateColumns();
|
||||
indicator.classList.add('hidden');
|
||||
btn.disabled = false;
|
||||
btn.textContent = '生成 PR 描述';
|
||||
btn.classList.remove('opacity-70', 'cursor-not-allowed');
|
||||
},
|
||||
error(data) {
|
||||
done = true;
|
||||
showToast('生成失败: ' + (data.message || '未知错误'), 'error');
|
||||
indicator.classList.add('hidden');
|
||||
btn.disabled = false;
|
||||
@@ -272,21 +214,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Markdown rendering (delegates to shared Markdown.render)
|
||||
function renderMarkdown(text) {
|
||||
return Markdown.render(text);
|
||||
}
|
||||
|
||||
function buildMarkdown(f) {
|
||||
const parts = [];
|
||||
if (f.title) parts.push(`# ${f.title}`);
|
||||
if (f.type) parts.push(`**类型**: ${f.type}`);
|
||||
if (f.summary) parts.push(`## 摘要\n${f.summary}`);
|
||||
if (f.details) parts.push(`## 详细说明\n${f.details}`);
|
||||
if (f.impact) parts.push(`## 影响\n${f.impact}`);
|
||||
return parts.join('\n\n');
|
||||
}
|
||||
|
||||
function copyMarkdown() {
|
||||
const markdown = markdownContent || document.getElementById('pr-markdown').textContent;
|
||||
if (!markdown.trim()) {
|
||||
|
||||
Reference in New Issue
Block a user