"use client"; import Image from "next/image"; import React from "react"; import { AnimatePresence, motion } from "framer-motion"; import { Box, Chip, Collapse, FormControl, IconButton, MenuItem, Paper, Select, Stack, TextField, Typography, alpha, useTheme, } from "@mui/material"; import SendRounded from "@mui/icons-material/SendRounded"; import StopRounded from "@mui/icons-material/StopRounded"; import MicRounded from "@mui/icons-material/MicRounded"; import KeyboardArrowDownRounded from "@mui/icons-material/KeyboardArrowDownRounded"; import KeyboardArrowUpRounded from "@mui/icons-material/KeyboardArrowUpRounded"; import BoltRounded from "@mui/icons-material/BoltRounded"; import AutoAwesomeRounded from "@mui/icons-material/AutoAwesomeRounded"; import VerifiedUserRounded from "@mui/icons-material/VerifiedUserRounded"; import AdminPanelSettingsRounded from "@mui/icons-material/AdminPanelSettingsRounded"; import WarningAmberRounded from "@mui/icons-material/WarningAmberRounded"; import type { AgentRuntimeState } from "@/lib/agentRuntime"; import type { AgentModelOption } from "@/lib/chatModels"; import type { AgentApprovalMode, AgentModel } from "@/lib/chatStream"; export type AgentComposerHandle = { focus: () => void; clear: () => void; append: (text: string) => void; setValue: (value: string) => void; getValue: () => string; }; type AgentComposerProps = { isHydrating?: boolean; runtimeState?: AgentRuntimeState; isStreaming: boolean; isListening: boolean; isSttSupported: boolean; presets: string[]; onSend: (prompt: string) => void; onAbort: () => void; onStartListening: () => void; onStopListening: () => void; modelOptions: AgentModelOption[]; selectedModel?: AgentModel; onModelChange: (model: AgentModel) => void; approvalMode: AgentApprovalMode; onApprovalModeChange: (mode: AgentApprovalMode) => void; }; const approvalModeOptions: ReadonlyArray<{ value: AgentApprovalMode; label: string; description: string; icon: React.ElementType; }> = [ { value: "request", label: "请求批准", description: "白名单外的工具权限逐次确认", icon: VerifiedUserRounded, }, { value: "auto", label: "自动批准", description: "低风险自动批准,其余仍需确认", icon: AdminPanelSettingsRounded, }, { value: "always", label: "始终允许", description: "除明确禁止项外自动放行", icon: WarningAmberRounded, }, ]; const getApprovalModeOption = (value: AgentApprovalMode) => approvalModeOptions.find((option) => option.value === value) ?? approvalModeOptions[0]; const renderModelIcon = ( icon: AgentModelOption["icon"] | undefined, props?: React.ComponentProps, ) => icon === "bolt" ? ( ) : ( ); export const AgentComposer = React.forwardRef(function AgentComposer({ isHydrating = false, runtimeState = "ready", isStreaming, isListening, isSttSupported, presets, onSend, onAbort, onStartListening, onStopListening, modelOptions, selectedModel, onModelChange, approvalMode, onApprovalModeChange, }, ref) { const theme = useTheme(); const inputRef = React.useRef(null); const [input, setInput] = React.useState(""); const [isPresetOpen, setIsPresetOpen] = React.useState(false); const isRuntimeReady = runtimeState === "ready"; const canSend = input.trim().length > 0 && !isStreaming && !isHydrating && isRuntimeReady; const placeholder = isHydrating ? "正在加载对话记录..." : runtimeState === "checking" ? "正在连接 Agent 服务..." : runtimeState === "models_unavailable" ? "模型尚未加载,暂时无法发送消息" : runtimeState === "unavailable" ? "Agent 服务未就绪,暂时无法发送消息" : "描述你的分析目标,或点击上方指令库..."; const selectedModelOption = modelOptions.find((model) => model.id === selectedModel); const selectedApprovalModeOption = getApprovalModeOption(approvalMode); 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 || !isRuntimeReady) return; setInput(""); onSend(prompt); }, [input, isHydrating, isRuntimeReady, isStreaming, onSend]); return ( 管网分析快捷指令 setIsPresetOpen((value) => !value)} aria-label={isPresetOpen ? "收起常用管网任务" : "展开常用管网任务"} sx={{ width: 28, height: 28, color: "text.secondary", bgcolor: alpha("#fff", 0.5) }} > {isPresetOpen ? ( ) : ( )} {presets.map((prompt) => ( { setInput(prompt); setIsPresetOpen(false); window.setTimeout(() => { inputRef.current?.focus(); }, 0); }} sx={{ height: 32, borderRadius: "16px", bgcolor: alpha("#fff", 0.7), border: `1px solid ${alpha("#00acc1", 0.15)}`, color: "text.primary", fontWeight: 600, fontSize: '0.85rem', boxShadow: `0 2px 6px ${alpha("#000", 0.03)}`, backdropFilter: "blur(10px)", "&:hover": { bgcolor: alpha("#fff", 0.95), boxShadow: `0 4px 10px ${alpha("#00acc1", 0.2)}`, borderColor: alpha("#00acc1", 0.4), color: "#00acc1" } }} /> ))} setInput(event.target.value)} onKeyDown={(event) => { if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); handleSend(); } }} placeholder={placeholder} fullWidth multiline maxRows={5} variant="standard" disabled={isHydrating || !isRuntimeReady} InputProps={{ disableUnderline: true, sx: { px: 1, py: 0.5, fontSize: "1rem", lineHeight: 1.6, fontWeight: 500, color: "text.primary" }, }} /> {isSttSupported ? ( isListening ? ( ) : ( ) ) : null} {isStreaming ? ( ) : ( )} DeepSeek Powered by DeepSeek V4 · TJWater Agent Intelligence ); });