// ============================================================================ // Vision Tool — 在线图片识别(原生单页) // 进入方式:固定进入密钥(存于数据库,首次启动自动初始化) // ============================================================================ const $ = (id) => document.getElementById(id); // ---------- 白昼/黑夜主题 ---------- const THEME_KEY = "vt_theme"; function currentTheme() { const saved = localStorage.getItem(THEME_KEY); if (saved === "dark" || saved === "light") return saved; // 未设置时跟随系统偏好 return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; } function applyTheme(theme) { document.documentElement.dataset.theme = theme; localStorage.setItem(THEME_KEY, theme); $("themeBtn").textContent = theme === "dark" ? "☀️" : "🌙"; } applyTheme(currentTheme()); $("themeBtn").addEventListener("click", () => { applyTheme(document.documentElement.dataset.theme === "dark" ? "light" : "dark"); }); // ---------- API 封装 ---------- async function api(path, options = {}) { const headers = options.headers || {}; if (!(options.body instanceof FormData)) { headers["Content-Type"] = "application/json"; } const resp = await fetch(path, { ...options, headers }); let data; try { data = await resp.json(); } catch { throw new Error(`HTTP ${resp.status}: 响应解析失败`); } if (data.code !== 0) throw new Error(data.message || `HTTP ${resp.status}`); return data.data; } // ---------- 进入密钥 ---------- const KEY_STORAGE = "vt_access_key"; function isUnlocked() { return !!localStorage.getItem(KEY_STORAGE); } function refreshUnlockState() { const unlocked = isUnlocked(); $("unlockLayer").hidden = unlocked; const area = $("accountArea"); area.innerHTML = ""; if (!unlocked) return; const chip = document.createElement("span"); chip.className = "user-chip"; chip.textContent = "🔓 已解锁"; area.appendChild(chip); // 一键复制 API 文档(以当前 URL 为基础地址) const docBtn = document.createElement("button"); docBtn.className = "btn btn-ghost"; docBtn.textContent = "📋 复制 API 文档"; docBtn.onclick = async () => { try { await navigator.clipboard.writeText(buildApiDoc()); docBtn.textContent = "已复制 ✓"; setTimeout(() => (docBtn.textContent = "📋 复制 API 文档"), 1500); } catch { alert("复制失败,请手动复制"); } }; area.appendChild(docBtn); const logout = document.createElement("button"); logout.className = "btn btn-ghost"; logout.textContent = "退出"; logout.onclick = () => { localStorage.removeItem(KEY_STORAGE); refreshUnlockState(); }; area.appendChild(logout); } // 生成 API 文档(Markdown,供智能体/开发者直接调用) function buildApiDoc() { const base = window.location.origin; const key = localStorage.getItem(KEY_STORAGE) || "19735500"; return `# Vision Tool — 在线图片识别 API 这是一个在线图片识别服务。 智能体可通过下面的 HTTP API 直接调用,识别图片内容。 ## 基础信息 - 服务地址: \`${base}\` - 进入密钥: \`${key}\`(可选,用于鉴权调用) ## 接口:图片识别 POST \`${base}/api/v1/vision/analyze\` multipart/form-data 字段: - \`images\`: 图片文件,可传多个(支持 png/jpg/gif/webp/bmp/tiff) - \`prompt\`: 可选,自定义分析提问(不传则默认详细描述图片内容) ### 免鉴权调用示例 \`\`\`bash curl -X POST ${base}/api/v1/vision/analyze \\ -F "images=@photo.png" \\ -F "prompt=这张图里有什么?" \`\`\` ### 带密钥调用示例 \`\`\`bash curl -X POST ${base}/api/v1/vision/analyze \\ -H "X-Access-Key: ${key}" \\ -F "images=@photo.png" \`\`\` ## 响应格式 \`\`\`json { "code": 0, "message": "ok", "data": { "result": "识别结果文本", "images": ["/uploads/xxx.png"], "usage": { "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0 } } } \`\`\` ## 其他接口 - GET \`${base}/api/health\` — 健康检查 - POST \`${base}/api/v1/auth/verify\` — 校验进入密钥 \`{"key": "${key}"}\` ## 用法建议 需要识别图片内容时,将图片路径交给智能体,让它执行上述 curl 命令即可。 `; } $("unlockBtn").addEventListener("click", unlock); $("unlockKey").addEventListener("keydown", (e) => { if (e.key === "Enter") unlock(); }); async function unlock() { const key = $("unlockKey").value.trim(); if (!key) { $("unlockError").textContent = "请输入进入密钥"; return; } try { await api("/api/v1/auth/verify", { method: "POST", body: JSON.stringify({ key }), }); localStorage.setItem(KEY_STORAGE, key); $("unlockKey").value = ""; $("unlockError").textContent = ""; refreshUnlockState(); } catch (err) { $("unlockError").textContent = err.message; } } // ---------- 图片选择与预览 ---------- let selectedFiles = []; let analyzing = false; const dropzone = $("dropzone"); const fileInput = $("fileInput"); dropzone.addEventListener("click", () => fileInput.click()); dropzone.addEventListener("dragover", (e) => { e.preventDefault(); dropzone.classList.add("dragover"); }); dropzone.addEventListener("dragleave", () => dropzone.classList.remove("dragover")); dropzone.addEventListener("drop", (e) => { e.preventDefault(); dropzone.classList.remove("dragover"); addFiles(e.dataTransfer.files); }); fileInput.addEventListener("change", () => { addFiles(fileInput.files); fileInput.value = ""; }); function addFiles(files) { for (const f of files) { if (!f.type.startsWith("image/")) continue; if (f.size > 10 * 1024 * 1024) { alert(`图片过大(>10MB): ${f.name}`); continue; } selectedFiles.push(f); } renderPreviews(); } function renderPreviews() { const list = $("previewList"); list.innerHTML = ""; selectedFiles.forEach((f, i) => { const item = document.createElement("div"); item.className = "preview-item"; const img = document.createElement("img"); img.src = URL.createObjectURL(f); img.alt = f.name; const rm = document.createElement("button"); rm.className = "remove"; rm.textContent = "×"; rm.onclick = () => { selectedFiles.splice(i, 1); renderPreviews(); }; item.append(img, rm); list.appendChild(item); }); $("analyzeBtn").disabled = selectedFiles.length === 0 || analyzing; } // ---------- 识别 ---------- $("analyzeBtn").addEventListener("click", analyze); async function analyze() { if (analyzing || selectedFiles.length === 0) return; analyzing = true; renderPreviews(); const btn = $("analyzeBtn"); btn.innerHTML = '识别中…'; const resultCard = $("resultCard"); resultCard.hidden = false; $("resultBody").textContent = "正在识别图片内容,请稍候(最长约 2 分钟)…"; $("resultMeta").textContent = ""; const fd = new FormData(); selectedFiles.forEach((f) => fd.append("images", f, f.name)); const prompt = $("promptInput").value.trim(); if (prompt) fd.append("prompt", prompt); try { const data = await api("/api/v1/vision/analyze", { method: "POST", body: fd }); $("resultBody").textContent = data.result; if (data.usage) { $("resultMeta").textContent = `token 用量: prompt ${data.usage.prompt_tokens} / completion ${data.usage.completion_tokens} / total ${data.usage.total_tokens}`; } } catch (err) { $("resultBody").textContent = "❌ 识别失败: " + err.message; } finally { analyzing = false; btn.textContent = "开始识别"; renderPreviews(); } } // 复制结果 $("copyBtn").addEventListener("click", async () => { const text = $("resultBody").textContent; try { await navigator.clipboard.writeText(text); $("copyBtn").textContent = "已复制 ✓"; setTimeout(() => ($("copyBtn").textContent = "复制"), 1500); } catch { alert("复制失败,请手动选择文本"); } }); // ---------- 启动 ---------- refreshUnlockState();