lumencode 1.3.3 → 1.3.4

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/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { COLORS, SCENARIO_COLORS, TEXT, ID, STORAGE } from './config.js';
2
2
  import { esc, fmt, fmtShort, destroyChart, destroyAllCharts, getChart, setChart, todayISO, fmtDate, TOOL_DISPLAY_NAMES, groupMcpByServer, aggregateToolsWithDualCounts } from './utils.js';
3
- import { createLatestRequestGuard, fetchTools, fetchReport, fetchConfig, saveConfig, fetchDetails, fetchSessions, fetchStepStats, fetchHooksStatus, updateHooks } from './api.js';
3
+ import { createLatestRequestGuard, fetchTools, fetchReport, fetchConfig, saveConfig, fetchDetails, fetchSessions, fetchStepStats, fetchHooksStatus, updateHooks, fetchSmartReportTools, fetchSmartReportRecord, generateSmartReport } from './api.js';
4
4
  import { renderWorkTypePie, renderModelBars, renderProjectBars, renderTimelineArea, renderCacheStack } from './charts.js';
5
5
  import { renderGitInsights, renderLineBlameEvidence } from './git-insights.js';
6
6
  import { loadWorkReport, copyWorkReport, downloadMarkdown, getWorkReportState, setWorkReportState } from './work-report.js';
@@ -36,6 +36,28 @@ function appState() {
36
36
  reportProjects: [],
37
37
  copied: false,
38
38
  reportHtml: '',
39
+ reportContentMode: 'source',
40
+ smartReportTools: [],
41
+ smartReportAgent: '',
42
+ smartReportStyle: ['default', 'workhorse'].includes(localStorage.getItem(STORAGE.SMART_REPORT_STYLE)) ? localStorage.getItem(STORAGE.SMART_REPORT_STYLE) : 'default',
43
+ smartReportStyleModalOpen: false,
44
+ smartReportLoading: false,
45
+ smartReportError: '',
46
+ smartReportMarkdown: '',
47
+ smartReportHtml: '',
48
+ smartReportCopied: false,
49
+ smartReportRecord: null,
50
+ smartReportRecordMeta: '',
51
+ smartReportNeedsUpdate: false,
52
+ smartReportUpdateMessage: '',
53
+ smartReportJob: null,
54
+ smartReportStatusMessage: '',
55
+ smartReportPollTimer: null,
56
+ smartReportElapsedTimer: null,
57
+ smartReportCompletionTimer: null,
58
+ smartReportStartedAt: '',
59
+ smartReportNow: Date.now(),
60
+ smartReportProgress: 0,
39
61
 
40
62
  /* constants */
41
63
  customStart: '',
@@ -199,6 +221,7 @@ function appState() {
199
221
  }
200
222
  });
201
223
  await this.loadTools();
224
+ await this.loadSmartReportTools();
202
225
  await this.loadHooksStatus();
203
226
  await this.loadStepStats();
204
227
  // 首次加载时先获取全量数据填充侧边栏,再按当前工具加载
@@ -233,6 +256,22 @@ function appState() {
233
256
  } catch (e) { console.warn('loadTools failed:', e); this.availableTools = []; }
234
257
  },
235
258
 
259
+ async loadSmartReportTools() {
260
+ try {
261
+ const data = await fetchSmartReportTools();
262
+ this.smartReportTools = data.tools || [];
263
+ const savedAgent = localStorage.getItem(STORAGE.SMART_REPORT_AGENT);
264
+ const firstDetected = this.smartReportTools.find(t => t.detected);
265
+ const savedDetected = this.smartReportTools.find(t => t.detected && t.name === savedAgent);
266
+ this.smartReportAgent = savedDetected?.name || firstDetected?.name || '';
267
+ await this.loadSmartReportRecord();
268
+ } catch (e) {
269
+ console.warn('loadSmartReportTools failed:', e);
270
+ this.smartReportTools = [];
271
+ this.smartReportAgent = '';
272
+ }
273
+ },
274
+
236
275
  async loadStepStats() {
237
276
  try {
238
277
  const data = await fetchStepStats();
@@ -312,6 +351,7 @@ function appState() {
312
351
 
313
352
  setTool(name) {
314
353
  this.activeTool = name;
354
+ this.resetSmartReportDisplay();
315
355
  this.loadCurrentView();
316
356
  if (this.view === 'report') this.loadReportContent();
317
357
  },
@@ -383,6 +423,7 @@ function appState() {
383
423
  this.customStart = '';
384
424
  this.customEnd = '';
385
425
  this.saveStateToHash();
426
+ this.resetSmartReportDisplay();
386
427
  this.loadCurrentView();
387
428
  if (this.view === 'report') this.loadReportContent();
388
429
  }
@@ -390,6 +431,7 @@ function appState() {
390
431
 
391
432
  onCustomStartChange() {
392
433
  if (this.customStart && this.customEnd) {
434
+ this.resetSmartReportDisplay();
393
435
  this.loadCurrentView();
394
436
  if (this.view === 'report') this.loadReportContent();
395
437
  }
@@ -397,6 +439,7 @@ function appState() {
397
439
 
398
440
  onCustomEndChange() {
399
441
  if (this.customStart && this.customEnd) {
442
+ this.resetSmartReportDisplay();
400
443
  this.loadCurrentView();
401
444
  if (this.view === 'report') this.loadReportContent();
402
445
  }
@@ -412,6 +455,7 @@ function appState() {
412
455
  }
413
456
  this.currentDate = d.toISOString().slice(0, 10);
414
457
  this.saveStateToHash();
458
+ this.resetSmartReportDisplay();
415
459
  this.loadCurrentView();
416
460
  if (this.view === 'report') this.loadReportContent();
417
461
  },
@@ -419,6 +463,7 @@ function appState() {
419
463
  onDateChange() {
420
464
  if (this.currentDate > this.today) this.currentDate = this.today;
421
465
  this.saveStateToHash();
466
+ this.resetSmartReportDisplay();
422
467
  this.loadCurrentView();
423
468
  if (this.view === 'report') this.loadReportContent();
424
469
  },
@@ -848,6 +893,7 @@ function appState() {
848
893
 
849
894
  async loadReportContent() {
850
895
  try {
896
+ if (!['detailed', 'brief'].includes(this.reportLevel)) this.reportLevel = 'detailed';
851
897
  const params = { tool: this.activeTool, period: this.period, date: this.currentDate, format: 'work', platform: this.reportPlatform, level: this.reportLevel };
852
898
  if (this.period === 'custom' && this.customStart && this.customEnd) {
853
899
  params.start = this.customStart;
@@ -862,24 +908,291 @@ function appState() {
862
908
  const markdown = await res.text();
863
909
  setWorkReportState({ markdown, platform: this.reportPlatform, level: this.reportLevel });
864
910
  this.reportHtml = this.renderMarkdownToReportHtml(markdown);
911
+ await this.loadSmartReportRecord();
865
912
  } catch (e) { console.warn('loadReportContent failed:', e); }
866
913
  },
867
914
 
868
915
  setReportLevel(level) {
869
- this.reportLevel = level;
916
+ this.reportLevel = ['detailed', 'brief'].includes(level) ? level : 'detailed';
917
+ this.resetSmartReportDisplay();
870
918
  this.loadReportContent();
871
919
  },
872
920
 
873
921
  setReportPlatform(platform) {
874
922
  this.reportPlatform = platform;
923
+ this.resetSmartReportDisplay();
875
924
  this.loadReportContent();
876
925
  },
877
926
 
878
927
  setReportProject(project) {
879
928
  this.reportProject = project;
929
+ this.resetSmartReportDisplay();
880
930
  this.loadReportContent();
881
931
  },
882
932
 
933
+ setSmartReportAgent(agent) {
934
+ this.smartReportAgent = agent;
935
+ localStorage.setItem(STORAGE.SMART_REPORT_AGENT, agent);
936
+ this.resetSmartReportDisplay();
937
+ this.loadSmartReportRecord();
938
+ },
939
+
940
+ resetSmartReportDisplay() {
941
+ this.stopSmartReportPolling();
942
+ this.stopSmartReportElapsedTimer();
943
+ this.stopSmartReportCompletionTimer();
944
+ this.smartReportError = '';
945
+ this.smartReportMarkdown = '';
946
+ this.smartReportHtml = '';
947
+ this.smartReportRecord = null;
948
+ this.smartReportRecordMeta = '';
949
+ this.smartReportNeedsUpdate = false;
950
+ this.smartReportUpdateMessage = '';
951
+ this.smartReportJob = null;
952
+ this.smartReportLoading = false;
953
+ this.smartReportStatusMessage = '';
954
+ this.smartReportStartedAt = '';
955
+ this.smartReportNow = Date.now();
956
+ this.smartReportProgress = 0;
957
+ this.reportContentMode = 'source';
958
+ },
959
+
960
+ smartReportParams() {
961
+ const params = {
962
+ agent: this.smartReportAgent,
963
+ tool: this.activeTool,
964
+ period: this.period,
965
+ date: this.currentDate,
966
+ level: this.reportLevel,
967
+ style: this.smartReportStyle,
968
+ platform: this.reportPlatform,
969
+ project: this.reportProject,
970
+ };
971
+ if (this.period === 'custom' && this.customStart && this.customEnd) {
972
+ params.start = this.customStart;
973
+ params.end = this.customEnd;
974
+ }
975
+ return params;
976
+ },
977
+
978
+ setReportContentMode(mode) {
979
+ this.reportContentMode = mode === 'smart' ? 'smart' : 'source';
980
+ },
981
+
982
+ openSmartReportStyleModal() {
983
+ if (!this.smartReportAgent || this.smartReportLoading) return;
984
+ this.smartReportStyleModalOpen = true;
985
+ },
986
+
987
+ closeSmartReportStyleModal() {
988
+ this.smartReportStyleModalOpen = false;
989
+ },
990
+
991
+ async confirmSmartReportStyle(style) {
992
+ this.smartReportStyle = style === 'workhorse' ? 'workhorse' : 'default';
993
+ localStorage.setItem(STORAGE.SMART_REPORT_STYLE, this.smartReportStyle);
994
+ this.closeSmartReportStyleModal();
995
+ this.resetSmartReportDisplay();
996
+ await this.generateSmartReportContent();
997
+ },
998
+
999
+ applySmartReportRecord(record, meta = {}) {
1000
+ if (record?.style) this.smartReportStyle = record.style;
1001
+ this.smartReportRecord = record || null;
1002
+ this.smartReportMarkdown = record?.markdown || '';
1003
+ this.smartReportHtml = this.renderMarkdownToReportHtml(this.smartReportMarkdown);
1004
+ this.smartReportRecordMeta = record ? this.formatSmartReportRecordMeta(record) : '';
1005
+ this.smartReportNeedsUpdate = !!record && !!meta.needsUpdate;
1006
+ this.smartReportUpdateMessage = this.smartReportNeedsUpdate ? '当前统计数据或原始报告已变化,建议重新生成智能报告。' : '';
1007
+ if (!record && this.reportContentMode === 'smart') this.reportContentMode = 'source';
1008
+ },
1009
+
1010
+ applySmartReportJob(job) {
1011
+ this.smartReportJob = job || null;
1012
+ if (!job) {
1013
+ this.smartReportLoading = false;
1014
+ this.smartReportStatusMessage = '';
1015
+ this.smartReportStartedAt = '';
1016
+ this.smartReportProgress = 0;
1017
+ this.stopSmartReportPolling();
1018
+ this.stopSmartReportElapsedTimer();
1019
+ this.stopSmartReportCompletionTimer();
1020
+ return;
1021
+ }
1022
+ if (job.status === 'running') {
1023
+ this.stopSmartReportCompletionTimer();
1024
+ this.smartReportLoading = true;
1025
+ this.smartReportError = '';
1026
+ this.smartReportStartedAt = job.startedAt || this.smartReportStartedAt || new Date().toISOString();
1027
+ if (this.smartReportProgress <= 0) this.smartReportProgress = 4;
1028
+ this.updateSmartReportProgress();
1029
+ this.startSmartReportElapsedTimer();
1030
+ this.smartReportStatusMessage = '后台生成中,页面可刷新,回来后会继续显示进度。';
1031
+ this.scheduleSmartReportPolling();
1032
+ return;
1033
+ }
1034
+ if (job.status === 'completed') {
1035
+ this.finishSmartReportProgress();
1036
+ return;
1037
+ }
1038
+ this.smartReportLoading = false;
1039
+ this.smartReportStatusMessage = '';
1040
+ this.smartReportStartedAt = '';
1041
+ this.smartReportProgress = 0;
1042
+ this.stopSmartReportPolling();
1043
+ this.stopSmartReportElapsedTimer();
1044
+ this.stopSmartReportCompletionTimer();
1045
+ if (job.status === 'failed') {
1046
+ this.smartReportError = job.error || '智能报告生成失败';
1047
+ showToast(this.smartReportError);
1048
+ }
1049
+ },
1050
+
1051
+ startSmartReportElapsedTimer() {
1052
+ this.smartReportNow = Date.now();
1053
+ if (this.smartReportElapsedTimer) return;
1054
+ this.smartReportElapsedTimer = setInterval(() => {
1055
+ this.smartReportNow = Date.now();
1056
+ this.updateSmartReportProgress();
1057
+ }, 1000);
1058
+ },
1059
+
1060
+ stopSmartReportElapsedTimer() {
1061
+ if (this.smartReportElapsedTimer) {
1062
+ clearInterval(this.smartReportElapsedTimer);
1063
+ this.smartReportElapsedTimer = null;
1064
+ }
1065
+ },
1066
+
1067
+ stopSmartReportCompletionTimer() {
1068
+ if (this.smartReportCompletionTimer) {
1069
+ clearTimeout(this.smartReportCompletionTimer);
1070
+ this.smartReportCompletionTimer = null;
1071
+ }
1072
+ },
1073
+
1074
+ updateSmartReportProgress() {
1075
+ if (!this.smartReportLoading || this.smartReportProgress >= 100) return;
1076
+ const startedAt = Date.parse(this.smartReportStartedAt || this.smartReportJob?.startedAt || '');
1077
+ if (!Number.isFinite(startedAt)) {
1078
+ this.smartReportProgress = Math.max(this.smartReportProgress, 4);
1079
+ return;
1080
+ }
1081
+ const seconds = Math.max(0, Math.floor((Date.now() - startedAt) / 1000));
1082
+ const eased = 1 - Math.exp(-seconds / 180);
1083
+ const target = Math.min(95, Math.round(6 + eased * 89));
1084
+ this.smartReportProgress = Math.max(this.smartReportProgress, target);
1085
+ },
1086
+
1087
+ finishSmartReportProgress() {
1088
+ this.stopSmartReportPolling();
1089
+ this.stopSmartReportElapsedTimer();
1090
+ this.stopSmartReportCompletionTimer();
1091
+ this.smartReportLoading = true;
1092
+ this.smartReportError = '';
1093
+ this.smartReportProgress = 100;
1094
+ this.smartReportStatusMessage = '生成完成,正在展示结果...';
1095
+ this.smartReportCompletionTimer = setTimeout(() => {
1096
+ this.smartReportCompletionTimer = null;
1097
+ this.smartReportLoading = false;
1098
+ this.smartReportStatusMessage = '';
1099
+ this.smartReportStartedAt = '';
1100
+ }, 1200);
1101
+ },
1102
+
1103
+ get smartReportElapsedLabel() {
1104
+ if (!this.smartReportLoading) return '';
1105
+ if (this.smartReportProgress >= 100) return '100%';
1106
+ const startedAt = Date.parse(this.smartReportStartedAt || this.smartReportJob?.startedAt || '');
1107
+ if (!Number.isFinite(startedAt)) return '正在启动后台任务';
1108
+ const seconds = Math.max(0, Math.floor((this.smartReportNow - startedAt) / 1000));
1109
+ if (seconds < 60) return `已等待 ${seconds} 秒`;
1110
+ const minutes = Math.floor(seconds / 60);
1111
+ const rest = seconds % 60;
1112
+ return `已等待 ${minutes} 分 ${String(rest).padStart(2, '0')} 秒`;
1113
+ },
1114
+
1115
+ scheduleSmartReportPolling() {
1116
+ this.stopSmartReportPolling();
1117
+ this.smartReportPollTimer = setTimeout(() => {
1118
+ this.smartReportPollTimer = null;
1119
+ this.loadSmartReportRecord();
1120
+ }, 2500);
1121
+ },
1122
+
1123
+ stopSmartReportPolling() {
1124
+ if (this.smartReportPollTimer) {
1125
+ clearTimeout(this.smartReportPollTimer);
1126
+ this.smartReportPollTimer = null;
1127
+ }
1128
+ },
1129
+
1130
+ formatSmartReportRecordMeta(record) {
1131
+ const updatedAt = record?.updatedAt ? new Date(record.updatedAt) : null;
1132
+ const time = updatedAt && !Number.isNaN(updatedAt.getTime()) ? updatedAt.toLocaleString('zh-CN', { hour12: false }) : '';
1133
+ const count = record?.generatedCount ? `第 ${record.generatedCount} 次生成` : '已生成';
1134
+ const styleLabel = record?.style === 'workhorse' ? '牛马风格' : '默认风格';
1135
+ return time ? `${styleLabel} · ${count} · ${time}` : `${styleLabel} · ${count}`;
1136
+ },
1137
+
1138
+ async loadSmartReportRecord() {
1139
+ if (!this.smartReportAgent) {
1140
+ this.resetSmartReportDisplay();
1141
+ return;
1142
+ }
1143
+ try {
1144
+ const data = await fetchSmartReportRecord(this.smartReportParams());
1145
+ this.smartReportError = '';
1146
+ this.applySmartReportRecord(data.record || null, { needsUpdate: data.needsUpdate });
1147
+ this.applySmartReportJob(data.job || null);
1148
+ if (data.job?.status === 'completed' && data.record) this.reportContentMode = 'smart';
1149
+ } catch (err) {
1150
+ console.warn('loadSmartReportRecord failed:', err);
1151
+ this.applySmartReportRecord(null);
1152
+ this.applySmartReportJob(null);
1153
+ }
1154
+ },
1155
+
1156
+ async generateSmartReportContent() {
1157
+ if (!this.smartReportAgent || this.smartReportLoading) return;
1158
+ this.smartReportLoading = true;
1159
+ this.smartReportError = '';
1160
+ this.smartReportStartedAt = new Date().toISOString();
1161
+ this.smartReportProgress = 4;
1162
+ this.smartReportStatusMessage = '正在提交后台生成任务...';
1163
+ this.startSmartReportElapsedTimer();
1164
+ try {
1165
+ const payload = this.smartReportParams();
1166
+ const data = await generateSmartReport(payload);
1167
+ this.applySmartReportRecord(data.record || (data.markdown ? { ...payload, markdown: data.markdown, generatedCount: 1, updatedAt: new Date().toISOString() } : null), { needsUpdate: false });
1168
+ this.applySmartReportJob(data.job || null);
1169
+ if (data.record && !data.job) this.reportContentMode = 'smart';
1170
+ } catch (err) {
1171
+ this.smartReportError = err.message || '智能报告生成失败';
1172
+ this.stopSmartReportElapsedTimer();
1173
+ showToast(this.smartReportError);
1174
+ } finally {
1175
+ if (this.smartReportJob?.status !== 'running') this.smartReportLoading = false;
1176
+ }
1177
+ },
1178
+
1179
+ async copySmartReport() {
1180
+ if (!this.smartReportMarkdown) return;
1181
+ await navigator.clipboard.writeText(this.smartReportMarkdown);
1182
+ this.smartReportCopied = true;
1183
+ setTimeout(() => this.smartReportCopied = false, 1400);
1184
+ },
1185
+
1186
+ downloadSmartReport() {
1187
+ if (!this.smartReportMarkdown) return;
1188
+ const blob = new Blob([this.smartReportMarkdown], { type: 'text/markdown;charset=utf-8' });
1189
+ const a = document.createElement('a');
1190
+ a.href = URL.createObjectURL(blob);
1191
+ a.download = `smart-report-${this.period}-${this.currentDate}.md`;
1192
+ a.click();
1193
+ URL.revokeObjectURL(a.href);
1194
+ },
1195
+
883
1196
  async copyReport() {
884
1197
  await copyWorkReport();
885
1198
  this.copied = true;
package/public/config.js CHANGED
@@ -7,6 +7,8 @@ export const API = {
7
7
  SESSIONS: '/api/sessions',
8
8
  STEP_STATS: '/api/step-stats',
9
9
  HOOKS: '/api/hooks',
10
+ SMART_REPORT_TOOLS: '/api/smart-report/tools',
11
+ SMART_REPORT: '/api/smart-report',
10
12
  };
11
13
 
12
14
  // 灰阶色板(按视觉权重从重到轻)
@@ -141,4 +143,6 @@ export const STORAGE = {
141
143
  CONFIG: 'ccusage-config',
142
144
  THEME: 'lc-theme',
143
145
  SIDEBAR_COLLAPSED: 'ccusage-sidebar-collapsed',
146
+ SMART_REPORT_AGENT: 'lc-smart-report-agent',
147
+ SMART_REPORT_STYLE: 'lc-smart-report-style',
144
148
  };
package/public/index.html CHANGED
@@ -562,7 +562,6 @@
562
562
  <div style="display:flex;border:1px solid var(--border);border-radius:6px;overflow:hidden;">
563
563
  <button class="period-btn" :class="reportLevel === 'detailed' ? 'active' : ''" @click="setReportLevel('detailed')" style="border-left:none">详细 Detail</button>
564
564
  <button class="period-btn" :class="reportLevel === 'brief' ? 'active' : ''" @click="setReportLevel('brief')">简略 Brief</button>
565
- <button class="period-btn" :class="reportLevel === 'boss' ? 'active' : ''" @click="setReportLevel('boss')">汇报 Boss</button>
566
565
  </div>
567
566
  <button class="btn btn-outline" @click="copyReport()" style="display:inline-flex;align-items:center;gap:6px;">
568
567
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
@@ -630,7 +629,22 @@
630
629
 
631
630
  <!-- Report Content -->
632
631
  <div style="margin-top:48px;display:grid;grid-template-columns:5fr 2fr;gap:24px;">
633
- <div class="work-report-content" id="reportContent" x-html="reportHtml"></div>
632
+ <div class="work-report-content report-content-frame" id="reportContent">
633
+ <div class="report-content-toggle">
634
+ <button class="report-toggle-btn" :class="reportContentMode === 'source' ? 'active' : ''" @click="setReportContentMode('source')">原报告</button>
635
+ <button class="report-toggle-btn" :class="reportContentMode === 'smart' ? 'active' : ''" @click="setReportContentMode('smart')" :disabled="!smartReportMarkdown">
636
+ <span>智能报告</span>
637
+ <span class="report-toggle-dot" x-show="smartReportNeedsUpdate" x-cloak></span>
638
+ </button>
639
+ </div>
640
+ <div x-show="reportContentMode === 'source'" x-html="reportHtml"></div>
641
+ <div x-show="reportContentMode === 'smart'" x-cloak>
642
+ <div class="smart-report-update-notice" x-show="smartReportNeedsUpdate" x-cloak>
643
+ 当前统计数据或原始报告已变化,这份 AI 报告可能不是最新内容。建议点击右侧“重新生成”更新。
644
+ </div>
645
+ <div x-html="smartReportHtml"></div>
646
+ </div>
647
+ </div>
634
648
 
635
649
  <!-- Sticky Meta Panel -->
636
650
  <aside>
@@ -639,6 +653,51 @@
639
653
  <span class="label">SUMMARY · 摘要</span>
640
654
  <p style="font-size:13px;line-height:1.7;opacity:0.85;margin-top:12px;" x-html="reportSummary"></p>
641
655
  </div>
656
+ <!-- Smart Report -->
657
+ <div class="card" style="padding:16px 20px;">
658
+ <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px;">
659
+ <span class="label">SMART REPORT · 智能报告</span>
660
+ <span class="label" x-show="smartReportRecordMeta" x-text="smartReportRecordMeta"></span>
661
+ </div>
662
+ <p style="font-size:12px;line-height:1.6;opacity:0.7;margin-bottom:12px;">
663
+ 基于当前统计维度和工作汇报生成。智能体只接收受限统计上下文。
664
+ </p>
665
+ <div class="smart-agent-list">
666
+ <template x-for="agent in smartReportTools" :key="agent.name">
667
+ <button class="smart-agent-btn" :class="{ active: smartReportAgent === agent.name, unavailable: !agent.detected }" @click="setSmartReportAgent(agent.name)" :disabled="!agent.detected" :title="agent.detected ? agent.version : agent.error">
668
+ <span class="smart-agent-dot" :class="agent.detected ? 'available' : 'missing'"></span>
669
+ <span class="smart-agent-name" x-text="agent.displayName"></span>
670
+ <span class="smart-agent-status" x-text="agent.detected ? '可连接' : '未检测'"></span>
671
+ </button>
672
+ </template>
673
+ </div>
674
+ <button class="btn btn-primary" style="width:100%;justify-content:center;" @click="openSmartReportStyleModal()" :disabled="smartReportLoading || !smartReportAgent">
675
+ <span class="smart-report-spinner" x-show="smartReportLoading" x-cloak></span>
676
+ <span x-text="smartReportLoading ? '生成中...' : (smartReportMarkdown ? (smartReportNeedsUpdate ? '更新智能报告' : '重新生成') : '生成智能报告')"></span>
677
+ </button>
678
+ <div x-show="smartReportTools.length > 0 && !smartReportTools.some(agent => agent.detected)" x-cloak style="margin-top:12px;padding:10px 12px;border:1px solid var(--border);border-radius:6px;font-size:12px;line-height:1.6;opacity:0.75;">
679
+ 未检测到可用的本地智能体命令。请确认 Claude Code、Codex 或 OpenCode 已安装,并且对应命令在启动 LumenCode 的终端 PATH 中可用。
680
+ </div>
681
+ <div x-show="smartReportError" x-cloak style="margin-top:12px;padding:10px 12px;border:1px solid var(--dest);border-radius:6px;color:var(--dest);font-size:12px;" x-text="smartReportError"></div>
682
+ <div x-show="smartReportNeedsUpdate" x-cloak style="margin-top:12px;padding:10px 12px;border:1px solid var(--dest);border-radius:6px;color:var(--dest);font-size:12px;line-height:1.6;" x-text="smartReportUpdateMessage"></div>
683
+ <div class="smart-report-progress" x-show="smartReportLoading" x-cloak>
684
+ <div class="smart-report-progress-head">
685
+ <span x-text="smartReportStatusMessage || '正在调用本地智能体分析当前报告数据...'"></span>
686
+ <span class="font-mono" x-text="smartReportElapsedLabel"></span>
687
+ </div>
688
+ <div class="smart-report-progress-track" role="progressbar" aria-valuemin="0" aria-valuemax="100" :aria-valuenow="smartReportProgress" :aria-valuetext="smartReportProgress + '%'">
689
+ <span class="smart-report-progress-bar" :style="'width:' + smartReportProgress + '%'"></span>
690
+ </div>
691
+ <div class="smart-report-progress-foot">本地智能体生成时间受报告大小和模型响应影响,可刷新页面后继续查看。</div>
692
+ </div>
693
+ <div x-show="smartReportMarkdown" x-cloak style="margin-top:14px;display:flex;gap:8px;">
694
+ <button class="btn btn-outline" style="flex:1;justify-content:center;" @click="setReportContentMode('smart')">查看</button>
695
+ <button class="btn btn-outline" style="flex:1;justify-content:center;" @click="copySmartReport()">
696
+ <span x-text="smartReportCopied ? '已复制' : '复制'"></span>
697
+ </button>
698
+ <button class="btn btn-outline" @click="downloadSmartReport()">.md</button>
699
+ </div>
700
+ </div>
642
701
  <!-- Project Selector -->
643
702
  <div class="card" style="padding:16px 20px;" x-show="reportProjects.length > 1" x-cloak>
644
703
  <span class="label">PROJECTS · 项目</span>
@@ -677,6 +736,31 @@
677
736
  </div>
678
737
  </main>
679
738
 
739
+ <!-- ── Smart Report Style Modal ── -->
740
+ <div id="smart-report-style-modal" class="modal-overlay" x-show="smartReportStyleModalOpen" x-cloak>
741
+ <div class="modal-backdrop" @click="closeSmartReportStyleModal()"></div>
742
+ <div class="modal-panel" style="max-width:460px;">
743
+ <div class="modal-header">
744
+ <h3 style="font-size:15px;font-weight:500;">选择智能报告风格</h3>
745
+ <button class="rail-btn-icon" @click="closeSmartReportStyleModal()" style="color:var(--foreground);opacity:0.65;">
746
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
747
+ </button>
748
+ </div>
749
+ <div class="modal-body" style="padding:16px 24px;">
750
+ <div class="smart-report-style-options">
751
+ <button class="smart-report-style-option" :class="smartReportStyle === 'default' ? 'active' : ''" @click="confirmSmartReportStyle('default')">
752
+ <span class="smart-report-style-title">默认风格</span>
753
+ <span class="smart-report-style-desc">专业分析口径,强调数据摘要、工作亮点、关键洞察和风险建议。</span>
754
+ </button>
755
+ <button class="smart-report-style-option" :class="smartReportStyle === 'workhorse' ? 'active' : ''" @click="confirmSmartReportStyle('workhorse')">
756
+ <span class="smart-report-style-title">牛马</span>
757
+ <span class="smart-report-style-desc">面向领导汇报的表达倾向,突出工作投入、产出价值、风险兜底和下一步计划。</span>
758
+ </button>
759
+ </div>
760
+ </div>
761
+ </div>
762
+ </div>
763
+
680
764
  <!-- ── Hooks Confirm Modal ── -->
681
765
  <div id="hooksConfirmModal" class="modal-overlay" style="display:none;">
682
766
  <div class="modal-backdrop" @click="hideHooksConfirmModal()"></div>
@@ -855,4 +939,3 @@
855
939
  <script type="module" src="/app.js"></script>
856
940
  </body>
857
941
  </html>
858
-