输入框状态剥离,避免受长信息列表渲染影响;覆写滚动条状态动作,不再强制拉到最底

This commit is contained in:
2026-06-03 15:01:24 +08:00
parent 888132a60f
commit fa3e6b6e84
3 changed files with 90 additions and 47 deletions
+41 -17
View File
@@ -28,44 +28,65 @@ import BoltRounded from "@mui/icons-material/BoltRounded";
import AutoAwesomeRounded from "@mui/icons-material/AutoAwesomeRounded";
import type { AgentModel } from "@/lib/chatStream";
export type AgentComposerHandle = {
focus: () => void;
clear: () => void;
append: (text: string) => void;
setValue: (value: string) => void;
getValue: () => string;
};
type AgentComposerProps = {
input: string;
inputRef: React.RefObject<HTMLInputElement | null>;
isHydrating?: boolean;
isStreaming: boolean;
isListening: boolean;
isSttSupported: boolean;
presets: string[];
onInputChange: (value: string) => void;
onSend: () => void;
onSend: (prompt: string) => void;
onAbort: () => void;
onStartListening: () => void;
onStopListening: () => void;
onPresetSelect: (prompt: string) => void;
selectedModel: AgentModel;
onModelChange: (model: AgentModel) => void;
};
export const AgentComposer = ({
input,
inputRef,
export const AgentComposer = React.forwardRef<AgentComposerHandle, AgentComposerProps>(function AgentComposer({
isHydrating = false,
isStreaming,
isListening,
isSttSupported,
presets,
onInputChange,
onSend,
onAbort,
onStartListening,
onStopListening,
onPresetSelect,
selectedModel,
onModelChange,
}: AgentComposerProps) => {
}, ref) {
const theme = useTheme();
const canSend = input.trim().length > 0 && !isStreaming && !isHydrating;
const inputRef = React.useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);
const [input, setInput] = React.useState("");
const [isPresetOpen, setIsPresetOpen] = React.useState(false);
const canSend = input.trim().length > 0 && !isStreaming && !isHydrating;
React.useImperativeHandle(
ref,
() => ({
focus: () => inputRef.current?.focus(),
clear: () => setInput(""),
append: (text: string) => setInput((prev) => prev + text),
setValue: (value: string) => setInput(value),
getValue: () => input,
}),
[input],
);
const handleSend = React.useCallback(() => {
const prompt = input.trim();
if (!prompt || isStreaming || isHydrating) return;
setInput("");
onSend(prompt);
}, [input, isHydrating, isStreaming, onSend]);
return (
<Box sx={{ px: 2, pb: 2, pt: 1, zIndex: 10 }}>
@@ -121,8 +142,11 @@ export const AgentComposer = ({
size="medium"
clickable
onClick={() => {
onPresetSelect(prompt);
setInput(prompt);
setIsPresetOpen(false);
window.setTimeout(() => {
inputRef.current?.focus();
}, 0);
}}
sx={{
height: 32,
@@ -165,11 +189,11 @@ export const AgentComposer = ({
<TextField
inputRef={inputRef}
value={input}
onChange={(event) => onInputChange(event.target.value)}
onChange={(event) => setInput(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
onSend();
handleSend();
}
}}
placeholder={isHydrating ? "正在加载对话记录..." : "描述你的分析目标,或点击上方指令库..."}
@@ -362,7 +386,7 @@ export const AgentComposer = ({
<motion.div key="send" initial={{ scale: 0 }} animate={{ scale: 1 }} exit={{ scale: 0 }}>
<IconButton
disabled={!canSend}
onClick={onSend}
onClick={handleSend}
aria-label="发送"
size="small"
sx={{
@@ -397,4 +421,4 @@ export const AgentComposer = ({
</Box>
</Box>
);
};
});