Skip to content
Snippets Groups Projects
Commit 0013e743 authored by Embruch, Gerd's avatar Embruch, Gerd
Browse files

added chat input an history update

parent 20dfd2ba
Branches
No related tags found
No related merge requests found
import React, { useEffect, useState } from 'react';
import Chat from './Chat';
import Heading from '../font/Heading';
import { RiArrowLeftCircleLine, RiArrowRightCircleLine } from 'react-icons/ri';
import { RiAddCircleLine, RiArrowLeftCircleLine, RiArrowRightCircleLine } from 'react-icons/ri';
import { useChat } from '../../contexts/Chat/ChatState';
import { useParams } from 'react-router-dom';
......@@ -17,7 +17,7 @@ const Chats = () => {
const { id } = useParams();
// ### CONNECT CONTEXT
const { fetchAllChats, chatHeadings } = useChat();
const { fetchAllChats, chatHeadings, currentChatId, selectChat } = useChat();
// ### FETCH CHATS;
useEffect(() => {
......@@ -26,8 +26,8 @@ const Chats = () => {
const getChats = async () => {
try {
// fetch all chats (and directly show id if provided)
await fetchAllChats(id);
// fetch all chats
await fetchAllChats(id || null);
} catch (error) {
console.error(error);
mergeBackendValidation(error.response.status, error.response.data);
......@@ -42,6 +42,7 @@ const Chats = () => {
};
}, []);
// #################################
// FUNCTIONS
// #################################
......@@ -52,6 +53,9 @@ const Chats = () => {
return (
<div className="row-start-2 row-span-2 border-r-2 border-UhhGrey flex flex-col">
<Heading level="6" className="text-center">Recent</Heading>
{<button onClick={() => { selectChat(null); }} disabled={currentChatId ? false : true} className='text-UhhBlue disabled:text-UhhLightBlue' title='start a new chat'><RiAddCircleLine /></button>}
<div className="p-1">
{showSidebar &&
chatHeadings.map((chat, index) => (
......
import React, { useEffect } from 'react';
import React, { useEffect, useRef } from 'react';
import Message from './Message';
import { useChat } from '../../contexts/Chat/ChatState';
import { Link } from 'react-router-dom';
......@@ -12,11 +12,14 @@ const Messages = () => {
// ### CONNECT CONTEXT
const { currentChatId, fetchChatHistory, chatHistory } = useChat();
// ### FETCH HISTORY EVERY TIME THE CHAT ID CHANGES
useEffect(() => {
// ### on run exec this code
const controller = new AbortController();
// ### fetch chat history based on current chat id
fetchChatHistory(currentChatId);
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
// ### return will be executed on unmounting this component
return () => {
// on unmount abort request
......@@ -24,6 +27,11 @@ const Messages = () => {
};
}, [currentChatId]);
// ### SCROLL TO BOTTOM
const bottomRef = useRef();
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [chatHistory]);
// #################################
// FUNCTIONS
......@@ -34,7 +42,7 @@ const Messages = () => {
// #################################
return (
<div className="row-start-2 overflow-auto">
{<Link to={`/onboarding/${currentChatId}`} className='text-UhhBlue' target='_blank' rel='noopener noreferrer'><RxBookmark /></Link>}
{currentChatId && <Link to={`/onboarding/${currentChatId}`} className='text-UhhBlue' target='_blank' rel='noopener noreferrer'><RxBookmark /></Link>}
{chatHistory.map((prompt, index) => (
<Message
key={index}
......@@ -43,7 +51,10 @@ const Messages = () => {
/>
))
}
{!chatHistory.length && <div className="text-center text-gray-500">No messages yet</div>}
<div ref={bottomRef}> </div>
</div>
);
};
......
import { zodResolver } from '@hookform/resolvers/zod';
import React from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { RiSendPlane2Line } from 'react-icons/ri';
import { RiLoopRightFill, RiSendPlane2Line } from 'react-icons/ri';
import { z } from 'zod';
import Input from '../form/Input';
import { mergeBackendValidation } from '../../utils/ErrorHandling';
import api from '../../utils/AxiosConfig';
import { useChat } from '../../contexts/Chat/ChatState';
import Select from '../form/Select';
function PromptInput() {
// #################################
......@@ -22,7 +23,7 @@ function PromptInput() {
// HOOKS
// #################################
// ### CONNECT CONTEXT
const { currentChatId } = useChat();
const { currentChatId, availableModels, updateChatHistory } = useChat();
// ### PREPARE FORM
const methods = useForm({
......@@ -43,12 +44,15 @@ function PromptInput() {
if (currentChatId) { inputs.chatId = currentChatId; };
// send data to api
try {
// send input to api
const result = await api.post('/ai/chat', inputs);
// TODO: update chat context
console.log("🚀 ~ handleSendForm ~ result:", result);
// update chat history
updateChatHistory(currentChatId, result.data.chat.chatHistory);
// clear input field
methods.resetField('input');
} catch (error) {
console.error(error);
// merge front & backend validation errors
mergeBackendValidation(error.response.status, error.response.data, methods.setError);
}
......@@ -63,8 +67,10 @@ function PromptInput() {
<div className="row-start-3 p-3 border-t-2 border-UhhGrey">
<FormProvider {...methods} >
<form onSubmit={methods.handleSubmit(handleSendForm)}>
<div className="flex content-center h-14">
<Input name="model" />
<div className="flex content-center h-14 relative">
{methods.formState.isSubmitting && <div className='absolute bg-white bg-opacity-60 z-10 h-full w-full flex items-center justify-center text-2xl'><RiLoopRightFill className='animate-spin' /></div>}
<Select name="model" options={availableModels} />
<Input name="input" type="text" placeholder="Type a message" className="block w-full h-8" />
<button type="submit" className="h-8 justify-center items-center bg-UhhBlue text-UhhWhite p-1 text-xs">
......
......@@ -44,7 +44,7 @@ function Select({ options, name, title, defaultValue, className, tooltip, type,
return (
<label htmlFor={id}>
{props.required && <RequiredBadge />}
{capitalizeFirstLetter(title)}
{title && capitalizeFirstLetter(title)}
{tooltip && <Tooltip>{tooltip}</Tooltip>}
<Controller
......
......@@ -5,10 +5,29 @@ const chatReducer = (state, action) => {
case CHAT_ACTIONS.SET_CHATS:
case CHAT_ACTIONS.SET_HEADINGS:
case CHAT_ACTIONS.UPDATE_CHATID:
case CHAT_ACTIONS.SET_HISTORY:
return action.payload;
{ return action.payload; }
case CHAT_ACTIONS.SET_HISTORY: {
if (!action.payload.id) return [];
// define a function to find the chat
const isSelectedChatId = element => element.id === action.payload.id;
// get index of matching item
const index = action.payload.chats.findIndex(isSelectedChatId);
// return history of chat
return action.payload.chats[index].chatHistory;
}
case CHAT_ACTIONS.SET_MODELS: {
const models = action.payload.data;
const modelNames = models.map(model => {
return { title: model.name, _id: model.name };
});
return modelNames;
}
default:
return state;
{ return state; }
}
};
......
......@@ -19,6 +19,7 @@ function ChatState({ children }) {
const [chatHeadings, dispatchChatHeadings] = useReducer(chatReducer, []);
const [currentChatId, dispatchCurrentChatId] = useReducer(chatReducer, null);
const [chatHistory, dispatchChatHistory] = useReducer(chatReducer, []);
const [availableModels, dispatchAvailableModels] = useReducer(chatReducer, []);
......@@ -35,6 +36,9 @@ function ChatState({ children }) {
fetchChatHeadings(items.data.chats);
// select chat if id is provided
if (id) selectChat(id);
// fetch available models
const models = await api.post('/ai/models', { filter: '' });
dispatchAvailableModels({ type: CHAT_ACTIONS.SET_MODELS, payload: models });
} catch (error) {
// display errors
mergeBackendValidation(error.response.status, error.response.data);
......@@ -61,16 +65,18 @@ function ChatState({ children }) {
// ### FETCH CHAT HISTORY
function fetchChatHistory(id) {
// return empty if no chat was chosen
if (!id) return [];
dispatchChatHistory({ type: CHAT_ACTIONS.SET_HISTORY, payload: { id, chats } });
}
// ### UPDATE CHAT HISTORY
function updateChatHistory(id, history) {
// define a function to find the chat
const isSelectedChatId = element => element.id === id;
// get index of matching item
const index = chats.findIndex(isSelectedChatId);
// return history of chat
const history = chats[index].chatHistory;
// save history in state
dispatchChatHistory({ type: CHAT_ACTIONS.SET_HISTORY, payload: history });
chats[index].chatHistory = history;
dispatchChatHistory({ type: CHAT_ACTIONS.SET_HISTORY, payload: { id, chats } });
}
......@@ -92,7 +98,9 @@ function ChatState({ children }) {
currentChatId,
selectChat,
fetchChatHistory,
chatHistory
chatHistory,
availableModels,
updateChatHistory
}}>
{children}
</ChatContext.Provider>
......
......@@ -2,5 +2,6 @@ export const CHAT_ACTIONS = {
SET_CHATS: 'set_chats',
SET_HEADINGS: 'set_headings',
SET_HISTORY: 'set_history',
UPDATE_CHATID: 'update_chatid'
UPDATE_CHATID: 'update_chatid',
SET_MODELS: 'set_models'
};
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment