backtrace-console 0.0.3 → 0.0.5
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 +23 -0
- package/bin/backtrace-server.js +19 -14
- package/bin/www +93 -0
- package/lib/BacktraceCodexTool.js +32 -0
- package/lib/backtrace/analysis.js +356 -0
- package/lib/backtrace/constants.js +27 -0
- package/lib/backtrace/options.js +278 -0
- package/lib/backtrace/query-download.js +117 -0
- package/lib/backtrace/query-session.js +229 -0
- package/lib/backtrace/query.js +506 -0
- package/lib/backtrace/repair-fingerprint.js +405 -0
- package/lib/backtrace/repair.js +530 -0
- package/lib/backtrace/tool.js +364 -0
- package/lib/backtrace/utils.js +297 -0
- package/lib/cli/args.js +177 -0
- package/lib/cli/run.js +191 -0
- package/lib/feishu.js +67 -0
- package/lib/scheduler.js +126 -0
- package/package.json +8 -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/index-page.js +504 -0
- package/public/index.html +138 -0
- package/public/stylesheets/style.css +186 -0
- 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 +10 -0
- package/routes/index.js +9 -0
- package/routes/users.js +9 -0
|
@@ -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>
|