From c714351819a748619b1975d7a50ccc382bd71056 Mon Sep 17 00:00:00 2001 From: xuhd Date: Mon, 1 Jun 2026 17:36:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=88=91=E7=9A=84=E7=AC=94=E8=AE=B0?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E4=B8=8E=E8=AF=A6=E6=83=85=E9=A1=B5=20UI=20?= =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=88Gemini=20=E9=A3=8E=E6=A0=BC=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - StudioNotebooksPage: 卡片改为灰底无边框圆角网格,hover 显示三点菜单,支持重命名(PATCH)和删除(DELETE) - StudioNotebookDetailPage: 详情页背景改为主题色渐变,内容垂直居中,输入框内联布局,模型选择器移至右侧,历史对话限显 5 条可展开/滚动 - api.ts: 新增 deleteSearchSpace、renameSearchSpace、uploadSourceText 接口 - 来源 Dialog:新增「复制的文字」面板,支持粘贴文本上传 Co-Authored-By: Claude Sonnet 4.6 --- frontend-web/src/core/studio/api.ts | 37 +++ .../src/pages/StudioNotebookDetailPage.tsx | 309 ++++++++++++------ .../src/pages/StudioNotebooksPage.tsx | 262 ++++++++++++--- 3 files changed, 456 insertions(+), 152 deletions(-) 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" + /> +