Explorar o código

feat(ui): ChatInput 改为圆角容器+底部工具栏布局

Mochocyang hai 3 meses
pai
achega
5e565b3562
Modificáronse 1 ficheiros con 48 adicións e 49 borrados
  1. 48 49
      src/components/chat/chat-input.tsx

+ 48 - 49
src/components/chat/chat-input.tsx

@@ -1,5 +1,5 @@
 import { useRef, useState, useCallback, useEffect, type ReactNode } from "react"
 import { useRef, useState, useCallback, useEffect, type ReactNode } from "react"
-import { Send, Square } from "lucide-react"
+import { ArrowUp, Square } from "lucide-react"
 import { Button } from "@/components/ui/button"
 import { Button } from "@/components/ui/button"
 import { isImeComposing } from "@/lib/keyboard-utils"
 import { isImeComposing } from "@/lib/keyboard-utils"
 import { useChatStore } from "@/stores/chat-store"
 import { useChatStore } from "@/stores/chat-store"
@@ -37,12 +37,11 @@ interface ChatInputProps {
   placeholder?: string
   placeholder?: string
   leadingControls?: ReactNode
   leadingControls?: ReactNode
   footerControls?: ReactNode
   footerControls?: ReactNode
-  inlineSendButton?: boolean
   value?: string
   value?: string
   onChange?: (value: string) => void
   onChange?: (value: string) => void
 }
 }
 
 
-export function ChatInput({ onSend, onStop, isStreaming, placeholder, leadingControls, footerControls, inlineSendButton = true, value: controlledValue, onChange }: ChatInputProps) {
+export function ChatInput({ onSend, onStop, isStreaming, placeholder, leadingControls, footerControls, value: controlledValue, onChange }: ChatInputProps) {
   const activeConversationId = useChatStore((state) => state.activeConversationId)
   const activeConversationId = useChatStore((state) => state.activeConversationId)
   const setConversationInputDraft = useChatStore((state) => state.setConversationInputDraft)
   const setConversationInputDraft = useChatStore((state) => state.setConversationInputDraft)
   const conversation = useChatStore((state) =>
   const conversation = useChatStore((state) =>
@@ -297,7 +296,7 @@ export function ChatInput({ onSend, onStop, isStreaming, placeholder, leadingCon
       : "拖动调整输入框高度,双击重置"
       : "拖动调整输入框高度,双击重置"
 
 
   return (
   return (
-    <div ref={rootRef} className="border-t">
+    <div ref={rootRef} className="border-t p-3">
       <div
       <div
         role="separator"
         role="separator"
         aria-label="拖动调整输入框高度"
         aria-label="拖动调整输入框高度"
@@ -308,53 +307,53 @@ export function ChatInput({ onSend, onStop, isStreaming, placeholder, leadingCon
       >
       >
         <span className={`h-0.5 w-10 rounded-full transition-colors ${handleBarColor}`} />
         <span className={`h-0.5 w-10 rounded-full transition-colors ${handleBarColor}`} />
       </div>
       </div>
-      {leadingControls ? (
-        <div className="px-3 pb-2">
-          {leadingControls}
+      <div className="rounded-2xl border bg-background shadow-sm overflow-hidden flex flex-col">
+        <div className="px-4 pt-3">
+          <textarea
+            ref={textareaRef}
+            value={value}
+            dir="auto"
+            onChange={handleInput}
+            onKeyDown={handleKeyDown}
+            placeholder={placeholder ?? "输入消息,Enter 发送,Shift+Enter 换行"}
+            disabled={isStreaming}
+            rows={1}
+            className="w-full resize-none bg-transparent px-0 py-2 text-sm placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
+            style={{ height: inputHeight, maxHeight: inputHeight, overflowY: "auto" }}
+          />
         </div>
         </div>
-      ) : null}
-      <div className="flex items-end gap-2 px-3 pb-3">
-        <textarea
-          ref={textareaRef}
-          value={value}
-          dir="auto"
-          onChange={handleInput}
-          onKeyDown={handleKeyDown}
-          placeholder={placeholder ?? "输入消息,Enter 发送,Shift+Enter 换行"}
-          disabled={isStreaming}
-          rows={1}
-          className="flex-1 resize-none rounded-md border bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
-          style={{ height: inputHeight, maxHeight: inputHeight, overflowY: "auto" }}
-        />
-        {inlineSendButton && (isStreaming ? (
-          <Button
-            variant="destructive"
-            size="icon"
-            onClick={onStop}
-            className="shrink-0"
-            title="停止生成"
-            aria-label="停止生成"
-          >
-            <Square className="h-4 w-4" />
-          </Button>
-        ) : (
-          <Button
-            size="icon"
-            onClick={handleSend}
-            disabled={!value.trim()}
-            className="shrink-0"
-            title="发送消息"
-            aria-label="发送消息"
-          >
-            <Send className="h-4 w-4" />
-          </Button>
-        ))}
-      </div>
-      {footerControls ? (
-        <div className="px-3 pb-2">
-          {footerControls}
+        <div className="flex items-center justify-between border-t px-3 py-2.5 gap-2">
+          {(leadingControls || footerControls) ? (
+            <div className="flex items-center gap-2 min-w-0 overflow-x-auto">
+              {leadingControls}
+              {footerControls}
+            </div>
+          ) : null}
+          {isStreaming ? (
+            <Button
+              variant="destructive"
+              size="icon"
+              onClick={onStop}
+              className="shrink-0"
+              title="停止生成"
+              aria-label="停止生成"
+            >
+              <Square className="h-4 w-4" />
+            </Button>
+          ) : (
+            <Button
+              size="icon"
+              onClick={handleSend}
+              disabled={!value.trim()}
+              className="shrink-0"
+              title="发送消息"
+              aria-label="发送消息"
+            >
+              <ArrowUp className="h-4 w-4" />
+            </Button>
+          )}
         </div>
         </div>
-      ) : null}
+      </div>
     </div>
     </div>
   )
   )
 }
 }