前端优化:chat-box、消息列表、rehype 及 Studio 相关交互改进

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
xuhdandClaude Sonnet 4.6 committed 2026-05-28 18:29:10 +08:00
1 parent b8a97f2138
commit 1ea521abab
5 files changed
+31 -8

No files matched your search

@@ -347,14 +347,14 @@ const ChatBox: React.FC<{ children: React.ReactNode; threadId: string }> = ({
/>
<ResizablePanel
className={cn(
"bg-[#fffdfa] transition-all duration-300 ease-in-out",
"bg-background transition-all duration-300 ease-in-out",
!sidePanelOpen && "opacity-0",
)}
id="artifacts"
>
<div
className={cn(
"h-full bg-[#fffdfa] p-4 transition-transform duration-300 ease-in-out",
"h-full bg-background p-4 transition-transform duration-300 ease-in-out",
sidePanelOpen ? "translate-x-0" : "translate-x-full",
)}
>
@@ -172,9 +172,21 @@ function MessageContent_({
extraRehypePlugins?: ReturnType<typeof useRehypeSplitWordsIntoSpans>;
}) {
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);
+2 -2
View File
@@ -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<number, number>();
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<number, number>();
let citeIndex = 0;
let last = 0;
let match: RegExpExecArray | null;
+7 -2
View File
@@ -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 }] },
@@ -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 (
<ThreadContext.Provider value={{ thread }}>
<ChatBox threadId={chatThreadId ?? ""}>
<div className="relative flex h-full flex-col overflow-hidden bg-background">
{/* 背景渐变(列表视图时显示) */}
{!isChatView && (
@@ -490,5 +494,7 @@ export default function StudioNotebookDetailPage() {
onToggle={handleToggleSource}
/>
</div>
</ChatBox>
</ThreadContext.Provider>
);
}