From 4ce800b82b529c7940abf7a5d0a36e1fb781dffb Mon Sep 17 00:00:00 2001 From: qixinbo Date: Sun, 15 Mar 2026 22:16:04 +0800 Subject: [PATCH] polish chat input --- frontend/src/components/ChatInterface.tsx | 501 +++++++++++++++------- 1 file changed, 355 insertions(+), 146 deletions(-) diff --git a/frontend/src/components/ChatInterface.tsx b/frontend/src/components/ChatInterface.tsx index 5845ca7..c822658 100644 --- a/frontend/src/components/ChatInterface.tsx +++ b/frontend/src/components/ChatInterface.tsx @@ -2,7 +2,7 @@ import { useState, useRef, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; -import { User, Loader2, Sparkles, ArrowUp, ChevronDown, Paperclip, Check, X, File as FileIcon, Square } from "lucide-react"; +import { User, Loader2, Sparkles, ArrowUp, ChevronDown, Paperclip, Check, X, File as FileIcon, Square, Plus, Database, Wand2, Search, Zap, LayoutGrid, CheckCircle2, Table, XCircle } from "lucide-react"; import { api } from "@/lib/api"; import { type ChartSpec } from "@/store/visualizationStore"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; @@ -46,6 +46,13 @@ interface DataFileContext { summary?: string; } +interface Skill { + id: string; + name: string; + description?: string; + type: string; +} + interface SessionData { key: string; metadata?: { @@ -63,6 +70,9 @@ export function ChatInterface() { const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); const [selectedDataSource, setSelectedDataSource] = useState("postgres-main"); + const [availableSkills, setAvailableSkills] = useState([]); + const [selectedSkillIds, setSelectedSkillIds] = useState([]); + const [isMenuOpen, setIsMenuOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); const scrollRef = useRef(null); const location = useLocation(); @@ -236,6 +246,51 @@ export function ChatInterface() { } }; + const handleRemoveFile = async () => { + setAttachedFile(null); + setActiveDataFile(null); + if (selectedDataSource.startsWith("upload")) { + setSelectedDataSource("postgres-main"); + } + await syncSessionFileContext(null); + }; + + const renderFileCard = () => { + const file = attachedFile || activeDataFile; + if (!file) return null; + return ( +
+
+
+ + +
+
{file.filename}
+
电子表格
+
+ + + + ); + }; + + useEffect(() => { + const fetchSkills = async () => { + try { + const skills = await api.get("/api/v1/skills"); + setAvailableSkills(skills); + } catch (err) { + console.error("Failed to fetch skills:", err); + } + }; + fetchSkills(); + }, []); + useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollIntoView({ behavior: 'smooth' }); @@ -303,6 +358,7 @@ export function ChatInterface() { message: messagePayload, session_id: activeSessionKey, model_id: effectiveModelId, + skill_ids: selectedSkillIds, source, prefer_sql_chart: preferSqlChart, file_url: fileUrl, @@ -390,6 +446,7 @@ export function ChatInterface() { message: messagePayload, session_id: activeSessionKey, model_id: effectiveModelId, + skill_ids: selectedSkillIds, source, prefer_sql_chart: preferSqlChart, file_url: fileUrl, @@ -427,32 +484,33 @@ export function ChatInterface() { }; return ( -
- {/* Top Bar */} -
+
+ {/* Header with Model Selection */} +
- - {currentModel ? (currentModel.name || currentModel.model) : "选择模型..."} - + + + {selectedModelId ? models.find(m => m.id === selectedModelId)?.name || 'DataClaw' : 'DataClaw'} + + - + - - + + 未找到模型 {models.map((model) => ( { setSelectedModelId(model.id); setModelOpen(false); }} - className="cursor-pointer" + className="flex items-center gap-2 py-2.5 cursor-pointer" >
- {model.name || model.model} + {model.name || model.model} {model.provider}
-
- 数据源 - -
@@ -508,74 +551,151 @@ export function ChatInterface() {
{/* Input Area */} -
-
- {activeDataFile && ( -
-
-
- -
- {activeDataFile.filename} +
+
+
+ {renderFileCard()} +
+
+ + + + + +
+ {/* Left Column: Data Source */} +
+
+ + 数据源 +
+
+ {[ + { id: 'postgres-main', label: 'Postgres (Main)', icon: Database }, + { id: 'clickhouse-main', label: 'Clickhouse', icon: Database }, + { id: 'upload', label: '本地文件上传', icon: FileIcon }, + ].map((ds) => ( + + ))} +
+
+ + {/* Right Column: Skills */} +
+
+ + Skills +
+
+ {availableSkills.length > 0 ? ( + availableSkills.map((skill) => { + const isSelected = selectedSkillIds.includes(skill.id); + return ( + + ); + }) + ) : ( +
+ +

暂无可用技能

+
+ )} +
+ {selectedSkillIds.length > 0 && ( +
+ +
+ )} +
+
+
+
+
+ + setInput(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && !isLoading && handleSend()} + placeholder="有问题,尽管问" + className="flex-1 bg-transparent border-none focus:ring-0 text-lg px-3 py-2 text-zinc-900 placeholder:text-zinc-300 outline-none" + disabled={isLoading} + /> + +
+
- -
- )} -