diff --git a/LLM_API.py b/LLM_API.py index 0ebbd75..d944804 100644 --- a/LLM_API.py +++ b/LLM_API.py @@ -7,6 +7,9 @@ base_url="http://35.164.11.19:3887/v1" ) +# LLM 调用超时(秒),避免 API 挂死时程序一直等待 +LLM_TIMEOUT = 600 # 10 分钟 + def callOpenAI(prompt: str) -> str: completion = client.chat.completions.create( messages=[ @@ -14,9 +17,7 @@ def callOpenAI(prompt: str) -> str: {"role": "user", "content": prompt} ], model="gpt-5.1", - #model= "gpt-4o-2024-08-06", - #model="gpt-4-1106-preview", - #model="gpt-4-0314", + timeout=LLM_TIMEOUT, ) message = completion.choices[0].message diff --git a/LLM_API_KJY.py b/LLM_API_KJY.py index 8cd7004..deff973 100644 --- a/LLM_API_KJY.py +++ b/LLM_API_KJY.py @@ -7,17 +7,17 @@ base_url="https://uni-api.cstcloud.cn/v1" ) +# LLM 调用超时(秒),避免 API 挂死时程序一直等待;大模型如 qwen3:235b 可能需数分钟 +LLM_TIMEOUT = 600 # 10 分钟 + def callOpenAI_KJY(prompt: str, modelname) -> str: completion = client.chat.completions.create( messages=[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt} ], - #model="deepseek-r1:671b", model=modelname, - #model= "gpt-4o-2024-08-06", - #model="gpt-4-1106-preview", - #model="gpt-4-0314", + timeout=LLM_TIMEOUT, ) message = completion.choices[0].message diff --git a/chipfuzz/assets/cover-flowchart.png b/chipfuzz/assets/cover-flowchart.png new file mode 100644 index 0000000..df54839 Binary files /dev/null and b/chipfuzz/assets/cover-flowchart.png differ diff --git a/chipfuzz/assets/main.js b/chipfuzz/assets/main.js index a23e1e5..42b5c0d 100644 --- a/chipfuzz/assets/main.js +++ b/chipfuzz/assets/main.js @@ -174,11 +174,17 @@ const setLogHint = (t) => { if (logHintEl) logHintEl.textContent = t; }; + // 限制实时日志行数,避免长时间运行后 DOM 过大导致卡死 + const LOG_MAX_LINES = 3000; + const LOG_TRIM_TO = 2400; const appendLog = (line) => { if (!logOutEl) return; logOutEl.textContent += (logOutEl.textContent ? "\n" : "") + line; + const lines = logOutEl.textContent.split("\n"); + if (lines.length > LOG_MAX_LINES) { + logOutEl.textContent = lines.slice(-LOG_TRIM_TO).join("\n"); + } logOutEl.scrollTop = logOutEl.scrollHeight; - // 解析覆盖率数据 parseCoverageData(line); }; @@ -251,11 +257,8 @@ item.textContent = codeLine; item.title = `第 ${lineNum} 行: ${codeLine}`; recentCoverageEl.insertBefore(item, recentCoverageEl.firstChild); - - // 限制数量 - const items = recentCoverageEl.querySelectorAll('.recent__item'); - if (items.length > 30) { - items[items.length - 1].remove(); + if (recentCoverageEl.children.length > 30) { + recentCoverageEl.lastElementChild?.remove(); } // 收集够了就停止 @@ -364,10 +367,10 @@ const delta = currentCoverage - initialCoverageValue; if (delta > 0) { coverageDeltaEl.textContent = `+${delta.toFixed(3)}%`; - coverageDeltaEl.style.color = "#4ade80"; // 绿色 + coverageDeltaEl.style.color = "var(--success, #34d399)"; // 绿色 } else if (delta < 0) { coverageDeltaEl.textContent = `${delta.toFixed(3)}%`; - coverageDeltaEl.style.color = "#f87171"; // 红色 + coverageDeltaEl.style.color = "var(--danger, #f87171)"; // 红色 } else { coverageDeltaEl.textContent = "+0%"; coverageDeltaEl.style.color = "#888"; // 灰色 @@ -422,8 +425,8 @@ datasets: [{ label: isBar ? "等待数据" : "覆盖率 (%)", data: isBar ? [0] : [], - backgroundColor: isBar ? 'rgba(169, 182, 218, 0.2)' : 'rgba(231, 76, 60, 0.1)', - borderColor: isBar ? 'rgba(169, 182, 218, 0.5)' : 'rgba(231, 76, 60, 0.5)', + backgroundColor: isBar ? 'rgba(45, 212, 191, 0.35)' : 'rgba(45, 212, 191, 0.2)', + borderColor: isBar ? 'rgba(45, 212, 191, 0.9)' : 'rgba(45, 212, 191, 0.9)', borderWidth: 1 }] }, @@ -433,22 +436,22 @@ scales: isBar ? { y: { beginAtZero: true, - grid: { color: 'rgba(255, 255, 255, 0.05)' }, - ticks: { color: 'rgba(169, 182, 218, 0.8)', font: { size: 10 } } + grid: { color: 'rgba(0, 0, 0, 0.08)' }, + ticks: { color: '#475569', font: { size: 13, weight: '500' } } }, x: { - grid: { color: 'rgba(255, 255, 255, 0.05)' }, - ticks: { color: 'rgba(169, 182, 218, 0.8)', font: { size: 10 } } + grid: { color: 'rgba(0, 0, 0, 0.08)' }, + ticks: { color: '#475569', font: { size: 13, weight: '500' } } } } : { y: { beginAtZero: false, - grid: { color: 'rgba(255, 255, 255, 0.05)' }, - ticks: { color: 'rgba(169, 182, 218, 0.8)', font: { size: 10 } } + grid: { color: 'rgba(0, 0, 0, 0.08)' }, + ticks: { color: '#475569', font: { size: 13, weight: '500' } } }, x: { - grid: { color: 'rgba(255, 255, 255, 0.05)' }, - ticks: { color: 'rgba(169, 182, 218, 0.8)', font: { size: 10 } } + grid: { color: 'rgba(0, 0, 0, 0.08)' }, + ticks: { color: '#475569', font: { size: 13, weight: '500' } } } }, plugins: { @@ -456,9 +459,9 @@ display: true, position: 'top', labels: { - color: 'rgba(169, 182, 218, 0.9)', - font: { size: 10 }, - padding: 8, + color: '#334155', + font: { size: 13, weight: '600' }, + padding: 10, usePointStyle: true } } @@ -501,11 +504,11 @@ const rate = summary.compile_success_rate || 0; if (rate > 0) { compileSuccessRateEl.textContent = `${rate.toFixed(2)}%`; - compileSuccessRateEl.style.color = rate >= 80 ? "#4ade80" : rate >= 50 ? "#fbbf24" : "#f87171"; + compileSuccessRateEl.style.color = rate >= 80 ? "var(--success, #34d399)" : rate >= 50 ? "var(--warning, #fbbf24)" : "var(--danger, #f87171)"; console.log(`[统计] 编译成功率: ${rate.toFixed(2)}%`); } else { compileSuccessRateEl.textContent = "-"; - compileSuccessRateEl.style.color = "#888"; + compileSuccessRateEl.style.color = "var(--muted)"; console.log('[统计] 编译成功率: 无数据'); } } @@ -518,7 +521,7 @@ const rate = summary.emulator_success_rate || 0; if (rate > 0) { emulatorSuccessRateEl.textContent = `${rate.toFixed(2)}%`; - emulatorSuccessRateEl.style.color = rate >= 80 ? "#4ade80" : rate >= 50 ? "#fbbf24" : "#f87171"; + emulatorSuccessRateEl.style.color = rate >= 80 ? "var(--success, #34d399)" : rate >= 50 ? "var(--warning, #fbbf24)" : "var(--danger, #f87171)"; console.log(`[统计] 模拟器执行成功率: ${rate.toFixed(2)}%`); } else { emulatorSuccessRateEl.textContent = "-"; @@ -535,7 +538,7 @@ const rate = summary.coverage_improved_rate ?? 0; if (rate > 0 || (summary.total_llm_generations || 0) > 0) { coverageImprovedRateEl.textContent = `${(rate || 0).toFixed(2)}%`; - coverageImprovedRateEl.style.color = rate >= 80 ? "#4ade80" : rate >= 50 ? "#fbbf24" : "#f87171"; + coverageImprovedRateEl.style.color = rate >= 80 ? "var(--success, #34d399)" : rate >= 50 ? "var(--warning, #fbbf24)" : "var(--danger, #f87171)"; console.log(`[统计] 占 LLM 生成比例: ${(rate || 0).toFixed(2)}%`); } else { coverageImprovedRateEl.textContent = "-"; @@ -620,15 +623,15 @@ { label: 'LLM 生成次数', data: llmCounts, - backgroundColor: 'rgba(74, 144, 226, 0.6)', - borderColor: 'rgba(74, 144, 226, 1)', + backgroundColor: 'rgba(45, 212, 191, 0.5)', + borderColor: 'rgba(45, 212, 191, 0.95)', borderWidth: 1 }, { label: '模拟器成功执行次数', data: emulatorCounts, - backgroundColor: 'rgba(80, 200, 120, 0.6)', - borderColor: 'rgba(80, 200, 120, 1)', + backgroundColor: 'rgba(94, 234, 212, 0.45)', + borderColor: 'rgba(94, 234, 212, 0.9)', borderWidth: 1 } ] @@ -639,25 +642,14 @@ scales: { y: { beginAtZero: true, - grid: { - color: 'rgba(255, 255, 255, 0.05)' - }, - ticks: { - color: 'rgba(169, 182, 218, 0.8)', - font: { - size: 10 - } - } + grid: { color: 'rgba(0, 0, 0, 0.08)' }, + ticks: { color: '#475569', font: { size: 13, weight: '500' } } }, x: { - grid: { - color: 'rgba(255, 255, 255, 0.05)' - }, + grid: { color: 'rgba(0, 0, 0, 0.08)' }, ticks: { - color: 'rgba(169, 182, 218, 0.8)', - font: { - size: 10 - }, + color: '#475569', + font: { size: 13, weight: '500' }, maxRotation: 45, minRotation: 0 } @@ -668,11 +660,9 @@ display: true, position: 'top', labels: { - color: 'rgba(169, 182, 218, 0.9)', - font: { - size: 10 - }, - padding: 8, + color: '#334155', + font: { size: 13, weight: '600' }, + padding: 10, usePointStyle: true } } @@ -695,14 +685,20 @@ return; } - // 按时间排序 + // 按时间排序;若出现“先升后降”,把峰点当作异常值剔除 const sortedData = [...coverageData].sort((a, b) => a.timestamp - b.timestamp); - - const timestamps = sortedData.map(d => { + const toRemove = new Set(); + for (let i = 0; i < sortedData.length - 1; i++) { + const pct = Number(sortedData[i].coverage_percentage) || 0; + const nextPct = Number(sortedData[i + 1].coverage_percentage) || 0; + if (pct > nextPct) toRemove.add(i); // 峰点(升后降的“升”)标为异常 + } + const cleaned = sortedData.filter((_, i) => !toRemove.has(i)); + const timestamps = cleaned.map(d => { const date = new Date(d.timestamp * 1000); return date.toLocaleTimeString(); }); - const coveragePercentages = sortedData.map(d => d.coverage_percentage || 0); + const coveragePercentages = cleaned.map(d => Number(d.coverage_percentage) || 0); if (coverageChart) { coverageChart.data.labels = timestamps; @@ -716,8 +712,8 @@ datasets: [{ label: '覆盖率 (%)', data: coveragePercentages, - borderColor: 'rgba(231, 76, 60, 1)', - backgroundColor: 'rgba(231, 76, 60, 0.1)', + borderColor: 'rgba(45, 212, 191, 0.95)', + backgroundColor: 'rgba(45, 212, 191, 0.18)', borderWidth: 2, fill: true, tension: 0.4 @@ -729,34 +725,19 @@ scales: { y: { beginAtZero: false, - grid: { - color: 'rgba(255, 255, 255, 0.05)' - }, - ticks: { - color: 'rgba(169, 182, 218, 0.8)', - font: { - size: 10 - } - }, - title: { - display: false - } + grid: { color: 'rgba(0, 0, 0, 0.08)' }, + ticks: { color: '#475569', font: { size: 13, weight: '500' } }, + title: { display: false } }, x: { - grid: { - color: 'rgba(255, 255, 255, 0.05)' - }, + grid: { color: 'rgba(0, 0, 0, 0.08)' }, ticks: { - color: 'rgba(169, 182, 218, 0.8)', - font: { - size: 10 - }, + color: '#475569', + font: { size: 13, weight: '500' }, maxRotation: 45, minRotation: 0 }, - title: { - display: false - } + title: { display: false } } }, plugins: { @@ -764,11 +745,9 @@ display: true, position: 'top', labels: { - color: 'rgba(169, 182, 218, 0.9)', - font: { - size: 10 - }, - padding: 8, + color: '#334155', + font: { size: 13, weight: '600' }, + padding: 10, usePointStyle: true } } @@ -853,6 +832,13 @@ return { base, token, runId }; }; + const stopL2Polling = () => { + if (l2Timer) { + clearInterval(l2Timer); + l2Timer = null; + } + }; + const disconnect = () => { if (es) { es.close(); @@ -865,16 +851,27 @@ stopCoveragePolling(); stopSuccessSeedsPolling(); stopStatisticsPolling(); + stopL2Polling(); setConnState("未连接"); setLogHint("等待连接…"); }; const authHeaders = (token) => (token ? { Authorization: `Bearer ${token}` } : {}); + const FETCH_TIMEOUT_MS = 30000; const fetchJson = async (url, token) => { - const res = await fetch(url, { headers: { ...authHeaders(token) } }); - if (!res.ok) throw new Error(`HTTP ${res.status}`); - return await res.json(); + const ctrl = new AbortController(); + const tid = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS); + try { + const res = await fetch(url, { headers: { ...authHeaders(token) }, signal: ctrl.signal }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + return await res.json(); + } catch (e) { + if (e.name === "AbortError") throw new Error("请求超时"); + throw e; + } finally { + clearTimeout(tid); + } }; const connectSSE = ({ base, token, runId }) => { @@ -930,7 +927,7 @@ } }; tick(); - pollTimer = setInterval(tick, 1500); + pollTimer = setInterval(tick, 2000); // 2s 轮询,减轻服务与前端压力 }; const connect = async () => { @@ -970,6 +967,9 @@ connectPolling({ base, token, runId }); }; + const paramModuleEl = document.getElementById("paramModule"); + const paramStartModuleIndexEl = document.getElementById("paramStartModuleIndex"); + if (btnStartRun) { btnStartRun.addEventListener("click", async () => { const { base, token } = getConfig(); @@ -979,16 +979,18 @@ } // 获取任务参数 + const startIdx = parseInt(paramStartModuleIndexEl?.value, 10); const params = { - module: document.getElementById("paramModule")?.value || "Bku", + module: (paramModuleEl?.value || "").trim() || "LogPerfEndpoint", + start_module_index: (Number.isNaN(startIdx) || startIdx < 1) ? null : startIdx, model: document.getElementById("paramModel")?.value || "qwen3:235b", mode: document.getElementById("paramMode")?.value || "continue", max_iterations: parseInt(document.getElementById("paramMaxIterations")?.value) || 13, num: parseInt(document.getElementById("paramNum")?.value) || 100, - auto_switch: document.getElementById("paramAutoSwitch")?.checked ?? true, // 默认true(checkbox默认checked) + auto_switch: document.getElementById("paramAutoSwitch")?.checked ?? true, use_spec: document.getElementById("paramUseSpec")?.checked || false, run_existing_seeds: document.getElementById("paramRunExistingSeeds")?.checked || false, - // 使用默认路径,不再从前端获取 + llm_report: document.getElementById("paramLlmReport")?.checked || false, coverage_filename_origin: "/root/XiangShan/logs/annotated/", coverage_filename_later: "/root/XiangShan/logs2/annotated/", global_annotated_dir: "/root/XiangShan/logs_global/annotated", @@ -1127,12 +1129,11 @@ // 更新模块列表 if (l2ModulesListEl && data.modules) { l2ModulesListEl.innerHTML = ""; - + for (const [name, stats] of Object.entries(data.modules)) { const item = document.createElement("div"); item.className = "l2-module-item"; - item.style.cssText = "padding: 8px 12px; background: var(--card-bg); border-radius: 6px; border: 1px solid var(--border);"; - + if (stats.exists) { const rate = stats.coverage_rate; let statusIcon = "🔴"; @@ -1144,28 +1145,22 @@ statusIcon = "🟡"; statusColor = "#eab308"; } - item.innerHTML = ` -
- ${statusIcon} ${name} - ${rate}% -
-
- ${stats.covered_lines}/${stats.total_lines} 行 +
+ ${statusIcon} ${name} + ${rate}%
+
${stats.covered_lines}/${stats.total_lines} 行
`; } else { item.innerHTML = ` -
- ⚪ ${name} - N/A -
-
- 文件不存在 +
+ ⚪ ${name} + N/A
+
文件不存在
`; } - l2ModulesListEl.appendChild(item); } } diff --git a/chipfuzz/assets/style.css b/chipfuzz/assets/style.css index f99583f..806261a 100644 --- a/chipfuzz/assets/style.css +++ b/chipfuzz/assets/style.css @@ -1,997 +1,1286 @@ -:root{ - --bg: #0b1020; - --bg2:#0f1730; - --card:#101a36; - --text:#e9eefc; - --muted:#a9b6da; - --line: rgba(255,255,255,.10); - --shadow: 0 18px 50px rgba(0,0,0,.35); - --primary:#7c5cff; - --primary2:#4ee6b5; - --danger:#ff4d6d; - --radius: 18px; - --radius2: 14px; - --container: 1120px; - --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - --sans: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif; -} - -[data-theme="light"]{ - --bg:#f6f7fb; - --bg2:#ffffff; - --card:#ffffff; - --text:#0f172a; - --muted:#475569; - --line: rgba(15,23,42,.12); - --shadow: 0 18px 45px rgba(2,6,23,.12); - --primary:#5b4bff; - --primary2:#1ad6a3; -} - -*{box-sizing:border-box} -html,body{height:100%} -body{ - margin:0; - font-family:var(--sans); - color:var(--text); - background: - radial-gradient(900px 500px at 15% 10%, rgba(124,92,255,.20), transparent 60%), - radial-gradient(900px 500px at 80% 20%, rgba(78,230,181,.16), transparent 60%), - radial-gradient(900px 700px at 50% 120%, rgba(255,77,109,.10), transparent 60%), - linear-gradient(180deg, var(--bg), var(--bg2)); - line-height:1.55; -} - -a{color:inherit; text-decoration:none} -b{font-weight:700} - -.container{ +/* ============================================================ + ChipFuzzer — 全站视觉大改:配色 / 字体 / 卡片 / 表单 / 指标 + ============================================================ */ + +:root { + /* 主色:深青绿 teal,参考图高级感 */ + --bg: #0f1419; + --bg2: #161b22; + --bg3: #1c2128; + --card: #161b22; + --card-hover: #1f2937; + --text: #e6edf3; + --text-soft: #b1bac4; + --muted: #8b949e; + --line: rgba(255, 255, 255, 0.08); + --line-strong: rgba(255, 255, 255, 0.12); + --shadow: 0 6px 28px rgba(0, 0, 0, 0.3); + --shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.35); + --primary: #0d9488; + --primary-dim: rgba(13, 148, 136, 0.14); + --primary-border: rgba(13, 148, 136, 0.38); + --success: #22c55e; + --success-dim: rgba(34, 197, 94, 0.12); + --danger: #ef4444; + --danger-dim: rgba(239, 68, 68, 0.12); + --warning: #eab308; + --radius: 12px; + --radius-sm: 8px; + --radius-lg: 16px; + --container: 1920px; + --space-xs: 6px; + --space-sm: 12px; + --space-md: 20px; + --space-lg: 28px; + --space-xl: 40px; + --space-2xl: 56px; + --mono: "JetBrains Mono", "Fira Code", ui-monospace, "Cascadia Code", Consolas, monospace; + --sans: "Outfit", ui-sans-serif, system-ui, -apple-system, sans-serif; +} + +[data-theme="light"] { + /* 浅色主题:端庄大气,有层次、有主色点缀 */ + --bg: #f5f6f8; + --bg2: #fafbfc; + --bg3: #e8eaed; + --card: #ffffff; + --card-hover: #f8f9fa; + --text: #1a1d24; + --text-soft: #3d4452; + --muted: #6b7280; + --line: rgba(0, 0, 0, 0.08); + --line-strong: rgba(0, 0, 0, 0.14); + --shadow: 0 2px 12px rgba(0, 0, 0, 0.04), 0 1px 0 rgba(15, 118, 110, 0.03); + --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.08), 0 2px 0 rgba(15, 118, 110, 0.02); + --primary: #0f766e; + --primary-dim: rgba(15, 118, 110, 0.08); + --primary-border: rgba(15, 118, 110, 0.28); + --success: #16a34a; + --success-dim: rgba(34, 197, 94, 0.12); + --danger: #dc2626; + --danger-dim: rgba(220, 38, 38, 0.1); + --warning: #ca8a04; +} + +* { box-sizing: border-box; } +html { font-size: 24px; } +html, body { height: 100%; } +body { + margin: 0; + font-family: var(--sans); + font-size: 1rem; + font-weight: 500; + color: var(--text); + background: var(--bg); + background-image: radial-gradient(ellipse 90% 50% at 50% -10%, rgba(13, 148, 136, 0.07), transparent 60%); + line-height: 1.6; + -webkit-font-smoothing: antialiased; +} +[data-theme="light"] body { + background: linear-gradient(180deg, #f8f9fb 0%, #f0f2f5 100%); + background-attachment: fixed; +} +[data-theme="light"] body::before { + content: ""; + position: fixed; + inset: 0; + background: radial-gradient(ellipse 90% 60% at 50% -10%, rgba(15, 118, 110, 0.04), transparent 50%), + radial-gradient(ellipse 60% 40% at 80% 100%, rgba(15, 118, 110, 0.02), transparent 45%); + pointer-events: none; + z-index: 0; +} + +main { position: relative; z-index: 1; } +a { color: inherit; text-decoration: none; } +b { font-weight: 700; } + +.container { max-width: var(--container); margin: 0 auto; - padding: 0 20px; + padding: 0 16px; } -.skip-link{ - position:absolute; - left:-999px; - top:10px; - background:var(--card); - border:1px solid var(--line); - padding:10px 14px; - border-radius:12px; - z-index:999; +.skip-link { + position: absolute; + left: -999px; + top: 10px; + background: var(--card); + border: 1px solid var(--line); + padding: 10px 14px; + border-radius: var(--radius-sm); + z-index: 999; } -.skip-link:focus{left:10px} +.skip-link:focus { left: 12px; } -.header{ +/* ----- Header ----- */ +.header { position: sticky; top: 0; z-index: 50; - backdrop-filter: blur(14px); - background: rgba(10, 15, 30, .55); + background: rgba(16, 24, 38, 0.88); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border-bottom: 1px solid var(--line); +} +[data-theme="light"] .header { + background: linear-gradient(180deg, rgba(255,255,255,0.98) 0%, rgba(248,250,252,0.95) 100%); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); border-bottom: 1px solid var(--line); + box-shadow: 0 1px 0 rgba(15, 118, 110, 0.04); } -[data-theme="light"] .header{background: rgba(255,255,255,.70)} -.header__inner{ - height: 74px; - display:flex; - align-items:center; - justify-content:space-between; +.header__inner { + height: 64px; + display: flex; + align-items: center; + justify-content: space-between; gap: 16px; } -.brand{display:flex; align-items:center; gap:12px; min-width:220px} -.brand__logo{ - width:42px; height:42px; - border-radius:14px; - display:grid; place-items:center; - background: - radial-gradient(12px 12px at 30% 30%, rgba(255,255,255,.55), transparent 60%), - linear-gradient(135deg, rgba(124,92,255,.95), rgba(78,230,181,.85)); - box-shadow: 0 10px 26px rgba(124,92,255,.25); - font-weight:800; - letter-spacing:.5px; -} -.brand--sm .brand__logo{width:36px;height:36px;border-radius:13px} -.brand__name{font-weight:800} -.brand__tag{color:var(--muted); font-size:12px; margin-top:2px} - -.nav{display:flex; gap:16px; align-items:center} -.nav__link{ - padding:10px 10px; - border-radius:12px; - color:var(--muted); - font-weight:600; - font-size:14px; -} -.nav__link:hover{background: rgba(255,255,255,.06); color: var(--text)} -[data-theme="light"] .nav__link:hover{background: rgba(15,23,42,.06)} - -.header__actions{display:flex; gap:10px; align-items:center} - -.btn{ - display:inline-flex; - align-items:center; - justify-content:center; - gap:10px; - border-radius: 14px; - border: 1px solid var(--line); - padding: 11px 14px; - font-weight: 700; - font-size: 14px; - cursor:pointer; - user-select:none; - background: rgba(255,255,255,.04); - transition: transform .12s ease, background .12s ease, border-color .12s ease; -} -.btn:hover{transform: translateY(-1px); background: rgba(255,255,255,.07)} -[data-theme="light"] .btn{background: rgba(15,23,42,.03)} -[data-theme="light"] .btn:hover{background: rgba(15,23,42,.06)} -.btn:active{transform: translateY(0)} - -.btn--primary{ - border-color: rgba(124,92,255,.55); - background: linear-gradient(135deg, rgba(124,92,255,.96), rgba(78,230,181,.80)); - color: #06101a; - box-shadow: 0 18px 36px rgba(124,92,255,.22); -} -.btn--primary:hover{background: linear-gradient(135deg, rgba(124,92,255,1), rgba(78,230,181,.88))} -.btn--ghost{background: rgba(255,255,255,.03)} -.btn--danger{ - border-color: rgba(255,77,109,.45); - color: rgba(255,77,109,1); -} -.btn--danger:hover{ - background: rgba(255,77,109,.12); - border-color: rgba(255,77,109,.65); -} -.btn--sm{padding:8px 10px; border-radius:12px; font-size:13px} -.btn[aria-disabled="true"]{opacity:.6; pointer-events:none} - -/* ----- Hero:加宽、大字、减少两侧空 ----- */ -.hero{ - padding: 80px 0 56px; - text-align: center; +.brand { + display: flex; + align-items: center; + gap: 12px; + min-width: 200px; +} +.brand__logo { + width: 40px; + height: 40px; + border-radius: var(--radius-sm); + display: grid; + place-items: center; + background: linear-gradient(145deg, var(--primary), #0f766e); + color: #fff; + font-weight: 800; + font-size: 17px; + letter-spacing: -0.5px; +} +.brand--sm .brand__logo { width: 34px; height: 34px; font-size: 16px; } +.brand__name { font-weight: 700; font-size: 1.25rem; color: var(--text); } +.brand__tag { color: var(--muted); font-size: 18px; margin-top: 2px; font-weight: 500; } + +.nav { display: flex; gap: 4px; align-items: center; } +.nav__link { + padding: 8px 14px; + border-radius: var(--radius-sm); + color: var(--text-soft); + font-weight: 600; + font-size: 19px; + transition: color 0.2s, background 0.2s; +} +.nav__link:hover { + background: var(--primary-dim); + color: var(--primary); +} +[data-theme="light"] .nav__link:hover { + background: var(--primary-dim); + color: var(--primary); +} + +.header__actions { display: flex; gap: 8px; align-items: center; } + +/* ----- Buttons ----- */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + border-radius: var(--radius-sm); + border: 1px solid transparent; + padding: 10px 16px; + font-family: var(--sans); + font-weight: 600; + font-size: 18px; + cursor: pointer; + user-select: none; + transition: background 0.2s, border-color 0.2s, transform 0.15s; +} +.btn:hover { transform: translateY(-1px); } +.btn:active { transform: translateY(0); } +.btn--primary { + background: linear-gradient(145deg, var(--primary), #0f766e); + color: #fff; + border-color: transparent; + box-shadow: 0 2px 12px rgba(13, 148, 136, 0.25); +} +.btn--primary:hover { + filter: brightness(1.06); + box-shadow: 0 4px 16px rgba(13, 148, 136, 0.3); +} +.btn--ghost { + background: rgba(255, 255, 255, 0.04); + border-color: var(--line); + color: var(--text-soft); +} +.btn--ghost:hover { + background: var(--primary-dim); + border-color: var(--primary-border); + color: var(--primary); +} +[data-theme="light"] .btn--ghost { + background: rgba(15, 23, 42, 0.04); + color: var(--text-soft); +} +[data-theme="light"] .btn--ghost:hover { + background: var(--primary-dim); + border-color: var(--primary-border); + color: var(--primary); } -.hero__inner{ - max-width: min(960px, 92vw); +.btn--danger { + border-color: rgba(248, 113, 113, 0.4); + color: var(--danger); +} +.btn--danger:hover { + background: var(--danger-dim); + border-color: var(--danger); +} +.btn--sm { padding: 8px 12px; font-size: 18px; } +.btn[aria-disabled="true"] { opacity: 0.5; pointer-events: none; } + +/* ----- Hero(单一封面模块) ----- */ +.hero { + padding: var(--space-md) 0 var(--space-lg); +} +.hero__shell { + max-width: var(--container); margin: 0 auto; padding: 0 24px; } -.hero__kicker{ - font-size: 14px; +.hero-card { + background: var(--card); + border: 1px solid var(--line); + border-radius: var(--radius-lg); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25); + padding: var(--space-lg) var(--space-xl); + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-xl); + align-items: center; + position: relative; + overflow: hidden; +} +/* 封皮紧凑:单栏、无右侧图、只压低高度不缩窄 */ +.hero-card--compact { + grid-template-columns: 1fr; + padding: var(--space-md) var(--space-lg); +} +.hero-card--compact::before { + width: 100%; + background: radial-gradient(ellipse 80% 60% at 50% 0%, rgba(13, 148, 136, 0.06), transparent 55%); +} +.hero-card--compact .hero-copy { gap: 8px; } +.hero-card--compact .hero__title { font-size: clamp(40px, 8vw, 56px); } +.hero-card--compact .hero__tagline { font-size: 20px; } +.hero-card--compact .hero__desc { font-size: 17px; } +.hero-card--compact .hero__bullets { font-size: 16px; gap: 6px; } +.hero-card--compact .hero__cta { margin-top: 2px; } +.hero-card::before { + content: ""; + position: absolute; + top: 0; + right: 0; + width: 50%; + height: 100%; + background: radial-gradient(ellipse at 100% 50%, rgba(13, 148, 136, 0.08), transparent 60%); + pointer-events: none; +} +[data-theme="light"] .hero-card { + background: linear-gradient(135deg, #ffffff 0%, #fcfdfd 100%); + box-shadow: var(--shadow), 0 0 0 1px rgba(15, 118, 110, 0.06); + border: 1px solid var(--line); + border-left: 4px solid var(--primary); +} +[data-theme="light"] .hero-visual { + background: #fafbfc; + border: 1px solid rgba(15, 23, 42, 0.08); + box-shadow: none; +} +@media (max-width: 900px) { + .hero-card { + grid-template-columns: 1fr; + padding: var(--space-md) var(--space-lg); + } +} +.hero-copy { + display: flex; + flex-direction: column; + gap: var(--space-sm); + position: relative; + z-index: 1; +} +.hero__kicker { + font-size: 19px; font-weight: 600; - letter-spacing: .18em; + letter-spacing: 0.2em; text-transform: uppercase; - color: var(--muted); - margin: 0 0 20px; + color: var(--primary); + margin: 0; } -.hero__title{ - font-size: clamp(52px, 14vw, 96px); +[data-theme="light"] .hero__title { color: var(--primary); } +[data-theme="light"] .hero__tagline { color: var(--text-soft); } +.hero__title { + font-size: clamp(48px, 10vw, 76px); font-weight: 800; - letter-spacing: -0.04em; - line-height: 1; - margin: 0 0 12px; + letter-spacing: -0.03em; + line-height: 1.1; + margin: 0; color: var(--text); } -.hero__tagline{ +.hero__tagline { font-size: 24px; font-weight: 600; - color: var(--primary); - margin: 0 0 24px; - letter-spacing: .02em; + color: var(--text-soft); + margin: 0; } -.hero__desc{ - font-size: 18px; - line-height: 1.7; +.hero__desc { + font-size: 19px; + line-height: 1.65; color: var(--muted); - margin: 0 0 32px; - max-width: 52ch; - margin-left: auto; - margin-right: auto; + margin: 0; } -.hero__cta{ +.hero__bullets { + list-style: none; + margin: 0; + padding: 0; display: flex; - gap: 14px; - justify-content: center; - flex-wrap: wrap; - margin-bottom: 44px; + flex-direction: column; + gap: 10px; + color: var(--text-soft); + font-size: 18px; +} +.hero__bullets li { + position: relative; + padding-left: 16px; + line-height: 1.5; } -.hero__cta .btn{ padding: 14px 22px; font-size: 15px; } -.hero__flow{ +.hero__bullets li::before { + content: ""; + position: absolute; + left: 0; + top: 0.5em; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--primary); + opacity: 0.9; +} +.hero__cta { display: flex; - align-items: center; - justify-content: center; gap: 10px; flex-wrap: wrap; - font-size: 16px; - font-weight: 600; - color: var(--muted); + margin-top: 4px; +} +.hero__cta .btn { + padding: 13px 24px; + font-size: 18px; } -.hero__flow span:not(.hero__arrow){ opacity: .9; } -.hero__arrow{ - width: 7px; - height: 7px; - border-right: 2px solid var(--muted); - border-top: 2px solid var(--muted); - transform: rotate(45deg); - opacity: .6; +.hero-visual { + position: relative; + z-index: 1; + background: var(--bg2); + border: 1px solid var(--line); + border-radius: var(--radius); + padding: var(--space-md); } -.hero__tech{ +.hero-visual__header { display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 10px 18px; - margin-top: 40px; - padding-top: 32px; - border-top: 1px solid rgba(255,255,255,.06); - max-width: 100%; - margin-left: auto; - margin-right: auto; -} -[data-theme="light"] .hero__tech{border-top-color: rgba(15,23,42,.08)} -.hero__tech-item{ - font-size: 14px; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + font-size: 18px; font-weight: 600; - color: var(--muted); - padding: 8px 16px; + color: var(--text-soft); + letter-spacing: 0.05em; +} +.hero-visual__pill { + padding: 4px 10px; border-radius: 999px; - background: rgba(255,255,255,.04); - border: 1px solid rgba(255,255,255,.08); + background: rgba(13, 148, 136, 0.12); + border: 1px solid rgba(13, 148, 136, 0.3); + color: var(--primary); + font-size: 17px; } -[data-theme="light"] .hero__tech-item{background: rgba(15,23,42,.04); border-color: rgba(15,23,42,.1)} -.hero__tech-item:hover{ +.hero-flow { + display: flex; + align-items: stretch; + gap: 10px; +} +.hero-flow__node { + flex: 1; + min-height: 84px; + padding: 14px 12px; + border-radius: var(--radius-sm); + border: 1px solid var(--line); + background: rgba(255, 255, 255, 0.02); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 6px; + text-align: center; + transition: border-color 0.2s, box-shadow 0.2s; +} +.hero-flow__node strong { + font-size: 17px; color: var(--text); - border-color: rgba(124,92,255,.25); - background: rgba(124,92,255,.06); + font-weight: 700; +} +.hero-flow__node span { + font-size: 17px; + color: var(--muted); + line-height: 1.35; +} +.hero-flow__node--entry { + border-color: var(--line-strong); + background: rgba(255, 255, 255, 0.04); + border-left: 4px solid var(--muted); +} +[data-theme="light"] .hero-flow__node--entry { + background: rgba(0, 0, 0, 0.04); + border-left: 4px solid var(--muted); +} +.hero-flow__node--primary { + border-color: rgba(13, 148, 136, 0.5); + background: rgba(13, 148, 136, 0.06); + box-shadow: 0 0 0 1px rgba(13, 148, 136, 0.15); +} +.hero-flow__node--highlight { + border-color: rgba(52, 211, 153, 0.4); + background: rgba(52, 211, 153, 0.05); +} +.hero-flow__arrow { + flex-shrink: 0; + width: 24px; + align-self: center; + text-align: center; + color: var(--primary); + opacity: 0.7; + font-size: 16px; + font-weight: 700; + line-height: 1; +} +.hero-flow__arrow::before { + content: "→"; +} +.hero-flow__caption { + margin: 14px 0 0; + font-size: 17px; + font-weight: 600; + color: var(--text-soft); + text-align: center; +} +/* 封皮流程图:直接使用用户提供的图片 */ +.hero-flowchart-img { + display: block; + width: 100%; + height: auto; + margin-top: 10px; + border-radius: var(--radius); + border: 1px solid var(--line); +} +[data-theme="light"] .hero-flowchart-img { + border-color: rgba(15, 23, 42, 0.1); } -/* 保留:其他区块可能用到的 .stats / .stat */ -.stats{ - display:grid; +/* ----- Stats (legacy) ----- */ +.stats { + display: grid; grid-template-columns: repeat(3, 1fr); - gap: 12px; + gap: var(--space-sm); margin-top: 8px; } -.stat{ +.stat { border: 1px solid var(--line); - background: rgba(255,255,255,.03); - border-radius: 18px; - padding: 16px 14px; + background: var(--bg3); + border-radius: var(--radius); + padding: 14px 16px; +} +[data-theme="light"] .stat { background: var(--bg3); } +.stat__num { font-weight: 800; font-size: 24px; color: var(--text); } +.stat__label { color: var(--muted); font-weight: 600; font-size: 17px; margin-top: 6px; } +.stat--accent { + background: var(--primary-dim); + border-color: var(--primary-border); +} +[data-theme="light"] .stat--accent { + background: var(--primary-dim); + border-color: var(--primary-border); } -[data-theme="light"] .stat{background: rgba(15,23,42,.03)} -.stat__num{font-weight: 900; font-size: 20px; color: var(--text)} -.stat__label{color: var(--muted); font-weight: 600; font-size: 11px; margin-top: 6px; line-height: 1.45} -.stat--accent{background: linear-gradient(160deg, rgba(124,92,255,.08), rgba(78,230,181,.05)); border-color: rgba(124,92,255,.18)} -[data-theme="light"] .stat--accent{background: linear-gradient(160deg, rgba(91,75,255,.08), rgba(26,214,163,.06)); border-color: rgba(91,75,255,.2)} -.panel{ +/* ----- Panel (legacy) ----- */ +.panel { border-radius: var(--radius); border: 1px solid var(--line); - background: - radial-gradient(400px 180px at 15% 5%, rgba(124,92,255,.16), transparent 60%), - radial-gradient(380px 180px at 85% 5%, rgba(78,230,181,.12), transparent 60%), - rgba(255,255,255,.03); + background: var(--card); box-shadow: var(--shadow); - overflow:hidden; -} -.panel--hero{ - border-color: rgba(124,92,255,.2); - box-shadow: 0 20px 50px rgba(0,0,0,.25), 0 0 0 1px rgba(124,92,255,.08); -} -[data-theme="light"] .panel{background: - radial-gradient(360px 160px at 20% 10%, rgba(91,75,255,.12), transparent 65%), - radial-gradient(360px 160px at 80% 10%, rgba(26,214,163,.10), transparent 65%), - rgba(15,23,42,.02); -} -[data-theme="light"] .panel--hero{border-color: rgba(91,75,255,.25)} -.panel__top{ - display:flex; - align-items:center; - justify-content:space-between; - padding: 16px 18px; - border-bottom: 1px solid var(--line); - background: rgba(0,0,0,.15); + overflow: hidden; } -[data-theme="light"] .panel__top{background: rgba(15,23,42,.04)} -.panel__title{ - font-weight: 800; - font-size: 15px; - letter-spacing: .02em; - color: var(--text); +.panel--hero { border-color: var(--primary-border); } +[data-theme="light"] .panel { background: var(--card); } +.panel__top { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 18px; + border-bottom: 1px solid var(--line); + background: var(--bg3); } -.panel__pill{ +[data-theme="light"] .panel__top { background: var(--bg3); } +.panel__title { font-weight: 700; font-size: 18px; color: var(--text); } +.panel__pill { font-family: var(--mono); - font-size:12px; + font-size: 16px; color: var(--muted); border: 1px solid var(--line); - padding: 6px 10px; + padding: 5px 10px; border-radius: 999px; - background: rgba(255,255,255,.03); + background: var(--bg2); } -.panel__dots{display:flex; gap:6px} -.panel__dots span{ - width:10px; height:10px; border-radius:99px; - background: rgba(255,255,255,.10); +.panel__dots { display: flex; gap: 6px; } +.panel__dots span { + width: 8px; + height: 8px; + border-radius: 99px; + background: var(--muted); + opacity: 0.6; +} +.panel__dots span:nth-child(1) { background: var(--danger); } +.panel__dots span:nth-child(2) { background: var(--warning); } +.panel__dots span:nth-child(3) { background: var(--success); } +.panel__body { padding: 18px; } + +.diagram { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; } +.diagram__col { display: flex; flex-direction: column; gap: 14px; } +.node { + border-radius: var(--radius); border: 1px solid var(--line); -} -.panel__dots span:nth-child(1){background: rgba(255,77,109,.65); border-color: rgba(255,77,109,.35)} -.panel__dots span:nth-child(2){background: rgba(255,210,77,.55); border-color: rgba(255,210,77,.30)} -.panel__dots span:nth-child(3){background: rgba(78,230,181,.55); border-color: rgba(78,230,181,.30)} -.panel__body{padding: 18px} - -.diagram{ - display:grid; - grid-template-columns: repeat(3, 1fr); - gap: 14px; -} -.diagram__col{display:flex; flex-direction:column; gap: 14px} -.node{ - border-radius: 16px; - border: 1px solid var(--line); - background: rgba(255,255,255,.03); - padding: 14px 14px; - transition: border-color .2s, box-shadow .2s; -} -.node:hover{ - border-color: rgba(124,92,255,.2); - box-shadow: 0 6px 20px rgba(0,0,0,.12); -} -[data-theme="light"] .node{background: rgba(15,23,42,.02)} -.node--accent{ - background: linear-gradient(145deg, rgba(124,92,255,.2), rgba(78,230,181,.1)); - border-color: rgba(124,92,255,.3); -} -.node--accent:hover{ - border-color: rgba(124,92,255,.45); - box-shadow: 0 8px 24px rgba(124,92,255,.12); -} -[data-theme="light"] .node--accent{ - background: linear-gradient(145deg, rgba(91,75,255,.12), rgba(26,214,163,.08)); - border-color: rgba(91,75,255,.25); -} -.node__title{font-weight:800; font-size:14px; color: var(--text)} -.node__desc{color:var(--muted); font-size:11px; margin-top:6px; line-height:1.5} -.panel__foot{display:flex; gap:10px; flex-wrap:wrap; margin-top: 14px} -.badge{ - font-size:11px; - font-weight:700; + background: var(--bg3); + padding: 14px; + transition: border-color 0.2s, box-shadow 0.2s; +} +.node:hover { + border-color: var(--primary-border); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); +} +[data-theme="light"] .node { background: var(--bg3); } +.node--accent { + background: var(--primary-dim); + border-color: var(--primary-border); +} +[data-theme="light"] .node--accent { background: var(--primary-dim); } +.node__title { font-weight: 700; font-size: 18px; color: var(--text); } +.node__desc { color: var(--muted); font-size: 17px; margin-top: 6px; line-height: 1.5; } +.panel__foot { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; } +.badge { + font-size: 16px; + font-weight: 600; color: var(--muted); - border:1px solid var(--line); - background: rgba(255,255,255,.03); - padding: 8px 12px; + border: 1px solid var(--line); + background: var(--bg3); + padding: 6px 12px; border-radius: 999px; } -.section{padding: 46px 0} -/* hero 与下方内容衔接:过渡线、统一层级、不抢主标题 */ -.section--after-hero{ - padding-top: 44px; - padding-bottom: 48px; - border-top: 1px solid rgba(255,255,255,.06); +/* ----- Section ----- */ +.section { padding: var(--space-xl) 0; } +.section--after-hero { + padding-top: var(--space-lg); + padding-bottom: var(--space-xl); + border-top: 1px solid var(--line); } -[data-theme="light"] .section--after-hero{border-top-color: rgba(15,23,42,.08)} -.section__head{margin-bottom: 20px} -.section__head--compact{ - margin-bottom: 22px; - padding-left: 14px; +.section__head { margin-bottom: var(--space-md); } +.section__head--compact { + margin-bottom: var(--space-md); + padding-left: var(--space-sm); border-left: 3px solid var(--primary); } -.section__title{margin:0; font-size: 22px; font-weight: 700; letter-spacing:-.2px; color: var(--text)} -.section__head--compact .section__title{font-size: 20px; font-weight: 700} -.section__desc{margin: 6px 0 0; font-size: 14px; color: var(--muted); max-width: 60ch} -.section--alt{ - background: rgba(255,255,255,.02); +.section__title { + margin: 0; + font-size: 30px; + font-weight: 700; + letter-spacing: -0.02em; + color: var(--text); +} +.section__head--compact .section__title { font-size: 28px; } +.section__desc { + margin: var(--space-xs) 0 0; + font-size: 20px; + line-height: 1.55; + color: var(--muted); + max-width: 58ch; +} +.section--alt { + background: var(--bg2); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); } -[data-theme="light"] .section--alt{background: rgba(15,23,42,.02)} +[data-theme="light"] .section--alt { + background: linear-gradient(180deg, rgba(15, 118, 110, 0.02) 0%, var(--bg2) 50%, rgba(15, 118, 110, 0.02) 100%); + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); +} +[data-theme="light"] .section__title { color: var(--primary); font-weight: 700; } +[data-theme="light"] .section__head--compact { border-left-color: var(--primary); } -.grid{display:grid; gap: 12px} -.grid--3{grid-template-columns: repeat(3, 1fr)} -.grid--2{grid-template-columns: repeat(2, 1fr)} +.grid { display: grid; gap: var(--space-md); } +.grid--3 { grid-template-columns: repeat(3, 1fr); } +.grid--2 { grid-template-columns: repeat(2, 1fr); } -.card{ - border-radius: var(--radius2); +/* ----- Cards ----- */ +.card { + border-radius: var(--radius); + border: 1px solid var(--line); + background: var(--card); + padding: var(--space-md); + box-shadow: var(--shadow); + transition: border-color 0.2s, box-shadow 0.2s, background 0.2s; +} +.card:hover { + border-color: var(--line-strong); + box-shadow: var(--shadow-lg); + background: var(--card-hover); +} +[data-theme="light"] .card { + background: var(--card); border: 1px solid var(--line); - background: rgba(255,255,255,.03); - padding: 16px 16px; - box-shadow: 0 10px 26px rgba(0,0,0,.12); + box-shadow: var(--shadow); + border-left: 3px solid transparent; +} +[data-theme="light"] .card:hover { + border-left-color: var(--primary-border); + border-color: var(--line-strong); + box-shadow: var(--shadow-lg); +} +[data-theme="light"] .card__title { color: var(--primary); font-weight: 700; } +.card--tight { padding: var(--space-sm); } +.card--wide { grid-column: 1 / -1; } +.card--compact .card__title { margin-bottom: 2px; font-size: 22px; } +.card--compact .card__desc { margin: 0; font-size: 18px; } +.card__head { margin-bottom: var(--space-sm); } +.card__head--row { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 12px; + flex-wrap: wrap; } -[data-theme="light"] .card{background: rgba(255,255,255,.9)} -.card--tight{padding: 14px 14px} -.card--wide{grid-column: 1 / -1} -.card__title{margin:0; font-size: 16px; font-weight: 850} -.card__desc{margin: 8px 0 0; color: var(--muted)} -.card__list{ - margin: 10px 0 0; - padding-left: 18px; +.card__actions { display: flex; gap: 8px; flex-shrink: 0; } +.card__title { margin: 0; font-size: 22px; font-weight: 700; color: var(--text); } +.card__desc { margin: var(--space-xs) 0 0; font-size: 19px; line-height: 1.5; color: var(--muted); } +.charts-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + margin-top: var(--space-md); +} +.chart-block__label { + font-size: 18px; color: var(--muted); + margin-bottom: 8px; + font-weight: 600; } -.card__list li{margin: 6px 0} - -.timeline{ - display:grid; - grid-template-columns: 1fr; - gap: 10px; +.chart-block__canvas { + height: 280px; + position: relative; } -.step{ - display:flex; +.metrics--l2 { margin-bottom: 14px; } +.card__list { margin: var(--space-sm) 0 0; padding-left: var(--space-md); color: var(--muted); } +.card__list li { margin: var(--space-xs) 0; } + +.timeline { display: grid; grid-template-columns: 1fr; gap: 10px; } +.step { + display: flex; gap: 12px; - border-radius: var(--radius2); - border: 1px solid var(--line); - background: rgba(255,255,255,.03); - padding: 14px 14px; -} -[data-theme="light"] .step{background: rgba(15,23,42,.02)} -.step__badge{ - width: 34px; height: 34px; - border-radius: 12px; - display:grid; place-items:center; - font-weight: 900; - color:#06101a; - background: linear-gradient(135deg, rgba(124,92,255,1), rgba(78,230,181,.85)); + border-radius: var(--radius-sm); + border: 1px solid var(--line); + background: var(--bg3); + padding: 14px; +} +[data-theme="light"] .step { background: var(--bg3); } +.step__badge { + width: 36px; + height: 36px; + border-radius: var(--radius-sm); + display: grid; + place-items: center; + font-weight: 800; + color: #fff; + background: linear-gradient(145deg, var(--primary), #0f766e); flex: 0 0 auto; } -.step__title{margin:0; font-size: 16px} -.step__desc{margin: 6px 0 0; color: var(--muted)} +.step__title { margin: 0; font-size: 17px; } +.step__desc { margin: 6px 0 0; color: var(--muted); font-size: 17px; } -.pillRow{display:flex; flex-wrap:wrap; gap:8px; margin-top: 10px} -.pill{ +.pillRow { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; } +.pill { font-family: var(--mono); - font-size: 12px; - padding: 7px 10px; + font-size: 17px; + padding: 6px 10px; border-radius: 999px; border: 1px solid var(--line); color: var(--muted); - background: rgba(255,255,255,.03); + background: var(--bg3); } -.codeCard{ +.codeCard { border-radius: var(--radius); border: 1px solid var(--line); - overflow:hidden; - background: rgba(255,255,255,.03); + overflow: hidden; + background: var(--card); box-shadow: var(--shadow); } -[data-theme="light"] .codeCard{background: rgba(255,255,255,.9)} -.codeCard__top{ - display:flex; - align-items:center; - justify-content:space-between; - gap: 12px; - padding: 12px 14px; +[data-theme="light"] .codeCard { + background: var(--card); + border: 1px solid var(--line); + box-shadow: var(--shadow); + border-left: 3px solid rgba(15, 118, 110, 0.2); +} +.codeCard__top { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; border-bottom: 1px solid var(--line); + background: var(--bg3); } -.codeCard__title{font-weight: 850} -.code{ +.codeCard__title { font-weight: 700; font-size: 20px; } +.code { margin: 0; - padding: 14px; + padding: 16px; font-family: var(--mono); - font-size: 12.5px; + font-size: 18px; line-height: 1.6; - overflow:auto; + overflow: auto; } -.codeCard__hint{ - margin: 0; - padding: 10px 14px 14px; - color: var(--muted); +.codeCard__hint { margin: 0; padding: 12px 16px; color: var(--muted); font-size: 18px; } + +.contact { + display: grid; + grid-template-columns: 1.1fr 0.9fr; + gap: var(--space-lg); + align-items: start; } +.checklist { margin: 10px 0 0; padding-left: 20px; color: var(--muted); } +.checklist li { margin: 8px 0; } -.contact{ - display:grid; - grid-template-columns: 1.1fr .9fr; +.kv { margin-top: 12px; display: grid; gap: 8px; } +.kv__row { + display: flex; + justify-content: space-between; gap: 12px; - align-items:start; + padding: 12px 14px; + border: 1px solid var(--line); + border-radius: var(--radius-sm); + background: var(--bg3); } -.checklist{margin: 10px 0 0; padding-left: 18px; color: var(--muted)} -.checklist li{margin: 7px 0} - -.kv{margin-top: 12px; display:grid; gap: 8px} -.kv__row{display:flex; justify-content:space-between; gap: 12px; padding: 10px 10px; border: 1px solid var(--line); border-radius: 14px; background: rgba(255,255,255,.03)} -[data-theme="light"] .kv__row{background: rgba(15,23,42,.02)} -.kv__k{color: var(--muted); font-weight: 750} -.kv__v{font-weight: 800} -.contact__btns{display:flex; gap: 10px; flex-wrap:wrap; margin-top: 12px} +[data-theme="light"] .kv__row { background: var(--bg3); } +.kv__k { color: var(--muted); font-weight: 600; } +.kv__v { font-weight: 700; color: var(--text); } +.contact__btns { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; } -.footer{ - padding: 22px 0 34px; -} -.footer__inner{ +/* ----- Footer ----- */ +.footer { padding: var(--space-lg) 0 var(--space-xl); } +.footer__inner { border-top: 1px solid var(--line); - padding-top: 18px; - display:flex; - align-items:flex-start; - justify-content:space-between; - gap: 12px; + padding-top: var(--space-md); + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; } -.footer__meta{margin: 10px 0 0; color: var(--muted); font-size: 12px} -.footer__right{display:flex; gap: 12px; flex-wrap:wrap} -.footer__link{color: var(--muted); font-weight: 750} -.footer__link:hover{color: var(--text)} +.footer__meta { margin: 8px 0 0; color: var(--muted); font-size: 18px; } +.footer__right { display: flex; gap: 16px; flex-wrap: wrap; } +.footer__link { color: var(--muted); font-weight: 600; font-size: 19px; } +.footer__link:hover { color: var(--primary); } -.form{display:grid; gap:10px; margin-top:12px} -.field{display:grid; gap:6px} -.field__label{color: var(--muted); font-weight: 800; font-size: 12px} -.field__input{ +/* ----- Form ----- */ +.form { display: grid; gap: var(--space-sm); margin-top: var(--space-sm); } +.field { display: grid; gap: 6px; } +.field__label { color: var(--text-soft); font-weight: 600; font-size: 18px; } +.field__input { width: 100%; - border-radius: 14px; + border-radius: var(--radius-sm); border: 1px solid var(--line); - background: rgba(255,255,255,.03); + background: var(--bg3); color: var(--text); - padding: 11px 12px; + padding: 10px 14px; outline: none; - font-weight: 650; + font-size: 19px; + font-family: var(--sans); + font-weight: 500; + transition: border-color 0.2s, box-shadow 0.2s; +} +[data-theme="light"] .field__input { + background: var(--card); + border: 1px solid var(--line); } -[data-theme="light"] .field__input{background: rgba(15,23,42,.02)} -.field__input:focus{border-color: rgba(124,92,255,.55); box-shadow: 0 0 0 4px rgba(124,92,255,.12)} -select.field__input{ +.field__input:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px var(--primary-dim); +} +.field__input::placeholder { color: var(--muted); opacity: 0.9; } +select.field__input { cursor: pointer; appearance: none; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23888' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%236e7681' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; padding-right: 36px; } -[data-theme="light"] select.field__input{ - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23475569' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); -} - -/* 下拉选项样式 - 确保文字清晰可读 */ -select.field__input option{ - background-color: #1e293b; - color: #f1f5f9; +select.field__input option { + background-color: #111820; + color: #e6edf3; padding: 10px; - font-weight: 600; + font-weight: 500; } -[data-theme="light"] select.field__input option{ - background-color: #ffffff; +[data-theme="light"] select.field__input option { + background-color: #d4e2f0; color: #0f172a; } -.params{ +.params { border: 1px solid var(--line); - border-radius: 14px; + border-radius: var(--radius-sm); padding: 12px 14px; - background: rgba(255,255,255,.03); + background: var(--bg3); +} +[data-theme="light"] .params { + background: linear-gradient(135deg, rgba(15, 118, 110, 0.03) 0%, var(--bg2) 100%); + border: 1px solid var(--line); } -[data-theme="light"] .params{background: rgba(15,23,42,.02)} -.params__summary{ +.params__summary { cursor: pointer; - font-weight: 800; + font-weight: 700; color: var(--muted); user-select: none; list-style: none; } -.params__summary::-webkit-details-marker{display: none} -.params__summary::before{ +.params__summary::-webkit-details-marker { display: none; } +.params__summary::before { content: "▶ "; display: inline-block; transition: transform 0.2s; } -.params[open] .params__summary::before{transform: rotate(90deg)} +.params[open] .params__summary::before { transform: rotate(90deg); } -.mini{margin-top:12px; display:grid; gap:8px} -.mini__row{display:flex; justify-content:space-between; gap:12px; padding: 10px 10px; border: 1px solid var(--line); border-radius: 14px; background: rgba(255,255,255,.03)} -[data-theme="light"] .mini__row{background: rgba(15,23,42,.02)} -.mini__k{color: var(--muted); font-weight: 800} -.mini__v{font-weight: 900} +.mini { margin-top: 12px; display: grid; gap: 8px; } +.mini__row { + display: flex; + justify-content: space-between; + gap: 12px; + padding: 10px 12px; + border: 1px solid var(--line); + border-radius: var(--radius-sm); + background: var(--bg3); +} +[data-theme="light"] .mini__row { + background: var(--bg2); + border: 1px solid var(--line); +} +.mini__k { color: var(--muted); font-weight: 600; font-size: 18px; } +.mini__v { font-weight: 700; color: var(--text); font-size: 18px; } -.logbox{ +/* ----- Logbox ----- */ +.logbox { margin-top: 10px; border: 1px solid var(--line); - border-radius: 16px; - background: rgba(255,255,255,.03); - overflow:hidden; -} -[data-theme="light"] .logbox{background: rgba(15,23,42,.02)} -.logbox__top{ - display:flex; - align-items:center; - justify-content:space-between; - gap:10px; - padding: 10px 10px; + border-radius: var(--radius); + background: var(--bg2); + overflow: hidden; +} +[data-theme="light"] .logbox { + background: var(--bg2); + border: 1px solid var(--line); +} +.logbox__top { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 14px; border-bottom: 1px solid var(--line); + background: var(--bg3); } -.logbox__hint{color: var(--muted); font-weight: 800; font-size: 12px} -.logbox__pre{ - margin:0; - padding: 12px; - min-height: 280px; - max-height: 420px; - overflow:auto; +.logbox__hint { color: var(--muted); font-weight: 600; font-size: 18px; } +.logbox__pre { + margin: 0; + padding: 14px; + min-height: 380px; + max-height: 600px; + overflow: auto; font-family: var(--mono); - font-size: 12.5px; - line-height: 1.55; + font-size: 18px; + line-height: 1.6; white-space: pre-wrap; word-break: break-word; + color: var(--text-soft); } -.metrics{display:grid; grid-template-columns: 1fr 1fr; gap:10px; margin-top:12px} -.metric{ +/* ----- Metrics (执行统计 / 覆盖率) ----- */ +.metrics { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; + margin-top: 12px; +} +.metric { + border: 1px solid var(--line); + border-radius: var(--radius-sm); + padding: 14px 16px; + background: var(--bg3); + transition: border-color 0.2s; +} +.metric:hover { border-color: var(--line-strong); } +[data-theme="light"] .metric { + background: var(--bg2); border: 1px solid var(--line); - border-radius: 14px; - padding: 12px 12px; - background: rgba(255,255,255,.03); } -[data-theme="light"] .metric{background: rgba(15,23,42,.02)} -.metric:first-child{grid-column: 1 / -1} -.metric__label{color: var(--muted); font-weight: 800; font-size: 12px} -.metric__value{ - font-weight: 900; +.metric:first-child { grid-column: 1 / -1; } +.metric__label { + color: var(--muted); + font-weight: 600; + font-size: 18px; + margin-bottom: 6px; +} +.metric__value { + font-weight: 800; + font-size: 34px; + letter-spacing: -0.02em; + color: var(--primary); +} +.metric__value--sm { font-size: 28px; } +.metric__value--muted { color: var(--muted); } +.metric__value--success { color: var(--success); } +.metrics--compact { + grid-template-columns: repeat(3, 1fr); + gap: 10px; + margin-top: 0; +} +.metrics--compact .metric { + padding: 12px 14px; +} +.metrics--compact .metric__label { + font-size: 17px; + margin-bottom: 4px; +} +.metrics--compact .metric__value { font-size: 28px; - margin-top: 6px; - background: linear-gradient(135deg, rgba(124,92,255,1), rgba(78,230,181,1)); - -webkit-background-clip: text; - background-clip: text; - color: transparent; + margin-top: 0; + line-height: 1.2; } -.metric__value--sm{font-size: 22px} -.recent{ +.recent { border: 1px solid var(--line); - border-radius: 14px; - padding: 12px 12px; - background: rgba(255,255,255,.03); -} -[data-theme="light"] .recent{background: rgba(15,23,42,.02)} -.recent__title{font-weight: 800; font-size: 12px; color: var(--muted); margin-bottom: 8px} -.recent__list{ - max-height: 280px; - overflow:auto; - font-family: var(--mono); - font-size: 12px; - line-height: 1.6; + border-radius: var(--radius-sm); + padding: 12px 14px; + background: var(--bg3); } -.recent__empty{color: var(--muted); font-style: italic} -.recent__item{ - padding: 6px 8px; +[data-theme="light"] .recent { background: var(--bg3); } +.recent__title { font-weight: 600; font-size: 18px; color: var(--muted); margin-bottom: 8px; } +.recent__list { max-height: 260px; overflow: auto; font-family: var(--mono); font-size: 18px; line-height: 1.6; } +.recent__empty { color: var(--muted); font-style: italic; font-size: 18px; } +.recent__item { + padding: 8px 10px; margin: 4px 0; - border-left: 3px solid rgba(124,92,255,.65); - background: rgba(255,255,255,.02); + border-left: 3px solid var(--primary); + background: var(--primary-dim); border-radius: 4px; - font-family: 'Consolas', 'Monaco', monospace; - font-size: 12px; + font-family: var(--mono); + font-size: 18px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: default; + transition: background 0.2s; } -.recent__item:hover{ +.recent__item:hover { white-space: normal; word-break: break-all; - background: rgba(124,92,255,.15); -} -[data-theme="light"] .recent__item{background: rgba(15,23,42,.02)} -[data-theme="light"] .recent__item:hover{background: rgba(124,92,255,.12)} - -@media (max-width: 980px){ - .hero{padding: 56px 0 44px} - .stats{grid-template-columns: 1fr; } - .grid--3{grid-template-columns: 1fr; } - .grid--2{grid-template-columns: 1fr; } - .contact{grid-template-columns: 1fr; } - .nav{display:none} - .brand{min-width:auto} - .diagram{grid-template-columns: 1fr} + background: rgba(13, 148, 136, 0.2); } - -@media (min-width: 981px) and (max-width: 1300px){ - .grid--3{grid-template-columns: repeat(2, 1fr); } +[data-theme="light"] .recent__item { + background: var(--primary-dim); + border-left-color: var(--primary); } +[data-theme="light"] .recent__item:hover { background: var(--primary-dim); } - -/* L2 模块覆盖率样式 */ +/* ----- L2 模块覆盖率 ----- */ .l2-modules { display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); - gap: 10px; + grid-template-columns: repeat(5, 1fr); + gap: 14px; + margin-top: 16px; +} +@media (max-width: 900px) { + .l2-modules { grid-template-columns: repeat(3, 1fr); } +} +@media (max-width: 520px) { + .l2-modules { grid-template-columns: repeat(2, 1fr); } } - .l2-module-item { - padding: 10px 12px; - background: var(--bg2); - border-radius: var(--radius2); + display: flex; + flex-direction: column; + gap: 6px; + padding: 14px 16px; + min-width: 0; + background: var(--bg3); + border-radius: var(--radius-sm); border: 1px solid var(--line); - transition: all 0.2s ease; + transition: border-color 0.2s, transform 0.2s, box-shadow 0.2s; } - .l2-module-item:hover { - border-color: var(--primary); + border-color: var(--primary-border); transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(124,92,255,.15); + box-shadow: 0 4px 16px rgba(13, 148, 136, 0.12); +} +.l2-module-item__row { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; +} +.l2-module-item__name { + font-weight: 600; + font-size: 16px; + color: var(--text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +.l2-module-item__rate { + flex-shrink: 0; + font-weight: 700; + font-size: 18px; +} +.l2-module-item__lines { + font-size: 15px; + color: var(--muted); + margin-top: 2px; } - [data-theme="light"] .l2-module-item { - background: #f8fafc; + background: var(--bg2); + border: 1px solid var(--line); } - [data-theme="light"] .l2-module-item:hover { - box-shadow: 0 4px 12px rgba(91,75,255,.12); + border-color: var(--primary-border); + box-shadow: 0 4px 16px rgba(15, 118, 110, 0.08); } -/* ============================ - 验证流程实时展示(3 区块) - ============================ */ - +/* ----- 验证流程实时展示 ----- */ .workflow-summary-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 14px; + gap: 16px; } - .workflow-summary-card { background: var(--card); - border-radius: var(--radius2); + border-radius: var(--radius); border: 1px solid var(--line); - padding: 12px 14px; + padding: 14px 16px; display: flex; flex-direction: column; - gap: 8px; + gap: 10px; min-height: 200px; + transition: border-color 0.2s, box-shadow 0.2s; } - -.workflow-summary-card .workflow-output { - flex: 1; - max-height: 400px; - min-height: 150px; +.workflow-summary-card:hover { + border-color: var(--line-strong); + box-shadow: var(--shadow); } +.workflow-summary-card .workflow-output { flex: 1; max-height: 380px; min-height: 140px; } -/* LLM 用例框:加高 + 不要紫色下拉框,内容直接全部显示 */ -.workflow-summary-card--llm { - min-height: 420px; -} +.workflow-summary-card--llm { min-height: 400px; } .workflow-summary-card--llm .workflow-output { max-height: none !important; - min-height: 380px; + min-height: 360px; overflow-y: visible !important; overflow-x: visible; scrollbar-width: none; -ms-overflow-style: none; } -.workflow-summary-card--llm .workflow-output::-webkit-scrollbar { - display: none; -} +.workflow-summary-card--llm .workflow-output::-webkit-scrollbar { display: none; } .workflow-summary-card--llm .workflow-code-preview { max-height: none !important; overflow-y: visible !important; overflow-x: auto; - min-height: 340px; + min-height: 320px; scrollbar-width: none; -ms-overflow-style: none; } -.workflow-summary-card--llm .workflow-code-preview::-webkit-scrollbar { - display: none; -} - -.workflow-summary-header { - display: flex; - flex-direction: column; - gap: 4px; -} +.workflow-summary-card--llm .workflow-code-preview::-webkit-scrollbar { display: none; } +.workflow-summary-header { display: flex; flex-direction: column; gap: 4px; } .workflow-summary-title { display: flex; align-items: center; gap: 8px; - font-size: 13px; - font-weight: 600; -} - -.workflow-summary-icon { - font-size: 16px; -} - -.workflow-summary-sub { - font-size: 11px; - color: var(--muted); - opacity: 0.9; -} - -@media (max-width: 1024px) { - .workflow-summary-grid { - grid-template-columns: repeat(1, minmax(0, 1fr)); - } + font-size: 20px; + font-weight: 700; + color: var(--text); } - -/* 流程输出样式 - 优化版 */ -.workflow-output-container { - margin-top: 20px; +.workflow-summary-icon { font-size: 24px; opacity: 0.9; } +.workflow-summary-sub { font-size: 18px; color: var(--muted); } +[data-theme="light"] .workflow-summary-card { + background: var(--card); + border: 1px solid var(--line); + box-shadow: var(--shadow); + border-left: 3px solid rgba(15, 118, 110, 0.2); } - -.workflow-output-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 12px; -} - -.workflow-output-title { - font-size: 14px; - font-weight: 600; - margin: 0; - color: var(--text); +[data-theme="light"] .workflow-summary-card:hover { + border-left-color: var(--primary-border); + border-color: var(--line-strong); + box-shadow: var(--shadow-lg); } +[data-theme="light"] .workflow-summary-title { color: var(--primary); font-weight: 700; } +.workflow-output-container { margin-top: 20px; } +.workflow-output-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } +.workflow-output-title { font-size: 18px; font-weight: 600; margin: 0; color: var(--text); } .workflow-output-empty { color: var(--muted); text-align: center; padding: 40px 20px; - font-size: 12px; -} - -/* 每个步骤内部的小型日志面板 */ -.step-log { - display: none; - margin-top: 8px; -} - -.workflow-step-card.is-open .step-log { - display: block; + font-size: 19px; } -.step-log .workflow-output { - max-height: 160px; - padding: 8px 10px; -} +.step-log { display: none; margin-top: 8px; } +.workflow-step-card.is-open .step-log { display: block; } +.step-log .workflow-output { max-height: 160px; padding: 8px 10px; } .workflow-output { font-family: var(--mono); - font-size: 11px; + font-size: 17px; line-height: 1.7; scrollbar-width: thin; - scrollbar-color: var(--primary) var(--card); - background: var(--card); + scrollbar-color: var(--primary) var(--bg2); + background: var(--bg2); border: 1px solid var(--line); - border-radius: var(--radius2); - padding: 16px; - max-height: 250px; + border-radius: var(--radius-sm); + padding: 14px; + max-height: 240px; overflow-y: auto; - position: relative; + color: var(--text-soft); } +.workflow-output::-webkit-scrollbar { width: 6px; } +.workflow-output::-webkit-scrollbar-track { background: var(--bg3); border-radius: 3px; } +.workflow-output::-webkit-scrollbar-thumb { background: var(--primary); border-radius: 3px; opacity: 0.6; } +.workflow-output::-webkit-scrollbar-thumb:hover { opacity: 1; } -.workflow-output::-webkit-scrollbar { - width: 8px; -} - -.workflow-output::-webkit-scrollbar-track { - background: var(--bg2); - border-radius: 4px; -} - -.workflow-output::-webkit-scrollbar-thumb { - background: var(--primary); - border-radius: 4px; - border: 2px solid var(--bg2); -} - -.workflow-output::-webkit-scrollbar-thumb:hover { - background: var(--primary2); -} - -/* 输出项样式优化 */ .workflow-output-item { - margin-bottom: 10px; - padding: 10px; - background: rgba(255, 255, 255, 0.02); + margin-bottom: 8px; + padding: 10px 12px; + background: var(--bg3); border-radius: 6px; - border-left: 3px solid currentColor; - transition: all 0.2s ease; -} - -.workflow-output-item:hover { - background: rgba(255, 255, 255, 0.04); - transform: translateX(2px); + border-left: 3px solid var(--primary); + transition: background 0.2s; } +.workflow-output-item:hover { background: var(--primary-dim); } -/* 覆盖率分析摘要:四合一框 */ .workflow-cov-summary-box { padding: 12px 14px; - background: rgba(255, 255, 255, 0.03); - border-radius: 8px; + background: var(--bg3); + border-radius: var(--radius-sm); border: 1px solid var(--line); margin-bottom: 8px; } -.workflow-cov-summary-row { - font-size: 12px; - line-height: 1.6; - color: var(--text); - margin-bottom: 6px; -} -.workflow-cov-summary-row:last-child { - margin-bottom: 0; -} -.workflow-cov-summary-label { - opacity: 0.85; - margin-right: 8px; - font-weight: 500; -} +.workflow-cov-summary-row { font-size: 18px; line-height: 1.6; color: var(--text); margin-bottom: 6px; } +.workflow-cov-summary-row:last-child { margin-bottom: 0; } +.workflow-cov-summary-label { color: var(--muted); margin-right: 8px; font-weight: 500; } -/* 验证流程:一个 case 一块(编译命令、编译是否成功、仿真命令、仿真是否成功) */ -.workflow-case-block .workflow-case-rows { - display: flex; - flex-direction: column; - gap: 6px; - margin-top: 6px; -} +.workflow-case-block .workflow-case-rows { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; } .workflow-case-row { - font-size: 11px; + font-size: 17px; line-height: 1.5; - color: var(--text); + color: var(--text-soft); display: flex; align-items: flex-start; gap: 8px; } -.workflow-case-label { - flex-shrink: 0; - opacity: 0.85; - font-weight: 500; - min-width: 96px; -} -.workflow-case-value { - word-break: break-word; - white-space: pre-wrap; -} -.workflow-case-value.workflow-case-ok { - color: var(--success, #4ade80); -} -.workflow-case-value.workflow-case-fail { - color: var(--danger, #f87171); -} +.workflow-case-label { flex-shrink: 0; color: var(--muted); font-weight: 500; min-width: 96px; } +.workflow-case-value { word-break: break-word; white-space: pre-wrap; } +.workflow-case-value.workflow-case-ok { color: var(--success); } +.workflow-case-value.workflow-case-fail { color: var(--danger); } -.workflow-output-timestamp { - font-size: 9px; - color: var(--muted); - margin-bottom: 6px; - opacity: 0.7; - font-weight: 500; -} - -.workflow-output-text { - color: var(--text); - word-break: break-word; - white-space: pre-wrap; -} +.workflow-output-timestamp { font-size: 16px; color: var(--muted); margin-bottom: 6px; font-weight: 500; } +.workflow-output-text { color: var(--text); word-break: break-word; white-space: pre-wrap; } .workflow-code-preview { font-family: var(--mono); - font-size: 10px; + font-size: 16px; line-height: 1.6; - color: var(--text); - background: rgba(0, 0, 0, 0.2); + color: var(--text-soft); + background: var(--bg2); border: 1px solid var(--line); - border-radius: 4px; - padding: 8px 10px; - margin-top: 4px; + border-radius: 6px; + padding: 10px 12px; + margin-top: 6px; overflow-x: auto; white-space: pre; word-break: normal; max-height: 200px; overflow-y: auto; } +.workflow-code-preview::-webkit-scrollbar { width: 6px; height: 6px; } +.workflow-code-preview::-webkit-scrollbar-track { background: var(--bg3); border-radius: 3px; } +.workflow-code-preview::-webkit-scrollbar-thumb { background: var(--primary); border-radius: 3px; opacity: 0.5; } -.workflow-code-preview::-webkit-scrollbar { - width: 6px; - height: 6px; +/* ----- 参数表格 ----- */ +.codeCard table { + width: 100%; + border-collapse: collapse; + font-size: 17px; +} +.codeCard thead { background: var(--bg3); } +.codeCard th { + text-align: left; + padding: 12px 16px; + font-weight: 700; + color: var(--text-soft); + border-bottom: 1px solid var(--line); + font-size: 17px; + text-transform: uppercase; + letter-spacing: 0.04em; +} +.codeCard td { + padding: 12px 16px; + border-bottom: 1px solid var(--line); + color: var(--text-soft); +} +.codeCard tbody tr:hover { background: var(--bg3); } +.codeCard code { + font-family: var(--mono); + font-size: 17px; + color: var(--primary); + background: var(--primary-dim); + padding: 2px 6px; + border-radius: 4px; } -.workflow-code-preview::-webkit-scrollbar-track { - background: rgba(0, 0, 0, 0.1); - border-radius: 3px; +/* ----- Responsive ----- */ +@media (max-width: 980px) { + .hero { padding: var(--space-xl) 0 var(--space-lg); } + .stats { grid-template-columns: 1fr; } + .grid--3 { grid-template-columns: 1fr; } + .grid--2 { grid-template-columns: 1fr; } + .contact { grid-template-columns: 1fr; } + .nav { display: none; } + .brand { min-width: auto; } + .diagram { grid-template-columns: 1fr; } + .metrics { grid-template-columns: 1fr 1fr; } + .charts-row { grid-template-columns: 1fr; } + .metrics--compact { grid-template-columns: 1fr 1fr; } } -.workflow-code-preview::-webkit-scrollbar-thumb { - background: var(--primary); - border-radius: 3px; +@media (min-width: 981px) and (max-width: 1200px) { + .grid--3 { grid-template-columns: repeat(2, 1fr); } } -/* 已在上面 .workflow-summary-card--llm 区块统一设置 */ +@media (max-width: 1024px) { + .workflow-summary-grid { grid-template-columns: 1fr; } +} -/* 响应式调整 */ @media (max-width: 768px) { - .workflow-steps-container { - grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); - gap: 8px; - } + .workflow-steps-container { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px; } + .metrics { grid-template-columns: 1fr; } } diff --git a/chipfuzz/assets/workflow.js b/chipfuzz/assets/workflow.js index 26a82d1..d290937 100644 --- a/chipfuzz/assets/workflow.js +++ b/chipfuzz/assets/workflow.js @@ -443,7 +443,7 @@ } } } - }, 200); // 每 200ms 检查一次 + }, 400); // 每 400ms 检查一次,降低长时间运行时的 CPU 占用 // 同时保留 MutationObserver 作为备用 const observer = new MutationObserver(() => { @@ -483,11 +483,12 @@ } /** 从后端 API 获取最近生成的汇编代码(只保留最新一条) */ + let _recentAssemblyCodesNetworkErrorLogged = false; async function fetchRecentAssemblyCodes() { const base = ensureApiBase(); + if (!base) return; try { const url = `${base}/api/recent-assembly-codes?limit=1`; // 只获取最新1条 - console.log('[Workflow] 请求最近汇编代码:', url); const response = await fetch(url); if (!response.ok) { @@ -496,8 +497,8 @@ return; } + _recentAssemblyCodesNetworkErrorLogged = false; // 成功后重置,方便下次断线时再提示 const data = await response.json(); - console.log('[Workflow] API 返回数据:', data); if (data.error) { console.warn('[Workflow] API 返回错误:', data.error); @@ -507,13 +508,17 @@ genListEl.innerHTML = ''; const latestCode = data.codes[0]; const fileName = latestCode.name || ''; - console.log('[Workflow] 显示最新代码:', fileName); appendItem(genListEl, fileName ? `LLM 生成用例 · ${fileName}` : 'LLM 生成用例', latestCode.key_code, true); - } else { - console.log('[Workflow] 暂无汇编代码'); } } catch (error) { - console.error('[Workflow] 获取最近汇编代码异常:', error); + if (error.name === 'TypeError' && (error.message === 'Failed to fetch' || error.message.includes('fetch'))) { + if (!_recentAssemblyCodesNetworkErrorLogged) { + _recentAssemblyCodesNetworkErrorLogged = true; + console.warn('[Workflow] 无法连接服务端,请确认后端已启动且 API 地址正确(如 http://localhost:8080)'); + } + } else { + console.error('[Workflow] 获取最近汇编代码异常:', error); + } } } diff --git a/chipfuzz/index.html b/chipfuzz/index.html index e1831ec..f309e3d 100644 --- a/chipfuzz/index.html +++ b/chipfuzz/index.html @@ -11,7 +11,7 @@ @@ -30,10 +30,7 @@
- +
- +
+ +
@@ -330,16 +318,19 @@

验证流
-

L2 模块组覆盖率

-

单独统计 L2Cache、L2TLB 等关键模块的覆盖情况。 +

+
+

L2 模块组覆盖率

+

单独统计 L2Cache、L2TLB 等关键模块的覆盖情况。

+
-

- -
+
+
L2 总体覆盖率
-
+
-
+
点击刷新获取数据...
@@ -370,93 +362,93 @@

参数配置说明

命令行参数
- +
- - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + +
参数类型 / 可选值默认值说明
参数类型 / 可选值默认值说明
--coverage_filename_originstr<项目根>/logs_testcase/annotated/原始覆盖率文件目录(annotated 输出)。
--coverage_filename_originstr<项目根>/logs_testcase/annotated/原始覆盖率文件目录(annotated 输出)。
--coverage_filename_laterstr<项目根>/logs2/annotated/后续覆盖率文件目录(用于对比)。
--coverage_filename_laterstr<项目根>/logs2/annotated/后续覆盖率文件目录(用于对比)。
--global_annotated_dirstr<项目根>/logs_global/annotated全局覆盖率统计使用的 annotated 目录。
--global_annotated_dirstr<项目根>/logs_global/annotated全局覆盖率统计使用的 annotated 目录。
--modulestrCSR指定单模块名称(与自动多模块二选一)。
--modulestrCSR指定单模块名称(与自动多模块二选一)。
--modelstrKJY模型/配置标识(如 KJY)。
--modelstrKJY模型/配置标识(如 KJY)。
--numint100模块索引或自动模式下的模块数量。
--numint100模块索引或自动模式下的模块数量。
--datstr任务专属的 .dat 文件路径(可选)。
--datstr任务专属的 .dat 文件路径(可选)。
--modecontinue / freshcontinuecontinue=继续使用现有覆盖率;fresh=新建覆盖率文件。
--modecontinue / freshcontinuecontinue=继续使用现有覆盖率;fresh=新建覆盖率文件。
--max_iterationsint13每个模块最大尝试次数,达到后自动切换下一模块。
--max_iterationsint13每个模块最大尝试次数,达到后自动切换下一模块。
--auto_switchflag默认开启启用自动切换模块:当前模块完成或达最大次数后切到下一模块。
--auto_switchflag默认开启启用自动切换模块:当前模块完成或达最大次数后切到下一模块。
--no-auto-switchflag禁用自动切换模块。
--no-auto-switchflag禁用自动切换模块。
--use_specflagFalse启用 SPEC 文件分析,用 spec 中的模块接口信息指导测试生成。
--use_specflagFalse启用 SPEC 文件分析,用 spec 中的模块接口信息指导测试生成。
--run_existing_seedsflagFalse在开始 LLM 生成前,先运行 successed/<module>/ 下已有成功用例。
--run_existing_seedsflagFalse在开始 LLM 生成前,先运行 successed/<module>/ 下已有成功用例。
diff --git a/chipfuzz/server/app.py b/chipfuzz/server/app.py index 5fedc14..e119621 100644 --- a/chipfuzz/server/app.py +++ b/chipfuzz/server/app.py @@ -31,6 +31,10 @@ # 项目根目录(ChipFuzzer_cursor 所在位置) BASE_DIR = Path(os.environ.get("CHIPFUZZER_BASE", "/root/ChipFuzzer_cursor")).resolve() +# 确保可导入项目内模块(如 getmodulecoverstate) +import sys +if str(BASE_DIR) not in sys.path: + sys.path.insert(0, str(BASE_DIR)) # 运行记录目录 RUNS_DIR = Path(os.environ.get("CHIPFUZZER_RUNS", str(BASE_DIR / "runs"))).resolve() @@ -86,6 +90,7 @@ def _is_pid_running(pid: int) -> bool: class StartRunReq(BaseModel): module: str = "Bku" + start_module_index: Optional[int] = None # 从第 N 个模块开始(1=第一个),不传或 0 表示从头 model: str = "qwen3:235b" # origin: 用于读取初始未覆盖代码(基线) coverage_filename_origin: str = "/root/XiangShan/logs/annotated/" @@ -99,6 +104,7 @@ class StartRunReq(BaseModel): auto_switch: bool = True # 是否自动切换模块(默认开启) use_spec: bool = False # 是否使用 SPEC 文件分析 run_existing_seeds: bool = False # 是否运行已有成功用例 + llm_report: bool = False # 是否使用 LLM 生成用例报告(默认不写) # 记录当前运行模式,用于判断是否显示旧覆盖率 current_run_mode = {"mode": "continue", "fresh_start_time": 0} @@ -140,6 +146,39 @@ def list_runs() -> dict: return {"runs": runs} +# 默认 annotated 目录(与启动任务时的 coverage_filename_origin 一致) +DEFAULT_ANNOTATED_DIR = os.environ.get( + "CHIPFUZZER_ANNOTATED_DIR", + "/root/XiangShan/logs/annotated" +) + + +@app.get("/api/top-uncovered-modules") +def get_top_uncovered_modules( + num: int = Query(100, ge=1, le=200, description="返回前 N 个模块"), + annotated_dir: Optional[str] = Query(None, description="annotated 目录,不传则用默认"), +) -> dict: + """ + 获取未覆盖代码最多的模块列表,用于前端「起始模块」下拉。 + 开启自动切换时,任务会从所选模块开始验证,前面的模块会跳过。 + 返回: { "modules": [ {"rank": 1, "module": "LogPerfEndpoint", "uncovered": 41413}, ... ] } + """ + try: + from getmodulecoverstate import getTopUncoveredModulesWithCounts + dir_path = annotated_dir or DEFAULT_ANNOTATED_DIR + if not os.path.isdir(dir_path): + return {"modules": [], "error": f"目录不存在: {dir_path}"} + rows = getTopUncoveredModulesWithCounts(num, dir_path) + modules = [ + {"rank": i + 1, "module": name, "uncovered": count} + for i, (name, count) in enumerate(rows) + ] + return {"modules": modules} + except Exception as e: + import traceback + return {"modules": [], "error": str(e), "detail": traceback.format_exc()} + + @app.get("/api/recent-assembly-codes") def get_recent_assembly_codes(limit: int = Query(10, ge=1, le=50)) -> dict: """ @@ -301,15 +340,19 @@ def start_run(req: StartRunReq) -> dict: BACKEND_SCRIPT, "--module", req.module, "--model", req.model, + ] + if req.start_module_index is not None and req.start_module_index >= 1: + cmd.extend(["--start-index", str(req.start_module_index)]) + cmd.extend([ "--coverage_filename_origin", req.coverage_filename_origin, "--coverage_filename_later", req.coverage_filename_later, "--global_annotated_dir", req.global_annotated_dir, "--mode", req.mode, "--max_iterations", str(req.max_iterations), "--num", str(req.num), - "--dat", str(dat_file_path), # 添加 .dat 文件路径参数 - ] - + "--dat", str(dat_file_path), + ]) + # 如果启用自动切换模块 if req.auto_switch: cmd.append("--auto_switch") @@ -321,7 +364,11 @@ def start_run(req: StartRunReq) -> dict: # 如果启用运行已有成功用例 if req.run_existing_seeds: cmd.append("--run_existing_seeds") - + + # 如果启用 LLM 生成用例报告 + if req.llm_report: + cmd.append("--llm-report") + try: with log_path.open("ab", buffering=0) as out: p = subprocess.Popen( diff --git a/getmodulecoverstate.py b/getmodulecoverstate.py index 8c18a58..45e2830 100644 --- a/getmodulecoverstate.py +++ b/getmodulecoverstate.py @@ -102,6 +102,33 @@ def getTopUncoveredModules(num, Coverage_filename_origin_dir): return result +def getTopUncoveredModulesWithCounts(num, Coverage_filename_origin_dir): + """ + 获取未覆盖代码最多的 num 个模块列表,并返回每个模块的未覆盖行数。 + 供 Web API 使用,便于前端展示「从第 N 个模块开始」。 + 返回: [(module_name, uncovered_count), ...] + """ + import os + import glob + + sv_files = glob.glob(os.path.join(Coverage_filename_origin_dir, "*.sv")) + module_uncovered_counts = [] + + for sv_file in sv_files: + module_name = os.path.basename(sv_file).replace(".sv", "") + try: + with open(sv_file, "r", encoding='utf-8', errors='ignore') as f: + content = f.read() + uncovered_count = content.count("%000000") + if uncovered_count > 0: + module_uncovered_counts.append((module_name, uncovered_count)) + except Exception: + pass + + module_uncovered_counts.sort(key=lambda x: x[1], reverse=True) + return module_uncovered_counts[:num] + + def getTheMostUncoveredModule_debug(Coverage_filename_origin): filename = Coverage_filename_origin diff --git a/getuncoveredcodeline.py b/getuncoveredcodeline.py index 124b8a0..8695c30 100644 --- a/getuncoveredcodeline.py +++ b/getuncoveredcodeline.py @@ -54,9 +54,12 @@ def get_line_content_with_context(file_path, line_number, context_lines=6): return None +# 缓存 find_file_path 结果,避免同一文件重复 os.walk(未覆盖行数多时卡在开头的根因) +_find_file_path_cache = {} + def find_file_path(filename, search_dir='.'): """ - 在指定目录下递归查找文件 + 在指定目录下递归查找文件(带缓存,同一 (filename, search_dir) 只 walk 一次) 参数: filename (str): 要查找的文件名(如FPU.scala) @@ -65,10 +68,16 @@ def find_file_path(filename, search_dir='.'): 返回: str: 文件的完整路径,未找到返回None """ - for root, dirs, files in os.walk(search_dir): - if filename in files: - return os.path.join(root, filename) - return None + key = (filename, os.path.abspath(search_dir)) + if key not in _find_file_path_cache: + result = None + if os.path.exists(search_dir): + for root, dirs, files in os.walk(search_dir): + if filename in files: + result = os.path.join(root, filename) + break + _find_file_path_cache[key] = result + return _find_file_path_cache[key] def get_line_content(file_path, line_number): diff --git a/test_llm.py b/test_llm.py new file mode 100644 index 0000000..7c384d5 --- /dev/null +++ b/test_llm.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +"""快速测试 LLM API 是否可用(与 fuzzing 使用同一套调用)""" +import sys +import time + +def test_kjy(): + """测试 KJY 接口(qwen3:235b / deepseek-r1:671b)""" + print("测试 LLM_API_KJY(qwen3:235b)...") + try: + from LLM_API_KJY import callOpenAI_KJY + t0 = time.time() + # 短 prompt,30 秒超时足够 + out = callOpenAI_KJY("只回复一个词:OK", "qwen3:235b") + elapsed = time.time() - t0 + print(f" ✅ 成功 ({elapsed:.1f}s): {repr(out[:200])}") + return True + except Exception as e: + print(f" ❌ 失败: {e}") + return False + +def test_openai(): + """测试 OpenAI 接口(gpt-5.1)""" + print("测试 LLM_API(gpt-5.1)...") + try: + from LLM_API import callOpenAI + t0 = time.time() + out = callOpenAI("只回复一个词:OK") + elapsed = time.time() - t0 + print(f" ✅ 成功 ({elapsed:.1f}s): {repr(out[:200])}") + return True + except Exception as e: + print(f" ❌ 失败: {e}") + return False + +if __name__ == "__main__": + which = (sys.argv[1:1] or ["kjy"])[0].lower() + ok = False + if which in ("kjy", "qwen", "all"): + ok = test_kjy() or ok + if which in ("openai", "gpt", "all"): + ok = test_openai() or ok + if which not in ("kjy", "qwen", "openai", "gpt", "all"): + ok = test_kjy() + sys.exit(0 if ok else 1) diff --git a/xiangshan_fuzzing.py b/xiangshan_fuzzing.py index 5606255..b1ee352 100644 --- a/xiangshan_fuzzing.py +++ b/xiangshan_fuzzing.py @@ -869,9 +869,10 @@ def build_prompt(uncovered_code, good_seeds, scala_code, compile_error=None, no_ class ModuleCoverageSession: """围绕一个 module 的完整测试会话(包含已有 good seeds + LLM 新生成)。""" - def __init__(self, module_name: str, config: PathConfig, Coverage_filename_origin, Coverage_filename_later, model, global_coverage_manager=None, use_spec=False): + def __init__(self, module_name: str, config: PathConfig, Coverage_filename_origin, Coverage_filename_later, model, global_coverage_manager=None, use_spec=False, use_llm_report=True): self.module_name = module_name self.config = config + self.use_llm_report = use_llm_report # 是否使用 LLM 分析覆盖代码生成用例报告 self.emulator = EmulatorRunner(config) self.subproc = SubprocessRunner() self.uncovered_repo = UncoveredCodeRepository(config, Coverage_filename_origin, Coverage_filename_later) @@ -1228,10 +1229,25 @@ def run_llm_loop(self, max_iterations: int = 20, save_stats_callback=None) -> di except Exception as e: print(f"⚠️ 保存统计数据时出错: {e}") - if self.model == "qwen3:235b" or self.model == "deepseek-r1:671b": - result = callOpenAI_KJY(prompt, self.model) - else: - result = callOpenAI(prompt) + # LLM 调用:失败时重试 1 次,仍失败则跳过本 case,避免长时间卡死整轮 + result = None + for llm_attempt in range(2): + try: + if self.model == "qwen3:235b" or self.model == "deepseek-r1:671b": + result = callOpenAI_KJY(prompt, self.model) + else: + result = callOpenAI(prompt) + break + except Exception as e: + print(f"❌ LLM 调用失败 (尝试 {llm_attempt + 1}/2): {e}") + if llm_attempt == 0: + print(f" 将重试一次...") + else: + print(f" 跳过本 case,继续下一轮迭代(避免长时间卡死)") + self.fail_num += 1 + compile_error_info = None + if result is None: + continue end_time = time.time() elapsed_time = end_time - start_time @@ -1433,8 +1449,10 @@ def timeout_handler(signum, frame): coverage_info = self.global_coverage_manager.get_total_coverage_from_genhtml() if coverage_info and "coverage_percentage" in coverage_info: current_coverage = coverage_info["coverage_percentage"] + if self.statistics["coverage_data"]: + last_pct = self.statistics["coverage_data"][-1].get("coverage_percentage", 0) or 0 + current_coverage = max(current_coverage, last_pct) uncovered_count = self.global_coverage_manager.baseline_uncovered_count - self.statistics["coverage_data"].append({ "timestamp": time.time(), "coverage_percentage": current_coverage, @@ -1553,9 +1571,12 @@ def timeout_handler(signum, frame): coverage_info = self.global_coverage_manager.get_total_coverage_from_genhtml() if coverage_info and "coverage_percentage" in coverage_info: current_coverage = coverage_info["coverage_percentage"] + # 保证单调不降:若因缓存/重启等出现比上一点低则用上一点,避免图表“先升后降” + if self.statistics["coverage_data"]: + last_pct = self.statistics["coverage_data"][-1].get("coverage_percentage", 0) or 0 + current_coverage = max(current_coverage, last_pct) uncovered_count = self.global_coverage_manager.baseline_uncovered_count - # 记录覆盖率数据 self.statistics["coverage_data"].append({ "timestamp": time.time(), "coverage_percentage": current_coverage, @@ -1629,36 +1650,38 @@ def timeout_handler(signum, frame): ) print(f"🤖 正在调用 LLM 分析原因并生成改进代码...") - if self.model == "qwen3:235b" or self.model == "deepseek-r1:671b": - analysis_result = callOpenAI_KJY(analysis_prompt, self.model) - else: - analysis_result = callOpenAI(analysis_prompt) - - # 保存分析结果用于下次参考 - analysis_dir = "/root/ChipFuzzer_cursor/analysis_log" - os.makedirs(analysis_dir, exist_ok=True) - analysis_path = os.path.join( - analysis_dir, - f"analysis_{self.module_name}_{int(time.time())}.txt" - ) - with open(analysis_path, "w", encoding="utf-8") as f: - f.write(f"模块: {self.module_name}\n") - f.write(f"连续无覆盖次数: {consecutive_no_coverage}\n") - f.write(f"目标代码:\n{uncovered_code_line[:500]}\n") - f.write(f"LLM 分析:\n{analysis_result}\n") - - # 记录分析模式的交互 - self.agent_memory.record_interaction( - uncovered_code=uncovered_code_line, - prompt_type="analysis", - asm_code=raw_asm_code, - success=False, - compile_success=True, - coverage_improved=False, - strategy=f"analysis_mode_iteration_{iteration_count}", - feedback=str(analysis_result)[:500] - ) - print(f"💾 分析结果已保存: {analysis_path}") + try: + if self.model == "qwen3:235b" or self.model == "deepseek-r1:671b": + analysis_result = callOpenAI_KJY(analysis_prompt, self.model) + else: + analysis_result = callOpenAI(analysis_prompt) + except Exception as e: + print(f"❌ LLM 分析调用失败: {e},跳过分析,继续下一轮(避免卡死)") + analysis_result = None + if analysis_result is not None: + # 保存分析结果用于下次参考 + analysis_dir = "/root/ChipFuzzer_cursor/analysis_log" + os.makedirs(analysis_dir, exist_ok=True) + analysis_path = os.path.join( + analysis_dir, + f"analysis_{self.module_name}_{int(time.time())}.txt" + ) + with open(analysis_path, "w", encoding="utf-8") as f: + f.write(f"模块: {self.module_name}\n") + f.write(f"连续无覆盖次数: {consecutive_no_coverage}\n") + f.write(f"目标代码:\n{uncovered_code_line[:500]}\n") + f.write(f"LLM 分析:\n{analysis_result}\n") + self.agent_memory.record_interaction( + uncovered_code=uncovered_code_line, + prompt_type="analysis", + asm_code=raw_asm_code, + success=False, + compile_success=True, + coverage_improved=False, + strategy=f"analysis_mode_iteration_{iteration_count}", + feedback=str(analysis_result)[:500] + ) + print(f"💾 分析结果已保存: {analysis_path}") # 循环正常结束(无未覆盖代码) print(f"\n🎉 模块 [{self.module_name}] 测试完成!所有代码已覆盖!") @@ -1716,16 +1739,17 @@ def _handle_success_seed_if_any(self, covered_lines, asm_file_name, elf_file_nam shutil.copy(testcase_bin_path, os.path.join(GJ_SUCCESS_SEED_DIR, bin_file_name)) print(f"✅ BIN 文件已保存到 GJ_Success_Seed: {bin_file_name}") - # 生成并保存报告文件 - self._generate_case_report( - case_name=asm_file_name.replace(".S", ""), - module_name=self.module_name, - global_improved=global_improved, - global_reduced=global_reduced, - global_newly_covered=global_newly_covered, - covered_lines=covered_lines, - output_dir=GJ_SUCCESS_SEED_DIR - ) + # 生成并保存报告文件(仅当启用 --llm-report 时) + if self.use_llm_report: + self._generate_case_report( + case_name=asm_file_name.replace(".S", ""), + module_name=self.module_name, + global_improved=global_improved, + global_reduced=global_reduced, + global_newly_covered=global_newly_covered, + covered_lines=covered_lines, + output_dir=GJ_SUCCESS_SEED_DIR + ) # 2) 保存到 all_seed_dir with open(os.path.join(self.config.all_seed_dir, asm_file_name), 'w') as f: @@ -1856,10 +1880,13 @@ def _generate_case_report(self, case_name: str, module_name: str, global_improve """ report_file = os.path.join(output_dir, f"{case_name}.txt") - # 分析实际覆盖的模块和功能 + # 分析实际覆盖的模块和功能(仅在启用 --llm-report 时才会调用此函数) # 优先使用 global_newly_covered(全局新覆盖的代码),如果没有则使用 covered_lines lines_to_analyze = global_newly_covered if global_newly_covered else covered_lines - analysis = self._analyze_covered_modules(lines_to_analyze) + if lines_to_analyze: + analysis = self._analyze_covered_modules(lines_to_analyze) + else: + analysis = {"main_module": "未知", "module_distribution": {}, "main_function": "未知"} main_covered_module = analysis["main_module"] module_dist = analysis["module_distribution"] @@ -1966,7 +1993,14 @@ def parse_arguments(): type=str, default="CSR", # required=True, - help='target module' + help='目标/起始模块。开启 auto_switch 时表示从此模块开始验证;若指定了 --start-index 则以此序号为准' + ) + parser.add_argument( + '--start-index', + type=int, + default=None, + metavar='N', + help='从第 N 个模块开始验证(1=第一个)。与 auto_switch 配合:执行时先取前 num 个模块列表,从第 N 个开始往后验证,前面跳过' ) parser.add_argument( @@ -2034,6 +2068,13 @@ def parse_arguments(): help='运行已有的成功用例:在开始 LLM 生成之前,先运行 successed// 目录下的已有成功用例(默认:fresh 模式运行,continue 模式跳过)' ) + parser.add_argument( + '--llm-report', + action='store_true', + default=False, + help='启用 LLM 生成用例报告:成功用例保存时调用大模型分析覆盖代码并生成报告(默认不写报告)' + ) + return parser.parse_args() @@ -2094,25 +2135,25 @@ def main(): args.auto_switch = True print(f"ℹ️ 自动切换模块模式:默认启用(如需禁用,请使用 --no-auto-switch)") - if args.module and args.module != "auto": + # 优先支持「起始序号」:只填数字 N,从第 N 个模块开始(避免前端拉百条下拉) + if getattr(args, 'start_index', None) is not None and args.start_index >= 1: + all_modules = getTopUncoveredModules(num, args.coverage_filename_origin) + start_idx = min(args.start_index - 1, len(all_modules)) # 1-based -> 0-based + module_list = all_modules[start_idx:] + print(f"🔄 从第 {args.start_index} 个模块开始验证,共 {len(module_list)} 个模块(前 {start_idx} 个已跳过)") + elif args.module and args.module != "auto": # 单模块模式 if args.auto_switch: - # 如果开启了自动切换,先获取未覆盖代码最多的 num 个模块 - # 然后找到当前模块在列表中的位置,从该位置开始测试 all_modules = getTopUncoveredModules(num, args.coverage_filename_origin) if args.module in all_modules: - # 找到当前模块的位置,从该位置开始 start_idx = all_modules.index(args.module) module_list = all_modules[start_idx:] else: - # 如果当前模块不在列表中,先测试当前模块,然后测试列表中的模块 module_list = [args.module] + all_modules print(f"🔄 自动切换模式:将从模块 {args.module} 开始,共 {len(module_list)} 个模块") else: - # 不开启自动切换,只测试指定模块 module_list = [args.module] else: - # 自动选择模式:获取未覆盖代码最多的 num 个模块 module_list = getTopUncoveredModules(num, args.coverage_filename_origin) # 每模块最大尝试次数 @@ -2125,6 +2166,7 @@ def main(): print(f" 使用模型: {model}") print(f" 运行模式: {run_mode} ({'继续累积覆盖率' if run_mode == 'continue' else '创建新的覆盖率文件'})") print(f" SPEC 文件分析: {'启用' if args.use_spec else '禁用'}") + print(f" LLM 用例报告: {'启用' if args.llm_report else '不写报告(默认)'}") print(f" 全局 annotated 目录: {config.global_annotated_dir}") print(f" 累积覆盖率文件: {config.sum_dat_file}") print(f"=" * 60) @@ -2272,7 +2314,8 @@ def save_statistics_realtime(): Coverage_filename_origin, Coverage_filename_later, model, global_coverage_manager=global_coverage_manager, # 共享同一个实例 - use_spec=args.use_spec # 从命令行参数获取 + use_spec=args.use_spec, # 从命令行参数获取 + use_llm_report=args.llm_report # 是否使用 LLM 写用例报告(默认否) ) # 先跑已有的成功用例(这可能需要较长时间,特别是如果有多个用例)