diff --git a/src/app/api/chat/main.py b/src/app/api/chat/main.py index 5537bc4..e6b962c 100644 --- a/src/app/api/chat/main.py +++ b/src/app/api/chat/main.py @@ -3,25 +3,48 @@ from flask import Flask, request, jsonify from flask_cors import CORS from pathlib import Path +import json +import logging +logging.basicConfig(level=logging.DEBUG) # Add the parent directory to the system path to import model sys.path.append(str(Path(__file__).resolve().parents[2] / 'model')) from model import process_message # Import process_message from model.py app = Flask(__name__) -CORS(app) # This will enable CORS for all routes +CORS(app, resources={r"/api/*": { + "origins": "http://localhost:3000", # Adjust this as necessary + "methods": ["GET", "POST"], + "allow_headers": ["Content-Type", "Authorization"], + "supports_credentials": True +}}) -@app.route('/api/chat', methods=['POST']) +@app.route('/api/chat/send_message', methods=['POST']) def chat(): + app.logger.debug("Received request for /api/chat/send_message") + data = request.json user_message = data.get('message', '') if not user_message: + app.logger.error("No message provided in the request") return jsonify({"error": "No message provided"}), 400 response_message = process_message(user_message) + app.logger.debug("Processed message: %s", response_message) + return jsonify({"answer": response_message}) +@app.route('/api/chat/submit_responses', methods=['POST']) +def submit_responses(): + data = request.json + responses = data.get('responses', {}) + + with open('responses.json', 'w') as file: + json.dump(responses, file) + + return jsonify({"status": "Success", "message": "Data saved successfully"}) + if __name__ == '__main__': - app.run(host='0.0.0.0', port=8000) \ No newline at end of file + app.run(host='0.0.0.0', port=8000, debug=True) \ No newline at end of file diff --git a/src/app/chat/actionProvider.js b/src/app/chat/actionProvider.js deleted file mode 100644 index 09978fd..0000000 --- a/src/app/chat/actionProvider.js +++ /dev/null @@ -1,98 +0,0 @@ -class ActionProvider { - constructor(createChatBotMessage, setChatMessages) { - this.createChatBotMessage = createChatBotMessage; - this.setChatMessages = setChatMessages; - this.latestUserMessage = null; - } - handleMessage = async (message) => { - console.log("handleMessage called with message:", message); // Debug log - if (message.toLowerCase() === 'start') { - await this.handleStart(); - } else if (message.toLowerCase() === 'exit') { - this.handleExit(); - } else { - await this.handleGeneralMessage(message); - } - }; - - handleStart = async () => { - const questions = [ - "What questions help guide your family's decision-making?", - 'What are your family values?', - 'What is a statement or commitment that your family lives by?', - "What statement defines your family's vision?", - "What is your family's impact statement?", - ]; - - for (const question of questions) { - const botMessage = this.createChatBotMessage(question); - console.log("Adding bot message to state:", botMessage); // Debug log - this.setChatMessages(prevMessages => [...prevMessages, botMessage]); - - const userResponse = await this.waitForUserResponse(); - console.log("User responded with:", userResponse); // Debug log - const friendlyMessage = await this.sendMessageToAPI(userResponse); - const botReply = this.createChatBotMessage(friendlyMessage); - - this.setChatMessages(prevMessages => [...prevMessages, botReply]); - } - }; - - handleExit = () => { - const botMessage = this.createChatBotMessage('Goodbye!'); - console.log("Adding exit message to state:", botMessage); // Debug log - this.setChatMessages(prevMessages => [...prevMessages, botMessage]); - }; - - handleGeneralMessage = async (message) => { - const response = await this.sendMessageToAPI(message); - const botMessage = this.createChatBotMessage(response); - console.log("Adding general message to state:", botMessage); // Debug log - this.setChatMessages(prevMessages => [...prevMessages, botMessage]); - }; - - sendMessageToAPI = async (message) => { - try { - const response = await fetch('http://localhost:8000/api/chat', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ message }), - }); - - const text = await response.text(); // Get response body as text - console.log("HTTP Status:", response.status); - console.log("Response Body:", text); - - if (!response.ok) { - throw new Error(`HTTP error ${response.status}`); - } - - // Attempt to parse text as JSON - const data = JSON.parse(text); - console.log("Parsed JSON:", data); - return data; - } catch (error) { - console.error("Error during fetch or parsing:", error); - } - }; - - waitForUserResponse = () => new Promise((resolve) => { - const interval = setInterval(() => { - // console.log("Checking for user response:", this.latestUserMessage); // Additional logging - if (this.latestUserMessage) { - console.log("User responded with:", this.latestUserMessage); - clearInterval(interval); - resolve(this.latestUserMessage); - this.latestUserMessage = null; - } - }, 500); - }); - - setLatestUserMessage = (message) => { - console.log("Attempting to set latest user message:", message); - this.latestUserMessage = message; - console.log("Latest user message now set to:", this.latestUserMessage); - }; -} - -export default ActionProvider; diff --git a/src/app/chat/chat-input.tsx b/src/app/chat/chat-input.tsx index 0add87b..632fa5e 100644 --- a/src/app/chat/chat-input.tsx +++ b/src/app/chat/chat-input.tsx @@ -1,57 +1,200 @@ -"use client"; +// "use client"; +// import { useChatHandler } from '@/app/chat/hooks/use-chat-handler'; +// import { useRef, useState, FC } from 'react'; +// import { IconCirclePlus, IconPlayerStopFilled, IconSend } from '@tabler/icons-react'; +// import { Input } from '@/components/ui/input'; +// import TextareaAutosize from 'react-textarea-autosize'; +// import { useTranslation } from 'react-i18next'; +// import { cn } from '@/lib/utils'; +// // import { ChatFilesDisplay } from '@/components/chat/chat-files-display'; + +// interface ChatInputProps {} + +// export const ChatInput: FC = () => { +// const { t } = useTranslation(); +// const [isTyping, setIsTyping] = useState(false); + + +// const { +// chatInput, +// setChatInput, +// handleSendMessage, +// isGenerating, +// } = useChatHandler(); + +// const fileInputRef = useRef(null); + +// // useHotkey("l", handleFocusChatInput); + +// const handleKeyDown = (event: React.KeyboardEvent) => { +// if (!isTyping && event.key === 'Enter' && !event.shiftKey) { +// event.preventDefault(); +// if (chatInput.trim() !== '') { +// handleSendMessage(chatInput); +// setChatInput(''); +// } +// } +// }; + +// const handlePaste = (event: React.ClipboardEvent) => { +// const items = event.clipboardData.items; +// for (const item of items) { +// if (item.type.indexOf('image') === 0) { +// const file = item.getAsFile(); +// if (!file) return; +// // Handle the file selection here, e.g., set a state or call a handler function +// } +// } +// }; + +// return ( +// <> +//
+// {/* */} +//
+ +//
+// fileInputRef.current?.click()} +// /> + +// { +// if (!e.target.files) return; +// // Handle the file selection here, e.g., set a state or call a handler function +// }} +// /> + +// setChatInput(e.target.value)} +// value={chatInput} +// minRows={1} +// maxRows={18} +// onKeyDown={handleKeyDown} +// onPaste={handlePaste} +// onCompositionStart={() => setIsTyping(true)} +// onCompositionEnd={() => setIsTyping(false)} +// /> + +//
+// {isGenerating ? ( +// +// ) : ( +// { +// if (!chatInput) return; +// handleSendMessage(chatInput); +// setChatInput(''); +// }} +// size={30} +// /> +// )} +//
+//
+// +// ); +// }; + + + + +// "use client"; + +// import { FC, useState, KeyboardEvent, ChangeEvent } from 'react'; +// import { useTranslation } from 'react-i18next'; +// import { useChatHandler } from '@/app/chat/hooks/use-chat-handler'; // Ensure path is correct + +// interface ChatInputProps {} + +// export const ChatInput: FC = () => { +// const { t } = useTranslation(); +// const [chatInput, setChatInput] = useState(""); // Managed locally if not provided by useChatHandler + +// const { +// handleSendMessage, +// isGenerating, +// } = useChatHandler(); // Ensure useChatHandler provides these + +// const handleInputChange = (e: ChangeEvent) => { +// setChatInput(e.target.value); +// }; + +// const handleKeyPress = (e: KeyboardEvent) => { +// if (e.key === 'Enter' && chatInput.trim() !== '') { +// e.preventDefault(); // Prevent default to avoid form submit in some contexts +// handleSendMessage(chatInput); +// setChatInput(''); // Clear input field after sending +// } +// }; + +// return ( +//
+// +// {isGenerating && {t('sending')}} +//
+// ); +// }; + +import { FC, useState, KeyboardEvent, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; import { useChatHandler } from '@/app/chat/hooks/use-chat-handler'; -import { useRef, useState, FC } from 'react'; -import { IconCirclePlus, IconPlayerStopFilled, IconSend } from '@tabler/icons-react'; -import { Input } from '@/components/ui/input'; import TextareaAutosize from 'react-textarea-autosize'; -import { useTranslation } from 'react-i18next'; -import { cn } from '@/lib/utils'; -// import { ChatFilesDisplay } from '@/components/chat/chat-files-display'; +import { IconSend, IconCirclePlus, IconPlayerStopFilled } from '@tabler/icons-react'; + interface ChatInputProps {} export const ChatInput: FC = () => { const { t } = useTranslation(); - const [isTyping, setIsTyping] = useState(false); - - - const { - chatInput, - setChatInput, - handleSendMessage, - isGenerating, - } = useChatHandler(); - + const { handleSendMessage, isGenerating } = useChatHandler(); + const [chatInput, setChatInput] = useState(""); const fileInputRef = useRef(null); - // useHotkey("l", handleFocusChatInput); + const handleInputChange = (event: React.ChangeEvent) => { + setChatInput(event.target.value); + }; - const handleKeyDown = (event: React.KeyboardEvent) => { - if (!isTyping && event.key === 'Enter' && !event.shiftKey) { - event.preventDefault(); - if (chatInput.trim() !== '') { - handleSendMessage(chatInput); - setChatInput(''); - } + const handleKeyPress = (event: KeyboardEvent) => { + if (event.key === 'Enter' && !event.shiftKey && chatInput.trim() !== '') { + event.preventDefault(); // Stop form submission + handleSendMessage(chatInput); + setChatInput(''); } }; - const handlePaste = (event: React.ClipboardEvent) => { - const items = event.clipboardData.items; - for (const item of items) { - if (item.type.indexOf('image') === 0) { - const file = item.getAsFile(); - if (!file) return; - // Handle the file selection here, e.g., set a state or call a handler function - } - } + const handleFileChange = (event: React.ChangeEvent) => { + // Implement your logic for file handling here + console.log(event.target.files); }; return ( <>
- {/* */} + {/* Placeholder for any additional components like file displays */}
@@ -61,42 +204,33 @@ export const ChatInput: FC = () => { onClick={() => fileInputRef.current?.click()} /> - { - if (!e.target.files) return; - // Handle the file selection here, e.g., set a state or call a handler function - }} + onChange={handleFileChange} /> setChatInput(e.target.value)} + placeholder={t('Type start to begin creating your family charter...')} + onChange={handleInputChange} value={chatInput} minRows={1} - maxRows={18} - onKeyDown={handleKeyDown} - onPaste={handlePaste} - onCompositionStart={() => setIsTyping(true)} - onCompositionEnd={() => setIsTyping(false)} + maxRows={5} + onKeyDown={handleKeyPress} /> - +
{isGenerating ? ( ) : ( { if (!chatInput) return; handleSendMessage(chatInput); diff --git a/src/app/chat/chat-messages.tsx b/src/app/chat/chat-messages.tsx index 7f01dc7..d45f8d7 100644 --- a/src/app/chat/chat-messages.tsx +++ b/src/app/chat/chat-messages.tsx @@ -1,10 +1,11 @@ -import React, { useContext, useEffect, useRef } from 'react'; -import { ChatbotUIContext } from '@/app/chat/context'; -import { Message } from '@/app/chat/message'; +import React, { useEffect, useRef } from 'react'; +import { useChatbotUI } from '@/app/chat/context'; +import './styles.css'; export const ChatMessages = () => { - const { chatMessages } = useContext(ChatbotUIContext); + // const { chatMessages } = useContext(ChatbotUIContext); + const { chatMessages } = useChatbotUI(); const messagesEndRef = useRef(null); useEffect(() => { @@ -12,36 +13,15 @@ export const ChatMessages = () => { console.log("Updated messages:", chatMessages) }, [chatMessages]); - // return ( - //
- // {chatMessages.map((msg, index) => ( - //
- //

{msg.message}

- //
- // ))} - //
- //
- // ); return ( -
- {chatMessages.map((msg, index) => { - console.log("Rendering message:", msg); - return ( - {}} - onCancelEdit={() => {}} - onSubmitEdit={(newContent) => { - console.log("Edit submitted:", newContent); - // Update logic here - }} - /> - ); - })} +
+ {chatMessages.map((msg, index) => ( +
+
+ {msg.message} +
+
+ ))}
); }; diff --git a/src/app/chat/chat-ui.tsx b/src/app/chat/chat-ui.tsx index 89d48ff..d130515 100644 --- a/src/app/chat/chat-ui.tsx +++ b/src/app/chat/chat-ui.tsx @@ -1,22 +1,32 @@ -import { FC, useContext, useEffect, useState } from 'react'; -import { useParams } from 'next/navigation'; -import { ChatbotUIContext } from '@/app/chat/context'; +// import { FC, useContext, useEffect, useState } from 'react'; +// import { FC, useContext, useEffect } from 'react'; +import { FC, useEffect } from 'react'; + +// import { useParams } from 'next/navigation'; +// import { ChatbotUIContext } from '@/app/chat/context'; +import { useChatbotUI } from '@/app/chat/context'; import { useChatHandler } from '@/app/chat/hooks/use-chat-handler'; -import useHotkey from '@/lib/hooks/use-hotkey'; +// import useHotkey from '@/lib/hooks/use-hotkey'; import { useScroll } from '@/app/chat/hooks/use-scroll'; import { ChatInput } from '@/app/chat/chat-input'; import { ChatMessages } from '@/app/chat/chat-messages'; import { ChatScrollButtons } from '@/components/chat/chat-scroll-buttons'; -import Loading from '@/app/chat/loading'; +// import Loading from '@/app/chat/loading'; interface ChatUIProps {} export const ChatUI: FC = () => { - const { handleNewChat, handleFocusChatInput } = useChatHandler(); - useHotkey("o", handleNewChat); + // const { handleStart, handleFocusChatInput } = useChatHandler(); + // const { handleNewChat } = useChatHandler(); + const { handleNewChat } = useChatHandler(); + // useHotkey("o", handleStart); + + // const params = useParams(); + // const { isGenerating, chatMessages } = useContext(ChatbotUIContext); // Updated context hook usage - const params = useParams(); - const { isGenerating } = useContext(ChatbotUIContext); // Updated context hook usage + console.log("ChatUI Component Rendering"); + // console.log("Context State - isGenerating:", isGenerating); + // console.log("Context State - chatMessages:", chatMessages); const { messagesStartRef, @@ -29,19 +39,37 @@ export const ChatUI: FC = () => { scrollToTop } = useScroll(); - const [loading, setLoading] = useState(true); + useEffect(() => { + console.log("ChatUI mounted, initializing chat session..."); + handleNewChat(); // Safely call handleStart if it's defined + }, [handleNewChat]); + + // const [loading, setLoading] = useState(true); + + // useEffect(() => { + // console.log("Params chatid:", params.chatid, "isGenerating:", isGenerating); + // setLoading(!params.chatid || isGenerating); + // if (params.chatid) { + // handleFocusChatInput(); + // } + // console.log("Loading status:", loading); + // }, [params.chatid, handleFocusChatInput, isGenerating, loading]); + + // console.log("Final Check - Loading:", loading); + + // if (loading) { + // return ; + // } + + const { chatMessages } = useChatbotUI(); useEffect(() => { - // Adjusting loading logic based on the presence of a chat ID and isGenerating status - setLoading(!params.chatid || isGenerating); - if (params.chatid) { - handleFocusChatInput(); - } - }, [params.chatid, handleFocusChatInput, isGenerating]); + console.log("Chat messages in consumer updated:", chatMessages); + }, [chatMessages]); - if (loading) { - return ; - } + // if (isGenerating || !params.chatid) { + // return ; + // } return (
@@ -56,12 +84,13 @@ export const ChatUI: FC = () => {
-
- {/* Removing selectedChat and replacing it with a generic title */} -
- Current Chat Session + +
+
+ Videre Chatbot
+
diff --git a/src/app/chat/context.tsx b/src/app/chat/context.tsx index 3a6f2df..73a7d33 100644 --- a/src/app/chat/context.tsx +++ b/src/app/chat/context.tsx @@ -1,4 +1,4 @@ -import React, { useState, createContext, Dispatch, SetStateAction, ReactNode, FC } from 'react'; +import React, { createContext, useContext, useState, Dispatch, SetStateAction, ReactNode, FC } from 'react'; import { ChatMessage, ChatFile, MessageImage } from "@/app/chat/types/types"; interface ChatbotUIContextType { @@ -8,14 +8,8 @@ interface ChatbotUIContextType { setChatMessages: Dispatch>; chatFileItems: ChatFile[]; setChatFileItems: Dispatch>; - - abortController: AbortController | null; - setAbortController: Dispatch>; - firstTokenReceived: boolean; - setFirstTokenReceived: Dispatch>; isGenerating: boolean; setIsGenerating: Dispatch>; - chatFiles: ChatFile[]; setChatFiles: Dispatch>; chatImages: MessageImage[]; @@ -26,29 +20,17 @@ interface ChatbotUIContextType { setNewMessageImages: Dispatch>; showFilesDisplay: boolean; setShowFilesDisplay: Dispatch>; - - useRetrieval: boolean; - setUseRetrieval: Dispatch>; - sourceCount: number; - setSourceCount: Dispatch>; } - -export const ChatbotUIContext = createContext({ +const defaultValue: ChatbotUIContextType = { userInput: "", setUserInput: () => {}, chatMessages: [], setChatMessages: () => {}, chatFileItems: [], setChatFileItems: () => {}, - - abortController: null, - setAbortController: () => {}, - firstTokenReceived: false, - setFirstTokenReceived: () => {}, isGenerating: false, setIsGenerating: () => {}, - chatFiles: [], setChatFiles: () => {}, chatImages: [], @@ -59,12 +41,9 @@ export const ChatbotUIContext = createContext({ setNewMessageImages: () => {}, showFilesDisplay: false, setShowFilesDisplay: () => {}, +}; - useRetrieval: false, - setUseRetrieval: () => {}, - sourceCount: 4, - setSourceCount: () => {}, -}); +export const ChatbotUIContext = createContext(defaultValue); interface ChatbotUIProviderProps { children: ReactNode; @@ -74,50 +53,28 @@ export const ChatbotUIProvider: FC = ({ children }) => { const [userInput, setUserInput] = useState(""); const [chatMessages, setChatMessages] = useState([]); const [chatFileItems, setChatFileItems] = useState([]); - const [abortController, setAbortController] = useState(null); - const [firstTokenReceived, setFirstTokenReceived] = useState(false); const [isGenerating, setIsGenerating] = useState(false); const [chatFiles, setChatFiles] = useState([]); const [chatImages, setChatImages] = useState([]); const [newMessageFiles, setNewMessageFiles] = useState([]); const [newMessageImages, setNewMessageImages] = useState([]); const [showFilesDisplay, setShowFilesDisplay] = useState(false); - const [useRetrieval, setUseRetrieval] = useState(false); - const [sourceCount, setSourceCount] = useState(4); - - - // useEffect(() => { - // // Simulate fetching from an API - // fetch('api/files') - // .then(response => response.json()) - // .then(files => setChatFileItems(files.map(file => ({ - // id: file.id, - // name: file.name || 'No name provided', - // type: file.type || 'Unknown type', - // content: file.content, - // created_at: file.created_at - // })))); - // }, []); return ( - + {children} ); }; + +export const useChatbotUI = () => useContext(ChatbotUIContext); diff --git a/src/app/chat/hooks/use-chat-handler.tsx b/src/app/chat/hooks/use-chat-handler.tsx index 433f940..6ce34e6 100644 --- a/src/app/chat/hooks/use-chat-handler.tsx +++ b/src/app/chat/hooks/use-chat-handler.tsx @@ -1,60 +1,352 @@ -import { useState, useCallback, useContext, useMemo, useEffect } from 'react'; +// import { useState, useCallback, useContext, useEffect } from 'react'; +// import { ChatbotUIContext } from '@/app/chat/context'; +// import { ChatMessage } from '@/app/chat/types/types'; + +// const questions = [ +// "Enter up to three questions that guide your family’s decision making.", +// "What are your family values?", +// "What is a statement or commitment that your family lives by?", +// "What statement defines your family's vision?", +// "What is your family's impact statement?", +// ]; + +// export const useChatHandler = () => { +// const { chatMessages, setChatMessages } = useContext(ChatbotUIContext); +// const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); +// const [isGenerating, setIsGenerating] = useState(false); + +// const createChatBotMessage = useCallback((text, messageType): ChatMessage => { +// return { +// id: Date.now().toString(), +// type: messageType, +// message: text +// }; +// }, []); + +// // Initialize chat with a welcome message +// useEffect(() => { +// const welcomeMessage = createChatBotMessage("Welcome to Videre Chatbot!", 'bot'); +// setChatMessages([welcomeMessage]); +// }, [createChatBotMessage, setChatMessages]); + +// const sendMessageToAPI = useCallback(async (message) => { +// if (['start', 'exit'].includes(message.toLowerCase())) { +// console.log("Control message received, not sending to API:", message); +// return; +// } +// try { +// const response = await fetch('http://localhost:8000/api/chat', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ message }), +// }); +// if (!response.ok) throw new Error(`HTTP error ${response.status}`); +// const { answer } = await response.json(); +// return answer; +// } catch (error) { +// console.error("Error during fetch or parsing:", error); +// } +// }, []); + +// const handleGeneralMessage = useCallback(async (message) => { +// const response = await sendMessageToAPI(message); +// if(response) { +// const botMessage = createChatBotMessage(response, 'bot'); +// setChatMessages(prev => [...prev, botMessage]); +// } +// }, [sendMessageToAPI, createChatBotMessage, setChatMessages]); + +// const handleStart = useCallback(() => { +// if (currentQuestionIndex < questions.length) { +// const message = createChatBotMessage(questions[currentQuestionIndex], 'bot'); +// setChatMessages(prevMessages => [...prevMessages, message]); +// setCurrentQuestionIndex(current => current + 1); +// } +// }, [createChatBotMessage, currentQuestionIndex, setChatMessages]); + + +// const handleExit = useCallback(() => { +// const exitMessage = createChatBotMessage("Goodbye!", "bot"); +// setChatMessages(prev => [...prev, exitMessage]); +// }, [createChatBotMessage, setChatMessages]); + +// const handleSendMessage = useCallback(async (messageText: string) => { +// setIsGenerating(true); +// try { +// const message: ChatMessage = createChatBotMessage(messageText, 'user'); +// setChatMessages(prev => { +// console.log("Adding message:", message); // Log to confirm message is being added +// return [...prev, message]; +// }); + +// if (messageText.trim().toLowerCase() === "start" && currentQuestionIndex === 0) { +// handleStart(); +// } else if (messageText.trim().toLowerCase() === "exit") { +// handleExit(); +// } else { +// await handleGeneralMessage(messageText); +// } +// } catch (error) { +// console.error("Failed to handle message:", error); +// } finally { +// setIsGenerating(false); +// } +// }, [createChatBotMessage, handleStart, handleExit, handleGeneralMessage, setChatMessages, currentQuestionIndex]); + +// const handleNewChat = useCallback(() => { +// setChatMessages([]); // Clearing existing messages for new chat session +// setCurrentQuestionIndex(0); // Reset question index +// }, [setChatMessages]); + +// return { +// chatMessages, +// handleSendMessage, +// handleNewChat, +// handleStart, +// isGenerating, +// }; +// }; + + +// import { useState, useCallback, useContext } from 'react'; +// import { ChatbotUIContext } from '@/app/chat/context'; +// import { ChatMessage } from '@/app/chat/types/types'; +// import { v4 as uuidv4 } from 'uuid'; + +// const questions = [ +// "Enter up to three questions that guide your family’s decision making.", +// "What are your family values?", +// "What is a statement or commitment that your family lives by?", +// "What statement defines your family's vision?", +// "What is your family's impact statement?", +// ]; + +// export const useChatHandler = () => { +// const { chatMessages, setChatMessages } = useContext(ChatbotUIContext); +// const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); +// const [isGenerating, setIsGenerating] = useState(false); + +// const createChatBotMessage = useCallback((text, messageType): ChatMessage => ({ +// id: uuidv4(), +// type: messageType, +// message: text +// }), []); + +// const addMessage = useCallback((message: ChatMessage) => { +// setChatMessages(prevMessages => [...prevMessages, message]); +// }, [setChatMessages]); + +// const handleQuestions = useCallback(() => { +// console.log("Current Index Before Update:", currentQuestionIndex); +// if (currentQuestionIndex < questions.length) { +// addMessage(createChatBotMessage(questions[currentQuestionIndex], 'bot')); +// setCurrentQuestionIndex(current => { +// console.log("Updating Index From:", current, "To:", current + 1); +// return current + 1; +// }); +// } else { +// addMessage(createChatBotMessage("You may type exit to finish creating your charter.", 'bot')); +// } +// }, [addMessage, createChatBotMessage, currentQuestionIndex]); + +// const submitResponses = useCallback(async () => { +// // Filter messages to only include those from the user +// const userResponses = chatMessages.filter(msg => msg.type === 'user').map(msg => msg.message); + +// // Send the user responses to the backend +// try { +// const response = await fetch('http://localhost:8000/api/chat/submit_responses', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ responses: userResponses }), +// credentials: 'include' +// }); +// if (!response.ok) { +// throw new Error(`HTTP error ${response.status}`); +// } +// const result = await response.json(); +// console.log("Responses submitted successfully:", result); +// } catch (error) { +// console.error("Failed to submit responses:", error); +// } +// }, [chatMessages]); + +// const sendMessageToAPI = useCallback(async (message) => { +// try { +// const response = await fetch('http://localhost:8000/api/chat/send_message', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ message }), +// credentials: 'include' +// }); +// if (!response.ok) throw new Error(`HTTP error ${response.status}`); +// const { answer } = await response.json(); +// return answer; +// } catch (error) { +// console.error("Error during fetch or parsing:", error); +// return "Sorry, there was an error processing your message."; +// } +// }, []); + +// const handleSendMessage = useCallback(async(messageText: string) => { +// setIsGenerating(true); +// const normalizedText = messageText.trim().toLowerCase(); +// try { +// addMessage(createChatBotMessage(messageText, 'user')); + +// if (normalizedText === "start" && currentQuestionIndex === 0) { +// handleQuestions(); +// } else if (normalizedText === "exit") { +// await submitResponses(); +// setChatMessages([]); +// setCurrentQuestionIndex(0); +// addMessage(createChatBotMessage("Thank you, I have recorded your responses. Goodbye!", 'bot')); +// } else { +// const apiResponse = await sendMessageToAPI(messageText); +// addMessage(createChatBotMessage(apiResponse, 'bot')); +// handleQuestions(); +// } +// } finally { +// setIsGenerating(false); +// } +// }, [addMessage, handleQuestions, createChatBotMessage, currentQuestionIndex, setChatMessages, submitResponses, sendMessageToAPI]); + +// const handleNewChat = useCallback(() => { +// setChatMessages([]); // Clearing existing messages for new chat session +// setCurrentQuestionIndex(0); // Reset question index +// }, [setChatMessages]); + +// return { +// chatMessages, +// handleSendMessage, +// handleNewChat, +// isGenerating, +// }; +// }; + +import { useState, useCallback, useContext } from 'react'; import { ChatbotUIContext } from '@/app/chat/context'; -import ActionProvider from '@/app/chat/actionProvider'; +import { ChatMessage } from '@/app/chat/types/types'; +import { v4 as uuidv4 } from 'uuid'; + +const questions = [ + "Enter up to three questions that guide your family’s decision making.", + "What are your family values?", + "What is a statement or commitment that your family lives by?", + "What statement defines your family's vision?", + "What is your family's impact statement?", +]; export const useChatHandler = () => { const { chatMessages, setChatMessages } = useContext(ChatbotUIContext); - const [chatInput, setChatInput] = useState(''); + const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); const [isGenerating, setIsGenerating] = useState(false); + const [questionsCompleted, setQuestionsCompleted] = useState(false); - const createChatBotMessage = useCallback((message) => ({ - type: 'bot', - text: message + const createChatBotMessage = useCallback((text, messageType): ChatMessage => ({ + id: uuidv4(), + type: messageType, + message: text }), []); - const actionProvider = useMemo(() => new ActionProvider( - createChatBotMessage, - (update) => { - setChatMessages(prev => [...prev, ...(update.messages || [])]); + const addMessage = useCallback((message: ChatMessage) => { + setChatMessages(prevMessages => [...prevMessages, message]); + }, [setChatMessages]); + + const handleQuestions = useCallback(() => { + if (currentQuestionIndex < questions.length) { + addMessage(createChatBotMessage(questions[currentQuestionIndex], 'bot')); + setCurrentQuestionIndex(current => current + 1); + } else { + addMessage(createChatBotMessage("You may type exit to finish creating your charter.", 'bot')); + setQuestionsCompleted(true); // Mark the completion of questions } - ), [setChatMessages, createChatBotMessage]); + }, [addMessage, createChatBotMessage, currentQuestionIndex]); - // Handling new chat sessions - const handleNewChat = useCallback(() => { - setChatMessages([]); // Resetting the chat messages - actionProvider.handleStart(); // Assuming handleStart initializes a new chat session - }, [setChatMessages, actionProvider]); - - // Handling focus on chat input - const handleFocusChatInput = useCallback(() => { - const inputElement = document.getElementById('chat-input'); - if (inputElement) { - inputElement.focus(); + const submitResponses = useCallback(async () => { + // Logic to submit responses to the backend + // After submitting, allow API interactions + const userResponses = chatMessages.filter(msg => msg.type === 'user').map(msg => msg.message); + try { + const response = await fetch('http://localhost:8000/api/chat/submit_responses', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ responses: userResponses }), + credentials: 'include' + }); + if (!response.ok) { + throw new Error(`HTTP error ${response.status}`); + } + const result = await response.json(); + console.log("Responses submitted successfully:", result); + } catch (error) { + console.error("Failed to submit responses:", error); } - }, []); + }, [chatMessages]); - const handleSendMessage = useCallback(async (message) => { - setIsGenerating(true); + const sendMessageToAPI = useCallback(async (message) => { try { - await actionProvider.handleMessage(message); + const response = await fetch('http://localhost:8000/api/chat/send_message', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message }), + credentials: 'include' + }); + if (!response.ok) throw new Error(`HTTP error ${response.status}`); + const { answer } = await response.json(); + return answer; } catch (error) { - console.error("Failed to handle message:", error); + console.error("Error during fetch or parsing:", error); + return "Sorry, there was an error processing your message."; } - setIsGenerating(false); - }, [actionProvider]); + }, []); - useEffect(() => { - actionProvider.handleStart(); // Automatically start the conversation - }, [actionProvider]); + const handleSendMessage = useCallback(async (messageText: string) => { + setIsGenerating(true); + const normalizedText = messageText.trim().toLowerCase(); + try { + addMessage(createChatBotMessage(messageText, 'user')); + + // Start the questionnaire if "start" is input and it's the first interaction. + if (normalizedText === "start" && currentQuestionIndex === 0) { + handleQuestions(); + } else if (normalizedText === "exit" && questionsCompleted) { + // Allow exiting only if the questionnaire has been completed. + await submitResponses(); + setChatMessages([]); + setCurrentQuestionIndex(0); + setQuestionsCompleted(false); + addMessage(createChatBotMessage("Thank you, I have recorded your responses. Goodbye!", 'bot')); + } else { + // Handle general inquiries or continue with questions if the session has started. + if (currentQuestionIndex > 0 && currentQuestionIndex <= questions.length) { + handleQuestions(); + } else { + // If no questionnaire is active or needed, handle as a general inquiry. + if (questionsCompleted || currentQuestionIndex === 0) { + const apiResponse = await sendMessageToAPI(messageText); + addMessage(createChatBotMessage(apiResponse, 'bot')); + } else { + // Prompt to continue or start the questionnaire if not yet started. + addMessage(createChatBotMessage("Please type 'start' to begin the questionnaire, or ask anything else.", 'bot')); + } + } + } + } finally { + setIsGenerating(false); + } + }, [addMessage, handleQuestions, createChatBotMessage, currentQuestionIndex, setChatMessages, submitResponses, sendMessageToAPI, questionsCompleted]); + + const handleNewChat = useCallback(() => { + setChatMessages([]); + setCurrentQuestionIndex(0); + setQuestionsCompleted(false); // Reset the question completion status + }, [setChatMessages]); return { chatMessages, - chatInput, - setChatInput, handleSendMessage, - handleExitConversation: actionProvider.handleExit, // Assuming handleExit is defined on actionProvider handleNewChat, - handleFocusChatInput, isGenerating, }; }; diff --git a/src/app/chat/hooks/use-scroll.tsx b/src/app/chat/hooks/use-scroll.tsx index 3f42c39..68b6812 100644 --- a/src/app/chat/hooks/use-scroll.tsx +++ b/src/app/chat/hooks/use-scroll.tsx @@ -26,13 +26,7 @@ export const useScroll = () => { if (!isGenerating && userScrolled) { setUserScrolled(false) } - }, [isGenerating]) - - useEffect(() => { - if (isGenerating && !userScrolled) { - scrollToBottom() - } - }, [chatMessages]) + }, [isGenerating, userScrolled]) const handleScroll: UIEventHandler = useCallback(e => { const target = e.target as HTMLDivElement @@ -72,6 +66,12 @@ export const useScroll = () => { }, 100) }, []) + useEffect(() => { + if (isGenerating && !userScrolled) { + scrollToBottom() + } + }, [chatMessages, isGenerating, userScrolled, scrollToBottom]) + return { messagesStartRef, messagesEndRef, diff --git a/src/app/chat/message.tsx b/src/app/chat/message.tsx index 3009c17..8f76b72 100644 --- a/src/app/chat/message.tsx +++ b/src/app/chat/message.tsx @@ -9,12 +9,15 @@ export const Message: React.FC = ({ onCancelEdit, onSubmitEdit, }) => { - const [editContent, setEditContent] = useState(message.message); + const [editContent, setEditContent] = useState(message.message || ''); const handleEditSubmit = () => { - onSubmitEdit?.(editContent); - onCancelEdit?.(); + if (editContent !== undefined) { + onSubmitEdit?.(editContent); + } + onCancelEdit?.(); }; + return (
diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx index ebb6eb8..4536c52 100644 --- a/src/app/chat/page.tsx +++ b/src/app/chat/page.tsx @@ -1,41 +1,64 @@ "use client"; -import { ChatInput } from "@/app/chat/chat-input"; +import { useState, useContext, useEffect } from 'react'; +import { ChatbotUIContext, ChatbotUIProvider } from '@/app/chat/context'; +import { useChatHandler } from '@/app/chat/hooks/use-chat-handler'; +// import { ChatInput } from "@/app/chat/chat-input"; import { ChatUI } from "@/app/chat/chat-ui"; -import { Brand } from "@/components/ui/brand"; -import { ChatbotUIProvider, ChatbotUIContext } from "@/app/chat/context"; +// import { Brand } from "@/components/ui/brand"; import useHotkey from "@/lib/hooks/use-hotkey"; -import { useTheme } from "next-themes"; -import { useContext } from "react"; -import { useChatHandler } from "@/app/chat/hooks/use-chat-handler"; +// import { useTheme } from "next-themes"; export default function ChatPage() { const { chatMessages } = useContext(ChatbotUIContext); - const { handleNewChat, handleFocusChatInput } = useChatHandler(); - const { theme } = useTheme(); + const { handleNewChat } = useChatHandler(); + // const { theme } = useTheme(); + + const [chatStarted, setChatStarted] = useState(false); useHotkey("o", handleNewChat); - useHotkey("l", handleFocusChatInput); + useEffect(() => { + console.log("Chat messages changed:", chatMessages.map(m => m.message)); // Confirm messages are present + + const lastMessageText = chatMessages[chatMessages.length - 1]?.message?.toLowerCase() ?? ""; + + console.log("Last message text:", lastMessageText); // Should show the actual last message or an empty string + + if (!chatStarted && lastMessageText === "start") { + console.log("Start conditions met. Starting chat."); + setChatStarted(true); + } + }, [chatMessages, chatStarted]); // Keep chatStarted to avoid unnecessary re-renders when it changes + + console.log("Rendering ChatPage, Chat Started:", chatStarted); + + // return ( + // + // {!chatStarted ? ( + //
+ //
+ // + //
+ //
+ //
+ //
+ //
+ // + //
+ //
+ //
+ //
+ // ) : ( + // + // )} + // + // ); return ( - - {chatMessages.length === 0 ? ( -
-
- -
-
-
-
-
- -
-
-
-
- ) : ( + + - )} + ); } diff --git a/src/app/chat/styles.css b/src/app/chat/styles.css new file mode 100644 index 0000000..9659118 --- /dev/null +++ b/src/app/chat/styles.css @@ -0,0 +1,43 @@ +/* Basic container styling */ +.chat-container { + display: flex; + flex-direction: column; + max-width: 800px; /* Adjusted from a fixed pixel width to a percentage */ + margin: auto; + padding: 25px; + /* background: #f9f9f9; */ + height: 500px; + overflow-y: scroll; + /* border: 1px solid #ccc; Optional: adds a border for better visibility */ +} + +/* Generic message styling */ +.message { + margin-bottom: 10px; + padding: 8px 15px; + border-radius: 10px; + max-width: 100%; /* Allow messages to take full width of the container */ + word-wrap: break-word; +} + +/* Bot message styling */ +.bot-message { + align-self: flex-start; + /* background-color: #e0e0e0; */ + width: auto; /* Ensure messages can grow as needed */ +} + +.bot-text { + color: #333; +} + +/* User message styling */ +.user-message { + align-self: flex-end; + background-color: #474a4d; + width: auto; /* Ensure messages can grow as needed */ +} + +.user-text { + color: white; +} diff --git a/src/app/chat/types/types.ts b/src/app/chat/types/types.ts index 711aa05..58c2a53 100644 --- a/src/app/chat/types/types.ts +++ b/src/app/chat/types/types.ts @@ -23,10 +23,10 @@ export interface MessageProps { } export interface ChatMessage { - id: string; + id?: string; type: 'user' | 'bot'; - message: string; - fileItems: ChatFile[]; + message?: string; + fileItems?: ChatFile[]; } export interface ChatFile { diff --git a/src/app/model/model.py b/src/app/model/model.py index a19f672..f977176 100644 --- a/src/app/model/model.py +++ b/src/app/model/model.py @@ -1,5 +1,6 @@ import os +import json from dotenv import load_dotenv from pathlib import Path from langchain.chains.conversational_retrieval.base import ConversationalRetrievalChain @@ -39,9 +40,46 @@ memory=memory ) + # questions = [ + # "Enter up to three questions that guide your family’s decision making.", + # "What are your family values?", + # "What is a statement or commitment that your family lives by?", + # "What statement defines your family's vision?", + # "What is your family's impact statement?" + # ] + + # # Function to ask questions and record responses + # def ask_questions(questions): + # responses = {} + # for question in questions: + # print(f"Videre AI: {question}") + # response = input("You: ") + # responses[question] = response + # result = conversation_chain.invoke({"question": + # "Here is the response that the user provided: \n" + response + " \nIn your response back, I would like you to acknowledge the user's response in a friendly manner and then rephrase it back so that the user knows you understand their response. Please be sure to not ask a question back to the user, just rephrase their response back to them in a friendly manner. This is very crucial: If the response is very short (one or two words), makes no grammatical sense, and/or is blank/empty, then make sure you don't say anything."}) + # answer = result["answer"] + # print(f"Videre AI: {answer}") + # with open('family_charter_responses.json', 'w') as file: + # json.dump(responses, file) + # return responses + + # # The loop to interact with the user + # print("Welcome to VidereAI. Type 'exit' to end the conversation or 'start' to begin the family charter process.") + # while True: + # query = input("You: ") + # if query.lower() == 'exit': + # print("Goodbye!") + # break + # elif query.lower() == 'start': + # user_responses = ask_questions(questions) + # print("Videre AI: I have recorded all of your responses. Thank you for providing the information. If you would like to update your responses, please type 'start' again.") + # else: + # result = conversation_chain.invoke({"question": query}) + # answer = result["answer"] + # print(f"Videre AI: {answer}") + except Exception as e: - print(f"An error occurred during initialization: {e}") - exit(1) + print(f"An error occurred: {e}") # Function to process chat messages def process_message(user_message):