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/index.js +348 -343
- package/lib/aggregate.js +58 -3
- package/lib/attribution.js +8 -0
- package/lib/config.js +21 -8
- package/lib/git.js +195 -192
- package/lib/path-utils.js +18 -0
- package/lib/report.js +974 -54
- package/lib/scenario.js +29 -4
- package/lib/server.js +331 -316
- package/package.json +1 -1
- package/public/app.js +1170 -952
- package/public/config.js +1 -0
- package/public/export.js +11 -7
- package/public/index.html +77 -16
- package/public/style.css +248 -1
- package/public/utils.js +218 -0
package/public/config.js
CHANGED
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,
|
|
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,
|
|
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,
|
|
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
|
|
517
|
-
|
|
518
|
-
<
|
|
519
|
-
|
|
520
|
-
|
|
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="
|
|
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="
|
|
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
|
-
|
|
726
|
-
<div class="
|
|
727
|
-
<div class="form-group"
|
|
728
|
-
|
|
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
|
|
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);
|