diff --git a/frontend-web/src/core/studio/api.ts b/frontend-web/src/core/studio/api.ts index c8f6329c..ac492de8 100644 --- a/frontend-web/src/core/studio/api.ts +++ b/frontend-web/src/core/studio/api.ts @@ -49,6 +49,43 @@ export async function createSearchSpace( if (!res.ok) throw new Error(`创建失败:${res.status}`); } +export async function uploadSourceText( + notebookId: number, + content: string, + title: string, + token: string, +): Promise { + 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 { + 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 { + 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; diff --git a/frontend-web/src/pages/StudioNotebookDetailPage.tsx b/frontend-web/src/pages/StudioNotebookDetailPage.tsx index b711390a..57ff6d8f 100644 --- a/frontend-web/src/pages/StudioNotebookDetailPage.tsx +++ b/frontend-web/src/pages/StudioNotebookDetailPage.tsx @@ -10,7 +10,6 @@ import { Globe, Loader2, Plus, - Send, X, } from "lucide-react"; @@ -23,7 +22,7 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { useNotebookChat, useNotebookSources, useNotebookThreads, useStudioNotebooks } from "@/core/studio/hooks"; -import { fetchDocumentByChunk, uploadSourceFiles } from "@/core/studio/api"; +import { fetchDocumentByChunk, uploadSourceFiles, uploadSourceText } from "@/core/studio/api"; import type { DocumentWithChunks } from "@/core/studio/api"; import type { SourceItem } from "@/core/studio/types"; import { getStoredAuth } from "@/core/auth"; @@ -132,6 +131,11 @@ function SourcesDialog({ const fileInputRef = useRef(null); const [uploading, setUploading] = useState(false); const [uploadError, setUploadError] = useState(""); + const [panel, setPanel] = useState<"sources" | "text">("sources"); + const [textContent, setTextContent] = useState(""); + const [textTitle, setTextTitle] = useState(""); + const [submittingText, setSubmittingText] = useState(false); + const [textError, setTextError] = useState(""); const handleFileChange = async (e: React.ChangeEvent) => { const files = Array.from(e.target.files ?? []); @@ -149,6 +153,23 @@ function SourcesDialog({ } }; + const handleTextSubmit = async () => { + if (!textContent.trim()) return; + setSubmittingText(true); + setTextError(""); + try { + await uploadSourceText(notebookId, textContent.trim(), textTitle.trim(), token); + setTextContent(""); + setTextTitle(""); + setPanel("sources"); + onUploadSuccess(); + } catch (err) { + setTextError(err instanceof Error ? err.message : "上传失败"); + } finally { + setSubmittingText(false); + } + }; + return ( !v && onClose()}>
+ {/* 左侧导航 */}
-
-
- {isLoadingSources - ? Array.from({ length: 8 }).map((_, i) => ) - : sources.map((src) => ( - - ))} + {/* 右侧内容 */} + {panel === "sources" ? ( +
+
+ {isLoadingSources + ? Array.from({ length: 8 }).map((_, i) => ) + : sources.map((src) => ( + + ))} +
+ {!isLoadingSources && sources.length === 0 && ( +

暂无来源,请添加文件。

+ )}
- {!isLoadingSources && sources.length === 0 && ( -

暂无来源,请添加文件。

- )} -
+ ) : ( +
+ setTextTitle(e.target.value)} + placeholder="标题(可选)" + className="w-full rounded-lg border border-border bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-primary" + /> +