- 左栏实时渲染 Markdown,右栏实时显示源码 - 每个 SSE 事件触发 updateColumns() 同时更新两栏 - 加载状态精简为标题旁的小 spinner + 状态文字 - 移除独立的 #streaming 区域和原始 LLM 文本展示
This commit is contained in:
@@ -32,64 +32,29 @@
|
||||
<div id="load-error" class="hidden mt-3 p-3 bg-red-50 border border-red-200 rounded-md text-red-700 text-sm"></div>
|
||||
</div>
|
||||
|
||||
<!-- Output -->
|
||||
<!-- Output: two-column layout, updates in real-time during streaming -->
|
||||
<div id="output" class="bg-white rounded-lg shadow-md p-6 hidden">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-lg font-semibold">PR 描述</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<h2 class="text-lg font-semibold">PR 描述</h2>
|
||||
<div id="generating-indicator" class="hidden flex items-center gap-2">
|
||||
<div class="spinner inline-block"></div>
|
||||
<span id="streaming-status" class="text-xs text-gray-400"></span>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="copyMarkdown()" class="px-3 py-1 bg-gray-100 text-gray-700 rounded hover:bg-gray-200">
|
||||
复制 Markdown
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Streaming: show raw LLM output as it arrives -->
|
||||
<div id="streaming" class="hidden">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<div class="spinner inline-block"></div>
|
||||
<p class="text-gray-500 text-sm">正在生成 PR 描述...</p>
|
||||
<p id="streaming-status" class="text-xs text-gray-400"></p>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div class="text-xs text-gray-400 mb-1">渲染效果</div>
|
||||
<div id="pr-rendered" class="text-gray-700 prose text-sm p-4 border rounded-md bg-white min-h-[200px]"></div>
|
||||
</div>
|
||||
<div id="streaming-content" class="p-4 bg-gray-50 rounded-md max-h-96 overflow-y-auto">
|
||||
<pre id="streaming-text" class="whitespace-pre-wrap text-sm text-gray-700 font-mono"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Structured output (two-column after done) -->
|
||||
<div id="output-content">
|
||||
<div id="structured-fields">
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-500">标题</label>
|
||||
<p id="pr-title" class="text-lg font-semibold text-gray-900"></p>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-500">类型</label>
|
||||
<span id="pr-type" class="inline-block px-2 py-1 bg-blue-100 text-blue-800 rounded text-sm"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-500">摘要</label>
|
||||
<p id="pr-summary" class="text-gray-700"></p>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-500">影响</label>
|
||||
<p id="pr-impact" class="text-gray-700"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Two-column: rendered markdown (left) + raw markdown (right) -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-500 mb-2">详细说明</label>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div class="text-xs text-gray-400 mb-1">渲染效果</div>
|
||||
<div id="pr-details" class="text-gray-700 prose text-sm p-4 border rounded-md bg-white min-h-[120px]"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-gray-400 mb-1">Markdown 源码</div>
|
||||
<pre id="pr-markdown" class="whitespace-pre-wrap text-sm text-gray-800 font-mono p-4 border rounded-md bg-gray-50 min-h-[120px] overflow-auto"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-gray-400 mb-1">Markdown 源码</div>
|
||||
<pre id="pr-markdown" class="whitespace-pre-wrap text-sm text-gray-800 font-mono p-4 border rounded-md bg-gray-50 min-h-[200px] overflow-auto"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -210,85 +175,66 @@
|
||||
|
||||
const btn = document.getElementById('btn-generate');
|
||||
const output = document.getElementById('output');
|
||||
const streaming = document.getElementById('streaming');
|
||||
const outputContent = document.getElementById('output-content');
|
||||
const indicator = document.getElementById('generating-indicator');
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = '生成中...';
|
||||
btn.classList.add('opacity-70', 'cursor-not-allowed');
|
||||
output.classList.remove('hidden');
|
||||
streaming.classList.remove('hidden');
|
||||
document.getElementById('streaming-text').textContent = '';
|
||||
document.getElementById('streaming-status').textContent = '';
|
||||
indicator.classList.remove('hidden');
|
||||
|
||||
// Clear previous results
|
||||
document.getElementById('pr-title').textContent = '';
|
||||
document.getElementById('pr-type').textContent = '';
|
||||
document.getElementById('pr-summary').textContent = '';
|
||||
document.getElementById('pr-details').innerHTML = '';
|
||||
document.getElementById('pr-impact').textContent = '';
|
||||
document.getElementById('pr-rendered').innerHTML = '';
|
||||
document.getElementById('pr-markdown').textContent = '';
|
||||
markdownContent = '';
|
||||
|
||||
// Accumulate streaming content
|
||||
// Helper: update both columns from current fields
|
||||
const fields = { title: '', type: '', summary: '', details: '', impact: '' };
|
||||
let rawStreamText = '';
|
||||
function updateColumns() {
|
||||
markdownContent = buildMarkdown(fields);
|
||||
document.getElementById('pr-rendered').innerHTML = renderMarkdown(markdownContent);
|
||||
document.getElementById('pr-markdown').textContent = markdownContent;
|
||||
}
|
||||
|
||||
SSE.post(`/api/repos/${repoId}/generate`, {
|
||||
base: baseRef,
|
||||
head: headRef,
|
||||
}, {
|
||||
// Raw streaming content from LLM — show in real-time
|
||||
content(data) {
|
||||
const chunk = data.content || '';
|
||||
rawStreamText += chunk;
|
||||
document.getElementById('streaming-text').textContent = rawStreamText;
|
||||
// Auto-scroll to bottom
|
||||
const container = document.getElementById('streaming-content');
|
||||
container.scrollTop = container.scrollHeight;
|
||||
},
|
||||
title(data) {
|
||||
fields.title += data.content || '';
|
||||
document.getElementById('pr-title').textContent = fields.title;
|
||||
updateColumns();
|
||||
document.getElementById('streaming-status').textContent = '解析标题...';
|
||||
},
|
||||
type(data) {
|
||||
fields.type += data.content || '';
|
||||
document.getElementById('pr-type').textContent = fields.type;
|
||||
updateColumns();
|
||||
document.getElementById('streaming-status').textContent = '解析类型...';
|
||||
},
|
||||
summary(data) {
|
||||
fields.summary += data.content || '';
|
||||
document.getElementById('pr-summary').textContent = fields.summary;
|
||||
updateColumns();
|
||||
document.getElementById('streaming-status').textContent = '解析摘要...';
|
||||
},
|
||||
detail(data) {
|
||||
fields.details += data.content || '';
|
||||
document.getElementById('pr-details').innerHTML = renderMarkdown(fields.details);
|
||||
updateColumns();
|
||||
document.getElementById('streaming-status').textContent = '解析详细说明...';
|
||||
},
|
||||
impact(data) {
|
||||
fields.impact += data.content || '';
|
||||
document.getElementById('pr-impact').textContent = fields.impact;
|
||||
updateColumns();
|
||||
document.getElementById('streaming-status').textContent = '解析影响范围...';
|
||||
},
|
||||
markdown(data) {
|
||||
markdownContent += data.content || '';
|
||||
document.getElementById('pr-markdown').textContent = markdownContent;
|
||||
},
|
||||
done() {
|
||||
// Assemble full markdown from structured fields
|
||||
markdownContent = buildMarkdown(fields);
|
||||
document.getElementById('pr-markdown').textContent = markdownContent;
|
||||
// Hide streaming indicator; columns already visible
|
||||
streaming.classList.add('hidden');
|
||||
updateColumns();
|
||||
indicator.classList.add('hidden');
|
||||
btn.disabled = false;
|
||||
btn.textContent = '生成 PR 描述';
|
||||
btn.classList.remove('opacity-70', 'cursor-not-allowed');
|
||||
},
|
||||
error(data) {
|
||||
showToast('生成失败: ' + (data.message || '未知错误'), 'error');
|
||||
streaming.classList.add('hidden');
|
||||
indicator.classList.add('hidden');
|
||||
btn.disabled = false;
|
||||
btn.textContent = '生成 PR 描述';
|
||||
btn.classList.remove('opacity-70', 'cursor-not-allowed');
|
||||
|
||||
Reference in New Issue
Block a user