x
This commit is contained in:
+69
-16
@@ -26,13 +26,44 @@ _DESIGN_REPORT_SYSTEM = """你是一位资深注塑模具设计工程师,拥
|
||||
|
||||
要求:
|
||||
1. 使用中文
|
||||
2. 按 "问题摘要 → 关键风险 → 分模方案推荐 → 制造可行性 → 修改建议" 结构组织
|
||||
2. 按 "关键问题 → 工艺参数建议 → 改进建议" 结构组织
|
||||
3. 技术术语准确(如:锁模力、投影面积、分型面、滑块、斜顶、拔模角、缩痕、熔接痕)
|
||||
4. 每个建议标注优先级(高/中/低)和预计工时
|
||||
5. 报告末尾给出一个总体评分(1-10分)
|
||||
6. 如果数据不足以判断某项,明确标注"数据不足,需人工确认"
|
||||
4. 每个问题标注优先级(high / medium / low)
|
||||
5. 如果数据不足以判断某项,明确标注"数据不足,需人工确认"
|
||||
|
||||
直接输出 Markdown 格式报告,不要输出 JSON。"""
|
||||
严格输出 JSON,不要输出其他内容。JSON 格式:
|
||||
{
|
||||
"title": "模具设计评审报告",
|
||||
"overview": "一段 1-2 句话的整体概述",
|
||||
"sections": [
|
||||
{
|
||||
"heading": "关键问题",
|
||||
"type": "issues",
|
||||
"items": [
|
||||
{"level": "high", "content": "拔模角不足,建议增加到 2° 以上"},
|
||||
{"level": "medium", "content": "壁厚偏差较大,可能产生缩痕"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"heading": "工艺参数建议",
|
||||
"type": "params_table",
|
||||
"headers": ["参数", "推荐值", "说明"],
|
||||
"rows": [
|
||||
["锁模力", "150 吨", "基于投影面积计算"],
|
||||
["注塑温度", "230°C", "ABS 材料推荐值"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"heading": "改进建议",
|
||||
"type": "recommendations",
|
||||
"items": [
|
||||
"建议将主流道直径从 4mm 增加到 6mm",
|
||||
"建议在所有垂直面增加 1-2° 拔模角"
|
||||
]
|
||||
}
|
||||
],
|
||||
"overall_score": 7.5
|
||||
}"""
|
||||
|
||||
_DESIGN_REPORT_USER = """请根据以下模具分析数据生成评审报告:
|
||||
|
||||
@@ -62,7 +93,12 @@ _DESIGN_REPORT_USER = """请根据以下模具分析数据生成评审报告:
|
||||
- 收缩率:{shrinkage_rate}
|
||||
|
||||
## 原始设计建议
|
||||
{recommendations}"""
|
||||
{recommendations}
|
||||
|
||||
请生成 JSON 格式评审报告。issues 部分不要超过 8 条,每条内容简洁在一行内;
|
||||
params_table 至少要包含锁模力、成型周期、模仁材料、推荐型腔数 4 行;
|
||||
如果某项数据标记为"自动计算"或"自动选择",请在说明中注明"需人工确认";
|
||||
overall_score 范围 1-10。"""
|
||||
|
||||
_SIDE_ACTION_ANALYSIS_SYSTEM = """你是一位资深注塑模具结构工程师。
|
||||
请根据提供的 STP 分析结果,判断当前产品是否需要倒扣/抽芯机构,并输出标准化结论。
|
||||
@@ -166,17 +202,25 @@ class LLMService:
|
||||
self,
|
||||
analysis_result: Dict[str, Any],
|
||||
detailed_cavity_json: Optional[Dict[str, Any]] = None,
|
||||
) -> Optional[str]:
|
||||
"""生成模具设计评审报告 (Markdown)"""
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""生成模具设计评审报告 (结构化 JSON)"""
|
||||
if not self._enabled:
|
||||
return None
|
||||
|
||||
try:
|
||||
prompt = self._build_design_report_prompt(analysis_result, detailed_cavity_json)
|
||||
response = await self._chat(_DESIGN_REPORT_SYSTEM, prompt, self._max_tokens)
|
||||
if response:
|
||||
logger.info("LLM 设计报告生成成功 (%d 字符)", len(response))
|
||||
return response
|
||||
response = await self._chat(
|
||||
_DESIGN_REPORT_SYSTEM,
|
||||
prompt,
|
||||
self._max_tokens,
|
||||
expect_json=True,
|
||||
)
|
||||
if not response:
|
||||
return None
|
||||
result = self._parse_json_response(response)
|
||||
if result:
|
||||
logger.info("LLM 设计报告生成成功 (%d sections)", len(result.get("sections", [])))
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning("LLM 设计报告生成失败(不影响主流程): %s", e)
|
||||
return None
|
||||
@@ -214,10 +258,14 @@ class LLMService:
|
||||
|
||||
@staticmethod
|
||||
def compose_llm_report(
|
||||
design_report: Optional[str],
|
||||
design_report: Optional[Dict[str, Any]],
|
||||
side_action_analysis: Optional[Dict[str, Any]],
|
||||
) -> Optional[str]:
|
||||
"""将结构化倒扣分析打包进既有 llm_report 字段,避免改动外部协议。"""
|
||||
"""将结构化报告和倒扣分析打包进 llm_report 字段,避免改动外部协议。
|
||||
|
||||
设计报告以 <!--DESIGN_REPORT_BEGIN--> / <!--DESIGN_REPORT_END--> 包裹的 JSON 嵌入,
|
||||
倒扣分析以 <!--SIDE_ACTION_AI_BEGIN--> / <!--SIDE_ACTION_AI_END--> 包裹的 JSON 嵌入。
|
||||
"""
|
||||
sections: List[str] = []
|
||||
if side_action_analysis:
|
||||
payload = json.dumps(side_action_analysis, ensure_ascii=False)
|
||||
@@ -226,8 +274,13 @@ class LLMService:
|
||||
f"{payload}\n"
|
||||
"<!--SIDE_ACTION_AI_END-->"
|
||||
)
|
||||
if design_report and design_report.strip():
|
||||
sections.append(design_report.strip())
|
||||
if design_report:
|
||||
payload = json.dumps(design_report, ensure_ascii=False)
|
||||
sections.append(
|
||||
"<!--DESIGN_REPORT_BEGIN-->\n"
|
||||
f"{payload}\n"
|
||||
"<!--DESIGN_REPORT_END-->"
|
||||
)
|
||||
merged = "\n\n".join(sections).strip()
|
||||
return merged or None
|
||||
|
||||
|
||||
+85
-10
@@ -1526,6 +1526,17 @@ const ResultView = {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const parseEmbeddedDesignReport = (report) => {
|
||||
const source = String(report || '');
|
||||
const match = source.match(/<!--DESIGN_REPORT_BEGIN-->\s*([\s\S]*?)\s*<!--DESIGN_REPORT_END-->/);
|
||||
if (!match) return null;
|
||||
try {
|
||||
return JSON.parse(match[1]);
|
||||
} catch (error) {
|
||||
console.warn('LLM 设计报告解析失败:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const stripEmbeddedSideActionAi = (report) => String(report || '')
|
||||
.replace(/<!--SIDE_ACTION_AI_BEGIN-->\s*[\s\S]*?\s*<!--SIDE_ACTION_AI_END-->/, '')
|
||||
.trim();
|
||||
@@ -1625,9 +1636,21 @@ const ResultView = {
|
||||
'ai'
|
||||
) || fallbackSideActionAiAdvice.value;
|
||||
});
|
||||
const cleanedLlmReport = computed(() => stripEmbeddedSideActionAi(state.task?.llm_report || ''));
|
||||
const hasVisibleLlmReport = computed(() => Boolean(cleanedLlmReport.value));
|
||||
const llmReportHtml = computed(() => renderMarkdownToHtml(cleanedLlmReport.value));
|
||||
const designReport = computed(() =>
|
||||
parseEmbeddedDesignReport(state.task?.llm_report || '')
|
||||
);
|
||||
const hasVisibleDesignReport = computed(() =>
|
||||
designReport.value && (designReport.value.sections?.length > 0 || designReport.value.overview)
|
||||
);
|
||||
const designReportSections = computed(() => {
|
||||
const sections = designReport.value?.sections || [];
|
||||
return sections.filter(s => {
|
||||
if (s.type === 'issues') return s.items?.length > 0;
|
||||
if (s.type === 'params_table') return s.rows?.length > 0;
|
||||
if (s.type === 'recommendations') return s.items?.length > 0;
|
||||
return true;
|
||||
});
|
||||
});
|
||||
const stageTimingEntries = computed(() => {
|
||||
const timings = state.task?.stage_timings || {};
|
||||
return Object.entries(timings)
|
||||
@@ -1907,7 +1930,9 @@ const ResultView = {
|
||||
sideActionAiAdvice,
|
||||
hasVisibleLlmReport,
|
||||
dfmLevelSummary,
|
||||
llmReportHtml,
|
||||
designReport,
|
||||
hasVisibleDesignReport,
|
||||
designReportSections,
|
||||
stageTimingEntries,
|
||||
getWallThicknessSummary,
|
||||
countFeaturesByTypes,
|
||||
@@ -2006,10 +2031,10 @@ const ResultView = {
|
||||
<div class="result-card result-card-highlight" v-if="candidateSchemes.length > 1">
|
||||
<div class="summary-header">
|
||||
<h3>备选方案</h3>
|
||||
<span class="badge badge-warning">{{ candidateSchemes.length - 1 }} 个备选</span>
|
||||
<span class="badge badge-warning">1 个备选</span>
|
||||
</div>
|
||||
<div class="info-list">
|
||||
<template v-for="scheme in candidateSchemes.slice(1)" :key="'alt-' + scheme.scheme_id">
|
||||
<template v-for="scheme in candidateSchemes.slice(1, 2)" :key="'alt-' + scheme.scheme_id">
|
||||
<div class="info-item" style="padding: var(--space-1) 0; border-bottom: 1px solid var(--border-light);">
|
||||
<span class="info-label" style="cursor: pointer;" @click="selectScheme(scheme.scheme_id)">
|
||||
{{ scheme.title || scheme.scheme_id }}
|
||||
@@ -2357,10 +2382,60 @@ const ResultView = {
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div id="llm-report" class="viewer-section" v-if="hasVisibleLlmReport">
|
||||
<h3>LLM 设计报告</h3>
|
||||
<div class="result-card">
|
||||
<div class="markdown-preview" v-html="llmReportHtml"></div>
|
||||
<div id="llm-report" class="viewer-section" v-if="hasVisibleDesignReport">
|
||||
<div class="summary-header">
|
||||
<h3>LLM 设计报告</h3>
|
||||
<span class="badge" :class="(designReport.overall_score || 0) >= 7 ? 'badge-success' : (designReport.overall_score || 0) >= 5 ? 'badge-warning' : 'badge-error'">
|
||||
评分 {{ designReport.overall_score || 'N/A' }} / 10
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="designReport.overview" class="inline-note" style="margin-bottom: var(--space-4); font-size: 0.95rem; color: var(--text-primary);">
|
||||
{{ designReport.overview }}
|
||||
</div>
|
||||
<div class="result-grid">
|
||||
<template v-for="(sec, si) in designReportSections" :key="'dr-sec-' + si">
|
||||
<div class="result-card" :class="sec.type === 'issues' ? 'result-card-highlight' : ''">
|
||||
<h4 :style="sec.type === 'issues' ? 'color: var(--danger-color);' : ''">{{ sec.heading }}</h4>
|
||||
|
||||
<template v-if="sec.type === 'issues'">
|
||||
<div class="recommendations-list">
|
||||
<div
|
||||
v-for="(item, ii) in sec.items"
|
||||
:key="'dr-issue-' + ii"
|
||||
:class="['recommendation-item', item.level === 'high' ? 'high' : item.level === 'medium' ? 'medium' : 'low']"
|
||||
>
|
||||
<span class="priority-badge" :class="item.level || 'medium'" style="margin-right: var(--space-1);">{{ item.level === 'high' ? '🔴' : item.level === 'medium' ? '🟡' : '🟢' }}</span>
|
||||
<div class="recommendation-text">{{ item.content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="sec.type === 'params_table'">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="(h, hi) in sec.headers" :key="'dr-h-' + hi">{{ h }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, ri) in sec.rows" :key="'dr-r-' + ri">
|
||||
<td v-for="(cell, ci) in row" :key="'dr-c-' + ci">{{ cell }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<template v-else-if="sec.type === 'recommendations'">
|
||||
<div class="recommendations-list">
|
||||
<div class="recommendation-item low" v-for="(item, ii) in sec.items" :key="'dr-rec-' + ii">
|
||||
<div class="recommendation-text">💡 {{ item }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-else class="inline-note">{{ JSON.stringify(sec) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user