mirror of
https://github.com/harivansh-afk/RAG-ui.git
synced 2026-04-15 07:04:48 +00:00
working chat interface with n8n
This commit is contained in:
parent
8cfff27165
commit
bda69752c0
3 changed files with 160 additions and 37 deletions
7
.env
7
.env
|
|
@ -1,2 +1,9 @@
|
||||||
VITE_SUPABASE_URL=https://nvatjthzedykhikmttot.supabase.co
|
VITE_SUPABASE_URL=https://nvatjthzedykhikmttot.supabase.co
|
||||||
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im52YXRqdGh6ZWR5a2hpa210dG90Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzM1OTYxOTMsImV4cCI6MjA0OTE3MjE5M30.u4euR8U-XxxvOdLFmWJD2yrd4E_MPMt_X1yqRrDTF2I
|
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im52YXRqdGh6ZWR5a2hpa210dG90Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzM1OTYxOTMsImV4cCI6MjA0OTE3MjE5M30.u4euR8U-XxxvOdLFmWJD2yrd4E_MPMt_X1yqRrDTF2I
|
||||||
|
|
||||||
|
# N8N Configuration
|
||||||
|
VITE_N8N_WEBHOOK_URL=https://harivansh.app.n8n.cloud/webhook/chat-webhook
|
||||||
|
VITE_N8N_UPLOAD_WEBHOOK_URL=https://harivansh.app.n8n.cloud/webhook/upload-webhook
|
||||||
|
|
||||||
|
# Google Drive Configuration
|
||||||
|
VITE_GOOGLE_DRIVE_FOLDER_ID=your_drive_folder_id
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,64 @@ const setInStorage = <T>(key: string, data: T[]) => {
|
||||||
localStorage.setItem(key, JSON.stringify(data));
|
localStorage.setItem(key, JSON.stringify(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// N8N webhook configuration
|
||||||
|
const N8N_WEBHOOK_URL = import.meta.env.VITE_N8N_WEBHOOK_URL;
|
||||||
|
|
||||||
|
// Define the expected response type from n8n RAG workflow
|
||||||
|
interface N8NResponse {
|
||||||
|
success: boolean;
|
||||||
|
response: {
|
||||||
|
content: string;
|
||||||
|
role: 'assistant';
|
||||||
|
metadata?: {
|
||||||
|
sources?: string[];
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to send message to n8n webhook and handle response
|
||||||
|
const sendToN8N = async (sessionId: string, message: string): Promise<N8NResponse> => {
|
||||||
|
try {
|
||||||
|
console.log('Sending message to n8n:', { sessionId, message });
|
||||||
|
|
||||||
|
const response = await fetch(N8N_WEBHOOK_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
sessionId,
|
||||||
|
action: 'sendMessage',
|
||||||
|
chatInput: message
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Raw n8n response:', response);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`N8N webhook error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await response.json();
|
||||||
|
console.log('Parsed n8n response:', responseData);
|
||||||
|
|
||||||
|
return responseData;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending message to n8n:', error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
response: {
|
||||||
|
content: 'Sorry, I encountered an error processing your request.',
|
||||||
|
role: 'assistant',
|
||||||
|
metadata: {
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const chatService = {
|
export const chatService = {
|
||||||
async createChatInstance(userId: string, title: string): Promise<ChatInstance | null> {
|
async createChatInstance(userId: string, title: string): Promise<ChatInstance | null> {
|
||||||
try {
|
try {
|
||||||
|
|
@ -91,9 +149,10 @@ export const chatService = {
|
||||||
content: string,
|
content: string,
|
||||||
role: 'user' | 'assistant',
|
role: 'user' | 'assistant',
|
||||||
metadata?: ChatMessage['metadata']
|
metadata?: ChatMessage['metadata']
|
||||||
): Promise<ChatMessage | null> {
|
): Promise<ChatMessage[]> {
|
||||||
try {
|
try {
|
||||||
const message: ChatMessage = {
|
// Create and save the user message
|
||||||
|
const userMessage: ChatMessage = {
|
||||||
id: generateId(),
|
id: generateId(),
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
content,
|
content,
|
||||||
|
|
@ -102,10 +161,57 @@ export const chatService = {
|
||||||
metadata,
|
metadata,
|
||||||
};
|
};
|
||||||
|
|
||||||
const messages = getFromStorage<ChatMessage>('chat_messages');
|
let messages = getFromStorage<ChatMessage>('chat_messages');
|
||||||
messages.push(message);
|
messages.push(userMessage);
|
||||||
setInStorage('chat_messages', messages);
|
setInStorage('chat_messages', messages);
|
||||||
|
|
||||||
|
// If it's a user message, send to n8n and create assistant message
|
||||||
|
if (role === 'user') {
|
||||||
|
try {
|
||||||
|
const n8nResponse = await sendToN8N(chatId, content);
|
||||||
|
console.log('Creating assistant message with n8n response:', n8nResponse);
|
||||||
|
|
||||||
|
if (n8nResponse.success && n8nResponse.response) {
|
||||||
|
// Create and save the assistant's response
|
||||||
|
const assistantMessage: ChatMessage = {
|
||||||
|
id: generateId(),
|
||||||
|
chat_id: chatId,
|
||||||
|
content: n8nResponse.response.content,
|
||||||
|
role: 'assistant',
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
metadata: {
|
||||||
|
...n8nResponse.response.metadata,
|
||||||
|
make_response_id: userMessage.id // Link to the user's message
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
messages = getFromStorage<ChatMessage>('chat_messages');
|
||||||
|
messages.push(assistantMessage);
|
||||||
|
setInStorage('chat_messages', messages);
|
||||||
|
console.log('Assistant message saved:', assistantMessage);
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid response format from n8n');
|
||||||
|
}
|
||||||
|
} catch (n8nError) {
|
||||||
|
console.error('Failed to get response from n8n:', n8nError);
|
||||||
|
// Add an error message if n8n fails
|
||||||
|
const errorMessage: ChatMessage = {
|
||||||
|
id: generateId(),
|
||||||
|
chat_id: chatId,
|
||||||
|
content: 'Sorry, I encountered an error processing your request.',
|
||||||
|
role: 'assistant',
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
metadata: {
|
||||||
|
error: 'Failed to process message',
|
||||||
|
make_response_id: userMessage.id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
messages = getFromStorage<ChatMessage>('chat_messages');
|
||||||
|
messages.push(errorMessage);
|
||||||
|
setInStorage('chat_messages', messages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update last_message_at in chat instance
|
// Update last_message_at in chat instance
|
||||||
const chats = getFromStorage<ChatInstance>('chat_instances');
|
const chats = getFromStorage<ChatInstance>('chat_instances');
|
||||||
const chatIndex = chats.findIndex(chat => chat.id === chatId);
|
const chatIndex = chats.findIndex(chat => chat.id === chatId);
|
||||||
|
|
@ -114,10 +220,14 @@ export const chatService = {
|
||||||
setInStorage('chat_instances', chats);
|
setInStorage('chat_instances', chats);
|
||||||
}
|
}
|
||||||
|
|
||||||
return message;
|
// Return all messages for this chat
|
||||||
|
const updatedMessages = messages.filter(msg => msg.chat_id === chatId)
|
||||||
|
.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
|
||||||
|
console.log('Returning updated messages:', updatedMessages);
|
||||||
|
return updatedMessages;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding message:', error);
|
console.error('Error adding message:', error);
|
||||||
return null;
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export default function AskQuestion() {
|
||||||
const [chat, setChat] = useState<ChatInstance | null>(null);
|
const [chat, setChat] = useState<ChatInstance | null>(null);
|
||||||
const [isNewChat, setIsNewChat] = useState(false);
|
const [isNewChat, setIsNewChat] = useState(false);
|
||||||
const [hasExistingChats, setHasExistingChats] = useState<boolean | null>(null);
|
const [hasExistingChats, setHasExistingChats] = useState<boolean | null>(null);
|
||||||
|
const [isTyping, setIsTyping] = useState(false);
|
||||||
|
|
||||||
// Check for existing chats and redirect to most recent if on base path
|
// Check for existing chats and redirect to most recent if on base path
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -121,30 +122,20 @@ export default function AskQuestion() {
|
||||||
currentChatId = chatId;
|
currentChatId = chatId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add user message
|
// Show typing indicator before sending message
|
||||||
const userMessage = await chatService.addMessage(currentChatId, question.trim(), 'user');
|
setIsTyping(true);
|
||||||
if (userMessage) {
|
|
||||||
setMessages(prev => [...prev, userMessage]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Add user message and get all updated messages including the AI response
|
||||||
|
const updatedMessages = await chatService.addMessage(currentChatId, question.trim(), 'user');
|
||||||
|
setMessages(updatedMessages);
|
||||||
setQuestion('');
|
setQuestion('');
|
||||||
|
|
||||||
// TODO: Integrate with Make.com for AI response
|
|
||||||
// For now, using a placeholder response
|
|
||||||
const aiMessage = await chatService.addMessage(
|
|
||||||
currentChatId,
|
|
||||||
'This is a placeholder response. AI integration coming soon!',
|
|
||||||
'assistant',
|
|
||||||
{ make_response_id: 'placeholder' }
|
|
||||||
);
|
|
||||||
if (aiMessage) {
|
|
||||||
setMessages(prev => [...prev, aiMessage]);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError('Failed to send message');
|
setError('Failed to send message');
|
||||||
console.error('Failed to process message:', err);
|
console.error('Failed to process message:', err);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
setIsTyping(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -242,26 +233,41 @@ export default function AskQuestion() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
messages.map((message) => (
|
<>
|
||||||
<div
|
{messages.map((message) => (
|
||||||
key={message.id}
|
|
||||||
className={cn(
|
|
||||||
'flex w-full',
|
|
||||||
message.role === 'user' ? 'justify-end' : 'justify-start'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
|
key={message.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
'max-w-[80%] rounded-lg px-4 py-2',
|
'flex w-full',
|
||||||
message.role === 'user'
|
message.role === 'user' ? 'justify-end' : 'justify-start'
|
||||||
? 'bg-primary text-primary-foreground'
|
|
||||||
: 'bg-muted'
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{message.content}
|
<div
|
||||||
|
className={cn(
|
||||||
|
'max-w-[80%] rounded-lg px-4 py-2',
|
||||||
|
message.role === 'user'
|
||||||
|
? 'bg-primary text-primary-foreground'
|
||||||
|
: 'bg-muted'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{message.content}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))
|
{/* Typing indicator */}
|
||||||
|
{isTyping && (
|
||||||
|
<div className="flex w-full justify-start">
|
||||||
|
<div className="flex max-w-[80%] items-center space-x-2 rounded-lg bg-muted px-4 py-2">
|
||||||
|
<div className="flex space-x-1">
|
||||||
|
<span className="animate-bounce delay-0">•</span>
|
||||||
|
<span className="animate-bounce delay-150">•</span>
|
||||||
|
<span className="animate-bounce delay-300">•</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm text-muted-foreground">AI is thinking...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue