mirror of
https://github.com/arc53/DocsGPT
synced 2024-11-03 23:15:37 +00:00
Adds mock conversation api layer, adds reducers to handle various asyc
state and wires it with conversation UI
This commit is contained in:
parent
63859a814b
commit
a036a6b979
9
frontend/src/assets/spinner.svg
Normal file
9
frontend/src/assets/spinner.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg width="30" height="33" viewBox="0 0 30 33" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect width="30" height="33" fill="url(#pattern0)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_1_917" transform="scale(0.0166667 0.0151515)"/>
|
||||
</pattern>
|
||||
<image id="image0_1_917" width="60" height="66" xlink:href="data:image/png;base64,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"/>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
@ -1,15 +1,32 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import Hero from '../Hero';
|
||||
import { AppDispatch } from '../store';
|
||||
import ConversationBubble from './ConversationBubble';
|
||||
import ConversationInput from './ConversationInput';
|
||||
import { selectConversation } from './conversationSlice';
|
||||
import {
|
||||
addMessage,
|
||||
fetchAnswer,
|
||||
selectConversation,
|
||||
selectStatus,
|
||||
} from './conversationSlice';
|
||||
import Send from './../assets/send.svg';
|
||||
import Spinner from './../assets/spinner.svg';
|
||||
|
||||
export default function Conversation() {
|
||||
const messages = useSelector(selectConversation);
|
||||
const status = useSelector(selectStatus);
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const endMessageRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => endMessageRef?.current?.scrollIntoView());
|
||||
useEffect(() =>
|
||||
endMessageRef?.current?.scrollIntoView({ behavior: 'smooth' }),
|
||||
);
|
||||
|
||||
const handleQuestion = (question: string) => {
|
||||
dispatch(addMessage({ text: question, type: 'QUESTION' }));
|
||||
dispatch(fetchAnswer({ question }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-center p-6">
|
||||
@ -27,7 +44,30 @@ export default function Conversation() {
|
||||
})}
|
||||
{messages.length === 0 && <Hero className="mt-24"></Hero>}
|
||||
</div>
|
||||
<ConversationInput className="fixed bottom-2 w-10/12 md:w-[50%]"></ConversationInput>
|
||||
<div className="fixed bottom-2 flex w-10/12 md:w-[50%]">
|
||||
<div
|
||||
ref={inputRef}
|
||||
contentEditable
|
||||
className={`min-h-5 border-000000 overflow-x-hidden; max-h-24 w-full overflow-y-auto rounded-xl border bg-white p-2 pr-9 opacity-100 focus:border-2 focus:outline-none`}
|
||||
></div>
|
||||
{status === 'loading' ? (
|
||||
<img
|
||||
src={Spinner}
|
||||
className="relative right-9 animate-spin cursor-pointer"
|
||||
></img>
|
||||
) : (
|
||||
<img
|
||||
onClick={() => {
|
||||
if (inputRef.current?.textContent) {
|
||||
handleQuestion(inputRef.current.textContent);
|
||||
inputRef.current.textContent = '';
|
||||
}
|
||||
}}
|
||||
src={Send}
|
||||
className="relative right-9 cursor-pointer"
|
||||
></img>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
import Send from './../assets/send.svg';
|
||||
|
||||
export default function ConversationInput({
|
||||
className,
|
||||
}: {
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={`${className} flex`}>
|
||||
<div
|
||||
contentEditable
|
||||
className={`min-h-5 border-000000 overflow-x-hidden; max-h-24 w-full overflow-y-auto rounded-xl border bg-white p-2 pr-9 opacity-100 focus:border-2 focus:outline-none`}
|
||||
></div>
|
||||
<img
|
||||
onClick={() => console.log('here')}
|
||||
src={Send}
|
||||
className="relative right-9"
|
||||
></img>
|
||||
</div>
|
||||
);
|
||||
}
|
24
frontend/src/conversation/conversationApi.ts
Normal file
24
frontend/src/conversation/conversationApi.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Answer } from './conversationModels';
|
||||
|
||||
export function fetchAnswerApi(
|
||||
question: string,
|
||||
apiKey: string,
|
||||
): Promise<Answer> {
|
||||
// a mock answer generator, this is going to be replaced with real http call
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
let result = '';
|
||||
const characters =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const charactersLength = characters.length;
|
||||
let counter = 0;
|
||||
while (counter < 5) {
|
||||
result += characters.charAt(
|
||||
Math.floor(Math.random() * charactersLength),
|
||||
);
|
||||
counter += 1;
|
||||
}
|
||||
resolve({ answer: result, query: question, result });
|
||||
}, 3000);
|
||||
});
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
export type MESSAGE_TYPE = 'QUESTION' | 'ANSWER';
|
||||
export type Status = 'idle' | 'loading' | 'failed';
|
||||
|
||||
export interface Message {
|
||||
text: string;
|
||||
@ -7,4 +8,11 @@ export interface Message {
|
||||
|
||||
export interface ConversationState {
|
||||
conversation: Message[];
|
||||
status: Status;
|
||||
}
|
||||
|
||||
export interface Answer {
|
||||
answer: string;
|
||||
query: string;
|
||||
result: string;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import store from '../store';
|
||||
import { ConversationState, Message } from './conversationModels';
|
||||
import { fetchAnswerApi } from './conversationApi';
|
||||
import { Answer, ConversationState, Message } from './conversationModels';
|
||||
|
||||
// harcoding the initial state just for demo
|
||||
const initialState: ConversationState = {
|
||||
@ -24,13 +25,21 @@ const initialState: ConversationState = {
|
||||
{ text: 'what is ChatGPT', type: 'QUESTION' },
|
||||
{ text: 'ChatGPT is large learning model', type: 'ANSWER' },
|
||||
{ text: 'what is ChatGPT', type: 'QUESTION' },
|
||||
{
|
||||
text: 'ChatGPT is large learning model',
|
||||
type: 'ANSWER',
|
||||
},
|
||||
{ text: 'ChatGPT is large learning model', type: 'ANSWER' },
|
||||
],
|
||||
status: 'idle',
|
||||
};
|
||||
|
||||
export const fetchAnswer = createAsyncThunk<
|
||||
Answer,
|
||||
{ question: string },
|
||||
{ state: RootState }
|
||||
>('fetchAnswer', async ({ question }, { getState }) => {
|
||||
const state = getState();
|
||||
const answer = await fetchAnswerApi(question, state.preference.apiKey);
|
||||
return answer;
|
||||
});
|
||||
|
||||
export const conversationSlice = createSlice({
|
||||
name: 'conversation',
|
||||
initialState,
|
||||
@ -39,12 +48,30 @@ export const conversationSlice = createSlice({
|
||||
state.conversation.push(action.payload);
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
builder
|
||||
.addCase(fetchAnswer.pending, (state) => {
|
||||
state.status = 'loading';
|
||||
})
|
||||
.addCase(fetchAnswer.fulfilled, (state, action) => {
|
||||
state.status = 'idle';
|
||||
state.conversation.push({
|
||||
text: action.payload.answer,
|
||||
type: 'ANSWER',
|
||||
});
|
||||
})
|
||||
.addCase(fetchAnswer.rejected, (state) => {
|
||||
state.status = 'failed';
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { addMessage } = conversationSlice.actions;
|
||||
|
||||
type RootState = ReturnType<typeof store.getState>;
|
||||
|
||||
export const selectConversation = (state: RootState) =>
|
||||
state.conversation.conversation;
|
||||
|
||||
export const selectStatus = (state: RootState) => state.conversation.status;
|
||||
|
||||
export const { addMessage } = conversationSlice.actions;
|
||||
export default conversationSlice.reducer;
|
||||
|
@ -9,4 +9,5 @@ const store = configureStore({
|
||||
},
|
||||
});
|
||||
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
export default store;
|
||||
|
Loading…
Reference in New Issue
Block a user