import React, { createContext, useContext, useCallback, useRef, useState } from 'react' import type { ReactNode } from 'react' import type { ChatInputProps, FolderPermissionConfig, GenerationStatusConfig, FooterLeftModelSwitchConfig } from './types' export interface ChatInputContextValue { // Portal 主题透传(供 DropdownMenuContent/SubContent 等弹层继承) dataStyle?: string dataTheme?: string // 输入与发送 value: string onChange: (value: string) => void onSend?: (value: string) => void disabled: boolean placeholder: string maxRows: number form?: string textareaRef: React.RefObject setTextareaRef: (node: HTMLTextAreaElement | null) => void handleSend: () => void handleKeyDown: (e: React.KeyboardEvent) => void sendButtonStatus: 'normal' | 'generating' canSend: boolean // 文件夹 / 更多 / 模式切换(默认操作区左侧) hasFolderSelected: boolean setHasFolderSelected: (v: boolean) => void handleFolderSelect: (files: FileList) => void handleFolderClear: () => void folderPermission?: FolderPermissionConfig footerLeftConfig?: FooterLeftModelSwitchConfig showFolderButton: boolean showMoreButton: boolean folderButtonLabel: string onFolderButtonClick?: () => void onFolderClear?: () => void onMoreButtonClick?: () => void addLargeIcon?: ReactNode folderIcon?: ReactNode folderOpenIcon?: ReactNode historyIcon?: ReactNode arrowDownIcon?: ReactNode subMenuArrowIcon?: ReactNode folderFillIcon?: ReactNode closeIcon?: ReactNode // 框外 / 框内显隐(默认布局用) showGenerationStatus?: boolean generationStatus?: GenerationStatusConfig attachments?: { files?: ReactNode; images?: ReactNode } | ReactNode showFooter?: boolean } const ChatInputContext = createContext(null) export function useChatInputContext() { const ctx = useContext(ChatInputContext) if (!ctx) throw new Error('ChatInput compound components must be used within ChatInput.Root') return ctx } export type ChatInputRootProps = ChatInputProps & { children?: ReactNode } export function ChatInputRootProvider({ value: controlledValue, onChange, onSend, placeholder = 'What can I help you with today?', disabled = false, maxRows = 6, form, sendButtonStatus = 'normal', showFooter = true, showFolderButton = true, folderButtonLabel = 'Work in a Folder', onFolderButtonClick, onFolderClear, folderPermission, showMoreButton = true, onMoreButtonClick, footerLeftConfig, showGenerationStatus = false, generationStatus, attachments, addLargeIcon, folderIcon, folderOpenIcon, historyIcon, arrowDownIcon, subMenuArrowIcon, folderFillIcon, closeIcon, dataStyle, dataTheme, children, ...rest }: ChatInputRootProps) { const [internalValue, setInternalValue] = useState('') const textareaRef = useRef(null) const [hasFolderSelected, setHasFolderSelected] = useState(false) const isControlled = controlledValue !== undefined const value = isControlled ? controlledValue : internalValue const setTextareaRef = useCallback((node: HTMLTextAreaElement | null) => { (textareaRef as React.MutableRefObject).current = node }, []) const handleChange = useCallback( (v: string) => { if (!isControlled) setInternalValue(v) onChange?.(v) }, [isControlled, onChange] ) const handleSend = useCallback(() => { const raw = textareaRef.current?.value ?? value const trimmed = (raw ?? '').trim() if (!trimmed || disabled || !onSend) return onSend(trimmed) if (!isControlled) setInternalValue('') if (textareaRef.current) textareaRef.current.style.height = 'auto' }, [value, disabled, onSend, isControlled]) const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) { e.preventDefault() handleSend() } rest.onKeyDown?.(e) }, [handleSend, rest] ) const handleFolderSelect = useCallback(() => setHasFolderSelected(true), []) const handleFolderClear = useCallback(() => { setHasFolderSelected(false) onFolderClear?.() }, [onFolderClear]) const canSend = value.trim().length > 0 && !disabled const ctxValue: ChatInputContextValue = { dataStyle, dataTheme, value, onChange: handleChange, onSend, disabled, placeholder, maxRows, form, textareaRef, setTextareaRef, handleSend, handleKeyDown, sendButtonStatus, canSend, hasFolderSelected, setHasFolderSelected, handleFolderSelect, handleFolderClear, folderPermission, footerLeftConfig, showFolderButton, showMoreButton, folderButtonLabel, onFolderButtonClick, onFolderClear, onMoreButtonClick, addLargeIcon, folderIcon, folderOpenIcon, historyIcon, arrowDownIcon, subMenuArrowIcon, folderFillIcon, closeIcon, showGenerationStatus, generationStatus, attachments, showFooter, } return ( {children} ) } ChatInputRootProvider.displayName = 'ChatInputRootProvider' export { ChatInputContext }