Spaces:
Runtime error
Runtime error
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>皮镜 (SkinLens) - 智能皮肤诊疗助手</title> | |
| <style> | |
| /* --- 全局样式 --- */ | |
| @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap'); | |
| body { | |
| font-family: 'Noto Sans SC', sans-serif; | |
| background-color: #f4f7f9; | |
| margin: 0; | |
| padding: 20px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: flex-start; | |
| min-height: 100vh; | |
| color: #333; | |
| } | |
| /* --- 主容器 --- */ | |
| .container { | |
| width: 100%; | |
| max-width: 900px; | |
| background-color: #ffffff; | |
| border-radius: 12px; | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); | |
| padding: 40px; | |
| box-sizing: border-box; | |
| } | |
| /* --- 头部 --- */ | |
| .header { | |
| text-align: center; | |
| margin-bottom: 30px; | |
| border-bottom: 1px solid #e0e0e0; | |
| padding-bottom: 20px; | |
| } | |
| .header h1 { | |
| color: #1a73e8; | |
| margin: 0; | |
| font-weight: 500; | |
| } | |
| .header p { | |
| color: #5f6368; | |
| margin-top: 5px; | |
| font-size: 1rem; | |
| } | |
| /* --- 主内容区 --- */ | |
| .main-content { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 30px; | |
| } | |
| @media (max-width: 768px) { | |
| .main-content { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| /* --- 输入卡片样式 --- */ | |
| .card { | |
| background: #fff; | |
| padding: 25px; | |
| border-radius: 8px; | |
| border: 1px solid #dfe1e5; | |
| } | |
| .card h2 { | |
| font-size: 1.2rem; | |
| margin-top: 0; | |
| margin-bottom: 20px; | |
| color: #1a73e8; | |
| border-bottom: 2px solid #e8f0fe; | |
| padding-bottom: 10px; | |
| } | |
| /* --- 图片上传区域 --- */ | |
| .image-uploader { | |
| border: 2px dashed #d0d0d0; | |
| border-radius: 8px; | |
| padding: 20px; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: border-color 0.3s, background-color 0.3s; | |
| position: relative; | |
| } | |
| .image-uploader:hover { | |
| border-color: #1a73e8; | |
| background-color: #f8f9fa; | |
| } | |
| .image-uploader .upload-text { | |
| color: #5f6368; | |
| } | |
| #image-preview { | |
| max-width: 100%; | |
| max-height: 200px; | |
| margin-top: 15px; | |
| border-radius: 4px; | |
| display: none; | |
| } | |
| #image-input { | |
| display: none; | |
| } | |
| /* --- 文本输入区域 --- */ | |
| textarea { | |
| width: 100%; | |
| height: 150px; | |
| border-radius: 4px; | |
| border: 1px solid #d0d0d0; | |
| padding: 10px; | |
| font-family: inherit; | |
| font-size: 0.95rem; | |
| resize: vertical; | |
| box-sizing: border-box; | |
| } | |
| textarea:focus { | |
| outline: none; | |
| border-color: #1a73e8; | |
| box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.2); | |
| } | |
| /* --- 操作按钮 --- */ | |
| .actions { | |
| grid-column: 1 / -1; /* 跨越所有列 */ | |
| text-align: center; | |
| margin-top: 20px; | |
| } | |
| #analyze-btn { | |
| background-color: #1a73e8; | |
| color: white; | |
| border: none; | |
| padding: 12px 30px; | |
| border-radius: 25px; | |
| font-size: 1rem; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: background-color 0.3s, box-shadow 0.3s; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| #analyze-btn:hover { | |
| background-color: #185abc; | |
| box-shadow: 0 4px 12px rgba(26, 115, 232, 0.2); | |
| } | |
| #analyze-btn:disabled { | |
| background-color: #a0c3ff; | |
| cursor: not-allowed; | |
| } | |
| /* --- 加载动画 --- */ | |
| .loader { | |
| width: 18px; | |
| height: 18px; | |
| border: 2px solid #fff; | |
| border-bottom-color: transparent; | |
| border-radius: 50%; | |
| display: none; /* 初始隐藏 */ | |
| box-sizing: border-box; | |
| animation: rotation 1s linear infinite; | |
| } | |
| @keyframes rotation { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| /* --- 结果展示区域 --- */ | |
| #results-container { | |
| margin-top: 40px; | |
| display: none; /* 初始隐藏 */ | |
| border-top: 1px solid #e0e0e0; | |
| padding-top: 30px; | |
| } | |
| .results-grid { | |
| display: grid; | |
| gap: 20px; | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| } | |
| .result-card { | |
| background-color: #f8f9fa; | |
| border-radius: 8px; | |
| padding: 20px; | |
| border: 1px solid #e0e0e0; | |
| } | |
| .result-card h3 { | |
| margin-top: 0; | |
| color: #1a73e8; | |
| font-size: 1.1rem; | |
| margin-bottom: 15px; | |
| } | |
| .result-card ul { | |
| padding-left: 20px; | |
| margin: 0; | |
| color: #3c4043; | |
| } | |
| .result-card li { | |
| margin-bottom: 8px; | |
| } | |
| .diagnosis-item { | |
| border-bottom: 1px solid #e0e0e0; | |
| padding-bottom: 10px; | |
| margin-bottom: 10px; | |
| } | |
| .diagnosis-item:last-child { | |
| border-bottom: none; | |
| } | |
| .diagnosis-item strong { | |
| color: #202124; | |
| } | |
| .diagnosis-item .likelihood { | |
| font-weight: 500; | |
| padding: 2px 8px; | |
| border-radius: 12px; | |
| font-size: 0.8rem; | |
| margin-left: 8px; | |
| } | |
| .likelihood-高 { background-color: #e57373; color: white; } | |
| .likelihood-中 { background-color: #ffb74d; color: white; } | |
| .likelihood-低 { background-color: #81c784; color: white; } | |
| .error-message { | |
| color: #d93025; | |
| text-align: center; | |
| margin-top: 20px; | |
| display: none; /* 初始隐藏 */ | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>皮镜 (SkinLens)</h1> | |
| <p>您的智能皮肤科临床诊疗辅助伙伴</p> | |
| </div> | |
| <div class="main-content"> | |
| <!-- 图片上传卡片 --> | |
| <div class="card"> | |
| <h2>1. 上传皮肤图像</h2> | |
| <div class="image-uploader" id="image-uploader-box" onclick="document.getElementById('image-input').click();"> | |
| <p class="upload-text">点击或拖拽图片到此区域</p> | |
| <img id="image-preview" src="#" alt="Image Preview"/> | |
| </div> | |
| <input type="file" id="image-input" accept="image/jpeg, image/png"> | |
| </div> | |
| <!-- 病历信息卡片 --> | |
| <div class="card"> | |
| <h2>2. 输入病历信息</h2> | |
| <textarea id="prompt-input" placeholder="请输入患者主诉、现病史、查体等信息..."></textarea> | |
| </div> | |
| </div> | |
| <div class="actions"> | |
| <button id="analyze-btn"> | |
| <span id="btn-text">开始智能分析</span> | |
| <div class="loader" id="loader"></div> | |
| </button> | |
| </div> | |
| <p id="error-message" class="error-message"></p> | |
| <!-- 结果展示容器 --> | |
| <div id="results-container"> | |
| <div class="results-grid"> | |
| <div class="result-card" id="diagnosis-card"> | |
| <h3>鉴别诊断列表</h3> | |
| <div id="differential-diagnosis-content"></div> | |
| </div> | |
| <div class="result-card" id="features-card"> | |
| <h3>图像特征分析</h3> | |
| <ul id="image-features-content"></ul> | |
| </div> | |
| <div class="result-card" id="analysis-card" style="grid-column: 1 / -1;"> | |
| <h3>综合分析</h3> | |
| <p id="comprehensive-analysis-content"></p> | |
| </div> | |
| <div class="result-card" id="recommendations-card" style="grid-column: 1 / -1;"> | |
| <h3>下一步建议</h3> | |
| <div class="results-grid"> | |
| <div> | |
| <h4>辅助检查</h4> | |
| <ul id="further-examinations-content"></ul> | |
| </div> | |
| <div> | |
| <h4>治疗方向</h4> | |
| <ul id="treatment-suggestions-content"></ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const imageInput = document.getElementById('image-input'); | |
| const imagePreview = document.getElementById('image-preview'); | |
| const imageUploaderBox = document.getElementById('image-uploader-box'); | |
| const uploadText = document.querySelector('.upload-text'); | |
| const analyzeBtn = document.getElementById('analyze-btn'); | |
| const btnText = document.getElementById('btn-text'); | |
| const loader = document.getElementById('loader'); | |
| const promptInput = document.getElementById('prompt-input'); | |
| const resultsContainer = document.getElementById('results-container'); | |
| const errorMessage = document.getElementById('error-message'); | |
| let imageBase64 = null; | |
| // --- 事件监听:图片选择 --- | |
| imageInput.addEventListener('change', (event) => { | |
| const file = event.target.files[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| imagePreview.src = e.target.result; | |
| imagePreview.style.display = 'block'; | |
| uploadText.style.display = 'none'; | |
| // 转换为Base64 | |
| imageBase64 = e.target.result.split(',')[1]; | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| }); | |
| // --- 事件监听:分析按钮点击 --- | |
| analyzeBtn.addEventListener('click', async () => { | |
| if (!imageBase64 || !promptInput.value.trim()) { | |
| showError("请务必上传一张图片并填写病历信息。"); | |
| return; | |
| } | |
| // --- UI状态:开始分析 --- | |
| toggleLoading(true); | |
| resultsContainer.style.display = 'none'; | |
| errorMessage.style.display = 'none'; | |
| try { | |
| const response = await fetch('/api/analyze', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| image: imageBase64, | |
| text: promptInput.value | |
| }) | |
| }); | |
| if (!response.ok) { | |
| const errData = await response.json(); | |
| throw new Error(errData.error || `HTTP 错误! 状态: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| displayResults(data); | |
| } catch (error) { | |
| showError(`分析失败: ${error.message}`); | |
| } finally { | |
| // --- UI状态:分析结束 --- | |
| toggleLoading(false); | |
| } | |
| }); | |
| // --- 功能函数:切换加载状态 --- | |
| function toggleLoading(isLoading) { | |
| analyzeBtn.disabled = isLoading; | |
| if (isLoading) { | |
| btnText.textContent = "分析中..."; | |
| loader.style.display = 'block'; | |
| } else { | |
| btnText.textContent = "开始智能分析"; | |
| loader.style.display = 'none'; | |
| } | |
| } | |
| // --- 功能函数:显示错误信息 --- | |
| function showError(message) { | |
| errorMessage.textContent = message; | |
| errorMessage.style.display = 'block'; | |
| } | |
| // --- 功能函数:渲染结果到页面 --- | |
| function displayResults(data) { | |
| // 鉴别诊断 | |
| const diagnosisContent = document.getElementById('differential-diagnosis-content'); | |
| diagnosisContent.innerHTML = data.differential_diagnosis.map(item => ` | |
| <div class="diagnosis-item"> | |
| <strong>${item.diagnosis}</strong> | |
| <span class="likelihood likelihood-${item.likelihood}">${item.likelihood}</span> | |
| <p style="font-size:0.9rem; color:#5f6368; margin-top:5px;">依据: ${item.evidence}</p> | |
| </div> | |
| `).join(''); | |
| // 图像特征 | |
| document.getElementById('image-features-content').innerHTML = data.image_features.map(item => `<li>${item}</li>`).join(''); | |
| // 综合分析 | |
| document.getElementById('comprehensive-analysis-content').textContent = data.comprehensive_analysis; | |
| // 建议 | |
| document.getElementById('further-examinations-content').innerHTML = data.recommendations.further_examinations.map(item => `<li>${item}</li>`).join(''); | |
| document.getElementById('treatment-suggestions-content').innerHTML = data.recommendations.treatment_suggestions.map(item => `<li>${item}</li>`).join(''); | |
| resultsContainer.style.display = 'block'; | |
| // 平滑滚动到结果区域 | |
| resultsContainer.scrollIntoView({ behavior: 'smooth' }); | |
| } | |
| </script> | |
| </body> | |
| </html> |