backtrace-console 0.0.4 → 0.0.6
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/lib/feishu.js +5 -2
- package/lib/p4ops.js +72 -0
- package/lib/p4sync.js +35 -0
- package/lib/scheduler.js +101 -55
- package/package.json +2 -1
- package/public/chat-claude-core.js +650 -0
- package/public/chat-claude-send.js +241 -0
- package/public/chat-claude.html +84 -0
- package/public/chat-components.css +105 -0
- package/public/chat-core.js +27 -12
- package/public/chat-p4.js +113 -0
- package/public/chat-prompt.js +29 -0
- package/public/chat-send.js +3 -3
- package/public/chat.html +16 -1
- package/public/index-page.js +94 -67
- package/public/index.html +3 -0
- package/public/stylesheets/style.css +4 -3
- package/routes/backtrace-chat-claude.js +477 -0
- package/routes/backtrace-chat.js +88 -20
- package/routes/backtrace-fix-plan.js +65 -6
- package/routes/backtrace-p4.js +104 -0
- package/routes/backtrace-shared.js +32 -0
- package/routes/backtrace.js +2 -0
package/public/chat.html
CHANGED
|
@@ -55,15 +55,30 @@
|
|
|
55
55
|
</main>
|
|
56
56
|
|
|
57
57
|
<!-- 最右侧信息面板 -->
|
|
58
|
-
<aside class="info-panel" id="info-panel"
|
|
58
|
+
<aside class="info-panel" id="info-panel">
|
|
59
59
|
<div class="info-panel-header">Fingerprint 详情</div>
|
|
60
60
|
<div id="info-panel-content">
|
|
61
61
|
<div style="text-align: center; color: var(--text-muted); margin-top: 2rem;">加载中...</div>
|
|
62
62
|
</div>
|
|
63
|
+
|
|
64
|
+
<!-- P4 操作面板 -->
|
|
65
|
+
<div class="p4-panel">
|
|
66
|
+
<div class="p4-panel-header">Perforce 提交</div>
|
|
67
|
+
|
|
68
|
+
<div class="p4-action">
|
|
69
|
+
<div id="p4-pending-list" class="p4-pending" style="display:none"></div>
|
|
70
|
+
<textarea id="p4-submit-desc" class="p4-input" rows="2" placeholder="提交说明(AI 修复后自动填入)..."></textarea>
|
|
71
|
+
<button class="p4-btn" onclick="p4Submit()">↑ 提交 (submit)</button>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<pre id="p4-output" class="p4-output" style="display:none"></pre>
|
|
75
|
+
</div>
|
|
63
76
|
</aside>
|
|
64
77
|
|
|
78
|
+
<script src="/chat-prompt.js"></script>
|
|
65
79
|
<script src="/chat-core.js"></script>
|
|
66
80
|
<script src="/chat-render.js"></script>
|
|
67
81
|
<script src="/chat-send.js"></script>
|
|
82
|
+
<script src="/chat-p4.js"></script>
|
|
68
83
|
</body>
|
|
69
84
|
</html>
|
package/public/index-page.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
const generateFixBtn = document.getElementById('generateFixBtn');
|
|
20
20
|
const applyFixBtn = document.getElementById('applyFixBtn');
|
|
21
21
|
const rejectFixBtn = document.getElementById('rejectFixBtn');
|
|
22
|
+
const refreshLogsBtn = document.getElementById('refreshLogsBtn');
|
|
22
23
|
const logsTabBtn = document.getElementById('logsTabBtn');
|
|
23
24
|
const reportTabBtn = document.getElementById('reportTabBtn');
|
|
24
25
|
const summarizeErrorsBtn = document.getElementById('summarizeErrorsBtn');
|
|
@@ -26,14 +27,14 @@
|
|
|
26
27
|
const logsTab = document.getElementById('logsTab');
|
|
27
28
|
const reportTab = document.getElementById('reportTab');
|
|
28
29
|
|
|
29
|
-
let currentDirectory = '';
|
|
30
|
-
let currentLogDir = '';
|
|
31
|
-
let currentFile = null;
|
|
32
|
-
let currentFixPlan = '';
|
|
33
|
-
let currentRepairVersion = '';
|
|
34
|
-
let currentRepairPlanPath = '';
|
|
35
|
-
let currentDirectoryCanAnalyze = false;
|
|
36
|
-
|
|
30
|
+
let currentDirectory = '';
|
|
31
|
+
let currentLogDir = '';
|
|
32
|
+
let currentFile = null;
|
|
33
|
+
let currentFixPlan = '';
|
|
34
|
+
let currentRepairVersion = '';
|
|
35
|
+
let currentRepairPlanPath = '';
|
|
36
|
+
let currentDirectoryCanAnalyze = false;
|
|
37
|
+
|
|
37
38
|
function setStatus(text, mode) {
|
|
38
39
|
statusText.textContent = text;
|
|
39
40
|
statusText.dataset.mode = mode || 'idle';
|
|
@@ -67,21 +68,21 @@
|
|
|
67
68
|
return '获取时间 ' + date.toLocaleString('zh-CN', { hour12: false });
|
|
68
69
|
}
|
|
69
70
|
|
|
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
|
-
|
|
71
|
+
function getSelectedReportPath() {
|
|
72
|
+
if (currentFile && currentFile.kind === 'report' && currentFile.path) {
|
|
73
|
+
return currentFile.path;
|
|
74
|
+
}
|
|
75
|
+
const activeReportButton = reportFiles.querySelector('.file-row.is-active');
|
|
76
|
+
if (activeReportButton && activeReportButton.dataset && activeReportButton.dataset.path) {
|
|
77
|
+
return activeReportButton.dataset.path;
|
|
78
|
+
}
|
|
79
|
+
const firstReportButton = reportFiles.querySelector('.file-row[data-kind="report"]');
|
|
80
|
+
if (firstReportButton && firstReportButton.dataset && firstReportButton.dataset.path) {
|
|
81
|
+
return firstReportButton.dataset.path;
|
|
82
|
+
}
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
|
|
85
86
|
function updateDownloadButton() {
|
|
86
87
|
const reportPath = getSelectedReportPath();
|
|
87
88
|
downloadBtn.disabled = !reportPath;
|
|
@@ -100,14 +101,14 @@
|
|
|
100
101
|
rejectFixBtn.disabled = !enabled;
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
function resetFixPlan(text) {
|
|
104
|
-
currentFixPlan = '';
|
|
105
|
-
currentRepairVersion = '';
|
|
106
|
-
currentRepairPlanPath = '';
|
|
107
|
-
fixPlanContent.textContent = text || '请选择报告文件后点击“生成修复方案”。';
|
|
108
|
-
updateFixButtons();
|
|
109
|
-
}
|
|
110
|
-
|
|
104
|
+
function resetFixPlan(text) {
|
|
105
|
+
currentFixPlan = '';
|
|
106
|
+
currentRepairVersion = '';
|
|
107
|
+
currentRepairPlanPath = '';
|
|
108
|
+
fixPlanContent.textContent = text || '请选择报告文件后点击“生成修复方案”。';
|
|
109
|
+
updateFixButtons();
|
|
110
|
+
}
|
|
111
|
+
|
|
111
112
|
function switchTab(tabName) {
|
|
112
113
|
const isLogs = tabName === 'logs';
|
|
113
114
|
logsTabBtn.classList.toggle('is-active', isLogs);
|
|
@@ -183,6 +184,7 @@
|
|
|
183
184
|
currentDirectory = relativePath;
|
|
184
185
|
currentDirectoryCanAnalyze = false;
|
|
185
186
|
currentFile = null;
|
|
187
|
+
refreshLogsBtn.disabled = !relativePath;
|
|
186
188
|
updateDownloadButton();
|
|
187
189
|
updateAnalyzeCurrentButton();
|
|
188
190
|
resetFixPlan();
|
|
@@ -340,17 +342,17 @@
|
|
|
340
342
|
window.open(url, '_blank');
|
|
341
343
|
});
|
|
342
344
|
|
|
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;
|
|
345
|
+
generateFixBtn.addEventListener('click', async function() {
|
|
346
|
+
let reportPath = getSelectedReportPath();
|
|
347
|
+
if (!reportPath) {
|
|
348
|
+
const firstReportButton = reportFiles.querySelector('.file-row[data-kind="report"]');
|
|
349
|
+
if (firstReportButton && firstReportButton.dataset && firstReportButton.dataset.path) {
|
|
350
|
+
reportPath = firstReportButton.dataset.path;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (!reportPath) {
|
|
354
|
+
setStatus('请先选择报告文件', 'error');
|
|
355
|
+
return;
|
|
354
356
|
}
|
|
355
357
|
logClient('fix-plan', 'generate requested', { reportPath: reportPath });
|
|
356
358
|
generateFixBtn.disabled = true;
|
|
@@ -367,23 +369,23 @@
|
|
|
367
369
|
logClient('fix-plan', 'response received', { status: response.status, ok: response.ok });
|
|
368
370
|
const data = await response.json();
|
|
369
371
|
logClient('fix-plan', 'response json parsed', data);
|
|
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');
|
|
372
|
+
if (!response.ok || !data.ok) {
|
|
373
|
+
throw new Error(data.error || '生成修复方案失败');
|
|
374
|
+
}
|
|
375
|
+
currentFixPlan = data.plan || '';
|
|
376
|
+
currentRepairVersion = data.repairVersion || '';
|
|
377
|
+
currentRepairPlanPath = data.repairPlanPath || '';
|
|
378
|
+
fixPlanContent.textContent = currentFixPlan || 'Codex 未返回修复方案。';
|
|
379
|
+
updateFixButtons();
|
|
380
|
+
setStatus('修复方案已生成', 'success');
|
|
381
|
+
logClient('fix-plan', 'generate completed', { reportPath: reportPath, threadId: data.threadId, repairVersion: currentRepairVersion, repairPlanPath: currentRepairPlanPath });
|
|
382
|
+
} catch (error) {
|
|
383
|
+
currentFixPlan = '';
|
|
384
|
+
currentRepairVersion = '';
|
|
385
|
+
currentRepairPlanPath = '';
|
|
386
|
+
fixPlanContent.textContent = error.message;
|
|
387
|
+
updateFixButtons();
|
|
388
|
+
setStatus('生成修复方案失败', 'error');
|
|
387
389
|
logClient('fix-plan', 'generate failed', { reportPath: reportPath, message: error.message });
|
|
388
390
|
} finally {
|
|
389
391
|
updateDownloadButton();
|
|
@@ -404,14 +406,14 @@
|
|
|
404
406
|
body: JSON.stringify({ reportPath: reportPath, planText: currentFixPlan, repairVersion: currentRepairVersion, repairPlanPath: currentRepairPlanPath })
|
|
405
407
|
});
|
|
406
408
|
const data = await response.json();
|
|
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');
|
|
409
|
+
if (!response.ok || !data.ok) {
|
|
410
|
+
throw new Error(data.error || '应用修复方案失败');
|
|
411
|
+
}
|
|
412
|
+
fixPlanContent.textContent = data.resultText || '修复应用完成。';
|
|
413
|
+
currentFixPlan = data.resultText || currentFixPlan;
|
|
414
|
+
currentRepairVersion = data.repairVersion || currentRepairVersion;
|
|
415
|
+
updateFixButtons();
|
|
416
|
+
setStatus('修复方案已应用', 'success');
|
|
415
417
|
} catch (error) {
|
|
416
418
|
fixPlanContent.textContent = error.message;
|
|
417
419
|
updateFixButtons();
|
|
@@ -497,6 +499,31 @@
|
|
|
497
499
|
}
|
|
498
500
|
});
|
|
499
501
|
|
|
502
|
+
refreshLogsBtn.addEventListener('click', async function() {
|
|
503
|
+
if (!currentDirectory) return;
|
|
504
|
+
const fingerprint = currentDirectory.split(/[\\/]/).pop();
|
|
505
|
+
refreshLogsBtn.disabled = true;
|
|
506
|
+
setStatus('正在刷新日志对象: ' + shortenFingerprint(fingerprint), 'loading');
|
|
507
|
+
try {
|
|
508
|
+
const now = Math.floor(Date.now() / 1000);
|
|
509
|
+
const response = await fetch('/api/backtrace/run', {
|
|
510
|
+
method: 'POST',
|
|
511
|
+
headers: { 'content-type': 'application/json' },
|
|
512
|
+
body: JSON.stringify({ command: 'fingerprint', fingerprint: fingerprint, from: '1', to: String(now) })
|
|
513
|
+
});
|
|
514
|
+
const data = await response.json();
|
|
515
|
+
if (!response.ok || !data.ok) {
|
|
516
|
+
throw new Error(data.error || '刷新日志对象失败');
|
|
517
|
+
}
|
|
518
|
+
setStatus('日志对象刷新完成,正在更新目录', 'success');
|
|
519
|
+
await loadDirectoryContent(currentDirectory, currentLogDir, reportStatus.textContent);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
setStatus(error.message || '刷新日志对象失败', 'error');
|
|
522
|
+
} finally {
|
|
523
|
+
refreshLogsBtn.disabled = !currentDirectory;
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
|
|
500
527
|
updateDownloadButton();
|
|
501
528
|
updateAnalyzeCurrentButton();
|
|
502
529
|
resetFixPlan();
|
package/public/index.html
CHANGED
|
@@ -72,6 +72,9 @@
|
|
|
72
72
|
</div>
|
|
73
73
|
|
|
74
74
|
<div id="logsTab" class="tab-panel is-active">
|
|
75
|
+
<div class="logs-toolbar">
|
|
76
|
+
<button id="refreshLogsBtn" class="action-button secondary-action" type="button" disabled>刷新日志对象</button>
|
|
77
|
+
</div>
|
|
75
78
|
<div class="logs-split">
|
|
76
79
|
<div class="subpanel">
|
|
77
80
|
<div class="subpanel-head">日志目录</div>
|
|
@@ -109,6 +109,7 @@ code, pre { font-family: Consolas, Monaco, monospace; }
|
|
|
109
109
|
.tab-button.is-active { background: rgba(187, 90, 44, 0.12); color: var(--accent-strong); }
|
|
110
110
|
.tab-panel { display: none; padding: 14px; }
|
|
111
111
|
.tab-panel.is-active { display: block; }
|
|
112
|
+
.logs-toolbar { display: flex; justify-content: flex-end; padding: 0 0 10px; }
|
|
112
113
|
.logs-split { display: grid; grid-template-columns: minmax(220px, 280px) minmax(0, 1fr); gap: 14px; }
|
|
113
114
|
.subpanel { border: 1px solid var(--line); border-radius: 18px; background: rgba(255,255,255,0.52); overflow: hidden; }
|
|
114
115
|
.subpanel-head { padding: 12px 14px; border-bottom: 1px solid var(--line); font-weight: 700; color: var(--muted); }
|
|
@@ -142,8 +143,8 @@ code, pre { font-family: Consolas, Monaco, monospace; }
|
|
|
142
143
|
.viewer-side-actions, .tabs-header { flex-direction: column; }
|
|
143
144
|
.browser-main, .browser-sidebar { padding: 14px; }
|
|
144
145
|
.file-viewer { padding: 16px; }
|
|
145
|
-
}
|
|
146
|
-
|
|
146
|
+
}
|
|
147
|
+
|
|
147
148
|
|
|
148
149
|
#reportStatus {
|
|
149
150
|
color: var(--accent-strong);
|
|
@@ -158,7 +159,7 @@ code, pre { font-family: Consolas, Monaco, monospace; }
|
|
|
158
159
|
flex: 0 0 320px;
|
|
159
160
|
height: 320px;
|
|
160
161
|
min-height: 320px;
|
|
161
|
-
}
|
|
162
|
+
}
|
|
162
163
|
|
|
163
164
|
.status-tag {
|
|
164
165
|
display: inline-flex;
|