From bad6bfa34be622fe8c6c551a5e0dbfaea18ecd86 Mon Sep 17 00:00:00 2001 From: quant Date: Wed, 18 Feb 2026 16:48:48 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=89=8B=E6=9C=BA=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/admin.html | 262 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 252 insertions(+), 10 deletions(-) diff --git a/static/admin.html b/static/admin.html index d9138e1..47611f2 100644 --- a/static/admin.html +++ b/static/admin.html @@ -173,6 +173,11 @@ 塔罗牌识别 + + + 通用分割 + @@ -547,6 +552,151 @@ + +
+ +
+

+ + 通用图像分割任务 +

+ +
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ + +
+
+ + +

支持中英文,后端会自动翻译。多个对象可用逗号分隔。

+
+ +
+ + +
+ 0.0 + 1.0 +
+
+ +
+ + + + +
+ +
+ +
+
+
+
+ + +
+ +
+ +
+

{{ segmentResult.status === 'success' ? '分割成功' : '分割失败' }}

+

{{ segmentResult.message }}

+
+
+ + +
+

+ + 可视化结果 +

+
+ +
+
+ + +
+
+
+ +
+ +
+
+ {{ (seg.score * 100).toFixed(0) }}% +
+
+
+

{{ seg.label }}

+
+
+
+ + +
+

+ + JSON 结果 +

+
{{ JSON.stringify(segmentResult, null, 2) }}
+
+
+
+
@@ -972,6 +1122,21 @@ const tarotResult = ref(null); const isRecognizing = ref(false); + // Segment State + const segmentFile = ref(null); + const segmentImageUrl = ref(''); + const segmentPreview = ref(null); + const segmentPrompt = ref(''); + const segmentConfidence = ref(0.7); + const segmentOptions = ref({ + save_segment_images: false, + cutout: false, + perspective_correction: false, + highlight: false + }); + const segmentResult = ref(null); + const isSegmenting = ref(false); + // Filters const selectedTimeRange = ref('all'); const selectedType = ref('all'); @@ -1379,17 +1544,11 @@ } formData.append('expected_count', tarotExpectedCount.value); - // Use axios directly or a helper. Need to handle API Key if required by backend, - // but admin usually has session. Wait, the backend endpoints like /recognize_tarot - // require X-API-Key header. - // The admin page uses cookie for /admin/api/* but /recognize_tarot is a public API protected by Key. - // We should add the key to the header. - const config = { headers: { - 'X-API-Key': '123quant-speed' // Hardcoded as per fastAPI_tarot.py VALID_API_KEY + 'X-API-Key': '123quant-speed' }, - timeout: 120000 // 2分钟超时,大模型响应较慢 + timeout: 120000 }; const res = await axios.post('/recognize_tarot', formData, config); @@ -1399,7 +1558,6 @@ console.error(e); let msg = e.response?.data?.detail || e.message || '识别请求失败'; - // 针对 504 Gateway Timeout 或 请求超时做特殊提示 if (e.response && e.response.status === 504) { msg = '请求超时 (504):大模型处理时间较长。后台可能仍在运行,请稍后在“识别记录”中刷新查看结果。'; } else if (e.code === 'ECONNABORTED') { @@ -1415,6 +1573,85 @@ } }; + // --- Segment Actions --- + const handleSegmentFileChange = (event) => { + const file = event.target.files[0]; + if (file) { + segmentFile.value = file; + segmentImageUrl.value = ''; + segmentPreview.value = URL.createObjectURL(file); + segmentResult.value = null; + } + }; + + const handleSegmentUrlInput = () => { + if (segmentImageUrl.value) { + segmentFile.value = null; + segmentPreview.value = segmentImageUrl.value; + segmentResult.value = null; + } else { + segmentPreview.value = null; + } + }; + + const clearSegmentInput = () => { + segmentFile.value = null; + segmentImageUrl.value = ''; + segmentPreview.value = null; + segmentResult.value = null; + const fileInput = document.getElementById('dropzone-segment-file'); + if (fileInput) fileInput.value = ''; + }; + + const performSegment = async () => { + if ((!segmentFile.value && !segmentImageUrl.value) || !segmentPrompt.value) return; + + isSegmenting.value = true; + segmentResult.value = null; + + try { + const formData = new FormData(); + if (segmentFile.value) { + formData.append('file', segmentFile.value); + } else { + formData.append('image_url', segmentImageUrl.value); + } + formData.append('prompt', segmentPrompt.value); + formData.append('confidence', segmentConfidence.value); + formData.append('save_segment_images', segmentOptions.value.save_segment_images); + formData.append('cutout', segmentOptions.value.cutout); + formData.append('perspective_correction', segmentOptions.value.perspective_correction); + formData.append('highlight', segmentOptions.value.highlight); + + const config = { + headers: { + 'X-API-Key': '123quant-speed' + }, + timeout: 120000 + }; + + const res = await axios.post('/segment', formData, config); + segmentResult.value = res.data; + + } catch (e) { + console.error(e); + let msg = e.response?.data?.detail || e.message || '分割请求失败'; + + if (e.response && e.response.status === 504) { + msg = '请求超时 (504):处理时间较长。'; + } else if (e.code === 'ECONNABORTED') { + msg = '请求超时:网络连接中断或服务器响应过慢。'; + } + + segmentResult.value = { + status: 'failed', + message: msg + }; + } finally { + isSegmenting.value = false; + } + }; + // --- Navigation & Helpers --- const switchTab = (tab) => { const prevTab = currentTab.value; @@ -1506,6 +1743,7 @@ const map = { 'dashboard': '数据看板', 'tarot': '塔罗牌识别', + 'segment': '通用分割', 'history': '识别记录', 'files': '文件资源管理', 'prompts': '提示词工程', @@ -1519,6 +1757,7 @@ const map = { 'dashboard': '系统运行状态与核心指标概览', 'tarot': 'SAM3 + Qwen-VL 联合识别与分割', + 'segment': '基于文本提示的通用图像分割 (Grounded SAM)', 'history': '所有视觉识别任务的历史流水', 'files': '查看和管理生成的图像及JSON结果', 'prompts': '调整各个识别场景的 System Prompt', @@ -1734,7 +1973,10 @@ gpuUtilChartRef, gpuTempChartRef, // Tarot tarotFile, tarotImageUrl, tarotExpectedCount, tarotPreview, tarotResult, isRecognizing, - handleTarotFileChange, handleUrlInput, clearTarotInput, recognizeTarot + handleTarotFileChange, handleUrlInput, clearTarotInput, recognizeTarot, + // Segment + segmentFile, segmentImageUrl, segmentPreview, segmentPrompt, segmentConfidence, segmentOptions, segmentResult, isSegmenting, + handleSegmentFileChange, handleSegmentUrlInput, clearSegmentInput, performSegment }; } }).mount('#app');