czty's picture
Add files using upload-large-folder tool
b2c86fd verified
Raw
History Blame Contribute Delete
5.58 kB
/**
* API 层:管理 session 和 SSE 连接
*
* 流程:
* 1. createSession() → POST /session/new → 得到 session_id
* 2. streamChat(msg, sid, onEvent) → GET /chat/stream?... → 持续回调
*/
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 {
// Keep raw text if it is not JSON.
}
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();
}
/**
* 发送消息,通过 SSE 接收流式响应
* @param {string} message
* @param {string} sessionId
* @param {function} onEvent - 回调,每个事件触发一次
* 事件格式: { type: 'thinking'|'code'|'tool_use'|'result'|'error'|'done', ... }
* @returns {function} cancel - 调用取消连接
*/
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();
}