前端优化:chat-box、消息列表、rehype 及 Studio 相关交互改进
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
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
|
<ResizablePanel
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-[#fffdfa] transition-all duration-300 ease-in-out",
|
"bg-background transition-all duration-300 ease-in-out",
|
||||||
!sidePanelOpen && "opacity-0",
|
!sidePanelOpen && "opacity-0",
|
||||||
)}
|
)}
|
||||||
id="artifacts"
|
id="artifacts"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
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",
|
sidePanelOpen ? "translate-x-0" : "translate-x-full",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -172,9 +172,21 @@ function MessageContent_({
|
|||||||
extraRehypePlugins?: ReturnType<typeof useRehypeSplitWordsIntoSpans>;
|
extraRehypePlugins?: ReturnType<typeof useRehypeSplitWordsIntoSpans>;
|
||||||
}) {
|
}) {
|
||||||
const baseRehypePlugins = useRehypeSplitWordsIntoSpans(isLoading);
|
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(
|
const rehypePlugins = useMemo(
|
||||||
() => (extraRehypePlugins?.length ? [...baseRehypePlugins, ...extraRehypePlugins] : baseRehypePlugins),
|
() => (stableExtraPlugins.length ? [...stableExtraPlugins, ...baseRehypePlugins] : baseRehypePlugins),
|
||||||
[baseRehypePlugins, extraRehypePlugins],
|
[stableExtraPlugins, baseRehypePlugins],
|
||||||
);
|
);
|
||||||
const isHuman = message.type === "human";
|
const isHuman = message.type === "human";
|
||||||
const resolveArtifact = useArtifactResolver(threadId);
|
const resolveArtifact = useArtifactResolver(threadId);
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ const CITE_RE = /\[\[cite:(\d+)\]\]/g;
|
|||||||
|
|
||||||
export function rehypeCitations(sources: Array<{ id: number; title: string }>) {
|
export function rehypeCitations(sources: Array<{ id: number; title: string }>) {
|
||||||
const sourceMap = new Map(sources.map((s) => [s.id, s.title]));
|
const sourceMap = new Map(sources.map((s) => [s.id, s.title]));
|
||||||
|
const citeCounter = new Map<number, number>();
|
||||||
|
let citeIndex = 0;
|
||||||
|
|
||||||
return (tree: Root) => {
|
return (tree: Root) => {
|
||||||
visit(tree, "text", (node, index, parent) => {
|
visit(tree, "text", (node, index, parent) => {
|
||||||
@@ -14,8 +16,6 @@ export function rehypeCitations(sources: Array<{ id: number; title: string }>) {
|
|||||||
CITE_RE.lastIndex = 0;
|
CITE_RE.lastIndex = 0;
|
||||||
|
|
||||||
const newNodes: ElementContent[] = [];
|
const newNodes: ElementContent[] = [];
|
||||||
const citeCounter = new Map<number, number>();
|
|
||||||
let citeIndex = 0;
|
|
||||||
let last = 0;
|
let last = 0;
|
||||||
let match: RegExpExecArray | null;
|
let match: RegExpExecArray | null;
|
||||||
|
|
||||||
|
|||||||
@@ -89,10 +89,15 @@ export function useNotebookChat(
|
|||||||
|
|
||||||
const send = async (text: string, selectedSourceIds: number[]) => {
|
const send = async (text: string, selectedSourceIds: number[]) => {
|
||||||
if (thread.isLoading || !token) return;
|
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 =
|
const sourceIds =
|
||||||
selectedSourceIds.length > 0
|
selectedSourceIds.length > 0
|
||||||
? selectedSourceIds
|
? toDocIds(selectedSourceIds)
|
||||||
: sources.map((s) => s.id).filter(Boolean);
|
: sources.map((s) => s.document?.id).filter((id): id is number => id !== undefined);
|
||||||
|
|
||||||
await thread.submit(
|
await thread.submit(
|
||||||
{ messages: [{ type: "human", content: text }] },
|
{ messages: [{ type: "human", content: text }] },
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { MessageList } from "@/components/workspace/messages/message-list";
|
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";
|
import { rehypeCitations } from "@/core/rehype";
|
||||||
|
|
||||||
// ── 来源卡片图标 ────────────────────────────────────────
|
// ── 来源卡片图标 ────────────────────────────────────────
|
||||||
@@ -268,6 +270,8 @@ export default function StudioNotebookDetailPage() {
|
|||||||
const isChatView = activeThreadId !== null || hasMessages;
|
const isChatView = activeThreadId !== null || hasMessages;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<ThreadContext.Provider value={{ thread }}>
|
||||||
|
<ChatBox threadId={chatThreadId ?? ""}>
|
||||||
<div className="relative flex h-full flex-col overflow-hidden bg-background">
|
<div className="relative flex h-full flex-col overflow-hidden bg-background">
|
||||||
{/* 背景渐变(列表视图时显示) */}
|
{/* 背景渐变(列表视图时显示) */}
|
||||||
{!isChatView && (
|
{!isChatView && (
|
||||||
@@ -490,5 +494,7 @@ export default function StudioNotebookDetailPage() {
|
|||||||
onToggle={handleToggleSource}
|
onToggle={handleToggleSource}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</ChatBox>
|
||||||
|
</ThreadContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user