feat: workflow和chat分离
1,增加了创建workflow的页面 2.删除了event
This commit is contained in:
@@ -1,19 +1,22 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { MessageSquare, Activity, Terminal, ChevronRight, Plus } from 'lucide-react';
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { MessageSquare, Activity, ChevronRight, Plus } from 'lucide-react';
|
||||
import apiClient from '../../api/client';
|
||||
import type { ChatSession, Message } from '../../App';
|
||||
import type { ChatMessageDB } from '../../types';
|
||||
|
||||
interface ChatPanelProps {
|
||||
chatSessions: ChatSession[];
|
||||
setChatSessions: React.Dispatch<React.SetStateAction<ChatSession[]>>;
|
||||
activeSessionId: string | null;
|
||||
setActiveSessionId: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
onSessionsChanged?: () => void;
|
||||
}
|
||||
|
||||
export function ChatPanel({ chatSessions, setChatSessions, activeSessionId, setActiveSessionId }: ChatPanelProps) {
|
||||
export function ChatPanel({ chatSessions, setChatSessions, activeSessionId, setActiveSessionId, onSessionsChanged }: ChatPanelProps) {
|
||||
const [input, setInput] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [mode, setMode] = useState<'chat' | 'deploy'>('chat');
|
||||
const [loadingMessages, setLoadingMessages] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -28,6 +31,36 @@ export function ChatPanel({ chatSessions, setChatSessions, activeSessionId, setA
|
||||
scrollToBottom();
|
||||
}, [messages]);
|
||||
|
||||
const loadMessages = useCallback(async (chatId: string) => {
|
||||
setLoadingMessages(true);
|
||||
try {
|
||||
const response = await apiClient.get(`/api/v1/chat/${chatId}`);
|
||||
const dbMessages: ChatMessageDB[] = response.data?.messages || [];
|
||||
const mapped: Message[] = dbMessages.map((m) => ({
|
||||
id: m.message_id,
|
||||
role: m.message_owner === 'user' ? 'user' : 'assistant',
|
||||
content: m.message,
|
||||
timestamp: new Date(m.created_at).getTime(),
|
||||
}));
|
||||
setChatSessions((prev) =>
|
||||
prev.map((s) => (s.id === chatId ? { ...s, messages: mapped } : s))
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to load messages', error);
|
||||
} finally {
|
||||
setLoadingMessages(false);
|
||||
}
|
||||
}, [setChatSessions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeSessionId) {
|
||||
const session = chatSessions.find((s) => s.id === activeSessionId);
|
||||
if (session && session.messages.length === 0) {
|
||||
loadMessages(activeSessionId);
|
||||
}
|
||||
}
|
||||
}, [activeSessionId]);
|
||||
|
||||
const updateSessionMessages = (newMessages: Message[]) => {
|
||||
if (!activeSessionId) return;
|
||||
setChatSessions((prev) =>
|
||||
@@ -39,39 +72,30 @@ export function ChatPanel({ chatSessions, setChatSessions, activeSessionId, setA
|
||||
);
|
||||
};
|
||||
|
||||
const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file || !activeSessionId) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
setLoading(true);
|
||||
const handleNewChat = async () => {
|
||||
if (!onSessionsChanged) return;
|
||||
try {
|
||||
const response = await apiClient.post('/api/v1/adapter/client/upload', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
const response = await apiClient.post('/api/v1/chat', {
|
||||
title: '新对话',
|
||||
initial_message: '你好',
|
||||
});
|
||||
const aiMessage: Message = {
|
||||
id: Date.now().toString(),
|
||||
role: 'assistant',
|
||||
content: `已上传文件: ${response.data.filename}`,
|
||||
timestamp: Date.now(),
|
||||
const chatId: string = response.data.chat_id;
|
||||
const reply: string = response.data.reply || '你好!我是 kilostar 助手,有什么可以帮你的吗?';
|
||||
|
||||
const newSession: ChatSession = {
|
||||
id: chatId,
|
||||
title: '新对话',
|
||||
messages: [
|
||||
{ id: chatId + '_user', role: 'user', content: '你好', timestamp: Date.now() },
|
||||
{ id: chatId + '_ai', role: 'assistant', content: reply, timestamp: Date.now() },
|
||||
],
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
updateSessionMessages([...messages, aiMessage]);
|
||||
setChatSessions((prev) => [newSession, ...prev]);
|
||||
setActiveSessionId(chatId);
|
||||
onSessionsChanged();
|
||||
} catch (error) {
|
||||
console.error('Error uploading file', error);
|
||||
const errorMessage: Message = {
|
||||
id: Date.now().toString(),
|
||||
role: 'assistant',
|
||||
content: '文件上传失败。',
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
updateSessionMessages([...messages, errorMessage]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
console.error('Failed to create chat session', error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -86,21 +110,19 @@ export function ChatPanel({ chatSessions, setChatSessions, activeSessionId, setA
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
updateSessionMessages([...messages, userMessage]);
|
||||
const currentMessages = activeSession?.messages || [];
|
||||
updateSessionMessages([...currentMessages, userMessage]);
|
||||
setInput('');
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const promptModifier = mode === 'deploy' ? '[DEPLOY TASK] ' : '';
|
||||
const response = await apiClient.post('/api/v1/adapter/client', {
|
||||
message: promptModifier + userMessage.content,
|
||||
const response = await apiClient.post(`/api/v1/chat/${activeSessionId}/reply`, {
|
||||
message: userText,
|
||||
});
|
||||
|
||||
const responseData = response.data.message;
|
||||
let aiContent = responseData || 'I received your message.';
|
||||
const replyContent: string = response.data?.reply || '收到你的消息。';
|
||||
|
||||
// Auto-update title if it's the first user message
|
||||
if (messages.length <= 1 && userText.length > 0) {
|
||||
if (currentMessages.length <= 1 && userText.length > 0) {
|
||||
setChatSessions((prev) =>
|
||||
prev.map((s) =>
|
||||
s.id === activeSessionId
|
||||
@@ -113,20 +135,21 @@ export function ChatPanel({ chatSessions, setChatSessions, activeSessionId, setA
|
||||
const aiMessage: Message = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
role: 'assistant',
|
||||
content: aiContent,
|
||||
content: replyContent,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
updateSessionMessages([...messages, userMessage, aiMessage]);
|
||||
const updatedMessages = [...(currentMessages.length > 0 ? currentMessages : []), userMessage, aiMessage];
|
||||
updateSessionMessages(updatedMessages);
|
||||
} catch (error) {
|
||||
console.error('Error sending message', error);
|
||||
const errorMessage: Message = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
role: 'assistant',
|
||||
content: 'Sorry, I encountered an error communicating with the server.',
|
||||
content: '抱歉,与服务器通信时出错。',
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
updateSessionMessages([...messages, userMessage, errorMessage]);
|
||||
updateSessionMessages([...currentMessages, userMessage, errorMessage]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -139,23 +162,7 @@ export function ChatPanel({ chatSessions, setChatSessions, activeSessionId, setA
|
||||
<h2 className="text-xl font-semibold text-slate-600">kilostar Assistant</h2>
|
||||
<p className="text-slate-400 mt-2">Select a chat history or create a new one to start.</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
const newSession: ChatSession = {
|
||||
id: Date.now().toString(),
|
||||
title: 'New Chat',
|
||||
messages: [
|
||||
{
|
||||
id: Date.now().toString(),
|
||||
role: 'assistant',
|
||||
content: 'Hello! I am kilostar Assistant. How can I help you today?',
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
],
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
setChatSessions([newSession, ...chatSessions]);
|
||||
setActiveSessionId(newSession.id);
|
||||
}}
|
||||
onClick={handleNewChat}
|
||||
className="mt-6 px-6 py-2 bg-blue-200 text-slate-800 rounded-xl shadow-sm hover:bg-blue-300 transition-colors"
|
||||
>
|
||||
Start New Chat
|
||||
@@ -164,7 +171,6 @@ export function ChatPanel({ chatSessions, setChatSessions, activeSessionId, setA
|
||||
);
|
||||
}
|
||||
|
||||
// Notice we removed the outer padded div, since App.tsx is handling that layout now
|
||||
return (
|
||||
<div className="flex-1 flex flex-col bg-white overflow-hidden relative">
|
||||
<div className="h-14 border-b border-slate-100 bg-white flex items-center justify-between px-6 z-10 shrink-0">
|
||||
@@ -192,32 +198,35 @@ export function ChatPanel({ chatSessions, setChatSessions, activeSessionId, setA
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chat History */}
|
||||
<div className="flex-1 p-6 overflow-y-auto space-y-6 bg-white">
|
||||
{messages.map((msg) => (
|
||||
<div key={msg.id} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
||||
{msg.role === 'assistant' && (
|
||||
<div className="w-8 h-8 rounded-full bg-white border border-blue-100 flex items-center justify-center mr-3 mt-1 shadow-sm flex-shrink-0">
|
||||
<Activity size={16} className="text-blue-600" />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
msg.role === 'user'
|
||||
? 'bg-blue-100 text-slate-800 rounded-2xl rounded-tr-sm'
|
||||
: 'bg-slate-50 border border-slate-100 text-slate-700 rounded-2xl rounded-tl-sm'
|
||||
} p-4 max-w-[80%] shadow-sm`}
|
||||
>
|
||||
<p className="text-sm leading-relaxed mb-1 whitespace-pre-wrap">{msg.content}</p>
|
||||
{typeof msg.content === 'string' && msg.content.includes('-') && msg.role === 'assistant' && (msg.content.length === 36 || msg.content.includes('任务已创建')) && (
|
||||
<div className="mt-2 bg-white border border-slate-100 rounded-lg p-3 flex items-center text-sm shadow-sm">
|
||||
<Terminal size={16} className="text-slate-400 mr-2" />
|
||||
<span className="font-mono text-slate-600 text-xs">Task ID: {msg.content.substring(0, 36)}</span>
|
||||
</div>
|
||||
)}
|
||||
{loadingMessages ? (
|
||||
<div className="flex justify-center items-center h-full">
|
||||
<div className="flex space-x-2">
|
||||
<span className="h-2 w-2 bg-slate-400 rounded-full animate-bounce"></span>
|
||||
<span className="h-2 w-2 bg-slate-400 rounded-full animate-bounce delay-75"></span>
|
||||
<span className="h-2 w-2 bg-slate-400 rounded-full animate-bounce delay-150"></span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
) : (
|
||||
messages.map((msg) => (
|
||||
<div key={msg.id} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
||||
{msg.role === 'assistant' && (
|
||||
<div className="w-8 h-8 rounded-full bg-white border border-blue-100 flex items-center justify-center mr-3 mt-1 shadow-sm flex-shrink-0">
|
||||
<Activity size={16} className="text-blue-600" />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
msg.role === 'user'
|
||||
? 'bg-blue-100 text-slate-800 rounded-2xl rounded-tr-sm'
|
||||
: 'bg-slate-50 border border-slate-100 text-slate-700 rounded-2xl rounded-tl-sm'
|
||||
} p-4 max-w-[80%] shadow-sm`}
|
||||
>
|
||||
<p className="text-sm leading-relaxed mb-1 whitespace-pre-wrap">{msg.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
{loading && (
|
||||
<div className="flex justify-start">
|
||||
<div className="w-8 h-8 rounded-full bg-white border border-blue-100 flex items-center justify-center mr-3 mt-1 shadow-sm flex-shrink-0">
|
||||
@@ -235,10 +244,9 @@ export function ChatPanel({ chatSessions, setChatSessions, activeSessionId, setA
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
{/* Chat Input */}
|
||||
<div className="p-4 bg-white border-t border-slate-100 shrink-0">
|
||||
<div className="relative flex items-center">
|
||||
<input type="file" ref={fileInputRef} onChange={handleFileUpload} className="hidden" />
|
||||
<input type="file" ref={fileInputRef} className="hidden" />
|
||||
<button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="absolute left-2 p-1.5 text-slate-400 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors z-10 cursor-pointer"
|
||||
|
||||
Reference in New Issue
Block a user