- WorkspaceRoutes 给 StudioNotebookDetailPage 加 key={notebookId},切换笔记时强制重新挂载
- createSearchSpace 接口失败时解析响应体 detail 字段,直接展示服务端错误信息
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
157 lines
4.9 KiB
TypeScript
157 lines
4.9 KiB
TypeScript
import { getNotebookApiBaseURL } from "@/core/config";
|
|
import type { SourceItem, StudioNotebook } from "./types";
|
|
|
|
function base(): string {
|
|
return getNotebookApiBaseURL();
|
|
}
|
|
|
|
export async function fetchStudioToken(email: string): Promise<string> {
|
|
const apiBase = base();
|
|
if (!apiBase) throw new Error("VITE_AIPI_FRONTEND_URL 未配置");
|
|
|
|
const res = await fetch(
|
|
`${apiBase}/api/getTokenByEmail?email=${encodeURIComponent(email)}`,
|
|
);
|
|
if (!res.ok) throw new Error(`获取 Token 失败:${res.status}`);
|
|
|
|
const data = (await res.json()) as { token?: string } | string;
|
|
const token = typeof data === "string" ? data : data?.token;
|
|
if (!token) throw new Error("接口未返回有效 token");
|
|
return token;
|
|
}
|
|
|
|
export async function fetchStudioNotebooks(token: string): Promise<StudioNotebook[]> {
|
|
const apiBase = base();
|
|
if (!apiBase) throw new Error("VITE_AIPI_FRONTEND_URL 未配置");
|
|
|
|
const res = await fetch(`${apiBase}/api/v1/searchspaces`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
if (!res.ok) throw new Error(`获取笔记本列表失败:${res.status}`);
|
|
return res.json() as Promise<StudioNotebook[]>;
|
|
}
|
|
|
|
export async function createSearchSpace(
|
|
name: string,
|
|
token: string,
|
|
): Promise<void> {
|
|
const apiBase = base();
|
|
if (!apiBase) throw new Error("VITE_AIPI_FRONTEND_URL 未配置");
|
|
|
|
const res = await fetch(`${apiBase}/api/v1/searchspaces`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify({ name, description: "", citations_enabled: true }),
|
|
});
|
|
if (!res.ok) {
|
|
const body = await res.json().catch(() => null);
|
|
throw new Error(body?.detail || `创建失败:${res.status}`);
|
|
}
|
|
}
|
|
|
|
export async function uploadSourceText(
|
|
notebookId: number,
|
|
content: string,
|
|
title: string,
|
|
token: string,
|
|
): Promise<void> {
|
|
const apiBase = base();
|
|
if (!apiBase) throw new Error("VITE_AIPI_FRONTEND_URL 未配置");
|
|
const res = await fetch(`${apiBase}/api/v1/documents/textupload`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
body: JSON.stringify({ search_space_id: notebookId, content, title: title || "粘贴的文字" }),
|
|
});
|
|
if (!res.ok) throw new Error(`上传失败:${res.status}`);
|
|
}
|
|
|
|
export async function deleteSearchSpace(id: number, token: string): Promise<void> {
|
|
const apiBase = base();
|
|
if (!apiBase) throw new Error("VITE_AIPI_FRONTEND_URL 未配置");
|
|
const res = await fetch(`${apiBase}/api/v1/searchspaces/${id}`, {
|
|
method: "DELETE",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
if (!res.ok) throw new Error(`删除失败:${res.status}`);
|
|
}
|
|
|
|
export async function renameSearchSpace(id: number, name: string, token: string): Promise<void> {
|
|
const apiBase = base();
|
|
if (!apiBase) throw new Error("VITE_AIPI_FRONTEND_URL 未配置");
|
|
const res = await fetch(`${apiBase}/api/v1/searchspaces/${id}`, {
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
body: JSON.stringify({ name }),
|
|
});
|
|
if (!res.ok) throw new Error(`重命名失败:${res.status}`);
|
|
}
|
|
|
|
export interface ChunkRead {
|
|
id: number;
|
|
content: string;
|
|
document_id: number;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface DocumentWithChunks {
|
|
id: number;
|
|
title: string;
|
|
document_type: string;
|
|
file_type: string;
|
|
chunks: ChunkRead[];
|
|
}
|
|
|
|
export async function fetchDocumentByChunk(
|
|
chunkId: number,
|
|
token: string,
|
|
): Promise<DocumentWithChunks> {
|
|
const apiBase = base();
|
|
if (!apiBase) throw new Error("VITE_AIPI_FRONTEND_URL 未配置");
|
|
|
|
const res = await fetch(`${apiBase}/api/v1/documents/by-chunk/${chunkId}`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
if (!res.ok) throw new Error(`获取文档失败:${res.status}`);
|
|
return res.json() as Promise<DocumentWithChunks>;
|
|
}
|
|
|
|
export async function uploadSourceFiles(
|
|
notebookId: number,
|
|
files: File[],
|
|
token: string,
|
|
): Promise<void> {
|
|
const apiBase = base();
|
|
if (!apiBase) throw new Error("VITE_AIPI_FRONTEND_URL 未配置");
|
|
|
|
const formData = new FormData();
|
|
formData.append("search_space_id", String(notebookId));
|
|
for (const file of files) {
|
|
formData.append("files", file);
|
|
}
|
|
|
|
const res = await fetch(`${apiBase}/api/v1/documents/fileupload`, {
|
|
method: "POST",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body: formData,
|
|
});
|
|
if (!res.ok) throw new Error(`上传失败:${res.status}`);
|
|
}
|
|
|
|
export async function fetchNotebookSources(
|
|
notebookId: number,
|
|
token: string,
|
|
): Promise<SourceItem[]> {
|
|
const apiBase = base();
|
|
if (!apiBase) throw new Error("VITE_AIPI_FRONTEND_URL 未配置");
|
|
|
|
const res = await fetch(
|
|
`${apiBase}/api/v1/source/search-space/${notebookId}?limit=100`,
|
|
{ headers: { Authorization: `Bearer ${token}` } },
|
|
);
|
|
if (!res.ok) throw new Error(`获取来源失败:${res.status}`);
|
|
return res.json() as Promise<SourceItem[]>;
|
|
}
|