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
@@ -1,9 +1,59 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import AIWritingPage from '@/open-canvas/pages/AIWritingPage';
|
||||
import { getNotebookApiBaseURL } from '@/core/config';
|
||||
|
||||
interface NotebookTokenData {
|
||||
doc_ids: number[];
|
||||
search_space_id: number;
|
||||
user_id: string;
|
||||
user_token: string;
|
||||
}
|
||||
|
||||
export default function NotebookAIWritingPage() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const notebookId = searchParams.get('notebook_id');
|
||||
const token = searchParams.get('token');
|
||||
|
||||
const [data, setData] = useState<NotebookTokenData | null>(null);
|
||||
const [spaceName, setSpaceName] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
setError('缺少 token 参数');
|
||||
return;
|
||||
}
|
||||
const base = getNotebookApiBaseURL();
|
||||
fetch(`${base}/api/v1/ai-writing/notebook-token/${token}`)
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(`请求失败 ${res.status}`);
|
||||
return res.json() as Promise<NotebookTokenData>;
|
||||
})
|
||||
.then((d) => {
|
||||
setData(d);
|
||||
return fetch(`${base}/api/v1/searchspaces/${d.search_space_id}`, {
|
||||
headers: { Authorization: `Bearer ${d.user_token}` },
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) return null;
|
||||
return res.json() as Promise<{ name: string }>;
|
||||
})
|
||||
.then((space) => {
|
||||
if (space?.name) setSpaceName(space.name);
|
||||
})
|
||||
.catch((e: Error) => setError(e.message));
|
||||
}, [token]);
|
||||
|
||||
// Keep-alive:每 50 秒 ping 一次 token 接口,防止 Redis TTL 过期
|
||||
useEffect(() => {
|
||||
if (!token) return;
|
||||
const base = getNotebookApiBaseURL();
|
||||
const id = setInterval(() => {
|
||||
fetch(`${base}/api/v1/ai-writing/notebook-token/${token}`).catch(() => {});
|
||||
}, 50_000);
|
||||
return () => clearInterval(id);
|
||||
}, [token]);
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col overflow-hidden">
|
||||
@@ -20,12 +70,12 @@ export default function NotebookAIWritingPage() {
|
||||
{/* 分隔线 */}
|
||||
<div className="w-px h-4 bg-[#2d4f7a]" />
|
||||
|
||||
{/* 空间 ID */}
|
||||
{notebookId && (
|
||||
{/* 空间名称 */}
|
||||
{data?.search_space_id && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-[#7fb3e8] text-xs">空间</span>
|
||||
<span className="text-[#b3d4f5] text-xs font-semibold bg-[#1a3a60] border border-[#2d5080] rounded px-2 py-0.5">
|
||||
{notebookId}
|
||||
{spaceName ?? data.search_space_id}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -40,12 +90,20 @@ export default function NotebookAIWritingPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 主体:AIWritingPage 完整组件,含 context providers */}
|
||||
{/* 主体 */}
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
<AIWritingPage
|
||||
hideAdvancedFields
|
||||
initialNotebookId={notebookId ? Number(notebookId) : undefined}
|
||||
/>
|
||||
{error ? (
|
||||
<div className="flex items-center justify-center h-full text-red-400 text-sm">{error}</div>
|
||||
) : !data ? (
|
||||
<div className="flex items-center justify-center h-full text-[#7fb3e8] text-sm">加载中…</div>
|
||||
) : (
|
||||
<AIWritingPage
|
||||
hideAdvancedFields
|
||||
initialNotebookId={data.search_space_id}
|
||||
initialDocumentIds={data.doc_ids}
|
||||
studioToken={data.user_token}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user