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:
xuhdandClaude Sonnet 4.6 committed 2026-06-05 13:38:08 +08:00
1 parent e53c1a0f7b
commit 0e7149eec3
6 files changed
+140 -85

No files matched your search

@@ -24,6 +24,10 @@ interface AIWritingFormProps {
hideAdvancedFields?: boolean;
/** 预选笔记本 ID(传入时自动切换素材来源为「我的笔记」) */
initialNotebookId?: number;
/** 预选文档 ID 列表 */
initialDocumentIds?: number[];
/** 外部直接提供的 studio bearer token,跳过 getTokenByEmail */
studioToken?: string;
}
const INPUT_CLASS =
@@ -53,6 +57,8 @@ export function AIWritingForm({
disabled,
hideAdvancedFields = false,
initialNotebookId,
initialDocumentIds,
studioToken,
}: AIWritingFormProps) {
const {
intent, setIntent,
@@ -69,11 +75,11 @@ export function AIWritingForm({
const [selectedNotebookId, setSelectedNotebookId] = useState<number | null>(
initialNotebookId ?? null,
);
const [selectedDocumentIds, setSelectedDocumentIds] = useState<number[]>([]);
const [selectedDocumentIds, setSelectedDocumentIds] = useState<number[]>(initialDocumentIds ?? []);
const { models, isLoading: modelsLoading } = useModels();
const { notebooks, isLoading: notebooksLoading } = useStudioNotebooks();
const { sources, isLoading: sourcesLoading } = useNotebookSources(selectedNotebookId);
const { notebooks, isLoading: notebooksLoading } = useStudioNotebooks(!studioToken);
const { sources, isLoading: sourcesLoading } = useNotebookSources(selectedNotebookId, studioToken);
const { articleTypes, loading: typesLoading, refresh: refreshTypes } = useArticleTypes();
@@ -291,22 +297,25 @@ export function AIWritingForm({
{/* 选了"我的笔记"才展示笔记本 / 文档选择器 */}
{materialSource === 'notebook' && (
<div className="mt-2 space-y-2">
<Select
value={selectedNotebookId !== null ? String(selectedNotebookId) : ''}
onValueChange={handleNotebookChange}
disabled={disabled || notebooksLoading}
>
<SelectTrigger className={FIELD_TRIGGER_CLASS}>
<SelectValue placeholder={notebooksLoading ? '加载中…' : '选择笔记本'} />
</SelectTrigger>
<SelectContent>
{notebooks.map((nb) => (
<SelectItem key={nb.id} value={String(nb.id)} className="text-sm">
{nb.name}
</SelectItem>
))}
</SelectContent>
</Select>
{/* studioToken 模式:笔记本已锁定,不展示选择器 */}
{!studioToken && (
<Select
value={selectedNotebookId !== null ? String(selectedNotebookId) : ''}
onValueChange={handleNotebookChange}
disabled={disabled || notebooksLoading}
>
<SelectTrigger className={FIELD_TRIGGER_CLASS}>
<SelectValue placeholder={notebooksLoading ? '加载中…' : '选择笔记本'} />
</SelectTrigger>
<SelectContent>
{notebooks.map((nb) => (
<SelectItem key={nb.id} value={String(nb.id)} className="text-sm">
{nb.name}
</SelectItem>
))}
</SelectContent>
</Select>
)}
{selectedNotebookId !== null && (
<div className="space-y-1">
@@ -71,11 +71,16 @@ const STATUS_LABEL: Record<string, string> = {
export function AIWritingPanel({
hideAdvancedFields = false,
initialNotebookId,
initialDocumentIds,
studioToken,
}: {
hideAdvancedFields?: boolean;
initialNotebookId?: number;
initialDocumentIds?: number[];
studioToken?: string;
} = {}) {
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
const { state, startWriting, submitIntervention, pauseWriting, resumeWriting, reset } = useAIWriting();
const { historySession, isHistoryMode, openHistory, closeHistory } = useAIWritingHistory();
const { sessions, loading: sessionsLoading, refresh, rename, remove } = useAIWritingSessions();
@@ -354,6 +359,8 @@ export function AIWritingPanel({
onSubmit={startWriting}
hideAdvancedFields={hideAdvancedFields}
initialNotebookId={initialNotebookId}
initialDocumentIds={initialDocumentIds}
studioToken={studioToken}
/>
)}
@@ -115,11 +115,17 @@ const SETUP_LAYOUT = { [SETUP_CHAT_ID]: 65, [SETUP_FORM_ID]: 35 };
/**
* 写作配置表单面板(写作助手回填的目标),抽出复用:idle 时在右栏,开始写作后在左栏。
*/
function WritingFormPanel({ hideAdvancedFields }: { hideAdvancedFields: boolean }) {
interface NotebookProps {
initialNotebookId?: number;
initialDocumentIds?: number[];
studioToken?: string;
}
function WritingFormPanel({ hideAdvancedFields, initialNotebookId, initialDocumentIds, studioToken }: { hideAdvancedFields: boolean } & NotebookProps) {
return (
// 与原布局保持相同的 flex-1 min-h-0 包裹,确保内部 h-full 正确解析
<div className="flex-1 min-h-0 overflow-hidden">
<AIWritingPanel hideAdvancedFields={hideAdvancedFields} />
<AIWritingPanel hideAdvancedFields={hideAdvancedFields} initialNotebookId={initialNotebookId} initialDocumentIds={initialDocumentIds} studioToken={studioToken} />
</div>
);
}
@@ -129,7 +135,7 @@ function WritingFormPanel({ hideAdvancedFields }: { hideAdvancedFields: boolean
* - idle(初始问答阶段):左=「写作助手 Q&A」,右=「写作配置选项」表单;
* - 开始写作后:回到原布局——左=写作配置/时间线面板,右=草稿预览。
*/
function AIWritingLayout({ hideAdvancedFields }: { hideAdvancedFields: boolean }) {
function AIWritingLayout({ hideAdvancedFields, initialNotebookId, initialDocumentIds, studioToken }: { hideAdvancedFields: boolean } & NotebookProps) {
const layoutRef = useRef<GroupImperativeHandle>(null);
const { state } = useAIWriting();
const { isHistoryMode } = useAIWritingHistory();
@@ -162,7 +168,7 @@ function AIWritingLayout({ hideAdvancedFields }: { hideAdvancedFields: boolean }
minSize={20}
className="overflow-hidden flex flex-col"
>
<WritingFormPanel hideAdvancedFields={hideAdvancedFields} />
<WritingFormPanel hideAdvancedFields={hideAdvancedFields} initialNotebookId={initialNotebookId} initialDocumentIds={initialDocumentIds} studioToken={studioToken} />
</ResizablePanel>
</ResizablePanelGroup>
);
@@ -203,9 +209,13 @@ function AIWritingLayout({ hideAdvancedFields }: { hideAdvancedFields: boolean }
export default function AIWritingPage({
hideAdvancedFields = false,
initialNotebookId,
initialDocumentIds,
studioToken,
}: {
hideAdvancedFields?: boolean;
initialNotebookId?: number;
initialDocumentIds?: number[];
studioToken?: string;
} = {}) {
return (
<CanvasThreadProvider>
@@ -217,7 +227,7 @@ export default function AIWritingPage({
<TooltipProvider delayDuration={300}>
<div className="h-full flex flex-col overflow-hidden bg-background">
<div className="flex-1 min-h-0 overflow-hidden">
<AIWritingLayout hideAdvancedFields={hideAdvancedFields} />
<AIWritingLayout hideAdvancedFields={hideAdvancedFields} initialNotebookId={initialNotebookId} initialDocumentIds={initialDocumentIds} studioToken={studioToken} />
</div>
</div>
</TooltipProvider>