From 99b103909f99f549b3afb49dd05400ae4164deab Mon Sep 17 00:00:00 2001 From: John Rogers Date: Fri, 17 May 2024 10:11:20 +0100 Subject: [PATCH 1/5] Ensure instrument accordian expands by default on newly entered instrument --- src/components/Upload.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/Upload.js b/src/components/Upload.js index d86f8ee..f52c618 100644 --- a/src/components/Upload.js +++ b/src/components/Upload.js @@ -56,11 +56,16 @@ export default function Upload({ return dirty.current ? localFileInfos.current || [] : appFileInfos || []; }, [appFileInfos]); - const setFileInfos = useCallback((fi) => { - console.log("setting local FI " + JSON.stringify(fi)); - dirty.current = dirty.current + 1; - localFileInfos.current = fi; - }, []); + const setFileInfos = useCallback( + (fi) => { + console.log("setting local FI " + JSON.stringify(fi)); + dirty.current = dirty.current + 1; + localFileInfos.current = fi; + if (!(expanded && fi.map((i) => i.instrument_id).includes(expanded))) + setExpanded(fi[0].instrument_id); + }, + [expanded, setExpanded] + ); const syncFileInfos = useCallback(() => { console.log("syncing fileinfo"); @@ -375,7 +380,7 @@ export default function Upload({ key={instrument_id} expanded={expanded === instrument_id} onChange={(e, ex) => { - setExpanded(ex ? instrument_id : false); + if (ex) setExpanded(instrument_id); syncFileInfos(); }} > From a238409b6ad4868c65adc4ad56dbc89207825205 Mon Sep 17 00:00:00 2001 From: John Rogers Date: Fri, 17 May 2024 10:34:55 +0100 Subject: [PATCH 2/5] Added error prompts for manually defined but empty instruments --- src/components/Upload.js | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/components/Upload.js b/src/components/Upload.js index f52c618..2a0bdb8 100644 --- a/src/components/Upload.js +++ b/src/components/Upload.js @@ -57,12 +57,18 @@ export default function Upload({ }, [appFileInfos]); const setFileInfos = useCallback( - (fi) => { + (fi, forceExpanded) => { console.log("setting local FI " + JSON.stringify(fi)); dirty.current = dirty.current + 1; localFileInfos.current = fi; - if (!(expanded && fi.map((i) => i.instrument_id).includes(expanded))) + if (forceExpanded) { + setExpanded(forceExpanded); + } else if ( + fi.length && + !(expanded && fi.map((i) => i.instrument_id).includes(expanded)) + ) { setExpanded(fi[0].instrument_id); + } }, [expanded, setExpanded] ); @@ -227,18 +233,18 @@ export default function Upload({ const after = fileInfos.findIndex( (f) => f.instrument_id === after_instrument_id ); + const instrument_id = "ManuallyCreated" + String(new Date().getTime()); const newFileInfos = fileInfos .slice(0, after + 1) .concat([ { - instrument_id: String(new Date().getTime()), + instrument_id: instrument_id, instrument_name: "", questions: [{ question_no: "", question_text: "" }], }, ]) .concat(fileInfos.slice(after + 1)); - console.log(newFileInfos); - setFileInfos(newFileInfos); + setFileInfos(newFileInfos, instrument_id); syncFileInfos(); }; @@ -404,6 +410,16 @@ export default function Upload({ }} > (a = a + q.question_text), "") + .length === 0 + } + helperText={ + fi.questions.reduce((a, q) => (a = a + q.question_text), "") + .length === 0 + ? "You must add questions before this will be harmonised" + : false + } variant="standard" sx={{ pointerEvents: "auto", From f71e46ada9fdf0917e5401e64671fb2806798f2b Mon Sep 17 00:00:00 2001 From: John Rogers Date: Fri, 17 May 2024 12:41:37 +0100 Subject: [PATCH 3/5] added button to launch Google Drive conditionally based on current user staus - not yet functional --- src/components/GoogleDriveImport.js | 19 +++++++++++++++++++ src/components/Upload.js | 15 ++++++++++++++- src/img/google-drive.png | Bin 0 -> 4478 bytes 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/components/GoogleDriveImport.js create mode 100644 src/img/google-drive.png diff --git a/src/components/GoogleDriveImport.js b/src/components/GoogleDriveImport.js new file mode 100644 index 0000000..6606f3c --- /dev/null +++ b/src/components/GoogleDriveImport.js @@ -0,0 +1,19 @@ +import React from "react"; +import { Box, Button } from "@mui/material"; + +function GoogleDriveImport({ filesReceiver, sx }) { + return ( + + + + ); +} + +export default GoogleDriveImport; diff --git a/src/components/Upload.js b/src/components/Upload.js index 2a0bdb8..fa4f6e9 100644 --- a/src/components/Upload.js +++ b/src/components/Upload.js @@ -1,5 +1,6 @@ import React, { useState, useRef, memo, useMemo, useCallback } from "react"; import DragDrop from "./DragDrop"; +import GoogleDriveImport from "./GoogleDriveImport"; import { useData } from "../contexts/DataContext"; import { Box, @@ -29,6 +30,7 @@ import { simplifyApi } from "../utilities/simplifyApi"; import { ToastContainer, toast } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; import pdfTableExtractor from "../utilities/pdf-table-extractor"; +import { useAuth } from "../contexts/AuthContext"; export default function Upload({ appFileInfos, @@ -37,6 +39,7 @@ export default function Upload({ existingInstruments, ReactGA, }) { + const { currentUser } = useAuth(); const [loading, setLoading] = useState(false); const [parseError, setParseError] = useState(false); const [matchError, setMatchError] = useState(false); @@ -521,7 +524,17 @@ export default function Upload({ setState={setMatchError} /> - + + {currentUser && + currentUser.providerData && + currentUser.providerData + .map((p) => p.providerId) + .includes("google.com") && ( + + )} 4QPNlD0_o$Ql+357&ivdmT| z0?7Z>|Abm8WPt!c|5ICC%`6DCWrupqG{w{%c#K+G69_&HRilV3`B-kY+E1jMlZagW zZlkU&%2m&!Q{`0;D`bM!R18!k$v0oJ;z}>1pl9kH93I-umEzYbkVn7BemKY&ai7{C z960@sokwz03yQb!$ z_5W?4?IC^=wDnKTy&BNAM8Je>>yC#82>~fj49@NLp4#o1U}1fU7*R8H^M;`a3J8N zGt_x^A~VEQ>|j<&8!RD?`~E-Rhxw)b1{u$cQ|xM8oNtek`GBY}K}vQnbyqHiR0;I? zk+Ge3LTEKgqt$8SXS0+Pd9r5=n9cG`R$`@l`8}9A>{hse71=KxSG@ngWCo1WGx{39 z&BBJYO0s*bAc#>O3;&4tcD#9&eMo-lEwlo7K7bgn{PA2(=ShLvKDgfXY8E^$Ki+ zb3%$<4Gv@_Y%zEI7Q}Ytim;W+yt6(S$xXgny$S{}Q&1MCY}Jdz z5IaKiikuVD&NYhxfV&lLoC;m21U$Gf&cDI!09ALUt4N&sm6Pdm#m??W;ts%Sud*NT znDn;2klGLWd~KQzhQ^=2RCkRMBP?9u_N-_`=RG3_+E9<5Pv4P$6I$X(E_oH*32t;j zxIlZOd02GgXlPxP?Gx(&`C|=g_Ionko(1Jn)FiUO_lqVwUzdcE0^i{pEan8pnsL}r z!ykZzFP^~Y;un3+_tTV0^WGx!I$n<89pTO6O$;)#K&!Ft$62b#c7eigNMTSoue1VD zhYzA3yl|uk?K4&27FC(3R3!pa_ao&l0C@AK-i^!!y0Wf-J1dexp&zfseW|WHhI0W? zsl6f+lOBPJ=g&aEa~Ip4*6n<2PTx=|l`^<|#ZQrJldm4>Z8hH1acsK-q6eG+1KxFv zJn>P6sJ}HKDaPR{Evz}R{h7ba&}8M^_Y}GxpmzdekTKow`Qplea+L1_y+Xfc4v3@< zgH(c+NO$+=!*ereuY`{Zbd_yxj=K3;dl}$1qIfKEH)_g)%)jCCHuF&cbYhFtEa#n% zlMoDt2c;tTh44y{o>tk<>ik0tt%inbn7g?*{&?gsw)Usqdf4A_5M)(Dxx$Kt9Xrjp zksr_k4O$Z)H&u>prZPW=YPdviVIKIIpIyto{7pquV9muMJ^oMd|f z8Jkm)-m{axBA;7whZk9F9LvOAN32>{q^|%y65?6}TYT$Rvs3AY&GC+zql)R?fpNxQ z=ow+E^7wa~r?o~3q^E+w&60@~8z#s-^~;6@`$Nlg6$j=ISLNV4Hvlo0<9AaJqj$?u z*gxcTn_VmXtpPGURg1x?&Qc?TzOCcNfbH=1Z?Vyl@}os*Y5n*_a?hj|7L8M5w|S4I zPRlJRYmG~>sR|U~)HypJO2#x0H~VRMPH;VY2b~IQ16??>Ue>T7MC3-Jxp7gOcRshB2J_=3Dpv?@__|O-!XpsxbL_p@C%D__y&gw1 zY;QZ>E0(BU=Fd$Ici>8OBFlc{$}|fOpP5w>^oBX94P+VSxaBuiur5!EUeB(+w&2@r zD*{=JA+`(lC=*jfkpcC^uutgCPwqo+y(FmM52|g){IK3jMpeI>Xo2r$OH!lI<<7sp zZrfEm*5+{*DsgNmA0WacZmS34W z4~yQWz4&qwmlehu1+zvvSy3OgFKod^f@*%zeFZ7#?CeW=$bpmU=|b4pxODsat22_%vX}qgj2d=5!K=8n3$a zx}9!UX|Vv=65={|;@6E8+AgW1)vnX|oU;r;sb)`}YI^Uz#4v*{ipB&#P>+8=s+Q!- zA2Z-vQJ9tQ+g0=)qv9@1*gA_Tl8pi2=X(5@?Uo_6-B-o-vg3k|yA){p#)OkxJ~~Xa z0%$xB%dlrsct1$Zaia^e-*S%Cf{V{N_YF~b?nP#4b8DZ}6}@;*bOQt3Ym{8BtG@`A zd3p=hJFR!PUy<)J^_0Q2J{8%jmAnQVXzq>VcNaa!u4MIax-u6!ycVZotn9j;XhllvZ2d#k?Gvcw_g3NiR{d6U!7;yPXrOINcL7bMpN!bZ@StkFF zT!>kv7%qDqZ$o}x^P$|)rtN6zV9bDUEbV@Lw|k4?$493vhB!u3mb<6lsFhA>3VL6? zTkyYMQ~2wa>J^y2NAfv_LYiLt#mjc$4^e|u=*!rFZo9)VykhQBh$Qithlq9=rbv{*pHT$@P@~HXd6UKIH0KTgG#`P6o)jaX)#gbjCU0Bu=Az zXfEJ_c(<2C$sQ?=ID~#V;p4}gwhsrQPExo{?SM4%=K>M$C=aO=%=UFgqw4^WLKlRTh5(>f~s_BPy`hxiBGe0!*l*XMH1%F(V(zll)j`k0Yq z_14N4#5XE&=9vswaXQt_Nb~Mq1dcUS&OP}hMqaN4<~eJbT=4*K*l=r4GRD*rhRPB~ z0DH)fEmPAcJDyvP+!ft>LY?9YfukSOpD0A!Z(nkx0rWX)eWG)k`Xv0(bkLR4>)gp~ zkCb+EI_+ih&dPQ@hA4~ToF(;hNKy$mfr1Z0RW^u7o*|Q>MFwQawaxX+pO`mXd>G$J zNXqyoANW4;XmSj4at<~fxDAYpmguRW$J0%0(#TrH2~;%^>SV6roi9vtufEUwlu&A= zy$=BChRSjQ;;RE*Ew@lqXXgz?C3aGfmcMUb=U;82e#CNr+M?vLj7^~vHNbX1(pgcU zvZ$OaBZRVhsm$MLoFfQ%@~p z@~QVD1H+7n9sm|5z2xMe;8pqEfBYSe^n~CHvE;(gO)Ya9`XVsh$kV#z6P$!Xuk=YG>`t@c(QqdH02nbgz*+qQygRa!y zH32;TsC}x52#o!Fja5IlSY!seKRnTD|H27S9TuuG;Flt8F)WJN3qK|iZ`yBx*&{i+ z3*Q^=1`KRN9Q+h5Imv--hPa|QUHIot4H2;$yc6#z(iF=UHzcqs2!9&x zG`Kus4TSr#$`={QGF_79#bjbl=hm7Q(Rnyzan_r50(0NzET-Qre4FSCKbrRi3c~a2 z+PC9)rS+0@Enj5H(PW06w~!v;D!$e4MSAgXpEFKdwP+_iF*Sc`|6D(#65H#IY+X1z znN{r53(D->7{#vL#*|Cz1PGKQ`um*Mo}uJ4*Y5yNqv}v+1h;hRZ;R;5GkroErh2>- zoVYhQ^k0^)r!?Q%zk=mf<2bMW&=OBnT;8}G`k9=c_P@jg{~<^ipX~Vy`mN4rB&asW Q@h|(()-X`7g*in256}}~c>n+a literal 0 HcmV?d00001 From c6bb95cca5e352502d9383ab18902c1a95ff4f70 Mon Sep 17 00:00:00 2001 From: Thomas Wood Date: Sat, 25 May 2024 16:52:55 +0100 Subject: [PATCH 4/5] Add link to video --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 94a5fc6..0ad65e8 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ You can access Harmony in your browser at `http://localhost:3000/app#/`. ## How to run this front end and connect to localhost API +[Watch a video tutorial on how to run the Harmony front end locally and connect to Harmony API on localhost](https://youtu.be/1xp3Uh6dptg?si=g2CkXGEJCtevgdzY) + Run the [Harmony API](https://github.com/harmonydata/harmonyapi) app in Python. Open `.env` and change `REACT_APP_API_URL` to `http://localhost:8000` and change `REACT_APP_ABSOLUTE_URL_PREFIX` to `http://localhost:8000/app` (so that the front end knows that it's running on localhost and that it should connect to a localhost API) From 55f6a45e833db0fa6b31835f7c2661de05dd93a3 Mon Sep 17 00:00:00 2001 From: John Rogers Date: Tue, 28 May 2024 23:18:38 +0100 Subject: [PATCH 5/5] Added support for multiple models - moved Version and model to main menu --- .env | 1 + .env.development | 1 + package.json | 4 +- src/components/AppBar.js | 98 +++++++++++++++++++++++++++++++---- src/components/ThemeToggle.js | 33 ++++++------ src/contexts/DataContext.js | 19 +++++-- 6 files changed, 122 insertions(+), 34 deletions(-) diff --git a/.env b/.env index f7ac5ed..82490c4 100644 --- a/.env +++ b/.env @@ -3,4 +3,5 @@ REACT_APP_API_EXAMPLES=$REACT_APP_API_URL/text/examples REACT_APP_API_PARSE=$REACT_APP_API_URL/text/parse REACT_APP_API_MATCH=$REACT_APP_API_URL/text/match REACT_APP_API_VERSION=$REACT_APP_API_URL/info/version +REACT_APP_API_MODELS=$REACT_APP_API_URL/info/list-models REACT_APP_ABSOLUTE_URL_PREFIX=https://harmonydata.github.io diff --git a/.env.development b/.env.development index 7ecc1f1..c017fb8 100644 --- a/.env.development +++ b/.env.development @@ -3,4 +3,5 @@ REACT_APP_API_EXAMPLES=$REACT_APP_API_URL/text/examples REACT_APP_API_PARSE=$REACT_APP_API_URL/text/parse REACT_APP_API_MATCH=$REACT_APP_API_URL/text/match REACT_APP_API_VERSION=$REACT_APP_API_URL/info/version +REACT_APP_API_MODELS=$REACT_APP_API_URL/info/list-models REACT_APP_ABSOLUTE_URL_PREFIX=https://harmonydata.github.io diff --git a/package.json b/package.json index 01e4381..d56f352 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,11 @@ "mui-nested-menu": "^3.2.1", "nth-check": ">=2.1.1", "pdfjs-dist": "^4.0.269", - "react": "^18.2.0", + "react": "^18.3.1", "react-card-flip": "^1.2.0", "react-circular-progressbar": "^2.1.0", "react-cookie-consent": "^8.0.1", - "react-dom": "^18.2.0", + "react-dom": "^18.3.1", "react-drag-drop-files": "^2.3.10", "react-ga4": "^2.1.0", "react-pdf": "^7.6.0", diff --git a/src/components/AppBar.js b/src/components/AppBar.js index 20a828b..2387de5 100644 --- a/src/components/AppBar.js +++ b/src/components/AppBar.js @@ -11,6 +11,10 @@ import { Menu, Container, Divider, + FormControl, + InputLabel, + OutlinedInput, + ListItemText, } from "@mui/material"; import { Logout, JoinInner } from "@mui/icons-material/"; import { useAuth } from "../contexts/AuthContext"; @@ -28,6 +32,7 @@ const SettingsIcons = { function HarmonyAppBar() { const [anchorUser, setAnchorUser] = React.useState(null); const [apiVersion, setApiVersion] = React.useState(null); + const [allModels, setAllModels] = React.useState(); const [error, setError] = React.useState(null); const { currentUser, @@ -36,7 +41,7 @@ function HarmonyAppBar() { signInWithGitHub, signInWithTwitter, } = useAuth(); - const { getVersion } = useData(); + const { getVersion, getModels, currentModel, setCurrentModel } = useData(); React.useEffect(() => { getVersion() @@ -46,6 +51,24 @@ function HarmonyAppBar() { .catch((e) => setError("ERROR: API unreachable")); }, [getVersion]); + React.useEffect(() => { + getModels() + .then((models) => { + setAllModels(models); + }) + .catch((e) => setError("ERROR: API unreachable")); + }, [getModels]); + + const handleModelSelect = (event) => { + const model = event.target.value; + if ( + model.framework !== currentModel.framework || + model.model !== currentModel.model + ) { + setCurrentModel(model); + } + }; + const handleOpenUserMenu = (event) => { setAnchorUser(event.currentTarget); }; @@ -80,15 +103,27 @@ function HarmonyAppBar() { > - - {apiVersion && ( - Harmony API version: {apiVersion} - )} - {error && ( - - {error} - - )} + + + {error && ( + + {error} + + )} + @@ -105,7 +140,7 @@ function HarmonyAppBar() { Portuguese + + + Model + + + {settings.map((setting) => ( @@ -189,6 +259,12 @@ function HarmonyAppBar() { )} + + {apiVersion && ( + + Harmony API version: {apiVersion} + + )} diff --git a/src/components/ThemeToggle.js b/src/components/ThemeToggle.js index f208e85..dd48871 100644 --- a/src/components/ThemeToggle.js +++ b/src/components/ThemeToggle.js @@ -1,9 +1,9 @@ -import React from 'react'; -import { IconButton, Box } from '@mui/material'; -import { useTheme } from '@mui/material/styles'; -import Brightness4Icon from '@mui/icons-material/Brightness4'; -import Brightness7Icon from '@mui/icons-material/Brightness7'; -import { ColorModeContext } from "../contexts/ColorModeContext" +import React from "react"; +import { IconButton, Box } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import Brightness4Icon from "@mui/icons-material/Brightness4"; +import Brightness7Icon from "@mui/icons-material/Brightness7"; +import { ColorModeContext } from "../contexts/ColorModeContext"; export default function ThemeToggle() { const theme = useTheme(); @@ -11,24 +11,21 @@ export default function ThemeToggle() { return ( {theme.palette.mode} mode - - {theme.palette.mode === 'dark' ? ( + + {theme.palette.mode === "dark" ? ( ) : ( @@ -36,4 +33,4 @@ export default function ThemeToggle() { ); -} \ No newline at end of file +} diff --git a/src/contexts/DataContext.js b/src/contexts/DataContext.js index 6e52f59..cb3c267 100644 --- a/src/contexts/DataContext.js +++ b/src/contexts/DataContext.js @@ -22,7 +22,10 @@ export const useData = () => { export function DataProvider({ children }) { const { currentUser } = useAuth(); - + const [currentModel, setCurrentModel] = React.useState({ + framework: "azure_openai", + model: "fds-text-embedding-ada-002", + }); const retryablePostData = ({ url = "", data = {}, timeout = 8000 }) => { return new Promise(async (resolve, reject) => { var retries = 3; @@ -94,7 +97,7 @@ export function DataProvider({ children }) { const match = (instruments) => { return retryablePostData({ url: process.env.REACT_APP_API_MATCH, - data: { instruments: instruments }, + data: { instruments: instruments, parameters: currentModel }, timeout: 30000, }); }; @@ -107,9 +110,15 @@ export function DataProvider({ children }) { const getVersion = () => { return retryableGetData({ url: process.env.REACT_APP_API_VERSION, - timeout: 500, + timeout: 800, }).then((data) => data.harmony_version || "unknown"); }; + const getModels = () => { + return retryableGetData({ + url: process.env.REACT_APP_API_MODELS, + timeout: 800, + }); + }; const getPublicHarmonisations = async (docID) => { if (docID) { @@ -200,6 +209,7 @@ export function DataProvider({ children }) { delete m.q2.instrument; delete m.q1.nearest_match_from_mhc_auto; delete m.q2.nearest_match_from_mhc_auto; + m.model_used = currentModel; m.created = serverTimestamp(); return addDoc(collection(db, "mismatches"), m); @@ -237,6 +247,9 @@ export function DataProvider({ children }) { parse, match, getVersion, + getModels, + currentModel, + setCurrentModel, storeHarmonisation, getMyHarmonisations, getPublicHarmonisations,