merge: 合并 origin/xuhd,解决 message-list-item 和 WorkspaceRoutes 冲突
- message-list-item: 保留 EmbeddedPageContext + 消息内联编辑两套功能 - WorkspaceRoutes: 保留 IframeChatPage + NotebookDetailPageWithKey Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
712326387f
20 files changed
+859
-231
No files matched your search
@@ -69,7 +69,7 @@ export function getLangGraphBaseURL(isMock?: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 笔记本外部系统(Studio)API 基础地址,通过 VITE_NOTEBOOK_API_BASE_URL 配置。 */
|
||||
/** 笔记本外部系统后端 API 地址,通过 VITE_AIPI_BACKENP_URL 配置。 */
|
||||
export function getNotebookApiBaseURL(): string {
|
||||
if (env.NEXT_PUBLIC_NOTEBOOK_API_BASE_URL) {
|
||||
return env.NEXT_PUBLIC_NOTEBOOK_API_BASE_URL.replace(/\/+$/, "");
|
||||
|
||||
@@ -7,6 +7,8 @@ const CITE_RE = /\[\[cite:(\d+)\]\]/g;
|
||||
|
||||
export function rehypeCitations(sources: Array<{ id: number; title: string }>) {
|
||||
const sourceMap = new Map(sources.map((s) => [s.id, s.title]));
|
||||
const citeCounter = new Map<number, number>();
|
||||
let citeIndex = 0;
|
||||
|
||||
return (tree: Root) => {
|
||||
visit(tree, "text", (node, index, parent) => {
|
||||
@@ -14,8 +16,6 @@ export function rehypeCitations(sources: Array<{ id: number; title: string }>) {
|
||||
CITE_RE.lastIndex = 0;
|
||||
|
||||
const newNodes: ElementContent[] = [];
|
||||
const citeCounter = new Map<number, number>();
|
||||
let citeIndex = 0;
|
||||
let last = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ function base(): string {
|
||||
|
||||
export async function fetchStudioToken(email: string): Promise<string> {
|
||||
const apiBase = base();
|
||||
if (!apiBase) throw new Error("VITE_NOTEBOOK_API_BASE_URL 未配置");
|
||||
if (!apiBase) throw new Error("VITE_AIPI_FRONTEND_URL 未配置");
|
||||
|
||||
const res = await fetch(
|
||||
`${apiBase}/api/getTokenByEmail?email=${encodeURIComponent(email)}`,
|
||||
@@ -22,7 +22,7 @@ export async function fetchStudioToken(email: string): Promise<string> {
|
||||
|
||||
export async function fetchStudioNotebooks(token: string): Promise<StudioNotebook[]> {
|
||||
const apiBase = base();
|
||||
if (!apiBase) throw new Error("VITE_NOTEBOOK_API_BASE_URL 未配置");
|
||||
if (!apiBase) throw new Error("VITE_AIPI_FRONTEND_URL 未配置");
|
||||
|
||||
const res = await fetch(`${apiBase}/api/v1/searchspaces`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
@@ -36,7 +36,7 @@ export async function createSearchSpace(
|
||||
token: string,
|
||||
): Promise<void> {
|
||||
const apiBase = base();
|
||||
if (!apiBase) throw new Error("VITE_NOTEBOOK_API_BASE_URL 未配置");
|
||||
if (!apiBase) throw new Error("VITE_AIPI_FRONTEND_URL 未配置");
|
||||
|
||||
const res = await fetch(`${apiBase}/api/v1/searchspaces`, {
|
||||
method: "POST",
|
||||
@@ -46,7 +46,47 @@ export async function createSearchSpace(
|
||||
},
|
||||
body: JSON.stringify({ name, description: "", citations_enabled: true }),
|
||||
});
|
||||
if (!res.ok) throw new Error(`创建失败:${res.status}`);
|
||||
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 {
|
||||
@@ -69,7 +109,7 @@ export async function fetchDocumentByChunk(
|
||||
token: string,
|
||||
): Promise<DocumentWithChunks> {
|
||||
const apiBase = base();
|
||||
if (!apiBase) throw new Error("VITE_NOTEBOOK_API_BASE_URL 未配置");
|
||||
if (!apiBase) throw new Error("VITE_AIPI_FRONTEND_URL 未配置");
|
||||
|
||||
const res = await fetch(`${apiBase}/api/v1/documents/by-chunk/${chunkId}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
@@ -78,12 +118,34 @@ export async function fetchDocumentByChunk(
|
||||
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_NOTEBOOK_API_BASE_URL 未配置");
|
||||
if (!apiBase) throw new Error("VITE_AIPI_FRONTEND_URL 未配置");
|
||||
|
||||
const res = await fetch(
|
||||
`${apiBase}/api/v1/source/search-space/${notebookId}?limit=100`,
|
||||
|
||||
@@ -12,6 +12,7 @@ export function useStudioNotebooks(): {
|
||||
notebooks: StudioNotebook[];
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
token: string | undefined;
|
||||
} {
|
||||
const email = getStoredAuth()?.email ?? "";
|
||||
|
||||
@@ -35,6 +36,7 @@ export function useStudioNotebooks(): {
|
||||
notebooks: (notebooksQuery.data ?? []).filter((nb) => nb.is_owner),
|
||||
isLoading: tokenQuery.isLoading || notebooksQuery.isLoading,
|
||||
isError: tokenQuery.isError || notebooksQuery.isError,
|
||||
token: tokenQuery.data,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -89,10 +91,15 @@ export function useNotebookChat(
|
||||
|
||||
const send = async (text: string, selectedSourceIds: number[]) => {
|
||||
if (thread.isLoading || !token) return;
|
||||
// API 需要 document.id,而不是 source.id
|
||||
const toDocIds = (ids: number[]) =>
|
||||
ids
|
||||
.map((id) => sources.find((s) => s.id === id)?.document?.id)
|
||||
.filter((id): id is number => id !== undefined);
|
||||
const sourceIds =
|
||||
selectedSourceIds.length > 0
|
||||
? selectedSourceIds
|
||||
: sources.map((s) => s.id).filter(Boolean);
|
||||
? toDocIds(selectedSourceIds)
|
||||
: sources.map((s) => s.document?.id).filter((id): id is number => id !== undefined);
|
||||
|
||||
await thread.submit(
|
||||
{ messages: [{ type: "human", content: text }] },
|
||||
|
||||
Reference in new issue
Block a user