欢迎加入开源鸿蒙PC社区:
https://harmonypc.csdn.net/

Atomgit仓库地址:https://gitcode.com/feng8403000/mingshu_ai

匹配视频链接:
https://www.bilibili.com/video/BV1gw5b6vEsa

一、功能概述

症状诊断系统是命枢AI生命科学模拟器的核心功能之一,位于左侧面板的"病原体与侵害因子"区域。该系统通过自然语言处理技术,让用户能够输入身体症状描述,系统自动分析可能的感染类型并计算感染概率,为后续的免疫细胞对抗模拟提供数据支撑。

1.1 功能定位

症状诊断系统在整体架构中的作用:

层级 功能 说明
输入层 症状采集 用户输入或选择症状标签
处理层 AI分析 调用DeepSeek-V3模型进行症状分析
输出层 诊断结果 展示可能的感染源及其概率
应用层 战场集成 将诊断结果转化为对战系统中的病原体

1.2 核心特性

  • 智能症状识别:支持手动输入和快捷标签选择两种方式
  • AI驱动分析:基于大语言模型进行专业医疗分析
  • 概率排序展示:按照感染概率从高到低排列可能的病原体
  • 一键集成:诊断结果可直接添加到对战系统中
  • 多重诊断模式:提供症状诊断和AI问诊两种模式

二、UI设计详解

2.1 整体布局

症状诊断区域位于左侧面板顶部,采用卡片式设计,包含以下几个功能区域:

┌─────────────────────────────────────┐
│ 🤒 症状诊断                          │
├─────────────────────────────────────┤
│ [输入框] 输入身体反应,如:发热、...   │
├─────────────────────────────────────┤
│ 常见症状:                           │
│ 🌡️发热 😣头痛 🤧咳嗽 😫乏力 ...       │
├─────────────────────────────────────┤
│ [🤖 AI诊断] [❓ AI问诊]              │
├─────────────────────────────────────┤
│ [诊断结果区域]                       │
└─────────────────────────────────────┘

2.2 核心组件设计

2.2.1 症状输入框

输入框采用深色主题设计,与整体暗黑风格保持一致:

.symptom-input {
    width: 100%;
    height: 80px;
    padding: 12px;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.2);
    border-radius: 8px;
    color: #fff;
    font-size: 14px;
    resize: none;
}

设计要点:

  • 背景透明度为0.05,保持深色主题的层次感
  • 边框采用半透明白色,增加视觉深度
  • 圆角设计(8px),符合现代UI设计趋势
2.2.2 常见症状标签

常见症状标签采用可点击的标签按钮形式,支持快速选择:

<div class="symptoms-tags">
    <span class="symptom-tag" onclick="addSymptom('发热')">🌡️ 发热</span>
    <span class="symptom-tag" onclick="addSymptom('头痛')">😣 头痛</span>
    <span class="symptom-tag" onclick="addSymptom('咳嗽')">🤧 咳嗽</span>
    <span class="symptom-tag" onclick="addSymptom('乏力')">😫 乏力</span>
    <span class="symptom-tag" onclick="addSymptom('恶心')">🤢 恶心</span>
    <span class="symptom-tag" onclick="addSymptom('腹泻')">💩 腹泻</span>
    <span class="symptom-tag" onclick="addSymptom('呼吸困难')">😮‍💨 呼吸困难</span>
    <span class="symptom-tag" onclick="addSymptom('肌肉酸痛')">💪 肌肉酸痛</span>
    <span class="symptom-tag" onclick="addSymptom('喉咙痛')">🤒 喉咙痛</span>
    <span class="symptom-tag" onclick="addSymptom('鼻塞')">👃 鼻塞</span>
    <span class="symptom-tag" onclick="addSymptom('呕吐')">🤮 呕吐</span>
    <span class="symptom-tag" onclick="addSymptom('头晕')">😵 头晕</span>
</div>

标签样式设计:

.symptom-tag {
    display: inline-block;
    padding: 6px 12px;
    margin: 4px;
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.15);
    border-radius: 20px;
    font-size: 13px;
    cursor: pointer;
    transition: all 0.2s ease;
}

.symptom-tag:hover {
    background: rgba(78, 205, 196, 0.2);
    border-color: rgba(78, 205, 196, 0.5);
}

设计特点:

  • 使用emoji图标增强视觉识别性
  • 圆角胶囊形状(20px),现代感十足
  • 悬停效果增强交互反馈
2.2.3 诊断按钮

诊断区域包含两个核心按钮:

按钮 样式 功能
AI诊断 青绿色渐变 基于症状进行病原体分析
AI问诊 金黄色渐变 打开AI问答问诊模态框

按钮样式代码:

.analyze-btn {
    width: 100%;
    padding: 12px;
    margin-top: 10px;
    background: linear-gradient(135deg, #4ecdc4, #44a08d);
    border: none;
    border-radius: 8px;
    color: #fff;
    font-size: 16px;
    font-weight: bold;
    cursor: pointer;
    transition: transform 0.2s, box-shadow 0.2s;
}

.analyze-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 5px 20px rgba(78, 205, 196, 0.4);
}

.analyze-btn.diagnosis {
    background: linear-gradient(135deg, #fdcb6e, #f39c12);
    color: #1a1a2e;
}
2.2.4 诊断结果展示

诊断结果采用卡片列表形式展示,每个卡片包含:

<div class="infection-option" data-index="0">
    <div class="infection-header">
        <span class="infection-icon">🦠</span>
        <span class="infection-name">流感病毒</span>
        <span class="infection-prob">85%</span>
    </div>
    <div class="infection-details">
        <span class="infection-type">病毒</span>
        <span class="infection-severity">中度</span>
    </div>
    <div class="infection-reason">症状与流感典型表现高度匹配</div>
    <div class="infection-checkbox"></div>
</div>

设计要点:

  • 使用颜色区分概率等级(红色>70%,橙色40-70%,绿色<40%)
  • 使用颜色区分严重程度(红色重度,橙色中度,绿色轻度)
  • 支持多选和一键添加到战场

三、核心代码实现

3.1 症状添加功能

当用户点击症状标签时,触发addSymptom()函数:

function addSymptom(symptom) {
    const input = document.getElementById('symptomInput');
    const currentValue = input.value.trim();
    if (currentValue) {
        input.value = currentValue + '、' + symptom;
    } else {
        input.value = symptom;
    }
}

实现逻辑

  1. 获取输入框当前值
  2. 如果已有内容,使用顿号连接新症状
  3. 如果为空,直接设置新症状

3.2 AI查询核心函数

queryAI()是与AI模型交互的核心函数:

async function queryAI(prompt) {
    try {
        console.log('========== AI 请求开始 (queryAI) ==========');
        console.log('API_URL:', API_URL);
        console.log('API_KEY:', API_KEY.substring(0, 10) + '...');
        console.log('模型: deepseek-ai/DeepSeek-V3');
        console.log('prompt长度:', prompt.length, '字符');
        console.log('prompt预览:', prompt.substring(0, 100) + '...');

        const response = await fetch(API_URL, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${API_KEY}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                model: "deepseek-ai/DeepSeek-V3",
                messages: [{ role: "user", content: prompt }],
                stream: false,
                max_tokens: 2048,
                temperature: 0.6,
                top_p: 0.95,
                top_k: 50,
                frequency_penalty: 0,
                thinking_budget: 2048
            })
        });

        console.log('HTTP状态码:', response.status);

        if (!response.ok) {
            const errorText = await response.text();
            console.error('HTTP错误响应:', errorText);
            throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`);
        }

        const contentType = response.headers.get('content-type') || '';
        let fullResponse = '';
        let rawResponse = '';

        if (contentType.includes('text/event-stream') || contentType.includes('stream')) {
            console.log('检测到流式响应,使用SSE解析');
            const reader = response.body.getReader();
            const decoder = new TextDecoder('utf-8');
            
            while (true) {
                const { done, value } = await reader.read();
                if (done) break;
                const chunk = decoder.decode(value);
                rawResponse += chunk;
                const lines = chunk.split('\n');

                for (const line of lines) {
                    if (!line.startsWith('data:')) continue;
                    if (line.trim() === 'data:[DONE]') break;

                    try {
                        const jsonStr = line.replace(/^data:\s*/, '').trim();
                        if (jsonStr) {
                            const data = JSON.parse(jsonStr);
                            if (data.choices && data.choices[0]) {
                                if (data.choices[0].delta) {
                                    fullResponse += data.choices[0].delta.content || '';
                                } else if (data.choices[0].message) {
                                    fullResponse += data.choices[0].message.content || '';
                                }
                            }
                        }
                    } catch (e) {
                        console.warn('SSE解析错误:', e);
                    }
                }
            }
        } else {
            console.log('检测到非流式响应,使用普通解析');
            rawResponse = await response.text();
            console.log('原始响应文本:', rawResponse);
            
            try {
                const jsonData = JSON.parse(rawResponse);
                if (jsonData.choices && jsonData.choices[0]) {
                    if (jsonData.choices[0].delta) {
                        fullResponse = jsonData.choices[0].delta.content || '';
                    } else if (jsonData.choices[0].message) {
                        fullResponse = jsonData.choices[0].message.content || '';
                    }
                }
            } catch (e) {
                console.warn('JSON解析失败,尝试作为纯文本处理:', e);
                fullResponse = rawResponse;
            }
        }

        console.log('========== AI 响应解析结果 ==========');
        console.log('提取的文本内容:', fullResponse || '(空)');
        console.log('==========================================');

        return fullResponse;
    } catch (error) {
        console.error('========== AI 请求失败 ==========');
        console.error('错误:', error.message);
        console.log('==========================================');
        return null;
    }
}

核心配置参数说明

参数 说明
model deepseek-ai/DeepSeek-V3 使用的AI模型
max_tokens 2048 最大输出token数
temperature 0.6 温度参数,控制输出随机性
top_p 0.95 核采样参数
top_k 50 Top-K采样
stream false 是否流式响应

3.3 症状分析主函数

analyzeSymptoms()是症状诊断的入口函数:

async function analyzeSymptoms() {
    const symptoms = document.getElementById('symptomInput').value;
    const resultEl = document.getElementById('symptomResult');
    const statusEl = document.getElementById('symptomStatus');

    if (!symptoms.trim()) {
        alert('请输入身体反应症状');
        return;
    }

    statusEl.textContent = 'AI分析中...';
    statusEl.className = 'symptom-status loading';
    resultEl.classList.remove('show');

    const prompt = `根据以下身体症状,请分析可能感染的病毒或细菌。请返回3-5种最可能的感染情况,按照概率从高到低排序。

症状:${symptoms}

只返回纯JSON,不要包含任何其他文本。严格按照以下格式返回:
{
  "possibleInfections": [
    {
      "name": "流感病毒",
      "type": "virus",
      "probability": 0.85,
      "symptoms": ["发热", "咳嗽", "头痛"],
      "severity": "中度",
      "matchingReason": "症状与流感典型表现高度匹配"
    }
  ]
}

注意:
1. 只返回JSON,不要有其他任何文字说明
2. type字段只能是"virus"或"bacteria"
3. probability必须是0-1之间的小数
4. severity只能是"轻度"、"中度"或"重度"
5. 确保JSON格式正确,没有语法错误`;

    const result = await queryAI(prompt);

    console.log('========== 症状诊断 AI 返回 ==========');
    console.log('用户输入症状:', symptoms);
    console.log('AI返回原始文本:', result);
    console.log('======================================================');

    if (result && result.trim()) {
        try {
            const parsedData = parseAIResponse(result);
            
            console.log('解析后的数据:', parsedData);

            if (parsedData && parsedData.possibleInfections && 
                Array.isArray(parsedData.possibleInfections) && 
                parsedData.possibleInfections.length > 0) {
                symptomAnalysisResult = parsedData;
                displaySymptomAnalysis(parsedData);
                statusEl.textContent = '分析完成';
                statusEl.className = 'symptom-status success';
                resultEl.classList.add('show');
            } else {
                console.log('AI返回数据无效,使用默认数据');
                symptomAnalysisResult = { possibleInfections: defaultInfections };
                displaySymptomAnalysis(symptomAnalysisResult);
                statusEl.textContent = '使用默认数据';
                statusEl.className = 'symptom-status warning';
                resultEl.classList.add('show');
            }
        } catch (e) {
            console.log('解析失败,使用默认数据:', e);
            symptomAnalysisResult = { possibleInfections: defaultInfections };
            displaySymptomAnalysis(symptomAnalysisResult);
            statusEl.textContent = '使用默认数据';
            statusEl.className = 'symptom-status warning';
            resultEl.classList.add('show');
        }
    } else {
        console.log('AI返回空数据,使用默认数据');
        symptomAnalysisResult = { possibleInfections: defaultInfections };
        displaySymptomAnalysis(symptomAnalysisResult);
        statusEl.textContent = '使用默认数据';
        statusEl.className = 'symptom-status warning';
        resultEl.classList.add('show');
    }
}

执行流程

  1. 获取用户输入的症状
  2. 验证输入是否为空
  3. 设置加载状态
  4. 构造AI提示词
  5. 调用AI接口
  6. 解析响应数据
  7. 展示结果或使用默认数据

3.4 AI响应解析

parseAIResponse()函数负责解析AI返回的数据:

function parseAIResponse(response) {
    try {
        console.log('原始AI响应:', response);
        let jsonStr = response.trim();
        
        jsonStr = jsonStr.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
        
        const codeBlockMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
        if (codeBlockMatch) {
            jsonStr = codeBlockMatch[1];
            console.log('提取代码块:', jsonStr);
        }
        
        const braceStart = jsonStr.indexOf('{');
        const braceEnd = jsonStr.lastIndexOf('}');
        
        if (braceStart === -1 || braceEnd === -1 || braceStart > braceEnd) {
            console.error('未找到有效的JSON结构');
            return generateFallbackResponse();
        }
        
        jsonStr = jsonStr.substring(braceStart, braceEnd + 1);
        console.log('提取的JSON:', jsonStr);
        
        jsonStr = jsonStr.replace(/,\s*([}\]])/g, '$1');
        jsonStr = jsonStr.replace(/\s*(\{|\}|\[|\]|,|:)\s*/g, '$1');
        
        let data;
        try {
            data = JSON.parse(jsonStr);
        } catch (parseError) {
            console.error('JSON解析错误,尝试修复:', parseError.message);
            
            try {
                jsonStr = jsonStr
                    .replace(/(['"])?([a-zA-Z0-9_]+)(['"])?:/g, '"$2":')
                    .replace(/'/g, '"')
                    .replace(/,(\s*[}\]])/g, '$1');
                
                data = JSON.parse(jsonStr);
            } catch (secondError) {
                console.error('二次解析失败:', secondError.message);
                return generateFallbackResponse();
            }
        }
        
        if (!data.possibleInfections || !Array.isArray(data.possibleInfections)) {
            console.error('缺少possibleInfections数组');
            return generateFallbackResponse();
        }
        
        data.possibleInfections = data.possibleInfections.map((item, idx) => {
            let name = item.name || '未知感染';
            let type = item.type || 'virus';
            let probability = 0.5;
            let symptoms = Array.isArray(item.symptoms) ? item.symptoms : ['发热', '乏力'];
            let severity = item.severity || '中度';
            let matchingReason = item.matchingReason || '症状匹配';
            
            if (typeof item.probability === 'number') {
                probability = Math.max(0, Math.min(1, item.probability));
            } else if (typeof item.probability === 'string') {
                const parsed = parseFloat(item.probability);
                probability = isNaN(parsed) ? 0.5 : Math.max(0, Math.min(1, parsed));
            }
            
            if (!['virus', 'bacteria'].includes(type)) {
                type = 'virus';
            }
            
            if (!['轻度', '中度', '重度'].includes(severity)) {
                severity = '中度';
            }
            
            if (idx === 0) probability = Math.max(0.7, probability);
            if (idx === 1) probability = Math.max(0.5, Math.min(0.8, probability));
            
            return {
                name,
                type,
                probability,
                symptoms,
                severity,
                matchingReason
            };
        }).slice(0, 5);
        
        console.log('解析后的结果:', data);
        return data;
    } catch (e) {
        console.error('JSON解析最终失败:', e);
        return generateFallbackResponse();
    }
}

解析策略

  1. 去除多余换行符
  2. 提取代码块中的内容
  3. 定位JSON边界({ 和 })
  4. 清理JSON格式(去除尾部逗号等)
  5. 尝试解析,失败则尝试修复格式
  6. 验证并规范化数据字段
  7. 限制返回数量为5条

3.5 结果展示函数

displaySymptomAnalysis()负责将解析结果渲染到页面:

function displaySymptomAnalysis(data) {
    const resultEl = document.getElementById('symptomResult');
    let html = '<strong>可能的感染(点击选择):</strong><br><br>';

    if (data.possibleInfections && Array.isArray(data.possibleInfections)) {
        data.possibleInfections.forEach((inf, index) => {
            const probPercent = (inf.probability * 100).toFixed(0);
            const severityColor = inf.severity === '重度' ? '#ff6b6b' : 
                                 inf.severity === '中度' ? '#f39c12' : '#00b894';
            const typeIcon = inf.type === 'virus' ? '🦠' : '🦟';
            const isSelected = index === 0;
            const selectedClass = isSelected ? ' selected' : '';
            const checkbox = isSelected ? '☑' : '☐';
            const borderStyle = isSelected ? 'border-color: #00cec9; background: rgba(0, 206, 201, 0.1);' : '';

            html += `
                <div class="infection-option${selectedClass}" 
                     onclick="selectInfection(${index}, this)" 
                     data-index="${index}" 
                     style="${borderStyle}">
                    <div class="infection-header">
                        <span class="infection-icon">${typeIcon}</span>
                        <span class="infection-name">${inf.name}</span>
                        <span class="infection-prob" style="color: ${probPercent > 70 ? '#ff6b6b' : probPercent > 40 ? '#f39c12' : '#00b894'}">${probPercent}%</span>
                    </div>
                    <div class="infection-details">
                        <span class="infection-type">${inf.type === 'virus' ? '病毒' : '细菌'}</span>
                        <span class="infection-severity" style="color: ${severityColor}">${inf.severity}</span>
                    </div>
                    <div class="infection-reason">${inf.matchingReason || ''}</div>
                    <div class="infection-checkbox">${checkbox}</div>
                </div>
            `;
        });
    }

    if (data.recommendations && data.recommendations.length > 0) {
        html += '<br><strong>建议:</strong><br>';
        data.recommendations.forEach((rec, index) => {
            html += `${index + 1}. ${rec}<br>`;
        });
    }

    html += '<br><button class="confirm-selection-btn" onclick="confirmSelectedInfections()">确认添加选中的感染源到战场</button>';

    resultEl.innerHTML = html;
}

渲染逻辑

  1. 遍历感染列表
  2. 根据概率设置颜色(红色>70%,橙色40-70%,绿色<40%)
  3. 根据严重程度设置颜色
  4. 默认选中第一个结果
  5. 添加多选复选框
  6. 渲染确认按钮

3.6 确认选择并添加到战场

confirmSelectedInfections()函数处理用户选择并添加到对战系统:

function confirmSelectedInfections() {
    console.log('confirmSelectedInfections被调用');
    console.log('symptomAnalysisResult:', symptomAnalysisResult);
    
    if (typeof symptomAnalysisResult === 'undefined' || symptomAnalysisResult === null) {
        alert('请先进行症状分析');
        return;
    }
    
    if (!symptomAnalysisResult.possibleInfections) {
        alert('请先进行症状分析');
        return;
    }

    const selectedElements = document.querySelectorAll('.infection-option.selected');
    console.log('选中的元素数量:', selectedElements.length);
    
    if (selectedElements.length === 0) {
        alert('请至少选择一种感染源');
        return;
    }

    let addedCount = 0;
    let errorMessages = [];
    
    selectedElements.forEach(el => {
        const index = parseInt(el.dataset.index);
        console.log('处理index:', index);
        
        if (index >= symptomAnalysisResult.possibleInfections.length) {
            errorMessages.push('选项 ' + (index + 1) + ': 数据错误');
            return;
        }
        
        const inf = symptomAnalysisResult.possibleInfections[index];
        console.log('处理感染:', inf);

        if (!inf || !inf.type || !inf.name) {
            errorMessages.push('选项 ' + (index + 1) + ': 数据不完整');
            return;
        }

        const type = inf.type;
        const subtype = findMatchingSubtype(type, inf.name);
        console.log('匹配结果:', type, subtype);

        if (!subtype) {
            errorMessages.push(inf.name + ': 无法匹配');
            return;
        }
        
        if (!factorConfigs[type]) {
            errorMessages.push(inf.name + ': 类型"' + type + '"不存在');
            return;
        }
        
        if (!factorConfigs[type][subtype]) {
            errorMessages.push(inf.name + ': 找不到"' + subtype + '"');
            return;
        }

        const damageMultiplier = inf.severity === '重度' ? 1.5 : 
                                inf.severity === '中度' ? 1.0 : 0.7;
        const amount = Math.max(1, Math.min(5, Math.round(inf.probability * 5)));

        console.log('添加感染源:', inf.name, type, subtype, amount);
        
        for (let i = 0; i < amount; i++) {
            addFactor(type, subtype, damageMultiplier);
            addedCount++;
        }

        selectedFactors[type] = subtype;
        highlightSelectedFactor(type, subtype);
    });

    console.log('共添加了', addedCount, '个感染源');
    
    if (addedCount > 0) {
        analyzeFactors();
        alert('已成功添加 ' + addedCount + ' 个感染源到战场!');
    } else {
        if (errorMessages.length > 0) {
            alert('添加失败:\n' + errorMessages.join('\n'));
        } else {
            alert('没有成功添加任何感染源,请重试');
        }
    }
}

处理逻辑

  1. 验证分析结果是否存在
  2. 获取用户选中的感染源
  3. 验证选中数量
  4. 遍历选中项,匹配配置中的病原体
  5. 根据严重程度计算伤害倍率
  6. 根据概率计算数量
  7. 调用addFactor()添加到战场
  8. 更新UI高亮显示

3.7 名称匹配函数

findMatchingSubtype()用于将AI返回的感染名称匹配到系统配置:

function findMatchingSubtype(type, name) {
    console.log('寻找匹配:', { type, name });
    
    const configs = factorConfigs[type];
    if (!configs) {
        console.error('未找到类型配置:', type);
        return null;
    }

    const nameLower = name.toLowerCase();
    const nameNormalized = name.replace(/病毒|细菌|\s/g, '');

    for (const [key, config] of Object.entries(configs)) {
        const configNameLower = config.name.toLowerCase();
        const configNameNormalized = config.name.replace(/病毒|细菌|\s/g, '');
        
        if (config.name.includes(name) || name.includes(config.name)) {
            console.log('直接匹配:', key);
            return key;
        }
        if (configNameLower.includes(nameLower) || nameLower.includes(configNameLower)) {
            console.log('小写匹配:', key);
            return key;
        }
        if (configNameNormalized.includes(nameNormalized) || 
            nameNormalized.includes(configNameNormalized)) {
            console.log('归一化匹配:', key);
            return key;
        }
    }

    const keywordMap = {
        virus: {
            '流感': 'influenza',
            'influenza': 'influenza',
            '新冠': 'coronavirus',
            '冠状病毒': 'coronavirus',
            'coronavirus': 'coronavirus',
            'HIV': 'hiv',
            '肝炎': 'hepatitis',
            '疱疹': 'herpes',
            '登革': 'dengue',
            '狂犬': 'rabies',
            '麻疹': '麻疹',
            '埃博拉': 'ebola',
            'HPV': 'hpv'
        },
        bacteria: {
            '大肠杆菌': 'e-coli',
            'e-coli': 'e-coli',
            'ecoli': 'e-coli',
            '金黄葡萄球菌': 'staph',
            'staph': 'staph',
            '沙门': 'salmonella',
            '霍乱': 'cholera',
            '结核': 'tuberculosis',
            '肺炎': 'pneumonia',
            '破伤风': 'tetanus',
            '鼠疫': 'plague',
            '莱姆': 'lyme',
            '梅毒': 'syphilis'
        }
    };

    if (keywordMap[type]) {
        for (const [keyword, targetKey] of Object.entries(keywordMap[type])) {
            if (name.includes(keyword)) {
                console.log('关键词匹配:', targetKey);
                if (factorConfigs[type][targetKey]) return targetKey;
            }
        }
    }

    const keys = Object.keys(configs);
    const fallback = keys[0];
    console.log('使用默认:', fallback);
    return fallback;
}

匹配策略

  1. 直接名称匹配
  2. 小写转换匹配
  3. 归一化匹配(去除"病毒"、"细菌"等后缀)
  4. 关键词映射匹配
  5. 默认返回第一个配置项

四、AI问诊模态框系统

4.1 模态框结构

DiagnosisModal是一个独立的诊断模态框系统,支持两种模式:

模式 功能 触发方式
question 文本问答问诊 点击"AI问诊"按钮
image 图片分析问诊 点击"AI图片问诊"按钮

4.2 模态框创建

const DiagnosisModal = (function() {
    let modalContainer = null;
    let currentType = null;

    function createModal() {
        if (modalContainer) return;

        modalContainer = document.createElement('div');
        modalContainer.id = 'diagnosisModal';
        modalContainer.className = 'diagnosis-modal-overlay';
        modalContainer.style.cssText = `
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.85);
            z-index: 10000;
            justify-content: center;
            align-items: center;
            overflow: auto;
        `;

        const modalContent = document.createElement('div');
        modalContent.className = 'diagnosis-modal-content';
        modalContent.style.cssText = `
            background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
            border-radius: 16px;
            width: 90%;
            max-width: 550px;
            max-height: 90vh;
            overflow: hidden;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
            border: 1px solid rgba(255, 255, 255, 0.1);
        `;

        const modalHeader = document.createElement('div');
        modalHeader.style.cssText = `
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 20px 24px;
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        `;

        const title = document.createElement('h3');
        title.id = 'diagnosisModalTitle';
        title.style.cssText = `
            margin: 0;
            color: #fdcb6e;
            font-size: 20px;
        `;
        title.textContent = 'AI问诊';

        const closeBtn = document.createElement('button');
        closeBtn.className = 'diagnosis-modal-close';
        closeBtn.style.cssText = `
            width: 32px;
            height: 32px;
            border-radius: 50%;
            border: none;
            background: rgba(255, 255, 255, 0.1);
            color: #fff;
            font-size: 20px;
            cursor: pointer;
            transition: background 0.2s;
        `;
        closeBtn.innerHTML = '&times;';
        closeBtn.onclick = closeModal;
        closeBtn.onmouseover = () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.2);'; };
        closeBtn.onmouseout = () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.1);'; };

        modalHeader.appendChild(title);
        modalHeader.appendChild(closeBtn);

        const modalBody = document.createElement('div');
        modalBody.id = 'diagnosisModalBody';
        modalBody.style.cssText = `
            padding: 24px;
            max-height: calc(90vh - 120px);
            overflow-y: auto;
        `;

        modalContent.appendChild(modalHeader);
        modalContent.appendChild(modalBody);
        modalContainer.appendChild(modalContent);
        document.body.appendChild(modalContainer);
    }
    // ...
})();

4.3 问答问诊模式

function showQuestionDiagnosis() {
    document.getElementById('diagnosisModalTitle').innerHTML = '❓ AI问答问诊';
    
    const body = document.getElementById('diagnosisModalBody');
    body.innerHTML = `
        <style>
            .diagnosis-textarea {
                width: 100%;
                height: 120px;
                padding: 12px;
                background: rgba(255, 255, 255, 0.05);
                border: 1px solid rgba(255, 255, 255, 0.2);
                border-radius: 8px;
                color: #fff;
                font-size: 14px;
                resize: none;
                box-sizing: border-box;
                font-family: inherit;
            }
            .diagnosis-textarea::placeholder { color: #666; }
            .diagnosis-submit-btn {
                width: 100%;
                padding: 12px;
                margin-top: 15px;
                background: linear-gradient(135deg, #fdcb6e, #f39c12);
                border: none;
                border-radius: 8px;
                color: #1a1a2e;
                font-size: 16px;
                font-weight: bold;
                cursor: pointer;
            }
            .diagnosis-common-questions {
                margin-top: 15px;
            }
            .diagnosis-question-tag {
                display: inline-block;
                padding: 5px 12px;
                margin: 5px;
                background: rgba(255, 255, 255, 0.1);
                border-radius: 20px;
                font-size: 12px;
                cursor: pointer;
                transition: background 0.3s;
            }
            .diagnosis-question-tag:hover {
                background: rgba(78, 205, 196, 0.3);
            }
        </style>
        <textarea id="diagnosisQuestionInput" class="diagnosis-textarea" 
                  placeholder="请输入您的健康问题..."></textarea>
        <div class="diagnosis-common-questions">
            <h4>💬 常见问题:</h4>
            <div>
                <span class="diagnosis-question-tag" onclick="DiagnosisModal.setQuestion('最近总是失眠怎么办?')">失眠问题</span>
                <span class="diagnosis-question-tag" onclick="DiagnosisModal.setQuestion('经常头痛是什么原因?')">头痛原因</span>
                <span class="diagnosis-question-tag" onclick="DiagnosisModal.setQuestion('如何提高免疫力?')">提高免疫力</span>
                <span class="diagnosis-question-tag" onclick="DiagnosisModal.setQuestion('感冒了应该吃什么药?')">感冒用药</span>
                <span class="diagnosis-question-tag" onclick="DiagnosisModal.setQuestion('如何缓解疲劳?')">缓解疲劳</span>
                <span class="diagnosis-question-tag" onclick="DiagnosisModal.setQuestion('饮食不规律怎么办?')">饮食问题</span>
            </div>
        </div>
        <button class="diagnosis-submit-btn" onclick="DiagnosisModal.submitQuestion()">📤 提交问诊</button>
        <div id="diagnosisAnswerArea" style="display: none; margin-top: 20px;">
            <h3 style="color: #fdcb6e; margin-bottom: 15px;">📊 诊断建议</h3>
            <div id="diagnosisAnswerContent"></div>
        </div>
    `;
}

4.4 多技能协同诊断

问答问诊调用多个AI技能进行综合分析:

async function submitQuestion() {
    const input = document.getElementById('diagnosisQuestionInput');
    const question = input ? input.value.trim() : selectedQuestion;
    
    if (!question) {
        alert('请输入您的健康问题');
        return;
    }

    const answerArea = document.getElementById('diagnosisAnswerArea');
    const answerContent = document.getElementById('diagnosisAnswerContent');
    
    if (answerArea) answerArea.style.display = 'block';
    if (answerContent) answerContent.innerHTML = `
        <div class="diagnosis-loading">
            <div style="margin-bottom: 15px;">🔄 正在进行综合诊断分析...</div>
            <div id="skillProgress">
                <div class="skill-item" id="skill1">
                    <span class="skill-icon">🤖</span>
                    <span class="skill-name">QuestionDiagnosisSkill</span>
                    <span class="skill-status" id="status1">等待中...</span>
                </div>
                <div class="skill-item" id="skill2">
                    <span class="skill-icon">🧬</span>
                    <span class="skill-name">SymptomAnalysisSkill</span>
                    <span class="skill-status" id="status2">等待中...</span>
                </div>
                <div class="skill-item" id="skill3">
                    <span class="skill-icon">💡</span>
                    <span class="skill-name">HealthAdviceSkill</span>
                    <span class="skill-status" id="status3">等待中...</span>
                </div>
            </div>
        </div>
    `;

    try {
        console.log('========== 综合诊断开始 ==========');

        const updateStatus = (skillId, status, className) => {
            const statusEl = document.getElementById(status);
            if (statusEl) {
                statusEl.textContent = skillId;
                statusEl.className = 'skill-status ' + className;
            }
        };

        updateStatus('正在分析...', 'status1', 'running');
        const diagnosisPromise = QuestionDiagnosisSkill.diagnose(question).then(result => {
            updateStatus('✓ 完成', 'status1', 'completed');
            return result;
        }).catch(error => {
            updateStatus('✗ 失败', 'status1', 'failed');
            throw error;
        });

        updateStatus('正在分析...', 'status2', 'running');
        const analysisPromise = SymptomAnalysisSkill.analyze(question).then(result => {
            updateStatus('✓ 完成', 'status2', 'completed');
            return result;
        }).catch(error => {
            updateStatus('✗ 失败', 'status2', 'failed');
            throw error;
        });

        updateStatus('正在分析...', 'status3', 'running');
        const advicePromise = HealthAdviceSkill.getAdvice(question).then(result => {
            updateStatus('✓ 完成', 'status3', 'completed');
            return result;
        }).catch(error => {
            updateStatus('✗ 失败', 'status3', 'failed');
            throw error;
        });

        const [diagnosisResult, analysisResult, adviceResult] = await Promise.all([
            diagnosisPromise,
            analysisPromise,
            advicePromise
        ]);

        const combinedResult = combineResults(diagnosisResult, analysisResult, adviceResult);
        displayQuestionAnswer(combinedResult);

        console.log('========== 综合诊断完成 ==========');
    } catch (error) {
        console.error('诊断错误:', error.message);
        if (answerContent) {
            answerContent.innerHTML = '<div style="color: #e74c3c;">❌ 诊断失败:' + error.message + '</div>';
        }
    }
}

多技能协同流程

  1. 同时调用三个AI技能
  2. 实时更新各技能状态
  3. 合并分析结果
  4. 统一展示诊断报告

五、技能模块详解

5.1 SymptomAnalysisSkill

症状分析技能负责分析症状严重程度和可能影响的身体系统:

const SymptomAnalysisSkill = (function() {
    const API_URL = "https://api-ai.gitcode.com/v1/chat/completions";
    const API_KEY = "qBPRwKM_kHgbBjzzxvW9ws--";

    async function queryAI(prompt) {
        try {
            const response = await fetch(API_URL, {
                method: "POST",
                headers: {
                    "Authorization": `Bearer ${API_KEY}`,
                    "Content-Type": "application/json"
                },
                body: JSON.stringify({
                    model: "deepseek-ai/DeepSeek-V3",
                    messages: [{
                        role: "user",
                        content: prompt
                    }],
                    stream: false,
                    max_tokens: 1024,
                    temperature: 0.5,
                    top_p: 0.95
                })
            });

            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }

            const data = await response.json();
            if (data.choices && data.choices[0] && data.choices[0].message) {
                return data.choices[0].message.content;
            }
            return null;
        } catch (error) {
            console.error('Symptom Analysis AI Error:', error.message);
            throw error;
        }
    }

    async function analyze(symptoms) {
        console.log('Symptom Analysis Skill - analyzing:', symptoms);
        
        const prompt = `你是一位专业的医学症状分析专家。请分析以下症状描述,给出专业的医学分析:\n\n症状:${symptoms}\n\n请以JSON格式返回分析结果,包含以下字段:\n- symptoms: 识别到的具体症状列表(字符串数组)\n- severity: 症状严重程度(轻微/中等/严重,字符串)\n- possibleConditions: 可能的疾病或健康状况(字符串数组)\n- affectedSystems: 可能受影响的身体系统(字符串数组)\n\n请确保 severity 是字符串类型,不要是对象或数组。`;

        const response = await queryAI(prompt);
        
        if (!response) {
            return generateFallbackResponse();
        }

        try {
            const parsed = parseResponse(response);
            return sanitizeResponse(parsed);
        } catch (error) {
            console.error('Symptom Analysis parse error:', error.message);
            return generateFallbackResponse();
        }
    }

    return {
        analyze: analyze
    };
})();

5.2 QuestionDiagnosisSkill

问题诊断技能负责分析健康问题的可能原因和应对措施:

const QuestionDiagnosisSkill = (function() {
    const API_URL = "https://api-ai.gitcode.com/v1/chat/completions";
    const API_KEY = "qBPRwKM_kHgbBjzzxvW9ws--";

    async function diagnose(question) {
        console.log('Question Diagnosis Skill - diagnose called with:', question);
        
        const prompt = `你是一位专业的医疗健康顾问,请详细分析以下健康问题并给出专业建议:\n\n${question}\n\n请以JSON格式返回结果,包含以下字段:\n- possibleCauses: 可能的原因(字符串数组)\n- suggestions: 应对措施(字符串数组)\n- medicalAdvice: 是否需要就医及建议(字符串)\n- healthTips: 健康小贴士(字符串数组)\n\n请确保 medicalAdvice 是字符串类型,不要是对象或数组。`;

        const response = await queryAI(prompt);
        
        if (!response) {
            return generateFallbackResponse();
        }

        try {
            const parsed = parseResponse(response);
            return sanitizeResponse(parsed);
        } catch (error) {
            console.error('Question Diagnosis parse error:', error.message);
            return generateFallbackResponse();
        }
    }

    return {
        diagnose: diagnose,
        generateFallbackResponse: generateFallbackResponse
    };
})();

5.3 HealthAdviceSkill

健康建议技能提供生活方式建议:

const HealthAdviceSkill = (function() {
    const API_URL = "https://api-ai.gitcode.com/v1/chat/completions";
    const API_KEY = "qBPRwKM_kHgbBjzzxvW9ws--";

    async function getAdvice(condition) {
        console.log('Health Advice Skill - getting advice for:', condition);
        
        const prompt = `你是一位专业的健康管理顾问。请根据以下健康状况提供详细的生活建议:\n\n健康状况:${condition}\n\n请以JSON格式返回建议,包含以下字段:\n- sleepAdvice: 睡眠建议(字符串)\n- stressManagement: 压力管理建议(字符串)\n- hydration: 饮水建议(字符串)\n- restAdvice: 休息与活动建议(字符串)\n\n请确保所有字段都是字符串类型,不要嵌套对象或数组。`;

        const response = await queryAI(prompt);
        
        if (!response) {
            return generateFallbackResponse();
        }

        try {
            const parsed = parseResponse(response);
            return sanitizeResponse(parsed);
        } catch (error) {
            console.error('Health Advice parse error:', error.message);
            return generateFallbackResponse();
        }
    }

    return {
        getAdvice: getAdvice
    };
})();

六、工作流程总结

6.1 症状诊断流程

用户输入症状
      ↓
点击"AI诊断"按钮
      ↓
构造AI提示词
      ↓
调用queryAI()发送请求
      ↓
解析AI响应 (parseAIResponse)
      ↓
验证数据有效性
      ↓
渲染诊断结果 (displaySymptomAnalysis)
      ↓
用户选择感染源
      ↓
点击"确认添加到战场"
      ↓
匹配配置并添加 (confirmSelectedInfections)
      ↓
更新对战系统状态

6.2 AI问诊流程

点击"AI问诊"按钮
      ↓
打开诊断模态框
      ↓
用户输入问题
      ↓
点击"提交问诊"
      ↓
并行调用三个AI技能
      ↓
实时更新进度状态
      ↓
合并分析结果
      ↓
展示综合诊断报告

6.3 数据流向图

┌─────────────────────────────────────────────────────────────────┐
│                        用户界面                                │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐       │
│  │ 症状输入框   │    │ 常见症状标签 │    │   AI问诊    │       │
│  └──────┬──────┘    └──────┬──────┘    └──────┬──────┘       │
└─────────│──────────────────│──────────────────│───────────────┘
          ▼                  ▼                  ▼
┌─────────────────────────────────────────────────────────────────┐
│                      前端处理层                                │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  addSymptom()  →  analyzeSymptoms()  →  queryAI()       │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                      AI服务层                                  │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │         DeepSeek-V3 API (api-ai.gitcode.com)            │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                      响应处理层                                │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  parseAIResponse()  →  sanitizeResponse()               │  │
│  │       ↓                                                 │  │
│  │  displaySymptomAnalysis()  →  confirmSelectedInfections()│  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                      对战系统层                                │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │           addFactor()  →  battle.js 更新状态             │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

七、错误处理与容错机制

7.1 AI响应异常处理

系统实现了多层容错机制:

function generateFallbackResponse() {
    return {
        possibleInfections: [
            {
                name: '流感病毒',
                type: 'virus',
                probability: 0.85,
                symptoms: ['发热', '咳嗽', '头痛'],
                severity: '中度',
                matchingReason: '常见感染表现'
            },
            {
                name: '普通感冒',
                type: 'virus',
                probability: 0.55,
                symptoms: ['鼻塞', '流涕'],
                severity: '轻度',
                matchingReason: '典型症状'
            }
        ]
    };
}

容错策略

  1. AI请求失败 → 使用默认数据
  2. JSON解析失败 → 使用默认数据
  3. 数据格式异常 → 使用默认数据
  4. 网络超时 → 使用默认数据

7.2 数据验证机制

function sanitizeResponse(response) {
    const fallback = generateFallbackResponse();
    return {
        symptoms: ensureArray(response.symptoms, fallback.symptoms),
        severity: ensureString(response.severity, fallback.severity),
        possibleConditions: ensureArray(response.possibleConditions, fallback.possibleConditions),
        affectedSystems: ensureArray(response.affectedSystems, fallback.affectedSystems)
    };
}

function ensureArray(value, fallback) {
    if (value === null || value === undefined) return fallback;
    if (Array.isArray(value)) return value;
    if (typeof value === 'string') {
        try {
            const parsed = JSON.parse(value);
            return Array.isArray(parsed) ? parsed : fallback;
        } catch {
            return [value];
        }
    }
    return fallback;
}

function ensureString(value, fallback) {
    if (value === null || value === undefined) return fallback;
    if (typeof value === 'string') return value.trim() || fallback;
    if (typeof value === 'object') {
        try {
            return JSON.stringify(value);
        } catch {
            return fallback;
        }
    }
    return String(value).trim() || fallback;
}

八、性能优化策略

8.1 请求并行化

在AI问诊模式中,三个技能请求并行执行:

const [diagnosisResult, analysisResult, adviceResult] = await Promise.all([
    diagnosisPromise,
    analysisPromise,
    advicePromise
]);

8.2 状态缓存

let symptomAnalysisResult = null;

async function analyzeSymptoms() {
    // ... 分析逻辑 ...
    symptomAnalysisResult = parsedData;
}

function confirmSelectedInfections() {
    if (typeof symptomAnalysisResult === 'undefined' || symptomAnalysisResult === null) {
        alert('请先进行症状分析');
        return;
    }
}

8.3 渐进式加载

statusEl.textContent = 'AI分析中...';
statusEl.className = 'symptom-status loading';
resultEl.classList.remove('show');

// 分析完成后
statusEl.textContent = '分析完成';
statusEl.className = 'symptom-status success';
resultEl.classList.add('show');

九、总结

症状诊断系统是命枢AI生命科学模拟器的核心功能模块,实现了从症状采集到AI分析再到战场集成的完整流程。系统采用现代化的UI设计,支持多种交互方式,具备完善的错误处理和容错机制。

核心技术亮点

  1. 多技能协同诊断:同时调用三个AI技能进行综合分析
  2. 智能名称匹配:支持多种匹配策略,提高识别准确率
  3. 渐进式UI反馈:实时显示分析进度和状态
  4. 多层容错机制:确保系统稳定运行
  5. 模块化设计:各技能独立封装,便于维护和扩展

该系统为用户提供了直观、专业的症状诊断体验,为后续的免疫细胞对抗模拟奠定了数据基础。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐