Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Clients/src/domain/interfaces/i.modelInventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export interface IModelInventory {
security_assessment: boolean;
status: ModelInventoryStatus;
status_date: Date;
reference_link?: string;
biases?: string;
limitations?: string;
hosting_provider?: string;
is_demo?: boolean;
created_at?: Date;
updated_at?: Date;
Expand Down
182 changes: 169 additions & 13 deletions Clients/src/presentation/components/Modals/NewModelInventory/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ import { ModelInventoryStatus } from "../../../../domain/interfaces/i.modelInven
import { getAllEntities } from "../../../../application/repository/entity.repository";
import { User } from "../../../../domain/types/User";
import dayjs, { Dayjs } from "dayjs";
import { KeyboardArrowDown } from "@mui/icons-material";
import { ReactComponent as GreyDownArrowIcon } from "../../../assets/icons/chevron-down-grey.svg";
import { useModalKeyHandling } from "../../../../application/hooks/useModalKeyHandling";

import modelInventoryOptions from "../../../../../../Servers/templates/model-inventory.json";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don’t import JSON from Servers/ into Clients/

This crosses package boundaries and is likely blocked by the bundler (and breaks encapsulation). Serve it via an API route, move it to a shared package, or place it under Clients/public and fetch at runtime.

I can sketch either a small GET /model-inventory endpoint on the server or a client-side fetch from public/model-inventory.json—your call.

🤖 Prompt for AI Agents
In Clients/src/presentation/components/Modals/NewModelInventory/index.tsx around
line 33, the file currently imports Servers/templates/model-inventory.json which
crosses package boundaries and will break bundling; replace the direct import by
either (A) moving model-inventory.json into Clients/public and fetch it at
runtime (e.g., fetch('/model-inventory.json') and await JSON) or (B) expose a
server-side GET /model-inventory endpoint and fetch from the client (e.g.,
fetch('/api/model-inventory')), then use the fetched JSON in place of the
imported constant; update the component to handle async loading and errors
(loading state and try/catch) accordingly.


interface NewModelInventoryProps {
isOpen: boolean;
Expand All @@ -50,6 +51,10 @@ interface NewModelInventoryFormValues {
security_assessment: boolean;
status: ModelInventoryStatus;
status_date: string;
reference_link: string,
biases: string,
limitations: string,
hosting_provider: string,
}

interface NewModelInventoryFormErrors {
Expand All @@ -73,6 +78,10 @@ const initialState: NewModelInventoryFormValues = {
security_assessment: false,
status: ModelInventoryStatus.PENDING,
status_date: new Date().toISOString().split("T")[0],
reference_link: "",
biases: "",
limitations: "",
hosting_provider: "",
};

const statusOptions = [
Expand Down Expand Up @@ -170,6 +179,16 @@ const NewModelInventory: FC<NewModelInventoryProps> = ({
}));
}, [users]);

const modelInventoryList = useMemo(() => {
return modelInventoryOptions.map((u) => ({
_id: u.model,
name: `${u.provider} - ${u.model}`,
surname: u.model,
email: u.model
}));
}, []);

Comment on lines +182 to +190
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enrich model options with provider; drop unused fields

Include provider/model in the option object so selection can auto-fill both fields.

Apply this diff:

-  return modelInventoryOptions.map((u) => ({
-    _id: u.model,          
-    name: `${u.provider} - ${u.model}`,
-    surname: u.model,
-    email: u.model
-  }));
+  return modelInventoryOptions.map((u) => ({
+    _id: u.model,
+    name: `${u.provider} - ${u.model}`,
+    provider: u.provider,
+    model: u.model,
+  }));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const modelInventoryList = useMemo(() => {
return modelInventoryOptions.map((u) => ({
_id: u.model,
name: `${u.provider} - ${u.model}`,
surname: u.model,
email: u.model
}));
}, []);
const modelInventoryList = useMemo(() => {
return modelInventoryOptions.map((u) => ({
_id: u.model,
name: `${u.provider} - ${u.model}`,
provider: u.provider,
model: u.model,
}));
}, []);
🤖 Prompt for AI Agents
In Clients/src/presentation/components/Modals/NewModelInventory/index.tsx around
lines 181 to 189, the mapped option objects include unused fields and lack a
provider/model pair for autofill and also the useMemo has an empty dependency
array; change the mapping to return an object that includes provider and model
(e.g. _id built from provider+model, provider: u.provider, model: u.model, name:
`${u.provider} - ${u.model}`) and remove surname/email, and add
modelInventoryOptions to the useMemo dependency array so the list updates when
options change.


const handleOnTextFieldChange = useCallback(
(prop: keyof NewModelInventoryFormValues) =>
(event: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -354,7 +373,7 @@ const NewModelInventory: FC<NewModelInventoryProps> = ({
left: "50%",
transform: "translate(-50%, -50%)",
width: "fit-content",
maxHeight: "80vh",
maxHeight: "fit-content",
display: "flex",
flexDirection: "column",
bgcolor: theme.palette.background.modal,
Expand Down Expand Up @@ -425,17 +444,97 @@ const NewModelInventory: FC<NewModelInventoryProps> = ({
/>
</Suspense>
<Suspense fallback={<div>Loading...</div>}>
<Field
id="model"
label="Model"
width={220}
value={values.model}
onChange={handleOnTextFieldChange("model")}
error={errors.model}
isRequired
sx={fieldStyle}
placeholder="eg. GPT-4"
/>

<Box sx={{ display: 'flex', flexDirection: 'column', width: 220 }}>
<Typography
variant="body2"
sx={{ mb: 2, fontWeight: 450, color: theme.palette.text.primary }}
>
Model <Typography component="span" color="black">*</Typography>
</Typography>
<Autocomplete
id="model-input"
size="small"
freeSolo
value={values.model}
options={modelInventoryList || []}
getOptionLabel={(option) =>
typeof option === "string" ? option : option.name
}
onChange={(_event, newValue) => {
// Handle both option object and free text
if (typeof newValue === "string") {
setValues({ ...values, model: newValue });
} else if (newValue && typeof newValue === "object") {
setValues({ ...values, model: newValue.name });
} else {
setValues({ ...values, model: "" });
}
}}
onInputChange={(_event, newInputValue, reason) => {
if (reason === "input") {
setValues({ ...values, model: newInputValue });
}
}}
renderOption={(props, option) => (
<Box component="li" {...props}>
<Typography sx={{ fontSize: 13, color: theme.palette.text.primary }}>
{option.name}
</Typography>
</Box>
)}
popupIcon={<KeyboardArrowDown />}
renderInput={(params) => (
<TextField
{...params}
placeholder="Select or enter model"
error={Boolean(errors.model)}
helperText={errors.model}
variant="outlined"
sx={{
"& .MuiInputBase-root": {
height: 34,
minHeight: 34,
borderRadius: 2,
},
"& .MuiInputBase-input": {
padding: "0 8px",
fontSize: 13,
},
}}
/>
)}
// noOptionsText="No matching models"
filterOptions={(options, state) => {
const filtered = options.filter((option) =>
option.name.toLowerCase().includes(state.inputValue.toLowerCase())
);

if (filtered.length === 0) {
return [];
}

return filtered;
}}
slotProps={{
paper: {
sx: {
"& .MuiAutocomplete-listbox": {
"& .MuiAutocomplete-option": {
fontSize: 13,
color: theme.palette.text.primary,
padding: "8px 12px",
},
"& .MuiAutocomplete-option.Mui-focused": {
backgroundColor: theme.palette.background.accent,
},
},
},
},
}}
disabled={isLoadingUsers}
/>
</Box>
Comment on lines +447 to +537
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Selecting a predefined model should also populate provider (and keep free-text flow)

Currently only model updates; align with the inventory JSON by setting provider when an option is chosen. Also, disabling this Autocomplete while users load is unrelated—remove that.

Apply this diff:

-  <Autocomplete
+  <Autocomplete
       id="model-input"
       size="small"
       freeSolo
       value={values.model}
       options={modelInventoryList || []}
       getOptionLabel={(option) =>
         typeof option === "string" ? option : option.name
       }
-      onChange={(_event, newValue) => {
+      onChange={(_event, newValue) => {
         // Handle both option object and free text
         if (typeof newValue === "string") {
-          setValues({ ...values, model: newValue });
+          setValues((prev) => ({ ...prev, model: newValue }));
         } else if (newValue && typeof newValue === "object") {
-          setValues({ ...values, model: newValue.name });
+          // Use structured option fields
+          setValues((prev) => ({
+            ...prev,
+            model: newValue.model ?? newValue.name,
+            provider: newValue.provider ?? prev.provider,
+          }));
         } else {
-          setValues({ ...values, model: "" });
+          setValues((prev) => ({ ...prev, model: "" }));
         }
       }}
       onInputChange={(_event, newInputValue, reason) => {
         if (reason === "input") {
-          setValues({ ...values, model: newInputValue });
+          setValues((prev) => ({ ...prev, model: newInputValue }));
         }
       }}
@@
-      disabled={isLoadingUsers}
+      // Keep model selection usable regardless of user-loading state
+      disabled={false}
     />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Box sx={{ display: 'flex', flexDirection: 'column', width: 220 }}>
<Typography
variant="body2"
sx={{ mb: 2, fontWeight: 450, color: theme.palette.text.primary }}
>
Model <Typography component="span" color="black">*</Typography>
</Typography>
<Autocomplete
id="model-input"
size="small"
freeSolo
value={values.model}
options={modelInventoryList || []}
getOptionLabel={(option) =>
typeof option === "string" ? option : option.name
}
onChange={(_event, newValue) => {
// Handle both option object and free text
if (typeof newValue === "string") {
setValues({ ...values, model: newValue });
} else if (newValue && typeof newValue === "object") {
setValues({ ...values, model: newValue.name });
} else {
setValues({ ...values, model: "" });
}
}}
onInputChange={(_event, newInputValue, reason) => {
if (reason === "input") {
setValues({ ...values, model: newInputValue });
}
}}
renderOption={(props, option) => (
<Box component="li" {...props}>
<Typography sx={{ fontSize: 13, color: theme.palette.text.primary }}>
{option.name}
</Typography>
</Box>
)}
popupIcon={<KeyboardArrowDown />}
renderInput={(params) => (
<TextField
{...params}
placeholder="Select or enter model"
error={Boolean(errors.model)}
helperText={errors.model}
variant="outlined"
sx={{
"& .MuiInputBase-root": {
height: 34,
minHeight: 34,
borderRadius: 2,
},
"& .MuiInputBase-input": {
padding: "0 8px",
fontSize: 13,
},
}}
/>
)}
// noOptionsText="No matching models"
filterOptions={(options, state) => {
const filtered = options.filter((option) =>
option.name.toLowerCase().includes(state.inputValue.toLowerCase())
);
if (filtered.length === 0) {
return [];
}
return filtered;
}}
slotProps={{
paper: {
sx: {
"& .MuiAutocomplete-listbox": {
"& .MuiAutocomplete-option": {
fontSize: 13,
color: theme.palette.text.primary,
padding: "8px 12px",
},
"& .MuiAutocomplete-option.Mui-focused": {
backgroundColor: theme.palette.background.accent,
},
},
},
},
}}
disabled={isLoadingUsers}
/>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', width: 220 }}>
<Typography
variant="body2"
sx={{ mb: 2, fontWeight: 450, color: theme.palette.text.primary }}
>
Model <Typography component="span" color="black">*</Typography>
</Typography>
<Autocomplete
id="model-input"
size="small"
freeSolo
value={values.model}
options={modelInventoryList || []}
getOptionLabel={(option) =>
typeof option === "string" ? option : option.name
}
onChange={(_event, newValue) => {
// Handle both option object and free text
if (typeof newValue === "string") {
setValues((prev) => ({ ...prev, model: newValue }));
} else if (newValue && typeof newValue === "object") {
// Use structured option fields
setValues((prev) => ({
...prev,
model: newValue.model ?? newValue.name,
provider: newValue.provider ?? prev.provider,
}));
} else {
setValues((prev) => ({ ...prev, model: "" }));
}
}}
onInputChange={(_event, newInputValue, reason) => {
if (reason === "input") {
setValues((prev) => ({ ...prev, model: newInputValue }));
}
}}
renderOption={(props, option) => (
<Box component="li" {...props}>
<Typography sx={{ fontSize: 13, color: theme.palette.text.primary }}>
{option.name}
</Typography>
</Box>
)}
popupIcon={<KeyboardArrowDown />}
renderInput={(params) => (
<TextField
{...params}
placeholder="Select or enter model"
error={Boolean(errors.model)}
helperText={errors.model}
variant="outlined"
sx={{
"& .MuiInputBase-root": {
height: 34,
minHeight: 34,
borderRadius: 2,
},
"& .MuiInputBase-input": {
padding: "0 8px",
fontSize: 13,
},
}}
/>
)}
filterOptions={(options, state) => {
const filtered = options.filter((option) =>
option.name.toLowerCase().includes(state.inputValue.toLowerCase())
);
if (filtered.length === 0) {
return [];
}
return filtered;
}}
slotProps={{
paper: {
sx: {
"& .MuiAutocomplete-listbox": {
"& .MuiAutocomplete-option": {
fontSize: 13,
color: theme.palette.text.primary,
padding: "8px 12px",
},
"& .MuiAutocomplete-option.Mui-focused": {
backgroundColor: theme.palette.background.accent,
},
},
},
},
}}
disabled={false}
/>
</Box>
🧰 Tools
🪛 GitHub Actions: Frontend Checks

[error] 485-485: Cannot find name 'KeyboardArrowDown'. During 'npm run build', TS2304: Cannot find name 'KeyboardArrowDown' in src/presentation/components/Modals/NewModelInventory/index.tsx(485,35).

🤖 Prompt for AI Agents
In Clients/src/presentation/components/Modals/NewModelInventory/index.tsx around
lines 439 to 529, update the Autocomplete onChange/onInputChange handlers to
also set provider when a predefined option object is chosen (e.g. setValues({
...values, model: option.name, provider: option.provider })) and clear provider
when the user types free text or clears the field; remove the
disabled={isLoadingUsers} prop (or set disabled={false}) so loading users does
not block model entry. Ensure getOptionLabel/renderOption handling remains but
adjust onInputChange to explicitly set provider to "" for freeSolo input
changes.

</Suspense>
<Suspense fallback={<div>Loading...</div>}>
<Field
Expand Down Expand Up @@ -562,6 +661,63 @@ const NewModelInventory: FC<NewModelInventoryProps> = ({
)}
</Stack>

<Stack
direction={"row"}
gap={theme.spacing(8)}
>
<Suspense fallback={<div>Loading...</div>}>
<Field
id="reference_link"
label="Reference link"
width={"50%"}
value={values.reference_link}
onChange={handleOnTextFieldChange("reference_link")}
sx={fieldStyle}
placeholder="eg. www.org.ca"
/>
</Suspense>
<Suspense fallback={<div>Loading...</div>}>

<Field
id="biases"
label="Biases"
width={"50%"}
value={values.biases}
onChange={handleOnTextFieldChange("biases")}
sx={fieldStyle}
placeholder="Biases"
/>
</Suspense>
</Stack>

<Stack
direction={"row"}
gap={theme.spacing(8)}
>
<Suspense fallback={<div>Loading...</div>}>
<Field
id="hosting_provider"
label="Hosting provider"
value={values.hosting_provider}
width={"50%"}
onChange={handleOnTextFieldChange("hosting_provider")}
sx={fieldStyle}
placeholder="eg. OpenAI"
/>
</Suspense>
<Suspense fallback={<div>Loading...</div>}>
<Field
id="limitations"
label="Limitations"
width={"50%"}
value={values.limitations}
onChange={handleOnTextFieldChange("limitations")}
sx={fieldStyle}
placeholder="Limitation"
/>
</Suspense>
</Stack>

{/* Security Assessment Section */}
<Stack>
<FormControlLabel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ const NewModelRisk: FC<NewModelRiskProps> = ({
left: "50%",
transform: "translate(-50%, -50%)",
width: "fit-content",
maxHeight: "80vh",
maxHeight: "fit-content",
display: "flex",
flexDirection: "column",
bgcolor: theme.palette.background.modal,
Expand Down
4 changes: 4 additions & 0 deletions Clients/src/presentation/pages/ModelInventory/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,10 @@ const ModelInventory: React.FC = () => {
.toISOString()
.split("T")[0]
: new Date().toISOString().split("T")[0],
reference_link: selectedModelInventory.reference_link || "",
biases: selectedModelInventory.biases || "",
limitations: selectedModelInventory.limitations || "",
hosting_provider: selectedModelInventory.hosting_provider || "",
}
: undefined
}
Expand Down
Loading