backtrace-console 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app.js +3 -2
- package/bin/backtrace-server.js +101 -0
- package/bin/www +3 -0
- package/lib/backtrace/constants.js +4 -0
- package/lib/backtrace/query-download.js +117 -0
- package/lib/backtrace/query-session.js +229 -0
- package/lib/backtrace/query.js +11 -445
- package/lib/backtrace/repair.js +35 -0
- package/lib/backtrace/tool.js +34 -3
- package/lib/feishu.js +66 -0
- package/lib/scheduler.js +126 -0
- package/package.json +10 -4
- package/public/chat-components.css +569 -0
- package/public/chat-core.js +635 -0
- package/public/chat-layout.css +290 -0
- package/public/chat-render.js +308 -0
- package/public/chat-send.js +230 -0
- package/public/chat.html +69 -0
- package/public/{__inline_check__.js → index-page.js} +107 -54
- package/public/index.html +1 -505
- package/routes/backtrace-chat.js +389 -0
- package/routes/backtrace-files.js +88 -0
- package/routes/backtrace-fix-plan.js +53 -0
- package/routes/backtrace-run.js +128 -0
- package/routes/backtrace-shared.js +202 -0
- package/routes/backtrace.js +7 -861
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
function sendMessage() {
|
|
2
|
+
const text = chatInput.value.trim();
|
|
3
|
+
if ((!text && pendingImages.length === 0) || !currentSessionId) return;
|
|
4
|
+
|
|
5
|
+
const turnContainer = document.createElement('div');
|
|
6
|
+
turnContainer.className = 'turn-container';
|
|
7
|
+
const userMsg = createMessageElement(text, true);
|
|
8
|
+
// 在用户消息里追加图片预览
|
|
9
|
+
if (pendingImages.length > 0) {
|
|
10
|
+
const msgContent = userMsg.querySelector('.message-content');
|
|
11
|
+
if (msgContent) {
|
|
12
|
+
const imgBar = document.createElement('div');
|
|
13
|
+
imgBar.style.cssText = 'display:flex;flex-wrap:wrap;gap:0.4rem;margin-top:0.5rem;';
|
|
14
|
+
pendingImages.forEach(function(img) {
|
|
15
|
+
const imgEl = document.createElement('img');
|
|
16
|
+
imgEl.src = img.previewUrl;
|
|
17
|
+
imgEl.style.cssText = 'max-width:120px;max-height:120px;border-radius:8px;object-fit:cover;border:1px solid rgba(0,0,0,0.1);';
|
|
18
|
+
imgBar.appendChild(imgEl);
|
|
19
|
+
});
|
|
20
|
+
msgContent.appendChild(imgBar);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
turnContainer.appendChild(userMsg);
|
|
24
|
+
chatContainer.appendChild(turnContainer);
|
|
25
|
+
chatInput.value = '';
|
|
26
|
+
chatInput.style.height = 'auto';
|
|
27
|
+
const imagesToSend = pendingImages.slice();
|
|
28
|
+
pendingImages = [];
|
|
29
|
+
renderImagePreviews();
|
|
30
|
+
sendBtn.disabled = true;
|
|
31
|
+
scrollToBottom();
|
|
32
|
+
|
|
33
|
+
let finalPrompt = text;
|
|
34
|
+
if (!currentThreadId && currentFingerprintData) {
|
|
35
|
+
const errorMessage = currentFingerprintData.errorMessage || '-';
|
|
36
|
+
const classifiers = currentFingerprintData.classifiers || '-';
|
|
37
|
+
finalPrompt = `你是一个非常专业并资深的C++开发工程师和UE工程师。当前崩溃的关键信息如下:\nError Message: ${errorMessage}\nClassifiers: ${classifiers}\n\n请根据这些信息,回答我的问题:\n${text}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setComposerStatus('正在处理中,请稍候…');
|
|
41
|
+
scrollToBottom();
|
|
42
|
+
|
|
43
|
+
const agentMsg = createMessageElement('', false);
|
|
44
|
+
const contentContainer = agentMsg.querySelector('.message-content');
|
|
45
|
+
const typingIndicatorId = `current-typing-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
46
|
+
contentContainer.innerHTML = `
|
|
47
|
+
<div class="typing-indicator" id="${typingIndicatorId}">
|
|
48
|
+
<div class="dot"></div>
|
|
49
|
+
<div class="dot"></div>
|
|
50
|
+
<div class="dot"></div>
|
|
51
|
+
</div>
|
|
52
|
+
`;
|
|
53
|
+
turnContainer.appendChild(agentMsg);
|
|
54
|
+
scrollToBottom();
|
|
55
|
+
|
|
56
|
+
const messageState = {
|
|
57
|
+
contentContainer,
|
|
58
|
+
scrollContainer: turnContainer,
|
|
59
|
+
queue: '',
|
|
60
|
+
fullText: '',
|
|
61
|
+
renderedText: '',
|
|
62
|
+
blocks: [],
|
|
63
|
+
currentBlockText: '',
|
|
64
|
+
toolPanelElement: null,
|
|
65
|
+
phase: 'pre-tool',
|
|
66
|
+
typingActive: false,
|
|
67
|
+
typewritingInterval: null
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
fetch('/api/backtrace/chat', {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: {
|
|
73
|
+
'Content-Type': 'application/json'
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify({
|
|
76
|
+
prompt: finalPrompt,
|
|
77
|
+
userMessage: text,
|
|
78
|
+
threadId: currentThreadId,
|
|
79
|
+
fingerprint: fingerprint,
|
|
80
|
+
sessionId: currentSessionId,
|
|
81
|
+
images: imagesToSend.map(function(img) { return { data: img.data, mimeType: img.mimeType }; })
|
|
82
|
+
})
|
|
83
|
+
}).then(async response => {
|
|
84
|
+
if (!response.ok || !response.body) {
|
|
85
|
+
let errorMessage = `请求失败: ${response.status}`;
|
|
86
|
+
try {
|
|
87
|
+
const errorPayload = await response.json();
|
|
88
|
+
if (errorPayload && (errorPayload.error || errorPayload.message)) {
|
|
89
|
+
errorMessage = errorPayload.error || errorPayload.message;
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {}
|
|
92
|
+
throw new Error(errorMessage);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const reader = response.body.getReader();
|
|
96
|
+
const decoder = new TextDecoder('utf-8');
|
|
97
|
+
let chunkBuffer = '';
|
|
98
|
+
let typingIndicatorRemoved = false;
|
|
99
|
+
|
|
100
|
+
while (true) {
|
|
101
|
+
const { done, value } = await reader.read();
|
|
102
|
+
if (done) break;
|
|
103
|
+
|
|
104
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
105
|
+
chunkBuffer += chunk;
|
|
106
|
+
const lines = chunkBuffer.split('\n');
|
|
107
|
+
chunkBuffer = lines.pop();
|
|
108
|
+
|
|
109
|
+
for (const line of lines) {
|
|
110
|
+
if (line.startsWith('data: ')) {
|
|
111
|
+
const dataStr = line.substring(6).trim();
|
|
112
|
+
if (!dataStr) continue;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const data = JSON.parse(dataStr);
|
|
116
|
+
if (data.kind === 'agent_text' && data.text !== undefined) {
|
|
117
|
+
if (messageState.phase === 'tool') {
|
|
118
|
+
if (messageState.currentBlockText && messageState.currentBlockText.trim()) {
|
|
119
|
+
messageState.blocks.push(messageState.currentBlockText);
|
|
120
|
+
}
|
|
121
|
+
messageState.currentBlockText = '';
|
|
122
|
+
messageState.fullText = '';
|
|
123
|
+
messageState.phase = 'post-tool';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!typingIndicatorRemoved) {
|
|
127
|
+
const typingIndicator = document.getElementById(typingIndicatorId);
|
|
128
|
+
if (typingIndicator) typingIndicator.remove();
|
|
129
|
+
typingIndicatorRemoved = true;
|
|
130
|
+
}
|
|
131
|
+
if (!messageState.typingActive) {
|
|
132
|
+
startTypewriter(messageState);
|
|
133
|
+
}
|
|
134
|
+
setComposerStatus('正在整理最终答案…');
|
|
135
|
+
|
|
136
|
+
if (data.replace) {
|
|
137
|
+
const newText = data.text;
|
|
138
|
+
if (newText.startsWith(messageState.fullText)) {
|
|
139
|
+
const delta = newText.substring(messageState.fullText.length);
|
|
140
|
+
messageState.fullText = newText;
|
|
141
|
+
messageState.queue += delta;
|
|
142
|
+
} else {
|
|
143
|
+
if (messageState.currentBlockText && messageState.currentBlockText.trim()) {
|
|
144
|
+
messageState.blocks.push(messageState.currentBlockText);
|
|
145
|
+
}
|
|
146
|
+
messageState.currentBlockText = newText;
|
|
147
|
+
messageState.fullText = newText;
|
|
148
|
+
if (!messageState.typingActive) {
|
|
149
|
+
messageState.renderedText = messageState.blocks.length > 0
|
|
150
|
+
? messageState.blocks.join('\n\n') + '\n\n' + newText
|
|
151
|
+
: newText;
|
|
152
|
+
}
|
|
153
|
+
messageState.queue = '';
|
|
154
|
+
rerenderFullMessage(messageState);
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
messageState.fullText += data.text;
|
|
158
|
+
messageState.queue += data.text;
|
|
159
|
+
}
|
|
160
|
+
} else if (data.kind && data.kind.startsWith('tool_')) {
|
|
161
|
+
messageState.phase = 'tool';
|
|
162
|
+
if (!typingIndicatorRemoved) {
|
|
163
|
+
const typingIndicator = document.getElementById(typingIndicatorId);
|
|
164
|
+
if (typingIndicator) typingIndicator.remove();
|
|
165
|
+
typingIndicatorRemoved = true;
|
|
166
|
+
stopTypewriter(messageState);
|
|
167
|
+
}
|
|
168
|
+
setComposerStatus(data.kind === 'tool_call_completed' ? '工具已完成,正在整理结果…' : '正在调用工具并等待结果…');
|
|
169
|
+
const normalizedToolName = data.toolName || data.itemType || data.text || 'tool';
|
|
170
|
+
const payload = {
|
|
171
|
+
...data,
|
|
172
|
+
toolName: normalizedToolName,
|
|
173
|
+
output: data.output || data.text || '',
|
|
174
|
+
result: data.result || data.text || ''
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (!messageState.toolPanelElement) {
|
|
178
|
+
messageState.toolPanelElement = createToolMessageElement(data.kind, payload);
|
|
179
|
+
turnContainer.appendChild(messageState.toolPanelElement);
|
|
180
|
+
} else {
|
|
181
|
+
updateToolMessageElement(messageState.toolPanelElement, data.kind, payload);
|
|
182
|
+
}
|
|
183
|
+
scrollToBottom();
|
|
184
|
+
} else if (data.kind === 'done') {
|
|
185
|
+
if (data.threadId) {
|
|
186
|
+
currentThreadId = data.threadId;
|
|
187
|
+
}
|
|
188
|
+
clearComposerStatus();
|
|
189
|
+
if (!typingIndicatorRemoved) {
|
|
190
|
+
const typingIndicator = document.getElementById(typingIndicatorId);
|
|
191
|
+
if (typingIndicator) typingIndicator.remove();
|
|
192
|
+
typingIndicatorRemoved = true;
|
|
193
|
+
}
|
|
194
|
+
if (messageState.queue.length === 0) {
|
|
195
|
+
stopTypewriter(messageState);
|
|
196
|
+
rerenderFullMessage(messageState);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch (e) {
|
|
200
|
+
console.error('SSE JSON 解析错误:', e, '数据:', dataStr);
|
|
201
|
+
}
|
|
202
|
+
} else if (line.startsWith('event: error')) {
|
|
203
|
+
console.error('SSE Error Event:', line);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const waitInterval = setInterval(() => {
|
|
209
|
+
if (messageState.queue.length === 0) {
|
|
210
|
+
clearInterval(waitInterval);
|
|
211
|
+
stopTypewriter(messageState);
|
|
212
|
+
}
|
|
213
|
+
}, 50);
|
|
214
|
+
}).catch(err => {
|
|
215
|
+
clearComposerStatus();
|
|
216
|
+
const typingIndicator = document.getElementById(typingIndicatorId);
|
|
217
|
+
if (typingIndicator) {
|
|
218
|
+
typingIndicator.remove();
|
|
219
|
+
}
|
|
220
|
+
stopTypewriter(messageState);
|
|
221
|
+
setMessageContent(contentContainer, `请求发生错误: ${err.message}`);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function scrollToBottom() {
|
|
226
|
+
chatContainer.scrollTo({
|
|
227
|
+
top: chatContainer.scrollHeight,
|
|
228
|
+
behavior: 'smooth'
|
|
229
|
+
});
|
|
230
|
+
}
|
package/public/chat.html
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Agent Chat Console</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
|
|
10
|
+
<link rel="stylesheet" href="/chat-layout.css">
|
|
11
|
+
<link rel="stylesheet" href="/chat-components.css">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
|
|
15
|
+
<!-- 左侧边栏 -->
|
|
16
|
+
<aside class="sidebar">
|
|
17
|
+
<div class="sidebar-header">
|
|
18
|
+
Agent Console
|
|
19
|
+
</div>
|
|
20
|
+
<button class="new-chat-btn" onclick="createNewSession()">
|
|
21
|
+
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" height="16" width="16" xmlns="http://www.w3.org/2000/svg"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
|
22
|
+
新对话
|
|
23
|
+
</button>
|
|
24
|
+
<div id="session-list" class="session-list"></div>
|
|
25
|
+
</aside>
|
|
26
|
+
|
|
27
|
+
<!-- 右侧主区域 -->
|
|
28
|
+
<main class="main-content">
|
|
29
|
+
<header class="header">
|
|
30
|
+
<h2>AI 研发助手</h2>
|
|
31
|
+
<a href="/" class="back-link">← 返回控制台</a>
|
|
32
|
+
</header>
|
|
33
|
+
|
|
34
|
+
<!-- 聊天记录区 -->
|
|
35
|
+
<div class="chat-container" id="chat-container"></div>
|
|
36
|
+
|
|
37
|
+
<!-- 输入区 -->
|
|
38
|
+
<div class="input-container">
|
|
39
|
+
<div class="composer-status" id="composer-status"></div>
|
|
40
|
+
<div class="input-box">
|
|
41
|
+
<input type="file" id="image-input" accept="image/*" multiple style="display:none">
|
|
42
|
+
<button id="image-btn" class="image-btn" type="button" title="发送图片">
|
|
43
|
+
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" height="16" width="16" xmlns="http://www.w3.org/2000/svg"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>
|
|
44
|
+
</button>
|
|
45
|
+
<div style="flex:1; display:flex; flex-direction:column; min-width:0;">
|
|
46
|
+
<div id="image-preview-bar" style="display:none; flex-wrap:wrap; gap:0.4rem; padding:0.4rem 0.5rem 0;"></div>
|
|
47
|
+
<textarea id="chat-input" placeholder="输入你想问的问题..." rows="1"></textarea>
|
|
48
|
+
</div>
|
|
49
|
+
<button id="send-btn" class="send-btn">
|
|
50
|
+
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" height="16" width="16" xmlns="http://www.w3.org/2000/svg"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</main>
|
|
56
|
+
|
|
57
|
+
<!-- 最右侧信息面板 -->
|
|
58
|
+
<aside class="info-panel" id="info-panel" style="display: none;">
|
|
59
|
+
<div class="info-panel-header">Fingerprint 详情</div>
|
|
60
|
+
<div id="info-panel-content">
|
|
61
|
+
<div style="text-align: center; color: var(--text-muted); margin-top: 2rem;">加载中...</div>
|
|
62
|
+
</div>
|
|
63
|
+
</aside>
|
|
64
|
+
|
|
65
|
+
<script src="/chat-core.js"></script>
|
|
66
|
+
<script src="/chat-render.js"></script>
|
|
67
|
+
<script src="/chat-send.js"></script>
|
|
68
|
+
</body>
|
|
69
|
+
</html>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
const statusText = document.getElementById('statusText');
|
|
2
3
|
const directoryTree = document.getElementById('directoryTree');
|
|
3
4
|
const selectedDirectoryTitle = document.getElementById('selectedDirectoryTitle');
|
|
4
5
|
const selectedDirectoryPath = document.getElementById('selectedDirectoryPath');
|
|
@@ -20,16 +21,19 @@
|
|
|
20
21
|
const rejectFixBtn = document.getElementById('rejectFixBtn');
|
|
21
22
|
const logsTabBtn = document.getElementById('logsTabBtn');
|
|
22
23
|
const reportTabBtn = document.getElementById('reportTabBtn');
|
|
24
|
+
const summarizeErrorsBtn = document.getElementById('summarizeErrorsBtn');
|
|
23
25
|
const analyzeCurrentBtn = document.getElementById('analyzeCurrentBtn');
|
|
24
26
|
const logsTab = document.getElementById('logsTab');
|
|
25
27
|
const reportTab = document.getElementById('reportTab');
|
|
26
28
|
|
|
27
|
-
let currentDirectory = '';
|
|
28
|
-
let currentLogDir = '';
|
|
29
|
-
let currentFile = null;
|
|
30
|
-
let currentFixPlan = '';
|
|
31
|
-
let
|
|
32
|
-
|
|
29
|
+
let currentDirectory = '';
|
|
30
|
+
let currentLogDir = '';
|
|
31
|
+
let currentFile = null;
|
|
32
|
+
let currentFixPlan = '';
|
|
33
|
+
let currentRepairVersion = '';
|
|
34
|
+
let currentRepairPlanPath = '';
|
|
35
|
+
let currentDirectoryCanAnalyze = false;
|
|
36
|
+
|
|
33
37
|
function setStatus(text, mode) {
|
|
34
38
|
statusText.textContent = text;
|
|
35
39
|
statusText.dataset.mode = mode || 'idle';
|
|
@@ -63,17 +67,21 @@
|
|
|
63
67
|
return '获取时间 ' + date.toLocaleString('zh-CN', { hour12: false });
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
function getSelectedReportPath() {
|
|
67
|
-
if (currentFile && currentFile.kind === 'report' && currentFile.path) {
|
|
68
|
-
return currentFile.path;
|
|
69
|
-
}
|
|
70
|
-
const activeReportButton = reportFiles.querySelector('.file-row.is-active');
|
|
71
|
-
if (activeReportButton && activeReportButton.dataset && activeReportButton.dataset.path) {
|
|
72
|
-
return activeReportButton.dataset.path;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
function getSelectedReportPath() {
|
|
71
|
+
if (currentFile && currentFile.kind === 'report' && currentFile.path) {
|
|
72
|
+
return currentFile.path;
|
|
73
|
+
}
|
|
74
|
+
const activeReportButton = reportFiles.querySelector('.file-row.is-active');
|
|
75
|
+
if (activeReportButton && activeReportButton.dataset && activeReportButton.dataset.path) {
|
|
76
|
+
return activeReportButton.dataset.path;
|
|
77
|
+
}
|
|
78
|
+
const firstReportButton = reportFiles.querySelector('.file-row[data-kind="report"]');
|
|
79
|
+
if (firstReportButton && firstReportButton.dataset && firstReportButton.dataset.path) {
|
|
80
|
+
return firstReportButton.dataset.path;
|
|
81
|
+
}
|
|
82
|
+
return '';
|
|
83
|
+
}
|
|
84
|
+
|
|
77
85
|
function updateDownloadButton() {
|
|
78
86
|
const reportPath = getSelectedReportPath();
|
|
79
87
|
downloadBtn.disabled = !reportPath;
|
|
@@ -81,7 +89,9 @@
|
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
function updateAnalyzeCurrentButton() {
|
|
84
|
-
|
|
92
|
+
const enabled = !!(currentDirectory && currentDirectoryCanAnalyze);
|
|
93
|
+
analyzeCurrentBtn.disabled = !enabled;
|
|
94
|
+
summarizeErrorsBtn.disabled = !enabled;
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
function updateFixButtons() {
|
|
@@ -90,12 +100,14 @@
|
|
|
90
100
|
rejectFixBtn.disabled = !enabled;
|
|
91
101
|
}
|
|
92
102
|
|
|
93
|
-
function resetFixPlan(text) {
|
|
94
|
-
currentFixPlan = '';
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
function resetFixPlan(text) {
|
|
104
|
+
currentFixPlan = '';
|
|
105
|
+
currentRepairVersion = '';
|
|
106
|
+
currentRepairPlanPath = '';
|
|
107
|
+
fixPlanContent.textContent = text || '请选择报告文件后点击“生成修复方案”。';
|
|
108
|
+
updateFixButtons();
|
|
109
|
+
}
|
|
110
|
+
|
|
99
111
|
function switchTab(tabName) {
|
|
100
112
|
const isLogs = tabName === 'logs';
|
|
101
113
|
logsTabBtn.classList.toggle('is-active', isLogs);
|
|
@@ -124,7 +136,7 @@
|
|
|
124
136
|
'<button type="button" class="tree-directory-button" data-path="' + escapeHtml(item.path) + '" data-status="' + escapeHtml(statusLabel) + '">',
|
|
125
137
|
'<span class="node-badge dir">DIR</span>',
|
|
126
138
|
'<span class="file-row-main">',
|
|
127
|
-
'<strong>' + escapeHtml(shortenFingerprint(item.name)) + '</strong>',
|
|
139
|
+
'<strong>' + escapeHtml(shortenFingerprint(item.name)).substring(0,7) + '</strong>',
|
|
128
140
|
'<small>' + escapeHtml(formatFetchedTime(item.fetchedAt)) + '</small>',
|
|
129
141
|
'<span class="' + statusClass + '">' + escapeHtml(statusLabel) + '</span>',
|
|
130
142
|
'</span>',
|
|
@@ -175,7 +187,7 @@
|
|
|
175
187
|
updateAnalyzeCurrentButton();
|
|
176
188
|
resetFixPlan();
|
|
177
189
|
const displayName = relativePath ? shortenFingerprint(relativePath.split(/[\\/]/).pop()) : '未选择目录';
|
|
178
|
-
selectedDirectoryTitle.textContent = displayName;
|
|
190
|
+
selectedDirectoryTitle.textContent = displayName.substring(0,7);
|
|
179
191
|
selectedDirectoryPath.textContent = relativePath ? displayName : '请从左侧选择目录';
|
|
180
192
|
reportStatus.textContent = statusLabel || '未生成报告';
|
|
181
193
|
viewerTitle.textContent = '文件内容';
|
|
@@ -328,11 +340,17 @@
|
|
|
328
340
|
window.open(url, '_blank');
|
|
329
341
|
});
|
|
330
342
|
|
|
331
|
-
generateFixBtn.addEventListener('click', async function() {
|
|
332
|
-
|
|
333
|
-
if (!reportPath) {
|
|
334
|
-
|
|
335
|
-
|
|
343
|
+
generateFixBtn.addEventListener('click', async function() {
|
|
344
|
+
let reportPath = getSelectedReportPath();
|
|
345
|
+
if (!reportPath) {
|
|
346
|
+
const firstReportButton = reportFiles.querySelector('.file-row[data-kind="report"]');
|
|
347
|
+
if (firstReportButton && firstReportButton.dataset && firstReportButton.dataset.path) {
|
|
348
|
+
reportPath = firstReportButton.dataset.path;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (!reportPath) {
|
|
352
|
+
setStatus('请先选择报告文件', 'error');
|
|
353
|
+
return;
|
|
336
354
|
}
|
|
337
355
|
logClient('fix-plan', 'generate requested', { reportPath: reportPath });
|
|
338
356
|
generateFixBtn.disabled = true;
|
|
@@ -349,19 +367,23 @@
|
|
|
349
367
|
logClient('fix-plan', 'response received', { status: response.status, ok: response.ok });
|
|
350
368
|
const data = await response.json();
|
|
351
369
|
logClient('fix-plan', 'response json parsed', data);
|
|
352
|
-
if (!response.ok || !data.ok) {
|
|
353
|
-
throw new Error(data.error || '生成修复方案失败');
|
|
354
|
-
}
|
|
355
|
-
currentFixPlan = data.plan || '';
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
370
|
+
if (!response.ok || !data.ok) {
|
|
371
|
+
throw new Error(data.error || '生成修复方案失败');
|
|
372
|
+
}
|
|
373
|
+
currentFixPlan = data.plan || '';
|
|
374
|
+
currentRepairVersion = data.repairVersion || '';
|
|
375
|
+
currentRepairPlanPath = data.repairPlanPath || '';
|
|
376
|
+
fixPlanContent.textContent = currentFixPlan || 'Codex 未返回修复方案。';
|
|
377
|
+
updateFixButtons();
|
|
378
|
+
setStatus('修复方案已生成', 'success');
|
|
379
|
+
logClient('fix-plan', 'generate completed', { reportPath: reportPath, threadId: data.threadId, repairVersion: currentRepairVersion, repairPlanPath: currentRepairPlanPath });
|
|
380
|
+
} catch (error) {
|
|
381
|
+
currentFixPlan = '';
|
|
382
|
+
currentRepairVersion = '';
|
|
383
|
+
currentRepairPlanPath = '';
|
|
384
|
+
fixPlanContent.textContent = error.message;
|
|
385
|
+
updateFixButtons();
|
|
386
|
+
setStatus('生成修复方案失败', 'error');
|
|
365
387
|
logClient('fix-plan', 'generate failed', { reportPath: reportPath, message: error.message });
|
|
366
388
|
} finally {
|
|
367
389
|
updateDownloadButton();
|
|
@@ -379,16 +401,17 @@
|
|
|
379
401
|
const response = await fetch('/api/backtrace/fix-plan/apply', {
|
|
380
402
|
method: 'POST',
|
|
381
403
|
headers: { 'content-type': 'application/json' },
|
|
382
|
-
body: JSON.stringify({ reportPath: reportPath, planText: currentFixPlan })
|
|
404
|
+
body: JSON.stringify({ reportPath: reportPath, planText: currentFixPlan, repairVersion: currentRepairVersion, repairPlanPath: currentRepairPlanPath })
|
|
383
405
|
});
|
|
384
406
|
const data = await response.json();
|
|
385
|
-
if (!response.ok || !data.ok) {
|
|
386
|
-
throw new Error(data.error || '应用修复方案失败');
|
|
387
|
-
}
|
|
388
|
-
fixPlanContent.textContent = data.resultText || '修复应用完成。';
|
|
389
|
-
currentFixPlan = data.resultText || currentFixPlan;
|
|
390
|
-
|
|
391
|
-
|
|
407
|
+
if (!response.ok || !data.ok) {
|
|
408
|
+
throw new Error(data.error || '应用修复方案失败');
|
|
409
|
+
}
|
|
410
|
+
fixPlanContent.textContent = data.resultText || '修复应用完成。';
|
|
411
|
+
currentFixPlan = data.resultText || currentFixPlan;
|
|
412
|
+
currentRepairVersion = data.repairVersion || currentRepairVersion;
|
|
413
|
+
updateFixButtons();
|
|
414
|
+
setStatus('修复方案已应用', 'success');
|
|
392
415
|
} catch (error) {
|
|
393
416
|
fixPlanContent.textContent = error.message;
|
|
394
417
|
updateFixButtons();
|
|
@@ -405,6 +428,37 @@
|
|
|
405
428
|
await runCollection();
|
|
406
429
|
});
|
|
407
430
|
|
|
431
|
+
summarizeErrorsBtn.addEventListener('click', async function() {
|
|
432
|
+
if (!currentDirectory || !currentDirectoryCanAnalyze) return;
|
|
433
|
+
const fingerprint = currentDirectory.split(/[\/]/).pop();
|
|
434
|
+
const originalText = summarizeErrorsBtn.textContent;
|
|
435
|
+
summarizeErrorsBtn.disabled = true;
|
|
436
|
+
summarizeErrorsBtn.textContent = '汇总中...';
|
|
437
|
+
setStatus('正在汇总错误日志: ' + shortenFingerprint(fingerprint), 'loading');
|
|
438
|
+
try {
|
|
439
|
+
const response = await fetch('/api/backtrace/run', {
|
|
440
|
+
method: 'POST',
|
|
441
|
+
headers: { 'content-type': 'application/json' },
|
|
442
|
+
body: JSON.stringify({
|
|
443
|
+
command: 'summarize-fingerprint-errors',
|
|
444
|
+
fingerprint: fingerprint
|
|
445
|
+
})
|
|
446
|
+
});
|
|
447
|
+
const data = await response.json();
|
|
448
|
+
if (!response.ok || !data.ok) {
|
|
449
|
+
throw new Error(data.error || '错误日志汇总失败');
|
|
450
|
+
}
|
|
451
|
+
setStatus('错误日志汇总完成,正在刷新目录', 'success');
|
|
452
|
+
await loadDirectoryIndex();
|
|
453
|
+
await loadDirectoryContent(currentDirectory, currentLogDir, reportStatus.textContent);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
setStatus(error.message || '错误日志汇总失败', 'error');
|
|
456
|
+
} finally {
|
|
457
|
+
summarizeErrorsBtn.textContent = originalText;
|
|
458
|
+
updateAnalyzeCurrentButton();
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
408
462
|
analyzeCurrentBtn.addEventListener('click', async function() {
|
|
409
463
|
if (!currentDirectory || !currentDirectoryCanAnalyze) return;
|
|
410
464
|
const fingerprint = currentDirectory.split(/[\\/]/).pop();
|
|
@@ -448,4 +502,3 @@
|
|
|
448
502
|
resetFixPlan();
|
|
449
503
|
switchTab('logs');
|
|
450
504
|
loadDirectoryIndex();
|
|
451
|
-
|