重构组件,优化性能并移除不必要的属性;撤销滚动条修改;
This commit is contained in:
@@ -85,7 +85,12 @@ const formatToolTitle = (item: ChatProgress) => {
|
|||||||
return item.title;
|
return item.title;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AgentProgressTimeline = ({ progress, isAborted }: { progress: ChatProgress[], isAborted?: boolean }) => {
|
type AgentProgressTimelineProps = {
|
||||||
|
progress: ChatProgress[];
|
||||||
|
isAborted?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AgentProgressTimelineInner = ({ progress, isAborted }: AgentProgressTimelineProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [nowMs, setNowMs] = useState(() => Date.now());
|
const [nowMs, setNowMs] = useState(() => Date.now());
|
||||||
|
|
||||||
@@ -356,3 +361,12 @@ export const AgentProgressTimeline = ({ progress, isAborted }: { progress: ChatP
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const AgentProgressTimeline = React.memo(
|
||||||
|
AgentProgressTimelineInner,
|
||||||
|
(prevProps, nextProps) =>
|
||||||
|
prevProps.progress === nextProps.progress &&
|
||||||
|
prevProps.isAborted === nextProps.isAborted,
|
||||||
|
);
|
||||||
|
|
||||||
|
AgentProgressTimeline.displayName = "AgentProgressTimeline";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import React from "react";
|
import React, { useMemo } from "react";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
@@ -85,15 +85,21 @@ export const AgentTurn = React.memo(
|
|||||||
const [editDraft, setEditDraft] = React.useState(message.content);
|
const [editDraft, setEditDraft] = React.useState(message.content);
|
||||||
const rootMessageId = message.branchRootId ?? message.id;
|
const rootMessageId = message.branchRootId ?? message.id;
|
||||||
|
|
||||||
const parsedAssistantSections =
|
const parsedAssistantSections = useMemo(
|
||||||
!isUser && !isErrorMessage
|
() =>
|
||||||
? parseAssistantMessageSections(message.content)
|
!isUser && !isErrorMessage
|
||||||
: null;
|
? parseAssistantMessageSections(message.content)
|
||||||
|
: null,
|
||||||
|
[isErrorMessage, isUser, message.content],
|
||||||
|
);
|
||||||
const answerContent = parsedAssistantSections?.answer ?? message.content;
|
const answerContent = parsedAssistantSections?.answer ?? message.content;
|
||||||
const contentSegments: ContentSegment[] =
|
const contentSegments: ContentSegment[] = useMemo(
|
||||||
!isUser && !isErrorMessage
|
() =>
|
||||||
? parseContentWithToolCalls(answerContent).segments
|
!isUser && !isErrorMessage
|
||||||
: [{ type: "text", content: answerContent }];
|
? parseContentWithToolCalls(answerContent).segments
|
||||||
|
: [{ type: "text", content: answerContent }],
|
||||||
|
[answerContent, isErrorMessage, isUser],
|
||||||
|
);
|
||||||
|
|
||||||
if (isUser) {
|
if (isUser) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ type AgentWorkspaceProps = {
|
|||||||
branchGroups: BranchGroup[];
|
branchGroups: BranchGroup[];
|
||||||
branchTransition: BranchTransition | null;
|
branchTransition: BranchTransition | null;
|
||||||
isStreaming: boolean;
|
isStreaming: boolean;
|
||||||
scrollContainerRef: React.RefObject<HTMLDivElement | null>;
|
|
||||||
onScroll: React.UIEventHandler<HTMLDivElement>;
|
|
||||||
bottomRef: React.RefObject<HTMLDivElement | null>;
|
bottomRef: React.RefObject<HTMLDivElement | null>;
|
||||||
speakingMessageId: string | null;
|
speakingMessageId: string | null;
|
||||||
speechState: SpeechState;
|
speechState: SpeechState;
|
||||||
@@ -157,8 +155,6 @@ export const AgentWorkspace = ({
|
|||||||
branchGroups,
|
branchGroups,
|
||||||
branchTransition,
|
branchTransition,
|
||||||
isStreaming,
|
isStreaming,
|
||||||
scrollContainerRef,
|
|
||||||
onScroll,
|
|
||||||
bottomRef,
|
bottomRef,
|
||||||
speakingMessageId,
|
speakingMessageId,
|
||||||
speechState,
|
speechState,
|
||||||
@@ -220,8 +216,6 @@ export const AgentWorkspace = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
ref={scrollContainerRef}
|
|
||||||
onScroll={onScroll}
|
|
||||||
sx={{
|
sx={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
|
|||||||
@@ -30,10 +30,8 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const bottomRef = useRef<HTMLDivElement>(null);
|
const bottomRef = useRef<HTMLDivElement>(null);
|
||||||
const workspaceRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const composerRef = useRef<AgentComposerHandle | null>(null);
|
const composerRef = useRef<AgentComposerHandle | null>(null);
|
||||||
const hasResetForOpenRef = useRef(false);
|
const hasResetForOpenRef = useRef(false);
|
||||||
const shouldAutoScrollRef = useRef(true);
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const currentProjectId = useProjectStore((state) => state.currentProjectId);
|
const currentProjectId = useProjectStore((state) => state.currentProjectId);
|
||||||
|
|
||||||
@@ -84,20 +82,11 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
|
|||||||
getModel: () => selectedModel,
|
getModel: () => selectedModel,
|
||||||
});
|
});
|
||||||
|
|
||||||
const syncAutoScrollState = useCallback(() => {
|
|
||||||
const container = workspaceRef.current;
|
|
||||||
if (!container) return;
|
|
||||||
const distanceFromBottom =
|
|
||||||
container.scrollHeight - container.scrollTop - container.clientHeight;
|
|
||||||
shouldAutoScrollRef.current = distanceFromBottom <= 120;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const scrollToBottom = useCallback((behavior: ScrollBehavior = "smooth") => {
|
const scrollToBottom = useCallback((behavior: ScrollBehavior = "smooth") => {
|
||||||
bottomRef.current?.scrollIntoView({ behavior });
|
bottomRef.current?.scrollIntoView({ behavior });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!shouldAutoScrollRef.current) return;
|
|
||||||
scrollToBottom(isStreaming ? "auto" : "smooth");
|
scrollToBottom(isStreaming ? "auto" : "smooth");
|
||||||
}, [isStreaming, messages, scrollToBottom]);
|
}, [isStreaming, messages, scrollToBottom]);
|
||||||
|
|
||||||
@@ -110,7 +99,6 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
|
|||||||
hasResetForOpenRef.current = true;
|
hasResetForOpenRef.current = true;
|
||||||
|
|
||||||
const timer = window.setTimeout(() => {
|
const timer = window.setTimeout(() => {
|
||||||
shouldAutoScrollRef.current = true;
|
|
||||||
createSession();
|
createSession();
|
||||||
composerRef.current?.clear();
|
composerRef.current?.clear();
|
||||||
setIsHistoryOpen(false);
|
setIsHistoryOpen(false);
|
||||||
@@ -122,14 +110,12 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
|
|||||||
|
|
||||||
const handleSend = useCallback((prompt: string) => {
|
const handleSend = useCallback((prompt: string) => {
|
||||||
if (isStreaming) return;
|
if (isStreaming) return;
|
||||||
shouldAutoScrollRef.current = true;
|
|
||||||
void sendPrompt(prompt);
|
void sendPrompt(prompt);
|
||||||
}, [isStreaming, sendPrompt]);
|
}, [isStreaming, sendPrompt]);
|
||||||
|
|
||||||
const handleNewConversation = useCallback(() => {
|
const handleNewConversation = useCallback(() => {
|
||||||
handleStopSpeech();
|
handleStopSpeech();
|
||||||
stopListening();
|
stopListening();
|
||||||
shouldAutoScrollRef.current = true;
|
|
||||||
createSession();
|
createSession();
|
||||||
composerRef.current?.clear();
|
composerRef.current?.clear();
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
@@ -144,17 +130,12 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
|
|||||||
|
|
||||||
const handleSelectSession = useCallback(
|
const handleSelectSession = useCallback(
|
||||||
(storageSessionId: string) => {
|
(storageSessionId: string) => {
|
||||||
shouldAutoScrollRef.current = true;
|
|
||||||
composerRef.current?.clear();
|
composerRef.current?.clear();
|
||||||
void switchSession(storageSessionId);
|
void switchSession(storageSessionId);
|
||||||
},
|
},
|
||||||
[switchSession],
|
[switchSession],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleWorkspaceScroll = useCallback(() => {
|
|
||||||
syncAutoScrollState();
|
|
||||||
}, [syncAutoScrollState]);
|
|
||||||
|
|
||||||
const handleDeleteSession = useCallback(
|
const handleDeleteSession = useCallback(
|
||||||
(storageSessionId: string) => {
|
(storageSessionId: string) => {
|
||||||
void removeSession(storageSessionId);
|
void removeSession(storageSessionId);
|
||||||
@@ -334,8 +315,6 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
|
|||||||
branchGroups={branchGroups}
|
branchGroups={branchGroups}
|
||||||
branchTransition={branchTransition}
|
branchTransition={branchTransition}
|
||||||
isStreaming={isStreaming}
|
isStreaming={isStreaming}
|
||||||
scrollContainerRef={workspaceRef}
|
|
||||||
onScroll={handleWorkspaceScroll}
|
|
||||||
bottomRef={bottomRef}
|
bottomRef={bottomRef}
|
||||||
speakingMessageId={speakingMessageId}
|
speakingMessageId={speakingMessageId}
|
||||||
speechState={speechState}
|
speechState={speechState}
|
||||||
|
|||||||
Reference in New Issue
Block a user