feat: 前端输入框体验优化

- AI 响应完成后自动聚焦输入框
- 输入框和发送按钮居中对齐
- 隐藏输入框滚动条
- 新建话题无需输入名称,自动生成默认标题
This commit is contained in:
ooodc 2026-05-29 23:12:53 +08:00
parent 06756a4816
commit 3d9c981c2a
5 changed files with 32 additions and 13 deletions

View File

@ -142,12 +142,9 @@ function App() {
return
}
const title = prompt('Enter topic title:')
if (title) {
const cmd = createTopic(title)
const cmd = createTopic()
handleCommand(cmd)
sendMessage({ type: 'command', payload: JSON.stringify(cmd) })
}
}, [sendMessage, handleCommand, createTopic, sessionId, isReadOnly])
const handleSwitchTopic = useCallback(

View File

@ -27,6 +27,7 @@ export function ChatContainer({
<MessageInput
onSend={onSendMessage}
disabled={isLoading}
isLoading={isLoading}
isReadOnly={isReadOnly}
channelName={channelName}
/>

View File

@ -4,6 +4,7 @@ import { useState, useRef, useEffect } from 'react'
interface MessageInputProps {
onSend: (content: string) => void
disabled?: boolean
isLoading?: boolean
placeholder?: string
isReadOnly?: boolean
channelName?: string
@ -12,12 +13,14 @@ interface MessageInputProps {
export function MessageInput({
onSend,
disabled = false,
isLoading = false,
placeholder = '输入消息...按 / 查看命令',
isReadOnly = false,
channelName,
}: MessageInputProps) {
const [content, setContent] = useState('')
const textareaRef = useRef<HTMLTextAreaElement>(null)
const wasLoadingRef = useRef(false)
useEffect(() => {
const textarea = textareaRef.current
@ -27,6 +30,14 @@ export function MessageInput({
}
}, [content])
// 当 isLoading 从 true 变为 false 时,自动聚焦输入框
useEffect(() => {
if (wasLoadingRef.current && !isLoading && !isReadOnly) {
textareaRef.current?.focus()
}
wasLoadingRef.current = isLoading
}, [isLoading, isReadOnly])
const handleSend = () => {
if (content.trim() && !disabled && !isReadOnly) {
onSend(content.trim())
@ -74,8 +85,8 @@ export function MessageInput({
return (
<div className="border-t border-white/8 bg-[#12121a]/80 backdrop-blur-md p-4">
<div className="flex gap-3 items-end max-w-5xl mx-auto">
<div className="flex-1 relative">
<div className="flex gap-3 items-center max-w-5xl mx-auto">
<div className="flex-1 relative flex items-center">
<textarea
ref={textareaRef}
value={content}
@ -84,14 +95,14 @@ export function MessageInput({
placeholder={placeholder}
disabled={disabled}
rows={1}
className="w-full resize-none rounded-xl border border-white/10 bg-[#1a1a25] px-4 py-3 pr-12 text-sm text-white placeholder:text-zinc-500 focus:border-[#00f0ff]/50 focus:outline-none focus:ring-1 focus:ring-[#00f0ff]/20 disabled:opacity-50 transition-all"
className="w-full resize-none rounded-xl border border-white/10 bg-[#1a1a25] px-4 py-3 pr-12 text-sm text-white placeholder:text-zinc-500 focus:border-[#00f0ff]/50 focus:outline-none focus:ring-1 focus:ring-[#00f0ff]/20 disabled:opacity-50 transition-all self-center scrollbar-hide"
/>
<Sparkles className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-zinc-600 pointer-events-none" />
</div>
<button
onClick={handleSend}
disabled={disabled || !content.trim()}
className="flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-r from-[#00f0ff] to-[#3b82f6] text-white shadow-lg shadow-[#00f0ff]/20 hover:shadow-xl hover:shadow-[#00f0ff]/30 hover:scale-105 disabled:opacity-50 disabled:hover:scale-100 disabled:cursor-not-allowed transition-all"
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-gradient-to-r from-[#00f0ff] to-[#3b82f6] text-white shadow-lg shadow-[#00f0ff]/20 hover:shadow-xl hover:shadow-[#00f0ff]/30 hover:scale-105 disabled:opacity-50 disabled:hover:scale-100 disabled:cursor-not-allowed transition-all"
>
{disabled ? (
<Loader2 className="h-4 w-4 animate-spin" />

View File

@ -47,7 +47,7 @@ interface UseChatReturn {
// Topic 方法
selectTopic: (topicId: string) => void
createTopic: (title: string) => Command
createTopic: (title?: string) => Command
switchTopic: (topicId: string) => Command
// 初始化方法
@ -425,10 +425,10 @@ export function useChat(): UseChatReturn {
setMessages([])
}, [])
const createTopic = useCallback((title: string): Command => {
const createTopic = useCallback((title?: string): Command => {
return {
type: 'create_session',
title,
title: title || `话题 ${new Date().toLocaleString('zh-CN', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}`,
}
}, [])

View File

@ -45,6 +45,16 @@
background: var(--accent-blue);
}
/* Hide scrollbar utility */
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
/* Base styles */
* {
box-sizing: border-box;