大活
This commit is contained in:
131
art-agent/frontend/src/components/chat/chat-input.tsx
Normal file
131
art-agent/frontend/src/components/chat/chat-input.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
"use client";
|
||||
|
||||
import { useRef, useState, type KeyboardEvent } from "react";
|
||||
|
||||
interface ChatInputProps {
|
||||
onSend: (text: string, refImage: File | null) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export function ChatInput({ onSend, disabled }: ChatInputProps) {
|
||||
const [text, setText] = useState("");
|
||||
const [refImage, setRefImage] = useState<File | null>(null);
|
||||
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleSubmit = () => {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed || disabled) return;
|
||||
onSend(trimmed, refImage);
|
||||
setText("");
|
||||
setRefImage(null);
|
||||
setPreviewUrl(null);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
setRefImage(file);
|
||||
setPreviewUrl(URL.createObjectURL(file));
|
||||
};
|
||||
|
||||
const removeRefImage = () => {
|
||||
setRefImage(null);
|
||||
if (previewUrl) URL.revokeObjectURL(previewUrl);
|
||||
setPreviewUrl(null);
|
||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border-t border-[var(--border)] bg-[var(--bg-secondary)] px-4 py-3">
|
||||
{/* 参考图预览 */}
|
||||
{previewUrl && (
|
||||
<div className="mb-3 flex items-start gap-2">
|
||||
<div className="relative">
|
||||
<img
|
||||
src={previewUrl}
|
||||
alt="参考图"
|
||||
className="w-16 h-16 rounded-lg object-cover border border-[var(--border)]"
|
||||
/>
|
||||
<button
|
||||
onClick={removeRefImage}
|
||||
className="absolute -top-1.5 -right-1.5 w-5 h-5 rounded-full
|
||||
bg-red-500 text-white text-xs flex items-center justify-center
|
||||
hover:bg-red-600 cursor-pointer"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-xs text-[var(--text-secondary)] mt-1">参考图已添加</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-end gap-2">
|
||||
{/* 上传参考图按钮 */}
|
||||
<button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={disabled}
|
||||
className="flex-shrink-0 w-10 h-10 rounded-lg border border-[var(--border)]
|
||||
bg-[var(--bg-tertiary)] text-[var(--text-secondary)]
|
||||
hover:text-[var(--text-primary)] hover:border-[var(--accent)]
|
||||
flex items-center justify-center transition-colors
|
||||
disabled:opacity-50 cursor-pointer"
|
||||
title="上传参考图"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
||||
<circle cx="8.5" cy="8.5" r="1.5" />
|
||||
<polyline points="21 15 16 10 5 21" />
|
||||
</svg>
|
||||
</button>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={handleFileChange}
|
||||
className="hidden"
|
||||
/>
|
||||
|
||||
{/* 文字输入框 */}
|
||||
<textarea
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="描述你想要的美术资源..."
|
||||
disabled={disabled}
|
||||
rows={1}
|
||||
className="flex-1 resize-none rounded-lg border border-[var(--border)]
|
||||
bg-[var(--bg-tertiary)] text-[var(--text-primary)]
|
||||
placeholder:text-[var(--text-secondary)]
|
||||
px-4 py-2.5 text-sm leading-relaxed
|
||||
focus:outline-none focus:border-[var(--accent)]
|
||||
disabled:opacity-50 transition-colors
|
||||
min-h-[42px] max-h-[120px]"
|
||||
style={{ fieldSizing: "content" } as React.CSSProperties}
|
||||
/>
|
||||
|
||||
{/* 发送按钮 */}
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={disabled || !text.trim()}
|
||||
className="flex-shrink-0 w-10 h-10 rounded-lg
|
||||
bg-[var(--accent)] text-white
|
||||
hover:bg-[var(--accent-hover)]
|
||||
flex items-center justify-center transition-colors
|
||||
disabled:opacity-50 cursor-pointer"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user