import React, { forwardRef, useCallback } from 'react' import type { ReactNode } from 'react' import { cva } from 'class-variance-authority' import { cn } from '@/lib/utils' import { SendButton } from '../send-button' import { IconButton } from '../../basic/icon-button' import { MoreLine } from '../../basic/icons-inline' import { GenerationStatusBar } from '../generation-status-bar' import { ChatInputTextarea } from './chat-input-textarea' import { ChatInputFolderSelector } from './chat-input-folder-selector' import { ChatInputModelSwitcher } from './chat-input-model-switcher' import { useChatInputContext } from './context' const chatInputContainerVariants = cva( [ 'flex flex-col w-full bg-bg-base border border-primary-border rounded-xl transition-all duration-200', /* 参考 Claude 输入框:2 级阴影默认,4 级 focus,使用本项目 1~4 级 shadow token */ 'shadow-[0_0.25rem_1.25rem_var(--color-shadow-secondary)]', 'hover:shadow-[0_0.25rem_1.25rem_var(--color-shadow-secondary)]', 'focus-within:shadow-[0_0.25rem_1.25rem_var(--color-shadow-quaternary)]', 'hover:focus-within:shadow-[0_0.25rem_1.25rem_var(--color-shadow-quaternary)]', ].join(' '), { variants: { disabled: { true: 'opacity-50 cursor-not-allowed', false: '' } }, defaultVariants: { disabled: false }, } ) // Box 使用 rounded-xl (= --radius-xl) const attachmentRowStyles = 'flex flex-wrap items-center gap-2 px-3 pt-3 pb-2' const contentStyles = 'flex flex-1 items-center min-h-6 p-3' const footerStyles = 'flex shrink-0 items-center justify-between w-full self-stretch gap-2 p-3' // ----- 框外 ----- // Above 高度 = 主题下 md 控件高度;宽度 = Box 宽度 - 2*圆角,居中;无水平 padding export interface ChatInputAboveProps { className?: string style?: React.CSSProperties children?: ReactNode } export function ChatInputAbove({ className, style, children }: ChatInputAboveProps) { return (
{children}
) } ChatInputAbove.displayName = 'ChatInputAbove' // ----- 框容器 ----- export interface ChatInputBoxProps { className?: string children?: ReactNode } export function ChatInputBox({ className, children }: ChatInputBoxProps) { const ctx = useChatInputContext() return (
{children}
) } ChatInputBox.displayName = 'ChatInputBox' // ----- 框内·第一层 附件区 ----- export interface ChatInputAttachmentsProps { className?: string children?: ReactNode } export function ChatInputAttachments({ className, children }: ChatInputAttachmentsProps) { if (!children) return null return
{children}
} ChatInputAttachments.displayName = 'ChatInputAttachments' // ----- 框内·第二层 输入区 ----- export interface ChatInputInputProps extends Omit, 'value' | 'onChange' | 'onKeyDown'> { placeholder?: string maxRows?: number } export const ChatInputInput = forwardRef( ({ placeholder, maxRows, ...props }, ref) => { const ctx = useChatInputContext() const setRef = useCallback( (node: HTMLTextAreaElement | null) => { ctx.setTextareaRef(node) if (typeof ref === 'function') ref(node) else if (ref) (ref as React.MutableRefObject).current = node }, [ctx, ref] ) return (
) } ) ChatInputInput.displayName = 'ChatInputInput' // ----- 框内·第三层 操作区 ----- export interface ChatInputActionsProps { className?: string children?: ReactNode } export function ChatInputActions({ className, children }: ChatInputActionsProps) { return
{children}
} ChatInputActions.displayName = 'ChatInputActions' export interface ChatInputActionsLeftProps { className?: string children?: ReactNode } export function ChatInputActionsLeft({ className, children }: ChatInputActionsLeftProps) { return
{children}
} ChatInputActionsLeft.displayName = 'ChatInputActionsLeft' export interface ChatInputActionsRightProps { className?: string children?: ReactNode } export function ChatInputActionsRight({ className, children }: ChatInputActionsRightProps) { return
{children}
} ChatInputActionsRight.displayName = 'ChatInputActionsRight' // ----- 固定·框内右上角放大按钮 ----- export interface ChatInputExpandButtonProps extends Omit, 'children'> { children?: ReactNode } export function ChatInputExpandButton({ className, style, ...props }: ChatInputExpandButtonProps) { return (