diff --git a/static/style.css b/static/style.css
index 84301de..d2e7d7d 100644
--- a/static/style.css
+++ b/static/style.css
@@ -1364,6 +1364,74 @@ optgroup {
gap: var(--space-4);
}
+.mechanism-visual-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
+ gap: var(--space-4);
+ margin-top: var(--space-4);
+}
+
+.mechanism-visual-card {
+ display: grid;
+ grid-template-columns: 220px minmax(0, 1fr);
+ gap: 0;
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-xl);
+ background: var(--bg-primary);
+ overflow: hidden;
+}
+
+.mechanism-visual-svg {
+ background: linear-gradient(180deg, #eff6ff 0%, #f8fafc 100%);
+ border-right: 1px solid var(--border-light);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 180px;
+}
+
+.mechanism-svg {
+ width: 100%;
+ height: 100%;
+}
+
+.mechanism-visual-body {
+ padding: var(--space-4);
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-4);
+}
+
+.mechanism-kpis {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: var(--space-3);
+}
+
+.mechanism-kpi {
+ padding: var(--space-3);
+ border-radius: var(--radius-lg);
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-light);
+}
+
+.mechanism-kpi-label {
+ display: block;
+ font-size: var(--text-xs);
+ color: var(--text-tertiary);
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ margin-bottom: var(--space-1);
+}
+
+.mechanism-kpi-value {
+ display: block;
+ font-size: var(--text-sm);
+ font-weight: var(--font-semibold);
+ color: var(--text-primary);
+ word-break: break-word;
+}
+
.mechanism-card {
border: 1px solid var(--border-light);
border-radius: var(--radius-lg);
@@ -1392,7 +1460,7 @@ optgroup {
}
.mechanism-actions {
- margin-top: var(--space-4);
+ margin-top: auto;
display: flex;
justify-content: flex-end;
}
@@ -1480,6 +1548,19 @@ optgroup {
.info-list {
grid-template-columns: 1fr;
}
+
+ .mechanism-visual-card {
+ grid-template-columns: 1fr;
+ }
+
+ .mechanism-visual-svg {
+ border-right: 0;
+ border-bottom: 1px solid var(--border-light);
+ }
+
+ .mechanism-kpis {
+ grid-template-columns: 1fr;
+ }
}
.info-item {
@@ -1586,6 +1667,11 @@ optgroup {
background: var(--warning-bg);
}
+.recommendation-item.critical {
+ border-left-color: var(--error);
+ background: #fef2f2;
+}
+
.recommendation-item.medium {
border-left-color: var(--info);
background: var(--info-bg);
@@ -1612,6 +1698,11 @@ optgroup {
color: white;
}
+.priority-badge.critical {
+ background: var(--error);
+ color: white;
+}
+
.priority-badge.medium {
background: var(--info);
color: white;
@@ -1628,6 +1719,123 @@ optgroup {
color: var(--text-primary);
}
+.dfm-card-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+ gap: var(--space-4);
+}
+
+.dfm-card {
+ border-radius: var(--radius-xl);
+ border: 1px solid var(--border-light);
+ padding: var(--space-4);
+ background: var(--bg-primary);
+}
+
+.dfm-card-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-3);
+ margin-bottom: var(--space-3);
+}
+
+.dfm-card-message {
+ font-size: var(--text-sm);
+ color: var(--text-primary);
+ line-height: 1.7;
+}
+
+.dfm-card-hint {
+ margin-top: var(--space-3);
+ font-size: var(--text-xs);
+ color: var(--text-tertiary);
+}
+
+.dfm-level-critical {
+ border-color: #fecaca;
+ background: #fef2f2;
+}
+
+.dfm-level-high {
+ border-color: #fde68a;
+ background: #fff7ed;
+}
+
+.dfm-level-medium {
+ border-color: #bfdbfe;
+ background: #eff6ff;
+}
+
+.dfm-level-low {
+ border-color: #bbf7d0;
+ background: #f0fdf4;
+}
+
+.markdown-preview {
+ color: var(--text-secondary);
+ font-size: var(--text-sm);
+ line-height: 1.75;
+}
+
+.markdown-preview h1,
+.markdown-preview h2,
+.markdown-preview h3,
+.markdown-preview h4,
+.markdown-preview h5,
+.markdown-preview h6 {
+ color: var(--text-primary);
+ margin: 0 0 var(--space-3);
+}
+
+.markdown-preview p,
+.markdown-preview ul,
+.markdown-preview ol,
+.markdown-preview blockquote,
+.markdown-preview pre {
+ margin: 0 0 var(--space-4);
+}
+
+.markdown-preview ul,
+.markdown-preview ol {
+ padding-left: 1.25rem;
+}
+
+.markdown-preview code {
+ font-family: var(--font-mono);
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-sm);
+ padding: 0.1rem 0.35rem;
+}
+
+.markdown-preview pre {
+ padding: var(--space-4);
+ border-radius: var(--radius-lg);
+ background: var(--gray-900);
+ overflow-x: auto;
+}
+
+.markdown-preview pre code {
+ background: transparent;
+ border: 0;
+ color: #f8fafc;
+ padding: 0;
+}
+
+.markdown-preview blockquote {
+ padding: var(--space-3) var(--space-4);
+ border-left: 4px solid var(--primary-300);
+ background: var(--primary-50);
+ border-radius: var(--radius-md);
+ color: var(--text-secondary);
+}
+
+.markdown-preview a {
+ color: var(--primary-700);
+ text-decoration: underline;
+}
+
.recommendation-reason {
font-size: var(--text-xs);
color: var(--text-tertiary);
diff --git a/static/vue-app.js b/static/vue-app.js
index 27c32d4..991a37f 100644
--- a/static/vue-app.js
+++ b/static/vue-app.js
@@ -1520,6 +1520,24 @@ const ResultView = {
const sliderMechanisms = computed(() => selectedSideActions.value?.slider_mechanisms || []);
const lifterMechanisms = computed(() => selectedSideActions.value?.lifter_mechanisms || []);
const sideActionRecommendations = computed(() => selectedSideActions.value?.recommendations || []);
+ const allMechanisms = computed(() => {
+ const sliders = sliderMechanisms.value.map((item, index) => ({ type: 'slider', index, item }));
+ const lifters = lifterMechanisms.value.map((item, index) => ({ type: 'lifter', index, item }));
+ return [...sliders, ...lifters];
+ });
+ const dfmLevelSummary = computed(() => {
+ const base = { critical: 0, high: 0, medium: 0, low: 0 };
+ (selectedDfmViolations.value || []).forEach((item) => {
+ const level = String(item.level || 'medium').toLowerCase();
+ if (base[level] === undefined) {
+ base.medium += 1;
+ } else {
+ base[level] += 1;
+ }
+ });
+ return base;
+ });
+ const llmReportHtml = computed(() => renderMarkdownToHtml(state.task?.llm_report || ''));
const stageTimingEntries = computed(() => {
const timings = state.task?.stage_timings || {};
return Object.entries(timings)
@@ -1553,8 +1571,7 @@ const ResultView = {
{ id: 'side-actions', label: '抽芯建议' },
{ id: 'dfm-check', label: 'DFM' },
{ id: 'export-cam', label: '导出/CAM' },
- { id: 'analysis-details', label: '分析详情' },
- { id: 'diagnostic-info', label: '诊断' }
+ { id: 'llm-report', label: '设计报告' }
];
const selectScheme = (schemeId) => {
@@ -1627,6 +1644,138 @@ const ResultView = {
if (type === 'lifter') return '斜顶抽芯';
return type || '机构';
};
+ const formatMechanismTypeShort = (type) => type === 'slider' ? '滑块' : type === 'lifter' ? '斜顶' : '机构';
+ const normalizeVector = (vector, fallback = [0.8, -0.6, 0.5]) => {
+ if (!Array.isArray(vector) || vector.length < 3) return fallback;
+ const values = vector.map(v => Number(v) || 0);
+ const length = Math.sqrt(values.reduce((acc, val) => acc + val * val, 0));
+ if (!length) return fallback;
+ return values.map(v => v / length);
+ };
+ const mechanismSchematicSvg = (type, item = {}) => {
+ const direction = normalizeVector(type === 'slider' ? item.slide_direction : item.location);
+ const location = normalizeVector(item.location, [0.3, 0.2, 0.5]);
+ const startX = 120 + (location[0] * 18);
+ const startY = 90 - (location[1] * 16);
+ const endX = startX + (direction[0] * 42);
+ const endY = startY - (direction[2] * 28) + (direction[1] * 12);
+ const mechanismColor = type === 'slider' ? '#2563eb' : '#7c3aed';
+ const label = type === 'slider' ? '滑块抽芯' : '斜顶抽芯';
+ return `
+
+ `;
+ };
+ const escapeHtml = (value) => String(value || '')
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ const applyInlineMarkdown = (text) => {
+ let html = escapeHtml(text);
+ html = html.replace(/`([^`]+)`/g, '$1');
+ html = html.replace(/\*\*([^*]+)\*\*/g, '$1');
+ html = html.replace(/\*([^*]+)\*/g, '$1');
+ html = html.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '$1');
+ return html;
+ };
+ const renderMarkdownToHtml = (markdown) => {
+ const source = String(markdown || '').replace(/\r\n/g, '\n').trim();
+ if (!source) return '';
+ const lines = source.split('\n');
+ const html = [];
+ let inList = false;
+ let inCode = false;
+ let codeBuffer = [];
+ const closeList = () => {
+ if (inList) {
+ html.push('');
+ inList = false;
+ }
+ };
+ const closeCode = () => {
+ if (inCode) {
+ html.push(`
${escapeHtml(codeBuffer.join('\n'))}`);
+ inCode = false;
+ codeBuffer = [];
+ }
+ };
+ lines.forEach((rawLine) => {
+ const line = rawLine.trimEnd();
+ if (line.startsWith('```')) {
+ closeList();
+ if (inCode) {
+ closeCode();
+ } else {
+ inCode = true;
+ codeBuffer = [];
+ }
+ return;
+ }
+ if (inCode) {
+ codeBuffer.push(rawLine);
+ return;
+ }
+ const trimmed = line.trim();
+ if (!trimmed) {
+ closeList();
+ html.push('');
+ return;
+ }
+ const headingMatch = trimmed.match(/^(#{1,6})\s+(.*)$/);
+ if (headingMatch) {
+ closeList();
+ const level = headingMatch[1].length;
+ html.push(`${applyInlineMarkdown(trimmed)}
`); + return; + } + if (/^>\s?/.test(trimmed)) { + closeList(); + html.push(`${applyInlineMarkdown(trimmed.replace(/^>\s?/, ''))}`); + return; + } + closeList(); + html.push(`
${applyInlineMarkdown(trimmed)}
`); + }); + closeList(); + closeCode(); + return html.filter(Boolean).join('\n'); + }; const openMechanismDetail = (type, index, item) => { state.activeMechanismDetail = { type, @@ -1736,6 +1885,9 @@ const ResultView = { sliderMechanisms, lifterMechanisms, sideActionRecommendations, + allMechanisms, + dfmLevelSummary, + llmReportHtml, stageTimingEntries, getWallThicknessSummary, countFeaturesByTypes, @@ -1745,6 +1897,8 @@ const ResultView = { formatVector, formatMechanismSize, formatMechanismType, + formatMechanismTypeShort, + mechanismSchematicSvg, openMechanismDetail, closeMechanismDetail, primaryRecommendation, @@ -2169,63 +2323,37 @@ const ResultView = { + -| 规则 | -级别 | -说明 | -
|---|---|---|
| {{ item.rule || 'N/A' }} | -{{ item.level || 'medium' }} | -{{ item.message || 'N/A' }} | -
| 阶段 | -耗时 | -
|---|---|
| {{ formatStageName(stage) }} | -{{ formatTiming(duration) }} | -
{{ state.task.llm_report }}
-