void;
}
->(function ConversationBubble({ message, type, className }, ref) {
- return (
-
-
-
- {type === 'ERROR' && (
-

- )}
-
{message}
+>(function ConversationBubble(
+ { message, type, className, feedback, handleFeedback },
+ ref,
+) {
+ const [showFeedback, setShowFeedback] = useState(false);
+ let bubble;
+
+ if (type === 'QUESTION') {
+ bubble = (
+
-
- );
+ );
+ } else {
+ bubble = (
+
setShowFeedback(true)}
+ onMouseLeave={() => setShowFeedback(false)}
+ >
+
+
+ {type === 'ERROR' && (
+

+ )}
+
{message}
+
+
+ handleFeedback?.('LIKE')}
+ >
+
+
+ handleFeedback?.('DISLIKE')}
+ >
+
+
+ );
+ }
+ return bubble;
});
export default ConversationBubble;
-
-// TODO : split question and answer into two diff JSX
diff --git a/frontend/src/conversation/conversationApi.ts b/frontend/src/conversation/conversationApi.ts
index e48d8368..a0575682 100644
--- a/frontend/src/conversation/conversationApi.ts
+++ b/frontend/src/conversation/conversationApi.ts
@@ -1,6 +1,8 @@
-import { Answer } from './conversationModels';
+import { Answer, FEEDBACK } from './conversationModels';
import { Doc } from '../preferences/preferenceApi';
+const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
+
export function fetchAnswerApi(
question: string,
apiKey: string,
@@ -23,8 +25,6 @@ export function fetchAnswerApi(
selectedDocs.model +
'/';
- const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
-
return fetch(apiHost + '/api/answer', {
method: 'POST',
headers: {
@@ -51,8 +51,26 @@ export function fetchAnswerApi(
});
}
-function getRandomInt(min: number, max: number) {
- min = Math.ceil(min);
- max = Math.floor(max);
- return Math.floor(Math.random() * (max - min) + min); // The maximum is exclusive and the minimum is inclusive
+export function sendFeedback(
+ prompt: string,
+ response: string,
+ feedback: FEEDBACK,
+) {
+ return fetch(`${apiHost}/api/feedback`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ question: prompt,
+ answer: response,
+ feedback: feedback,
+ }),
+ }).then((response) => {
+ if (response.ok) {
+ return Promise.resolve();
+ } else {
+ return Promise.reject();
+ }
+ });
}
diff --git a/frontend/src/conversation/conversationModels.ts b/frontend/src/conversation/conversationModels.ts
index c30977d1..92ed976f 100644
--- a/frontend/src/conversation/conversationModels.ts
+++ b/frontend/src/conversation/conversationModels.ts
@@ -1,5 +1,6 @@
export type MESSAGE_TYPE = 'QUESTION' | 'ANSWER' | 'ERROR';
export type Status = 'idle' | 'loading' | 'failed';
+export type FEEDBACK = 'LIKE' | 'DISLIKE';
export interface Message {
text: string;
@@ -7,7 +8,7 @@ export interface Message {
}
export interface ConversationState {
- conversation: Message[];
+ queries: Query[];
status: Status;
}
@@ -16,3 +17,10 @@ export interface Answer {
query: string;
result: string;
}
+
+export interface Query {
+ prompt: string;
+ response?: string;
+ feedback?: FEEDBACK;
+ error?: string;
+}
diff --git a/frontend/src/conversation/conversationSlice.ts b/frontend/src/conversation/conversationSlice.ts
index a822a380..c728b9e0 100644
--- a/frontend/src/conversation/conversationSlice.ts
+++ b/frontend/src/conversation/conversationSlice.ts
@@ -1,10 +1,10 @@
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import store from '../store';
import { fetchAnswerApi } from './conversationApi';
-import { Answer, ConversationState, Message } from './conversationModels';
+import { Answer, ConversationState, Query } from './conversationModels';
const initialState: ConversationState = {
- conversation: [],
+ queries: [],
status: 'idle',
};
@@ -27,8 +27,18 @@ export const conversationSlice = createSlice({
name: 'conversation',
initialState,
reducers: {
- addMessage(state, action: PayloadAction
) {
- state.conversation.push(action.payload);
+ addQuery(state, action: PayloadAction) {
+ state.queries.push(action.payload);
+ },
+ updateQuery(
+ state,
+ action: PayloadAction<{ index: number; query: Partial }>,
+ ) {
+ const index = action.payload.index;
+ state.queries[index] = {
+ ...state.queries[index],
+ ...action.payload.query,
+ };
},
},
extraReducers(builder) {
@@ -38,27 +48,22 @@ export const conversationSlice = createSlice({
})
.addCase(fetchAnswer.fulfilled, (state, action) => {
state.status = 'idle';
- state.conversation.push({
- text: action.payload.answer,
- type: 'ANSWER',
- });
+ state.queries[state.queries.length - 1].response =
+ action.payload.answer;
})
.addCase(fetchAnswer.rejected, (state, action) => {
state.status = 'failed';
- state.conversation.push({
- text: 'Something went wrong. Please try again later.',
- type: 'ERROR',
- });
+ state.queries[state.queries.length - 1].error =
+ 'Something went wrong. Please try again later.';
});
},
});
type RootState = ReturnType;
-export const selectConversation = (state: RootState) =>
- state.conversation.conversation;
+export const selectQueries = (state: RootState) => state.conversation.queries;
export const selectStatus = (state: RootState) => state.conversation.status;
-export const { addMessage } = conversationSlice.actions;
+export const { addQuery, updateQuery } = conversationSlice.actions;
export default conversationSlice.reducer;
diff --git a/frontend/svg.d.ts b/frontend/svg.d.ts
new file mode 100644
index 00000000..6326494c
--- /dev/null
+++ b/frontend/svg.d.ts
@@ -0,0 +1,7 @@
+declare module '*.svg' {
+ import * as React from 'react';
+
+ export const ReactComponent: React.FunctionComponent<
+ React.SVGProps & { title?: string }
+ >;
+}
diff --git a/frontend/tailwind.config.cjs b/frontend/tailwind.config.cjs
index 703d3c4f..2c44007a 100644
--- a/frontend/tailwind.config.cjs
+++ b/frontend/tailwind.config.cjs
@@ -15,6 +15,7 @@ module.exports = {
'gray-1000': '#F6F6F6',
'gray-2000': 'rgba(0, 0, 0, 0.5)',
'gray-3000': 'rgba(243, 243, 243, 1)',
+ 'gray-4000': '#949494',
'red-1000': 'rgb(254, 202, 202)',
'red-2000': '#F44336',
'red-3000': '#621B16',
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
index 3d0a51a8..c4d54426 100644
--- a/frontend/tsconfig.json
+++ b/frontend/tsconfig.json
@@ -16,6 +16,6 @@
"noEmit": true,
"jsx": "react-jsx"
},
- "include": ["src"],
+ "include": ["src", "svg.d.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 627a3196..c129badf 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -1,7 +1,8 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
+import svgr from 'vite-plugin-svgr';
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [react()],
+ plugins: [react(), svgr()],
});