diff --git a/application/app.py b/application/app.py index bb349cb3..3dbd07c7 100644 --- a/application/app.py +++ b/application/app.py @@ -189,6 +189,33 @@ def check_docs(): return {"status": 'loaded'} +@app.route("/api/feedback", methods=["POST"]) +def api_feedback(): + data = request.get_json() + question = data["question"] + answer = data["answer"] + feedback = data["feedback"] + + print('-' * 5) + print("Question: " + question) + print("Answer: " + answer) + print("Feedback: " + feedback) + print('-' * 5) + response = requests.post( + url="https://86x89umx77.execute-api.eu-west-2.amazonaws.com/docsgpt-feedback", + headers={ + "Content-Type": "application/json; charset=utf-8", + }, + data=json.dumps({ + "answer": answer, + "question": question, + "feedback": feedback + }) + ) + return {"status": 'ok'} + + + # handling CORS @app.after_request def after_request(response): diff --git a/frontend/.env.development b/frontend/.env.development new file mode 100644 index 00000000..d9292fb3 --- /dev/null +++ b/frontend/.env.development @@ -0,0 +1,2 @@ +# Please put appropriate value +VITE_API_HOST = https://docsapi.arc53.com \ No newline at end of file diff --git a/frontend/.env.production b/frontend/.env.production new file mode 100644 index 00000000..46e738b1 --- /dev/null +++ b/frontend/.env.production @@ -0,0 +1 @@ +VITE_API_HOST = https://docsapi.arc53.com \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 098c0473..7e901646 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -560,6 +560,122 @@ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz", "integrity": "sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA==" }, + "@rollup/pluginutils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", + "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + } + }, + "@svgr/babel-plugin-add-jsx-attribute": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz", + "integrity": "sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==", + "dev": true + }, + "@svgr/babel-plugin-remove-jsx-attribute": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-6.5.0.tgz", + "integrity": "sha512-8zYdkym7qNyfXpWvu4yq46k41pyNM9SOstoWhKlm+IfdCE1DdnRKeMUPsWIEO/DEkaWxJ8T9esNdG3QwQ93jBA==", + "dev": true + }, + "@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-6.5.0.tgz", + "integrity": "sha512-NFdxMq3xA42Kb1UbzCVxplUc0iqSyM9X8kopImvFnB+uSDdzIHOdbs1op8ofAvVRtbg4oZiyRl3fTYeKcOe9Iw==", + "dev": true + }, + "@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz", + "integrity": "sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg==", + "dev": true + }, + "@svgr/babel-plugin-svg-dynamic-title": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz", + "integrity": "sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw==", + "dev": true + }, + "@svgr/babel-plugin-svg-em-dimensions": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz", + "integrity": "sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA==", + "dev": true + }, + "@svgr/babel-plugin-transform-react-native-svg": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz", + "integrity": "sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg==", + "dev": true + }, + "@svgr/babel-plugin-transform-svg-component": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz", + "integrity": "sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ==", + "dev": true + }, + "@svgr/babel-preset": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-6.5.1.tgz", + "integrity": "sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw==", + "dev": true, + "requires": { + "@svgr/babel-plugin-add-jsx-attribute": "^6.5.1", + "@svgr/babel-plugin-remove-jsx-attribute": "*", + "@svgr/babel-plugin-remove-jsx-empty-expression": "*", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^6.5.1", + "@svgr/babel-plugin-svg-dynamic-title": "^6.5.1", + "@svgr/babel-plugin-svg-em-dimensions": "^6.5.1", + "@svgr/babel-plugin-transform-react-native-svg": "^6.5.1", + "@svgr/babel-plugin-transform-svg-component": "^6.5.1" + } + }, + "@svgr/core": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-6.5.1.tgz", + "integrity": "sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==", + "dev": true, + "requires": { + "@babel/core": "^7.19.6", + "@svgr/babel-preset": "^6.5.1", + "@svgr/plugin-jsx": "^6.5.1", + "camelcase": "^6.2.0", + "cosmiconfig": "^7.0.1" + } + }, + "@svgr/hast-util-to-babel-ast": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz", + "integrity": "sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw==", + "dev": true, + "requires": { + "@babel/types": "^7.20.0", + "entities": "^4.4.0" + } + }, + "@svgr/plugin-jsx": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz", + "integrity": "sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw==", + "dev": true, + "requires": { + "@babel/core": "^7.19.6", + "@svgr/babel-preset": "^6.5.1", + "@svgr/hast-util-to-babel-ast": "^6.5.1", + "svg-parser": "^2.0.4" + } + }, + "@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "dev": true + }, "@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -581,6 +697,12 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, "@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -1097,6 +1219,12 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, "camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -1219,6 +1347,19 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, + "cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1331,6 +1472,21 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, "es-abstract": { "version": "1.21.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", @@ -1917,6 +2073,12 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2335,6 +2497,12 @@ "is-typed-array": "^1.1.10" } }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, "is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -2532,6 +2700,12 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2576,6 +2750,12 @@ "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", "dev": true }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, "lint-staged": { "version": "13.1.1", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.1.1.tgz", @@ -3052,6 +3232,18 @@ "callsites": "^3.0.0" } }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3652,6 +3844,12 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true + }, "tailwindcss": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz", @@ -3835,6 +4033,16 @@ "rollup": "^3.10.0" } }, + "vite-plugin-svgr": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-2.4.0.tgz", + "integrity": "sha512-q+mJJol6ThvqkkJvvVFEndI4EaKIjSI0I3jNFgSoC9fXAz1M7kYTVUin8fhUsFojFDKZ9VHKtX6NXNaOLpbsHA==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.2", + "@svgr/core": "^6.5.1" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index a8ffc131..4abaeba3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -49,6 +49,7 @@ "prettier-plugin-tailwindcss": "^0.2.2", "tailwindcss": "^3.2.4", "typescript": "^4.9.5", - "vite": "^4.1.0" + "vite": "^4.1.0", + "vite-plugin-svgr": "^2.4.0" } } diff --git a/frontend/src/assets/dislike.svg b/frontend/src/assets/dislike.svg new file mode 100644 index 00000000..9a51f1e2 --- /dev/null +++ b/frontend/src/assets/dislike.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/like.svg b/frontend/src/assets/like.svg new file mode 100644 index 00000000..9c2902f7 --- /dev/null +++ b/frontend/src/assets/like.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/conversation/Conversation.tsx b/frontend/src/conversation/Conversation.tsx index a33a4fe1..de8ace00 100644 --- a/frontend/src/conversation/Conversation.tsx +++ b/frontend/src/conversation/Conversation.tsx @@ -1,19 +1,22 @@ -import { useEffect, useRef } from 'react'; +import { Fragment, useEffect, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import Hero from '../Hero'; import { AppDispatch } from '../store'; import ConversationBubble from './ConversationBubble'; import { - addMessage, + addQuery, fetchAnswer, - selectConversation, + selectQueries, selectStatus, + updateQuery, } from './conversationSlice'; import Send from './../assets/send.svg'; import Spinner from './../assets/spinner.svg'; +import { FEEDBACK, Query } from './conversationModels'; +import { sendFeedback } from './conversationApi'; export default function Conversation() { - const messages = useSelector(selectConversation); + const queries = useSelector(selectQueries); const status = useSelector(selectStatus); const dispatch = useDispatch(); const endMessageRef = useRef(null); @@ -21,34 +24,77 @@ export default function Conversation() { useEffect( () => endMessageRef?.current?.scrollIntoView({ behavior: 'smooth' }), - [messages], + [queries.length, queries[queries.length - 1]], ); const handleQuestion = (question: string) => { - dispatch(addMessage({ text: question, type: 'QUESTION' })); + dispatch(addQuery({ prompt: question })); dispatch(fetchAnswer({ question })); }; + const handleFeedback = (query: Query, feedback: FEEDBACK, index: number) => { + const prevFeedback = query.feedback; + dispatch(updateQuery({ index, query: { feedback } })); + sendFeedback(query.prompt, query.response!, feedback).catch(() => + dispatch(updateQuery({ index, query: { feedback: prevFeedback } })), + ); + }; + + const prepResponseView = (query: Query, index: number) => { + let responseView; + if (query.error) { + responseView = ( + + ); + } else if (query.response) { + responseView = ( + + handleFeedback(query, feedback, index) + } + > + ); + } + return responseView; + }; + return (
- {messages.length > 0 && ( + {queries.length > 0 && (
- {messages.map((message, index) => { + {queries.map((query, index) => { return ( - + + + {prepResponseView(query, index)} + ); })}
)} - {messages.length === 0 && } + {queries.length === 0 && }
void; } ->(function ConversationBubble({ message, type, className }, ref) { - return ( -
- -
- {type === 'ERROR' && ( - alert - )} -

{message}

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

{message}

+
-
- ); + ); + } else { + bubble = ( +
setShowFeedback(true)} + onMouseLeave={() => setShowFeedback(false)} + > + +
+ {type === 'ERROR' && ( + alert + )} +

{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()], });