lumencode 1.3.1 → 1.3.3

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/config.js CHANGED
@@ -30,6 +30,7 @@ export const SCENARIO_COLORS = {
30
30
  '阅读/研究': '#9878d0',
31
31
  '规划/设计': '#d49060',
32
32
  '代码审查': '#60b8b8',
33
+ '重构': '#7c6fae',
33
34
  '其他': '#989898',
34
35
  };
35
36
 
package/public/export.js CHANGED
@@ -1,6 +1,10 @@
1
1
  import { TEXT } from './config.js';
2
2
  import { esc, fmt, fmtShort, getChart } from './utils.js';
3
3
 
4
+ // 工具调用值兼容:{name: number} 或 {name: {calls, uses}}
5
+ const toolCalls = (v) => typeof v === 'number' ? v : (v.calls || 0);
6
+ const toolUses = (v) => typeof v === 'number' ? v : (v.uses || 0);
7
+
4
8
  // ── CSV 导出 ──
5
9
  export function exportCSV(data, period) {
6
10
  if (!data) return;
@@ -73,11 +77,11 @@ export function exportCSV(data, period) {
73
77
  lines.push('');
74
78
  }
75
79
 
76
- const toolEntries = Object.entries(usageStats.tools).sort((a, b) => b[1] - a[1]);
80
+ const toolEntries = Object.entries(usageStats.tools).sort((a, b) => toolCalls(b[1]) - toolCalls(a[1]));
77
81
  if (toolEntries.length > 0) {
78
82
  lines.push('# 工具使用');
79
- lines.push('工具,调用次数');
80
- for (const [name, count] of toolEntries) lines.push(`${name},${count}`);
83
+ lines.push('工具,调用次数,使用次数');
84
+ for (const [name, val] of toolEntries) lines.push(`${name},${toolCalls(val)},${toolUses(val)}`);
81
85
  lines.push('');
82
86
  }
83
87
 
@@ -121,7 +125,7 @@ export function printReport(data, period) {
121
125
 
122
126
  const projRows = Object.entries(usageStats.projects).sort((a, b) => b[1].requests - a[1].requests).map(([n, d]) => [n, d.requests, d.sessions instanceof Set ? d.sessions.size : (d.sessions || 0)]);
123
127
  const modelRows = Object.entries(usageStats.models).sort((a, b) => b[1].count - a[1].count).map(([n, d]) => [n, d.count, fmtShort(d.inputTokens), fmtShort(d.outputTokens)]);
124
- const toolRows = Object.entries(usageStats.tools).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([n, c]) => [n, c]);
128
+ const toolRows = Object.entries(usageStats.tools).sort((a, b) => toolCalls(b[1]) - toolCalls(a[1])).slice(0, 10).map(([n, v]) => [n, toolCalls(v), toolUses(v)]);
125
129
  const scenarioRows = Object.entries(usageStats.scenarios).sort((a, b) => b[1] - a[1]).map(([n, c]) => [n, c]);
126
130
 
127
131
  const html = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><title>Claude Code 使用${periodName}</title>
@@ -156,7 +160,7 @@ th{font-weight:600;background:#f8f9fa}
156
160
  </div>
157
161
  ${printTable('项目分布', ['项目', '请求数', '会话数'], projRows)}
158
162
  ${printTable('模型分布', ['模型', '请求数', '输入', '输出'], modelRows)}
159
- ${printTable('工具使用排行', ['工具', '调用次数'], toolRows)}
163
+ ${printTable('工具使用排行', ['工具', '调用次数', '使用次数'], toolRows)}
160
164
  ${printTable('场景分布', ['场景', '请求数'], scenarioRows)}
161
165
  ${gitStats && gitStats.commits > 0 ? printTable('Git 代码产出', ['指标', '数值'], (() => {
162
166
  const rows = [['提交次数', gitStats.commits], ['新增行数', '+' + fmt(gitStats.linesAdded)], ['删除行数', '-' + fmt(gitStats.linesDeleted)], ['变更文件', gitStats.filesChanged]];
@@ -229,7 +233,7 @@ export function exportHTML(data, period) {
229
233
 
230
234
  const projRows = Object.entries(usageStats.projects).sort((a, b) => b[1].requests - a[1].requests).map(([n, d]) => [n, d.requests, d.sessions instanceof Set ? d.sessions.size : (d.sessions || 0)]);
231
235
  const modelRows = Object.entries(usageStats.models).sort((a, b) => b[1].count - a[1].count).map(([n, d]) => [n, d.count, fmtShort(d.inputTokens), fmtShort(d.outputTokens), d.cost ? '$' + d.cost.toFixed(2) : '-']);
232
- const toolRows = Object.entries(usageStats.tools).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([n, c]) => [n, c]);
236
+ const toolRows = Object.entries(usageStats.tools).sort((a, b) => toolCalls(b[1]) - toolCalls(a[1])).slice(0, 10).map(([n, v]) => [n, toolCalls(v), toolUses(v)]);
233
237
  const scenarioRows = Object.entries(usageStats.scenarios).sort((a, b) => b[1] - a[1]).map(([n, c]) => [n, c]);
234
238
 
235
239
  const html = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><title>AI 编码助手使用${periodName}</title>
@@ -264,7 +268,7 @@ th{font-weight:600;background:#f8f9fa}
264
268
  </div>
265
269
  ${printTable('项目分布', ['项目', '请求数', '会话数'], projRows)}
266
270
  ${printTable('模型分布', ['模型', '请求数', '输入', '输出', '费用'], modelRows)}
267
- ${printTable('工具使用排行', ['工具', '调用次数'], toolRows)}
271
+ ${printTable('工具使用排行', ['工具', '调用次数', '使用次数'], toolRows)}
268
272
  ${printTable('场景分布', ['场景', '请求数'], scenarioRows)}
269
273
  ${data.costBreakdown?.models?.length ? printTable('模型费用', ['模型', '费用', '计费方式', '请求数'], data.costBreakdown.models.map(m => [m.name, '$' + (m.cost || 0).toFixed(2), m.mode === 'actual' ? '实际' : m.mode === 'estimated' ? '估算' : '未知', m.requests])) : ''}
270
274
  ${gitStats && gitStats.commits > 0 ? printTable('Git 代码产出', ['指标', '数值'], [['提交次数', gitStats.commits], ['新增行数', '+' + fmt(gitStats.linesAdded)], ['删除行数', '-' + fmt(gitStats.linesDeleted)], ['变更文件', gitStats.filesChanged]]) : ''}
package/public/index.html CHANGED
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html>
1
+ <!DOCTYPE html>
2
2
  <html lang="zh-CN">
3
3
  <head>
4
4
  <meta charset="UTF-8">
@@ -509,15 +509,31 @@
509
509
  <h2 class="section-head-title">工具调用排行</h2>
510
510
  <span class="label section-head-en">/ TOOL CALLS</span>
511
511
  <span class="section-head-line"></span>
512
+ <div style="display:flex;align-items:center;gap:8px;margin-left:auto;">
513
+ <div style="display:flex;gap:0;border:1px solid var(--border);border-radius:4px;overflow:hidden;">
514
+ <button class="period-btn" :class="toolRankTab === 'all' ? 'active' : ''" @click="setToolRankTab('all')" style="border:none;font-size:10px;padding:2px 8px;" x-text="'全部 ' + toolRankAllTotal"></button>
515
+ <button class="period-btn" :class="toolRankTab === 'skill' ? 'active' : ''" @click="setToolRankTab('skill')" style="border:none;font-size:10px;padding:2px 8px;" x-text="'Skill ' + toolRankSkillTotal"></button>
516
+ <button class="period-btn" :class="toolRankTab === 'mcp' ? 'active' : ''" @click="setToolRankTab('mcp')" style="border:none;font-size:10px;padding:2px 8px;" x-text="'MCP ' + toolRankMcpTotal"></button>
517
+ </div>
518
+ </div>
512
519
  </div>
513
520
  <div class="card" style="padding:24px;">
514
- <div id="toolCallsContainer" style="display:flex;flex-direction:column;gap:12px;">
515
- <template x-for="(t, i) in toolRankData" :key="t.name">
516
- <div class="tool-rank">
517
- <span class="font-mono" style="font-size:10px;opacity:0.4" x-text="String(i+1).padStart(2,'0')"></span>
518
- <span class="font-mono" style="font-size:12px" x-text="t.name"></span>
519
- <div class="bar-track"><div class="bar-fill" :style="'width:' + t.pct + '%;background:' + (i === 0 ? colors.rust : 'var(--foreground)')"></div></div>
520
- <span class="font-mono text-right" style="font-size:11px" x-text="t.value"></span>
521
+ <div id="toolCallsContainer" style="display:flex;flex-direction:column;gap:12px;max-height:420px;overflow-y:auto;padding-right:4px;">
522
+ <template x-for="(t, i) in toolRankData" :key="toolRankTab + '-' + t.name + '-' + i">
523
+ <div style="display:contents">
524
+ <!-- MCP 分组行 -->
525
+ <div x-show="t.isGroup" class="tool-rank-group">
526
+ <span class="font-mono" x-text="t.name"></span>
527
+ </div>
528
+ <!-- 普通排行行 -->
529
+ <div x-show="!t.isGroup" class="tool-rank" :class="toolRankTab === 'all' ? 'has-note' : ''">
530
+ <span class="font-mono" style="font-size:10px;opacity:0.4" x-text="String(t.rank || (i+1)).padStart(2,'0')"></span>
531
+ <span class="font-mono" style="font-size:12px" x-text="t.name" :title="t.name"></span>
532
+ <div class="bar-track"><div class="bar-fill" :style="'width:' + t.pct + '%;background:' + (t.rank === 1 ? colors.rust : 'var(--foreground)')"></div></div>
533
+ <span class="font-mono text-right" style="font-size:11px" x-text="t.calls === t.uses ? t.calls : t.calls + '/' + t.uses" :title="t.calls === t.uses ? '使用次数:' + t.calls : '调用次数 / 使用次数:' + t.calls + ' / ' + t.uses"></span>
534
+ <!-- 通俗备注(仅全部工具 Tab)-->
535
+ <span x-show="toolRankTab === 'all' && t.displayName" style="font-size:11px;opacity:0.45;white-space:nowrap;" x-text="t.displayName"></span>
536
+ </div>
521
537
  </div>
522
538
  </template>
523
539
  </div>
@@ -546,6 +562,7 @@
546
562
  <div style="display:flex;border:1px solid var(--border);border-radius:6px;overflow:hidden;">
547
563
  <button class="period-btn" :class="reportLevel === 'detailed' ? 'active' : ''" @click="setReportLevel('detailed')" style="border-left:none">详细 Detail</button>
548
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>
549
566
  </div>
550
567
  <button class="btn btn-outline" @click="copyReport()" style="display:inline-flex;align-items:center;gap:6px;">
551
568
  <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>
@@ -713,21 +730,64 @@
713
730
 
714
731
  <!-- ── Settings Modal ── -->
715
732
  <div id="settingsModal" class="modal-overlay" style="display:none;">
716
- <div class="modal-backdrop" onclick="document.getElementById('settingsModal').style.display='none'"></div>
717
- <div class="modal-panel">
733
+ <div class="modal-backdrop" onclick="closeSettings()"></div>
734
+ <div class="modal-panel" style="max-width:580px;">
718
735
  <div class="modal-header">
719
736
  <h3 style="font-size:15px;font-weight:500;">配置</h3>
720
- <button class="rail-btn-icon" onclick="document.getElementById('settingsModal').style.display='none'" style="color:var(--foreground);opacity:0.65;">
737
+ <button class="rail-btn-icon" onclick="closeSettings()" style="color:var(--foreground);opacity:0.65;">
721
738
  <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>
722
739
  </button>
723
740
  </div>
724
741
  <div class="modal-body">
725
- <div class="form-group"><label class="form-label">Claude 日志目录</label><input type="text" id="cfgClaudeDir" class="form-input"></div>
726
- <div class="form-group"><label class="form-label">本地项目路径(每行一个)</label><textarea id="cfgRepos" class="form-textarea" rows="3"></textarea></div>
727
- <div class="form-group"><label class="form-label">排除项目(每行一个)</label><textarea id="cfgExclude" class="form-textarea" rows="3"></textarea></div>
728
- <div class="form-group"><label class="form-label">场景关键词</label><textarea id="cfgKeywords" class="form-textarea" rows="5"></textarea><p class="form-hint">JSON 格式,如 { "coding": ["实现", "开发"] }</p></div>
742
+ <!-- 数据源 -->
743
+ <div class="cfg-section-title">数据源</div>
744
+ <div class="form-group">
745
+ <label class="form-label">Claude 日志目录</label>
746
+ <input type="text" id="cfgClaudeDir" class="form-input" placeholder="例如: C:/Users/xxx/.claude">
747
+ <p class="form-hint">Claude Code 会话日志的存放路径,通常是 ~/.claude</p>
748
+ </div>
749
+ <!-- 代码仓库 -->
750
+ <div class="cfg-section-title" style="margin-top:28px;">代码仓库</div>
751
+ <div class="form-group">
752
+ <label class="form-label">项目路径</label>
753
+ <div id="cfgReposTags" class="path-tags"></div>
754
+ <div class="path-add-box">
755
+ <span class="path-add-icon">
756
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
757
+ </span>
758
+ <input type="text" id="cfgReposInput" class="path-add-input" placeholder="粘贴或输入绝对路径..." onkeydown="if(event.key==='Enter')addPathTag('cfgReposTags','cfgReposInput')">
759
+ <button class="path-add-btn" onclick="addPathTag('cfgReposTags','cfgReposInput')">添加</button>
760
+ </div>
761
+ <p class="form-hint">按回车或点击添加,路径可单独删除</p>
762
+ </div>
763
+ <div class="form-group">
764
+ <label class="form-label">排除项目 <span style="opacity:0.4;font-weight:400;">(可选)</span></label>
765
+ <div id="cfgExcludeTags" class="path-tags"></div>
766
+ <div class="path-add-box">
767
+ <span class="path-add-icon">
768
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
769
+ </span>
770
+ <input type="text" id="cfgExcludeInput" class="path-add-input" placeholder="输入要排除的项目名..." onkeydown="if(event.key==='Enter')addPathTag('cfgExcludeTags','cfgExcludeInput')">
771
+ <button class="path-add-btn" onclick="addPathTag('cfgExcludeTags','cfgExcludeInput')">添加</button>
772
+ </div>
773
+ <p class="form-hint">按回车或点击添加,项目名称可单独删除</p>
774
+ </div>
775
+ <!-- 场景分类(默认折叠) -->
776
+ <div style="margin-top:28px;">
777
+ <button id="cfgKeywordsToggle" class="cfg-advanced-toggle" onclick="toggleKeywordsSection()">
778
+ <span>场景分类</span>
779
+ <svg id="cfgKeywordsArrow" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
780
+ </button>
781
+ <div id="cfgKeywordsSection" class="cfg-advanced-body" style="display:none;">
782
+ <p class="form-hint" style="margin-bottom:12px;">根据工具调用中的关键词自动判断工作场景,点击可编辑</p>
783
+ <div id="cfgKeywordsEditor"></div>
784
+ </div>
785
+ </div>
786
+ </div>
787
+ <div class="modal-footer">
788
+ <span id="cfgSaveHint" style="font-size:12px;margin-right:auto;"></span>
789
+ <button class="btn btn-primary" onclick="saveSettings()">保存配置</button>
729
790
  </div>
730
- <div class="modal-footer"><button class="btn btn-primary" onclick="saveSettings()">保存</button></div>
731
791
  </div>
732
792
  </div>
733
793
 
@@ -795,3 +855,4 @@
795
855
  <script type="module" src="/app.js"></script>
796
856
  </body>
797
857
  </html>
858
+
package/public/style.css CHANGED
@@ -759,7 +759,7 @@ input, textarea { font-family: inherit; color: inherit; }
759
759
  /* ── Tool Calls ── */
760
760
  .tool-rank {
761
761
  display: grid;
762
- grid-template-columns: 24px minmax(0, 1fr) 80px 48px;
762
+ grid-template-columns: 24px minmax(0, 1fr) 80px 64px;
763
763
  align-items: center;
764
764
  gap: 8px;
765
765
  margin-bottom: 12px;
@@ -770,6 +770,19 @@ input, textarea { font-family: inherit; color: inherit; }
770
770
  white-space: nowrap;
771
771
  }
772
772
  .tool-rank:last-child { margin-bottom: 0; }
773
+ .tool-rank.has-note {
774
+ grid-template-columns: 24px minmax(0, 1fr) 80px 64px 90px;
775
+ gap: 8px 12px;
776
+ }
777
+ .tool-rank-group {
778
+ display: flex;
779
+ align-items: center;
780
+ justify-content: center;
781
+ padding: 8px 0;
782
+ font-size: 11px;
783
+ opacity: 0.5;
784
+ letter-spacing: 0.08em;
785
+ }
773
786
 
774
787
  /* ── Timeline chart ── */
775
788
  .timeline-chart { position: relative; height: 280px; }
@@ -894,6 +907,240 @@ input, textarea { font-family: inherit; color: inherit; }
894
907
  .form-input:focus, .form-textarea:focus { border-color: var(--foreground); }
895
908
  .form-hint { font-size: 12px; opacity: 0.55; margin-top: 4px; }
896
909
 
910
+ /* ── Config Sections ── */
911
+ .cfg-section-title {
912
+ font-size: 11px;
913
+ font-weight: 600;
914
+ text-transform: uppercase;
915
+ letter-spacing: 0.5px;
916
+ color: var(--muted-foreground);
917
+ margin-bottom: 12px;
918
+ padding-bottom: 6px;
919
+ border-bottom: 1px solid var(--border);
920
+ }
921
+
922
+ /* ── Path Tags ── */
923
+ .path-tags {
924
+ display: flex;
925
+ flex-direction: column;
926
+ gap: 8px;
927
+ margin-bottom: 10px;
928
+ min-height: 0;
929
+ }
930
+ .path-tag {
931
+ display: flex;
932
+ align-items: center;
933
+ gap: 10px;
934
+ padding: 10px 14px;
935
+ background: var(--input-background);
936
+ border: 1px solid var(--border);
937
+ border-radius: 8px;
938
+ font-size: 13px;
939
+ color: var(--foreground);
940
+ transition: background 0.15s ease;
941
+ }
942
+ .path-tag:hover {
943
+ background: var(--muted);
944
+ }
945
+ .path-tag-icon {
946
+ flex-shrink: 0;
947
+ color: var(--accent);
948
+ opacity: 0.85;
949
+ }
950
+ .path-tag-icon svg {
951
+ display: block;
952
+ }
953
+ .path-tag-text {
954
+ flex: 1;
955
+ min-width: 0;
956
+ overflow: hidden;
957
+ text-overflow: ellipsis;
958
+ white-space: nowrap;
959
+ font-family: var(--font-mono);
960
+ font-size: 12px;
961
+ }
962
+ .path-tag-remove {
963
+ flex-shrink: 0;
964
+ width: 22px;
965
+ height: 22px;
966
+ display: flex;
967
+ align-items: center;
968
+ justify-content: center;
969
+ border-radius: 5px;
970
+ cursor: pointer;
971
+ color: var(--muted-foreground);
972
+ background: transparent;
973
+ border: none;
974
+ padding: 0;
975
+ transition: all 0.15s ease;
976
+ }
977
+ .path-tag-remove:hover {
978
+ color: var(--destructive);
979
+ background: var(--destructive-foreground);
980
+ }
981
+ .path-tag-remove svg {
982
+ display: block;
983
+ }
984
+
985
+ /* ── Path Add Box ── */
986
+ .path-add-box {
987
+ display: flex;
988
+ align-items: center;
989
+ gap: 8px;
990
+ padding: 8px 12px;
991
+ border: 1px dashed var(--border);
992
+ border-radius: 8px;
993
+ background: transparent;
994
+ transition: border-color 0.15s ease, background 0.15s ease;
995
+ }
996
+ .path-add-box:focus-within {
997
+ border-color: var(--ring);
998
+ background: var(--input-background);
999
+ border-style: solid;
1000
+ }
1001
+ .path-add-icon {
1002
+ flex-shrink: 0;
1003
+ color: var(--muted-foreground);
1004
+ opacity: 0.6;
1005
+ display: flex;
1006
+ align-items: center;
1007
+ }
1008
+ .path-add-input {
1009
+ flex: 1;
1010
+ min-width: 0;
1011
+ border: none;
1012
+ outline: none;
1013
+ background: transparent;
1014
+ font-size: 13px;
1015
+ color: var(--foreground);
1016
+ padding: 0;
1017
+ }
1018
+ .path-add-input::placeholder {
1019
+ color: var(--muted-foreground);
1020
+ opacity: 0.55;
1021
+ }
1022
+ .path-add-btn {
1023
+ flex-shrink: 0;
1024
+ padding: 5px 14px;
1025
+ font-size: 12px;
1026
+ font-weight: 500;
1027
+ border-radius: 6px;
1028
+ border: 1px solid var(--border);
1029
+ background: var(--input-background);
1030
+ color: var(--foreground);
1031
+ cursor: pointer;
1032
+ transition: all 0.15s ease;
1033
+ }
1034
+ .path-add-btn:hover {
1035
+ background: var(--foreground);
1036
+ color: var(--background);
1037
+ border-color: var(--foreground);
1038
+ }
1039
+
1040
+ /* ── Advanced Toggle ── */
1041
+ .cfg-advanced-toggle {
1042
+ display: flex;
1043
+ align-items: center;
1044
+ justify-content: space-between;
1045
+ width: 100%;
1046
+ padding: 10px 0;
1047
+ font-size: 11px;
1048
+ font-weight: 600;
1049
+ text-transform: uppercase;
1050
+ letter-spacing: 0.5px;
1051
+ color: var(--muted-foreground);
1052
+ background: none;
1053
+ border: none;
1054
+ border-bottom: 1px solid var(--border);
1055
+ cursor: pointer;
1056
+ transition: color 0.15s ease;
1057
+ }
1058
+ .cfg-advanced-toggle:hover {
1059
+ color: var(--foreground);
1060
+ }
1061
+ .cfg-advanced-toggle svg {
1062
+ transition: transform 0.2s ease;
1063
+ }
1064
+ .cfg-advanced-toggle.expanded svg {
1065
+ transform: rotate(180deg);
1066
+ }
1067
+ .cfg-advanced-body {
1068
+ padding-top: 16px;
1069
+ }
1070
+
1071
+ /* ── Keywords Editor ── */
1072
+ .kw-row {
1073
+ display: flex;
1074
+ align-items: flex-start;
1075
+ gap: 10px;
1076
+ margin-bottom: 10px;
1077
+ }
1078
+ .kw-label {
1079
+ width: 48px;
1080
+ flex-shrink: 0;
1081
+ font-size: 12px;
1082
+ font-weight: 500;
1083
+ padding-top: 4px;
1084
+ text-align: right;
1085
+ opacity: 0.7;
1086
+ }
1087
+ .kw-tags {
1088
+ flex: 1;
1089
+ display: flex;
1090
+ flex-wrap: wrap;
1091
+ gap: 4px;
1092
+ align-items: center;
1093
+ }
1094
+ .kw-tag {
1095
+ display: inline-flex;
1096
+ align-items: center;
1097
+ gap: 3px;
1098
+ padding: 2px 8px;
1099
+ border-radius: 4px;
1100
+ background: var(--secondary);
1101
+ font-size: 12px;
1102
+ line-height: 1.5;
1103
+ cursor: default;
1104
+ transition: background 0.15s;
1105
+ }
1106
+ .kw-tag:hover { background: var(--border); }
1107
+ .kw-tag-remove {
1108
+ cursor: pointer;
1109
+ opacity: 0.35;
1110
+ font-size: 14px;
1111
+ line-height: 1;
1112
+ margin-left: 1px;
1113
+ }
1114
+ .kw-tag-remove:hover { opacity: 0.8; }
1115
+ .kw-add-row {
1116
+ display: flex;
1117
+ gap: 4px;
1118
+ margin-top: 2px;
1119
+ }
1120
+ .kw-add-input {
1121
+ padding: 2px 8px;
1122
+ font-size: 12px;
1123
+ border: 1px solid var(--border);
1124
+ border-radius: 4px;
1125
+ background: var(--input-background);
1126
+ color: var(--foreground);
1127
+ outline: none;
1128
+ width: 100px;
1129
+ }
1130
+ .kw-add-input:focus { border-color: var(--foreground); }
1131
+ .kw-add-btn {
1132
+ font-size: 12px;
1133
+ padding: 2px 8px;
1134
+ border: 1px dashed var(--border);
1135
+ border-radius: 4px;
1136
+ background: none;
1137
+ color: var(--muted-foreground);
1138
+ cursor: pointer;
1139
+ }
1140
+ .kw-add-btn:hover { border-color: var(--foreground); color: var(--foreground); }
1141
+ .cfg-save-ok { color: #22c55e; }
1142
+ .cfg-save-err { color: var(--dest); }
1143
+
897
1144
  /* ── Work Report Content ── */
898
1145
  .work-report-content {
899
1146
  background: var(--card);