feat(notebook): load notebook space via token and pre-select docs/sources
- NotebookAIWritingPage reads `token` from URL, fetches space data from notebook-token API (search_space_id, doc_ids, user_token) and space name from searchspaces API; keep-alive ping every 50s resets Redis TTL - Pass user_token as studioToken through AIWritingPage → AIWritingLayout → WritingFormPanel → AIWritingPanel → AIWritingForm to skip getTokenByEmail when studio token is already available - useStudioNotebooks accepts enabled flag; useNotebookSources accepts overrideToken to bypass the internal token fetch - AIWritingForm hides notebook selector when studioToken is provided (notebook is pre-locked) and pre-selects initialDocumentIds Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
e53c1a0f7b
commit
0e7149eec3
6 files changed
+140
-85
No files matched your search
@@ -19,9 +19,6 @@ import {
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Search,
|
||||
Sparkles,
|
||||
Moon,
|
||||
BookOpen,
|
||||
Loader2,
|
||||
Plus,
|
||||
Trash2,
|
||||
@@ -33,7 +30,6 @@ import Empty from "./empty";
|
||||
import { NetworkActor } from "./network-actor";
|
||||
|
||||
// ── 适配层 ─────────────────────────────────────────────────
|
||||
const writeYLog = (..._args: unknown[]) => {};
|
||||
|
||||
function authenticatedFetch(url: string, init?: RequestInit) {
|
||||
const token = getStoredAuth()?.access_token;
|
||||
@@ -58,13 +54,7 @@ function getYUserId(): string {
|
||||
return "1157761008018653184";
|
||||
}
|
||||
|
||||
type SearchMode = "A" | "Y" | "knowledge";
|
||||
|
||||
const searchModes = [
|
||||
{ value: "A", label: "A系统", icon: Sparkles },
|
||||
{ value: "Y", label: "Y系统", icon: Moon },
|
||||
{ value: "knowledge", label: "知识库", icon: BookOpen },
|
||||
] as const;
|
||||
|
||||
const searchTypes = [
|
||||
{ value: "human", label: "人物基因库" },
|
||||
@@ -274,7 +264,7 @@ const DocumentUploadPopupContent: FC<{
|
||||
data?: { query: string; mode: string }
|
||||
) => Promise<void>;
|
||||
initialSearchText?: string;
|
||||
}> = ({ isOpen, onOpenChange, searchSpace, onUploadSuccess, closeDialog, initialSearchText }) => {
|
||||
}> = ({ isOpen, onOpenChange, searchSpace, onUploadSuccess, initialSearchText }) => {
|
||||
const searchSpaceId = String(searchSpace?.id);
|
||||
const backendUrl =
|
||||
typeof window !== "undefined"
|
||||
@@ -286,7 +276,6 @@ const DocumentUploadPopupContent: FC<{
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [activeTab, setActiveTab] = useState<"search" | "Y" | "upload" | "A" | "actor">("search");
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [importSources, setImportSources] = useState<unknown[]>([]);
|
||||
const [isImporting, setIsImporting] = useState(false);
|
||||
const [listData, setListData] = useState<listDataItem[]>([]);
|
||||
const [searchType, setSearchType] = useState("human");
|
||||
@@ -396,34 +385,7 @@ const DocumentUploadPopupContent: FC<{
|
||||
setSelectedSearchSources([]);
|
||||
};
|
||||
|
||||
const handleAllSearch = async (searchMode: SearchMode, text: string) => {
|
||||
if (isSearching || !text.trim()) return;
|
||||
setIsSearching(true);
|
||||
const reqBody = {
|
||||
query: text,
|
||||
search_space_id: searchSpaceId,
|
||||
search_mode: searchMode,
|
||||
};
|
||||
const reqUrl = `${backendUrl}/api/v1/source/new`;
|
||||
try {
|
||||
const response = await authenticatedFetch(reqUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(reqBody),
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json() as { result: unknown[] };
|
||||
writeYLog({ optDetail: "笔记检索数据", optType: 3, reqParam: reqBody, url: reqUrl, returnParam: JSON.stringify({ data }), optResult: "0" });
|
||||
const results = data.result as listDataItem[];
|
||||
results.forEach((item) => { item.checked = false; });
|
||||
setImportSources(results);
|
||||
}
|
||||
} catch (error) {
|
||||
writeYLog({ optDetail: "笔记检索数据", optType: 3, reqParam: reqBody, url: reqUrl, returnParam: JSON.stringify({ data: error }), optResult: "1" });
|
||||
} finally {
|
||||
setIsSearching(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function urlSafeBtoa(str: string): string {
|
||||
const encoder = new TextEncoder();
|
||||
@@ -599,7 +561,11 @@ const DocumentUploadPopupContent: FC<{
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="w-[90vw] max-w-[90vw] sm:max-w-[90vw] h-[90vh] flex flex-col p-0 gap-0 overflow-hidden border border-border bg-muted text-foreground [&>button]:right-3 [&>button]:top-4 [&>button]:opacity-80 hover:[&>button]:opacity-100 [&>button]:z-[100] [&>button_svg]:size-4">
|
||||
<DialogContent
|
||||
className="w-[90vw] max-w-[90vw] sm:max-w-[90vw] h-[90vh] flex flex-col p-0 gap-0 overflow-hidden border border-border bg-muted text-foreground [&>button]:right-3 [&>button]:top-4 [&>button]:opacity-80 hover:[&>button]:opacity-100 [&>button]:z-[100] [&>button_svg]:size-4"
|
||||
onPointerDownOutside={(e) => e.preventDefault()}
|
||||
onInteractOutside={(e) => e.preventDefault()}
|
||||
>
|
||||
<DialogTitle className="sr-only">添加来源</DialogTitle>
|
||||
<DialogDescription className="sr-only">从外部系统搜索并导入来源</DialogDescription>
|
||||
<div className="flex-1 min-h-0 relative overflow-hidden">
|
||||
|
||||
Reference in new issue
Block a user