diff --git a/frontend-web/src/components/workspace/chats/chat-box.tsx b/frontend-web/src/components/workspace/chats/chat-box.tsx index 2841cdfa..5a37b8e6 100644 --- a/frontend-web/src/components/workspace/chats/chat-box.tsx +++ b/frontend-web/src/components/workspace/chats/chat-box.tsx @@ -347,14 +347,14 @@ const ChatBox: React.FC<{ children: React.ReactNode; threadId: string }> = ({ />
diff --git a/frontend-web/src/components/workspace/messages/message-list-item.tsx b/frontend-web/src/components/workspace/messages/message-list-item.tsx index d61f8a90..bb2b7dcf 100644 --- a/frontend-web/src/components/workspace/messages/message-list-item.tsx +++ b/frontend-web/src/components/workspace/messages/message-list-item.tsx @@ -172,9 +172,21 @@ function MessageContent_({ extraRehypePlugins?: ReturnType; }) { const baseRehypePlugins = useRehypeSplitWordsIntoSpans(isLoading); + + // Streamdown splits content into per-block processors; calling the factory + // once here ensures all blocks share the same transformer instance (and thus + // the same citation counter) for this message. + const stableExtraPlugins = useMemo(() => { + if (!extraRehypePlugins?.length) return []; + return extraRehypePlugins.map((factory) => { + const transformer = factory(); + return () => transformer; + }); + }, [extraRehypePlugins]); + const rehypePlugins = useMemo( - () => (extraRehypePlugins?.length ? [...baseRehypePlugins, ...extraRehypePlugins] : baseRehypePlugins), - [baseRehypePlugins, extraRehypePlugins], + () => (stableExtraPlugins.length ? [...stableExtraPlugins, ...baseRehypePlugins] : baseRehypePlugins), + [stableExtraPlugins, baseRehypePlugins], ); const isHuman = message.type === "human"; const resolveArtifact = useArtifactResolver(threadId); diff --git a/frontend-web/src/core/rehype/index.ts b/frontend-web/src/core/rehype/index.ts index 14d72b11..7ed5be3e 100644 --- a/frontend-web/src/core/rehype/index.ts +++ b/frontend-web/src/core/rehype/index.ts @@ -7,6 +7,8 @@ const CITE_RE = /\[\[cite:(\d+)\]\]/g; export function rehypeCitations(sources: Array<{ id: number; title: string }>) { const sourceMap = new Map(sources.map((s) => [s.id, s.title])); + const citeCounter = new Map(); + let citeIndex = 0; return (tree: Root) => { visit(tree, "text", (node, index, parent) => { @@ -14,8 +16,6 @@ export function rehypeCitations(sources: Array<{ id: number; title: string }>) { CITE_RE.lastIndex = 0; const newNodes: ElementContent[] = []; - const citeCounter = new Map(); - let citeIndex = 0; let last = 0; let match: RegExpExecArray | null; diff --git a/frontend-web/src/core/studio/hooks.ts b/frontend-web/src/core/studio/hooks.ts index bb767664..f4df8385 100644 --- a/frontend-web/src/core/studio/hooks.ts +++ b/frontend-web/src/core/studio/hooks.ts @@ -89,10 +89,15 @@ export function useNotebookChat( const send = async (text: string, selectedSourceIds: number[]) => { if (thread.isLoading || !token) return; + // API 需要 document.id,而不是 source.id + const toDocIds = (ids: number[]) => + ids + .map((id) => sources.find((s) => s.id === id)?.document?.id) + .filter((id): id is number => id !== undefined); const sourceIds = selectedSourceIds.length > 0 - ? selectedSourceIds - : sources.map((s) => s.id).filter(Boolean); + ? toDocIds(selectedSourceIds) + : sources.map((s) => s.document?.id).filter((id): id is number => id !== undefined); await thread.submit( { messages: [{ type: "human", content: text }] }, diff --git a/frontend-web/src/pages/StudioNotebookDetailPage.tsx b/frontend-web/src/pages/StudioNotebookDetailPage.tsx index d0e944ba..4608d07c 100644 --- a/frontend-web/src/pages/StudioNotebookDetailPage.tsx +++ b/frontend-web/src/pages/StudioNotebookDetailPage.tsx @@ -36,6 +36,8 @@ import { SelectValue, } from "@/components/ui/select"; import { MessageList } from "@/components/workspace/messages/message-list"; +import { ThreadContext } from "@/components/workspace/messages/context"; +import { ChatBox } from "@/components/workspace/chats/chat-box"; import { rehypeCitations } from "@/core/rehype"; // ── 来源卡片图标 ──────────────────────────────────────── @@ -268,6 +270,8 @@ export default function StudioNotebookDetailPage() { const isChatView = activeThreadId !== null || hasMessages; return ( + +
{/* 背景渐变(列表视图时显示) */} {!isChatView && ( @@ -490,5 +494,7 @@ export default function StudioNotebookDetailPage() { onToggle={handleToggleSource} />
+
+
); }