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/public/chat.html CHANGED
@@ -55,15 +55,30 @@
55
55
  </main>
56
56
 
57
57
  <!-- 最右侧信息面板 -->
58
- <aside class="info-panel" id="info-panel" style="display: none;">
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>
@@ -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;