| |
| |
| |
| |
| |
| |
| |
|
|
| const EXPLICIT_BASE = import.meta.env.VITE_API_BASE_URL?.trim(); |
| let cachedBase = EXPLICIT_BASE || ""; |
|
|
| function buildBaseCandidates() { |
| if (typeof window === "undefined") { |
| return [EXPLICIT_BASE || "http://127.0.0.1:8000"].filter(Boolean); |
| } |
|
|
| const { protocol, hostname, port, origin } = window.location; |
| const inferred = [ |
| EXPLICIT_BASE, |
| port === "8000" ? origin : `${protocol}//${hostname}:8000`, |
| "http://127.0.0.1:8000", |
| "http://localhost:8000", |
| ].filter(Boolean); |
|
|
| return [...new Set(inferred)]; |
| } |
|
|
| function normalizeErrorText(text) { |
| const raw = String(text || "").trim(); |
| if (!raw) return "请求失败"; |
|
|
| try { |
| const parsed = JSON.parse(raw); |
| const detail = parsed?.detail; |
| if (typeof detail === "string") { |
| if (detail === "Not Found") { |
| return "接口不存在,后端可能还没有重启到最新版本。"; |
| } |
| return detail; |
| } |
| if (detail && typeof detail === "object") { |
| return JSON.stringify(detail); |
| } |
| } catch { |
| |
| } |
|
|
| if (raw.includes("Not Found")) { |
| return "接口不存在,后端可能还没有重启到最新版本。"; |
| } |
| return raw; |
| } |
|
|
| async function apiFetch(path, init = {}) { |
| const bases = cachedBase ? [cachedBase, ...buildBaseCandidates()] : buildBaseCandidates(); |
| let lastError = null; |
|
|
| for (const base of [...new Set(bases)]) { |
| try { |
| const response = await fetch(`${base}${path}`, init); |
| if (response.ok) { |
| cachedBase = base; |
| return response; |
| } |
|
|
| const text = await response.text(); |
| const message = normalizeErrorText(text); |
| lastError = new Error(message || `${response.status} ${response.statusText}`); |
|
|
| if (response.status !== 404) { |
| throw lastError; |
| } |
| } catch (error) { |
| lastError = error instanceof Error ? error : new Error(String(error)); |
| } |
| } |
|
|
| throw lastError || new Error("无法连接到后端服务"); |
| } |
|
|
| export function resolveApiUrl(path) { |
| const base = cachedBase || buildBaseCandidates()[0] || "http://127.0.0.1:8000"; |
| return `${base}${path}`; |
| } |
|
|
| export async function createSession(llmProvider = "claude") { |
| const res = await apiFetch("/session/new", { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json", |
| }, |
| body: JSON.stringify({ llm_provider: llmProvider }), |
| }); |
| const data = await res.json(); |
| return data.session_id; |
| } |
|
|
| export async function uploadSessionFiles(sessionId, files) { |
| if (!Array.isArray(files) || files.length === 0) { |
| throw new Error("No files selected"); |
| } |
| const form = new FormData(); |
| for (const file of files) { |
| form.append("files", file); |
| } |
| const res = await apiFetch(`/session/${sessionId}/upload`, { |
| method: "POST", |
| body: form, |
| }); |
| if (!res.ok) { |
| const text = await res.text(); |
| throw new Error(normalizeErrorText(text) || "Upload failed"); |
| } |
| return res.json(); |
| } |
|
|
| export async function uploadSessionFile(sessionId, file) { |
| return uploadSessionFiles(sessionId, [file]); |
| } |
|
|
| export async function stopSessionRun(sessionId) { |
| const res = await apiFetch(`/session/${sessionId}/stop`, { |
| method: "POST", |
| }); |
| return res.json(); |
| } |
|
|
| export async function listMetabolomicsTools() { |
| const res = await apiFetch("/metabolomics/tools"); |
| return res.json(); |
| } |
|
|
| export async function listMcpServers() { |
| const res = await apiFetch("/mcp/servers"); |
| return res.json(); |
| } |
|
|
| export async function generateScmetaMcp() { |
| const res = await apiFetch("/metabolomics/scmeta/generate", { method: "POST" }); |
| return res.json(); |
| } |
|
|
| export async function generateMcpByConverter({ toolName, llmProvider = "claude", manualPdf = null }) { |
| const form = new FormData(); |
| form.append("tool_name", toolName); |
| form.append("llm_provider", llmProvider); |
| if (manualPdf) { |
| form.append("manual_pdf", manualPdf); |
| } |
| const res = await apiFetch("/mcp/converter/generate", { |
| method: "POST", |
| body: form, |
| }); |
| return res.json(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function streamChat(message, sessionId, onEvent, options = {}) { |
| const base = cachedBase || buildBaseCandidates()[0] || "http://127.0.0.1:8000"; |
| const params = new URLSearchParams({ |
| message, |
| session_id: sessionId, |
| llm_provider: options.llmProvider || "deepseek", |
| metabolomics_enabled: String(Boolean(options.metabolomicsEnabled)), |
| metabolomics_tools: (options.metabolomicsTools || []).join(","), |
| paper_repro_enabled: "true", |
| paper_profile: "generic", |
| }); |
| const url = `${base}/chat/stream?${params}`; |
|
|
| const source = new EventSource(url); |
|
|
| source.onmessage = (e) => { |
| try { |
| const event = JSON.parse(e.data); |
| onEvent(event); |
| if (event.type === "done" || event.type === "error") { |
| source.close(); |
| } |
| } catch (err) { |
| console.error("SSE parse error", err); |
| } |
| }; |
|
|
| source.onerror = () => { |
| onEvent({ type: "error", content: "Connection error" }); |
| source.close(); |
| }; |
|
|
| |
| return () => source.close(); |
| } |
|
|