fix(frontend): resolve file conflicts

This commit is contained in:
yassinedorbozgithub 2025-06-24 11:26:46 +01:00
commit fba71210a2
9 changed files with 499 additions and 516 deletions

View File

@ -10,6 +10,7 @@ import { HttpModule } from '@nestjs/axios';
import { Global, Module } from '@nestjs/common';
import { InjectDynamicProviders } from 'nestjs-dynamic-providers';
import { ChatModule } from '@/chat/chat.module';
import { CmsModule } from '@/cms/cms.module';
import { NlpModule } from '@/nlp/nlp.module';
@ -26,7 +27,7 @@ import { HelperService } from './helper.service';
'dist/.hexabot/custom/extensions/helpers/**/*.helper.js',
)
@Module({
imports: [HttpModule, NlpModule, CmsModule],
imports: [HttpModule, NlpModule, CmsModule, ChatModule],
controllers: [HelperController],
providers: [HelperService],
exports: [HelperService],

View File

@ -29,6 +29,7 @@ export const DialogFormButtons = ({
return (
<Grid
p="0.3rem 1rem"
gap={1}
width="100%"
display="flex"
justifyContent="space-between"
@ -40,7 +41,7 @@ export const DialogFormButtons = ({
startIcon={<CloseIcon />}
{...cancelButtonProps}
>
{t(cancelButtonTitle)}
{cancelButtonProps?.text || t(cancelButtonTitle)}
</Button>
<Button
variant="contained"
@ -48,7 +49,7 @@ export const DialogFormButtons = ({
startIcon={<CheckIcon />}
{...confirmButtonProps}
>
{t(confirmButtonTitle)}
{confirmButtonProps?.text || t(confirmButtonTitle)}
</Button>
</Grid>
);

View File

@ -25,18 +25,19 @@ import {
const SelectableBox = styled(Box)({
position: "relative",
height: "30px",
marginBottom: "1rem",
"& .highlight, & .editable": {
position: "absolute",
top: 0,
display: "block",
width: "100%",
padding: "4px",
padding: "0 4px",
lineHeight: 1.5,
whiteSpaceCollapse: "preserve",
},
"& .editable": {
position: "relative",
backgroundColor: "transparent",
padding: "0px 4px",
color: "#000",
},
});
@ -169,10 +170,10 @@ const Selectable: FC<SelectableProps> = ({
) {
const inputContainer = editableRef.current;
let substring: string = "";
let input: HTMLInputElement | null = null;
let input: HTMLTextAreaElement | null = null;
if (inputContainer) {
input = inputContainer.querySelector("input");
input = inputContainer.querySelector("textarea");
if (
input &&
@ -267,6 +268,7 @@ const Selectable: FC<SelectableProps> = ({
/>
))}
<Input
multiline
ref={editableRef}
className="editable"
fullWidth

View File

@ -184,7 +184,6 @@ export default function NlpSample() {
{ defaultValues: data },
{
maxWidth: "md",
hasButtons: false,
},
);
},

View File

@ -6,19 +6,55 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import { FC, Fragment } from "react";
import AddIcon from "@mui/icons-material/Add";
import DeleteIcon from "@mui/icons-material/Delete";
import {
Box,
Button,
Chip,
debounce,
FormControl,
FormControlLabel,
FormLabel,
IconButton,
Radio,
RadioGroup,
Typography,
} from "@mui/material";
import { FC, Fragment, useCallback, useEffect, useMemo, useState } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form";
import { useQuery } from "react-query";
import { ContentContainer, ContentItem } from "@/app-components/dialogs";
import AutoCompleteEntitySelect from "@/app-components/inputs/AutoCompleteEntitySelect";
import AutoCompleteSelect from "@/app-components/inputs/AutoCompleteSelect";
import Selectable from "@/app-components/inputs/Selectable";
import { useCreate } from "@/hooks/crud/useCreate";
import { useGetFromCache } from "@/hooks/crud/useGet";
import { useUpdate } from "@/hooks/crud/useUpdate";
import { useApiClient } from "@/hooks/useApiClient";
import { useNlp } from "@/hooks/useNlp";
import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate";
import { EntityType } from "@/services/types";
import { ComponentFormProps } from "@/types/common/dialogs.types";
import { EntityType, Format } from "@/services/types";
import {
ComponentFormProps,
FormButtonsProps,
} from "@/types/common/dialogs.types";
import { ILanguage } from "@/types/language.types";
import { INlpEntity } from "@/types/nlp-entity.types";
import {
INlpDatasetKeywordEntity,
INlpDatasetPatternEntity,
INlpDatasetSample,
INlpDatasetSampleAttributes,
INlpDatasetTraitEntity,
INlpSample,
INlpSampleFormAttributes,
INlpSampleFull,
NlpSampleType,
} from "@/types/nlp-sample.types";
import NlpDatasetSample from "./NlpTrainForm";
import { INlpValue } from "@/types/nlp-value.types";
export const NlpSampleForm: FC<ComponentFormProps<INlpDatasetSample>> = ({
data: { defaultValues: nlpDatasetSample },
@ -28,15 +64,134 @@ export const NlpSampleForm: FC<ComponentFormProps<INlpDatasetSample>> = ({
}) => {
const { t } = useTranslate();
const { toast } = useToast();
const { mutate: updateSample } = useUpdate(EntityType.NLP_SAMPLE, {
const options = {
onError: () => {
toast.error(t("message.internal_server_error"));
},
onSuccess: () => {
toast.success(t("message.success_save"));
},
};
const { mutate: createSample } = useCreate<
EntityType.NLP_SAMPLE,
INlpDatasetSampleAttributes,
INlpSample,
INlpSampleFull
>(EntityType.NLP_SAMPLE, {
...options,
onSuccess: () => {
options.onSuccess();
refetchAllEntities();
reset({
...defaultValues,
text: "",
});
},
});
const onSubmitForm = (form: INlpSampleFormAttributes) => {
const { mutate: updateSample } = useUpdate<
EntityType.NLP_SAMPLE,
INlpDatasetSampleAttributes
>(EntityType.NLP_SAMPLE, options);
const {
allTraitEntities,
allKeywordEntities,
allPatternEntities,
refetchAllEntities,
} = useNlp();
const getNlpValueFromCache = useGetFromCache(EntityType.NLP_VALUE);
const defaultValues: INlpSampleFormAttributes = useMemo(
() => ({
type: nlpDatasetSample?.type || NlpSampleType.train,
text: nlpDatasetSample?.text || "",
language: nlpDatasetSample?.language || null,
traitEntities: [...allTraitEntities.values()].map((e) => {
return {
entity: e.name,
value:
(nlpDatasetSample?.entities || []).find(
(se) => se.entity === e.name,
)?.value || "",
};
}) as INlpDatasetTraitEntity[],
keywordEntities: (nlpDatasetSample?.entities || []).filter((e) =>
allKeywordEntities.has(e.entity),
) as INlpDatasetKeywordEntity[],
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[allKeywordEntities, allTraitEntities, JSON.stringify(nlpDatasetSample)],
);
const { handleSubmit, control, register, reset, setValue, watch } =
useForm<INlpSampleFormAttributes>({
defaultValues,
});
const currentText = watch("text");
const currentType = watch("type");
const { apiClient } = useApiClient();
const [patternEntities, setPatternEntities] = useState<
INlpDatasetPatternEntity[]
>([]);
const { fields: traitEntities, update: updateTraitEntity } = useFieldArray({
control,
name: "traitEntities",
});
const {
fields: keywordEntities,
insert: insertKeywordEntity,
update: updateKeywordEntity,
remove: removeKeywordEntity,
} = useFieldArray({
control,
name: "keywordEntities",
});
// Auto-predict on text change
const debounceSetText = useCallback(
debounce((text: string) => {
setValue("text", text);
}, 400),
[setValue],
);
const { isLoading } = useQuery({
queryKey: ["nlp-prediction", currentText],
queryFn: async () => {
return await apiClient.predictNlp(currentText);
},
onSuccess: (prediction) => {
const predictedTraitEntities: INlpDatasetTraitEntity[] =
prediction.entities.filter((e) => allTraitEntities.has(e.entity));
const predictedKeywordEntities = prediction.entities.filter((e) =>
allKeywordEntities.has(e.entity),
) as INlpDatasetKeywordEntity[];
const predictedPatternEntities = prediction.entities.filter((e) =>
allPatternEntities.has(e.entity),
) as INlpDatasetKeywordEntity[];
const language = prediction.entities.find(
({ entity }) => entity === "language",
);
setValue("language", language?.value || "");
setValue("traitEntities", predictedTraitEntities);
setValue("keywordEntities", predictedKeywordEntities);
setPatternEntities(predictedPatternEntities);
},
enabled:
// Inbox sample update
nlpDatasetSample?.type === "inbox" ||
// New sample
(!nlpDatasetSample && !!currentText),
});
const findInsertIndex = (newItem: INlpDatasetKeywordEntity): number => {
const index = keywordEntities.findIndex(
(entity) => entity.start && newItem.start && entity.start > newItem.start,
);
return index === -1 ? keywordEntities.length : index;
};
const [selection, setSelection] = useState<{
value: string;
start: number;
end: number;
} | null>(null);
const onSubmitForm = async (form: INlpSampleFormAttributes) => {
if (nlpDatasetSample?.id) {
updateSample(
{
@ -54,15 +209,328 @@ export const NlpSampleForm: FC<ComponentFormProps<INlpDatasetSample>> = ({
},
},
);
} else {
createSample({
text: form.text,
type: form.type,
entities: [...form.traitEntities, ...form.keywordEntities],
language: form.language,
});
}
};
useEffect(() => {
reset(defaultValues);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(defaultValues)]);
const cancelButtonProps = {
sx: {
display: "inline-block",
overflow: "hidden",
textAlign: "left",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
"& .MuiButton-startIcon": {
top: "4px",
margin: "auto 4px auto auto",
display: "inline-block",
position: "relative",
},
},
color: "secondary",
variant: "contained",
onClick: () => {
const newKeywordEntity = {
...selection,
entity: "",
} as INlpDatasetKeywordEntity;
const newIndex = findInsertIndex(newKeywordEntity);
selection && insertKeywordEntity(newIndex, newKeywordEntity);
setSelection(null);
},
disabled: !selection?.value,
startIcon: <AddIcon />,
} satisfies FormButtonsProps["cancelButtonProps"];
const confirmButtonProps = {
sx: { minWidth: "120px" },
value: "button.validate",
variant: "contained",
disabled: !(
currentText !== "" &&
currentType !== NlpSampleType.inbox &&
traitEntities.every((e) => e.value !== "") &&
keywordEntities.every((e) => e.value !== "")
),
onClick: handleSubmit(onSubmitForm),
} satisfies FormButtonsProps["confirmButtonProps"];
return (
<Wrapper onSubmit={() => {}} {...WrapperProps}>
<NlpDatasetSample
sample={nlpDatasetSample || undefined}
submitForm={onSubmitForm}
/>
<Wrapper
onSubmit={() => {}}
{...WrapperProps}
cancelButtonProps={{
...WrapperProps?.cancelButtonProps,
text: !selection?.value
? t("button.select_some_text")
: t("button.add_nlp_entity", { 0: selection.value }),
...cancelButtonProps,
}}
confirmButtonProps={{
...WrapperProps?.confirmButtonProps,
...confirmButtonProps,
}}
>
<form onSubmit={handleSubmit(onSubmitForm)}>
<ContentContainer>
<ContentItem
display="flex"
flexDirection="row"
justifyContent="space-between"
>
<Typography variant="h6" display="inline-block">
{t("title.nlp_train")}
</Typography>
<FormControl>
<FormLabel>{t("label.type")}</FormLabel>
<RadioGroup
row
defaultValue={
nlpDatasetSample?.type === NlpSampleType.test
? NlpSampleType.test
: NlpSampleType.train
}
>
{Object.values(NlpSampleType)
.filter((type) => type !== "inbox")
.map((type, index) => (
<FormControlLabel
key={index}
value={type}
control={<Radio {...register("type")} />}
label={t(`label.${type}`)}
/>
))}
</RadioGroup>
</FormControl>
</ContentItem>
<ContentItem>
<Selectable
defaultValue={currentText}
keywordEntities={keywordEntities}
patternEntities={patternEntities}
placeholder={t("placeholder.nlp_sample_text")}
onSelect={(newSelection, start, end) => {
newSelection !== selection?.value &&
setSelection({
value: newSelection,
start,
end,
});
}}
onChange={({ text, entities }) => {
debounceSetText(text);
setValue(
"keywordEntities",
entities.map(({ entity, value, start, end }) => ({
entity,
value,
start,
end,
})),
);
setPatternEntities([]);
}}
loading={isLoading}
/>
</ContentItem>
<Box display="flex" flexDirection="column">
<ContentItem
display="flex"
flexDirection="row"
maxWidth="50%"
gap={2}
>
<Controller
name="language"
control={control}
render={({ field }) => {
const { onChange, ...rest } = field;
return (
<AutoCompleteEntitySelect<ILanguage, "title", false>
fullWidth={true}
autoFocus
searchFields={["title", "code"]}
entity={EntityType.LANGUAGE}
format={Format.BASIC}
labelKey="title"
idKey="code"
label={t("label.language")}
multiple={false}
{...field}
onChange={(_e, selected) => {
onChange(selected?.code);
}}
{...rest}
/>
);
}}
/>
</ContentItem>
{traitEntities.map((traitEntity, index) => (
<ContentItem
key={traitEntity.id}
display="flex"
flexDirection="row"
maxWidth="50%"
gap={2}
>
<Controller
name={`traitEntities.${index}`}
rules={{ required: true }}
control={control}
render={({ field }) => {
const { onChange: _, value, ...rest } = field;
const options = (
allTraitEntities.get(traitEntity.entity)?.values || []
).map((v) => getNlpValueFromCache(v)!);
return (
<>
<AutoCompleteSelect<INlpValue, "value", false>
fullWidth={true}
options={options}
idKey="value"
labelKey="value"
label={value.entity}
multiple={false}
value={value.value}
onChange={(_e, selected, ..._) => {
updateTraitEntity(index, {
entity: value.entity,
value: selected?.value || "",
});
}}
{...rest}
/>
{value?.confidence &&
typeof value?.confidence === "number" && (
<Chip
sx={{ marginTop: 0.5 }}
variant="available"
label={`${(value?.confidence * 100).toFixed(
2,
)}% ${t("label.confidence")}`}
/>
)}
</>
);
}}
/>
</ContentItem>
))}
</Box>
<Box display="flex" flexDirection="column">
{keywordEntities.map((keywordEntity, index) => (
<ContentItem
key={keywordEntity.id}
display="flex"
maxWidth="50%"
gap={2}
>
<IconButton onClick={() => removeKeywordEntity(index)}>
<DeleteIcon />
</IconButton>
<Controller
name={`keywordEntities.${index}.entity`}
control={control}
render={({ field }) => {
const { onChange: _, ...rest } = field;
const options = [...allKeywordEntities.values()];
return (
<AutoCompleteSelect<INlpEntity, "name", false>
fullWidth={true}
options={options}
idKey="name"
labelKey="name"
label={t("label.nlp_entity")}
multiple={false}
onChange={(_e, selected, ..._) => {
updateKeywordEntity(index, {
...keywordEntities[index],
entity: selected?.name || "",
});
}}
{...rest}
/>
);
}}
/>
<Controller
name={`keywordEntities.${index}.value`}
control={control}
render={({ field }) => {
const { onChange: _, value, ...rest } = field;
const options = (
allKeywordEntities.get(keywordEntity.entity)?.values || []
).map((v) => getNlpValueFromCache(v)!);
return (
<AutoCompleteSelect<
INlpValue,
"value",
false,
false,
true
>
sx={{ width: "50%" }}
idKey="value"
labelKey="value"
label={t("label.value")}
multiple={false}
options={options}
value={value}
freeSolo={true}
getOptionLabel={(option) => {
return typeof option === "string"
? option
: option.value;
}}
onChange={(_e, selected, ..._) => {
selected &&
updateKeywordEntity(index, {
...keywordEntity,
value:
typeof selected === "string"
? selected
: selected.value,
});
}}
{...rest}
/>
);
}}
/>
</ContentItem>
))}
</Box>
</ContentContainer>
<ContentItem display="flex" justifyContent="space-between">
{nlpDatasetSample ? null : (
<>
<Button {...cancelButtonProps}>
{!selection?.value
? t("button.select_some_text")
: t("button.add_nlp_entity", { 0: selection.value })}
</Button>
<Button {...confirmButtonProps}>{t("button.validate")}</Button>
</>
)}
</ContentItem>
</form>
</Wrapper>
);
};

View File

@ -1,460 +0,0 @@
/*
* Copyright © 2025 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import AddIcon from "@mui/icons-material/Add";
import Check from "@mui/icons-material/Check";
import DeleteIcon from "@mui/icons-material/Delete";
import {
Box,
Button,
Chip,
debounce,
FormControl,
FormControlLabel,
FormLabel,
IconButton,
Radio,
RadioGroup,
Typography,
} from "@mui/material";
import { FC, useCallback, useEffect, useMemo, useState } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form";
import { useQuery } from "react-query";
import { ContentContainer, ContentItem } from "@/app-components/dialogs";
import AutoCompleteEntitySelect from "@/app-components/inputs/AutoCompleteEntitySelect";
import AutoCompleteSelect from "@/app-components/inputs/AutoCompleteSelect";
import Selectable from "@/app-components/inputs/Selectable";
import { useGetFromCache } from "@/hooks/crud/useGet";
import { useApiClient } from "@/hooks/useApiClient";
import { useNlp } from "@/hooks/useNlp";
import { useTranslate } from "@/hooks/useTranslate";
import { EntityType, Format } from "@/services/types";
import { ILanguage } from "@/types/language.types";
import { INlpEntity } from "@/types/nlp-entity.types";
import {
INlpDatasetKeywordEntity,
INlpDatasetPatternEntity,
INlpDatasetSample,
INlpDatasetTraitEntity,
INlpSampleFormAttributes,
NlpSampleType,
} from "@/types/nlp-sample.types";
import { INlpValue } from "@/types/nlp-value.types";
type NlpDatasetSampleProps = {
sample?: INlpDatasetSample;
submitForm: (params: INlpSampleFormAttributes) => void;
};
const NlpDatasetSample: FC<NlpDatasetSampleProps> = ({
sample,
submitForm,
}) => {
const { t } = useTranslate();
const {
allTraitEntities,
allKeywordEntities,
allPatternEntities,
refetchAllEntities,
} = useNlp();
const getNlpValueFromCache = useGetFromCache(EntityType.NLP_VALUE);
const defaultValues: INlpSampleFormAttributes = useMemo(
() => ({
type: sample?.type || NlpSampleType.train,
text: sample?.text || "",
language: sample?.language || null,
traitEntities: [...allTraitEntities.values()].map((e) => {
return {
entity: e.name,
value:
(sample?.entities || []).find((se) => se.entity === e.name)
?.value || "",
};
}) as INlpDatasetTraitEntity[],
keywordEntities: (sample?.entities || []).filter((e) =>
allKeywordEntities.has(e.entity),
) as INlpDatasetKeywordEntity[],
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[allKeywordEntities, allTraitEntities, JSON.stringify(sample)],
);
const { handleSubmit, control, register, reset, setValue, watch } =
useForm<INlpSampleFormAttributes>({
defaultValues,
});
const currentText = watch("text");
const currentType = watch("type");
const { apiClient } = useApiClient();
const [patternEntities, setPatternEntities] = useState<
INlpDatasetPatternEntity[]
>([]);
const { fields: traitEntities, update: updateTraitEntity } = useFieldArray({
control,
name: "traitEntities",
});
const {
fields: keywordEntities,
insert: insertKeywordEntity,
update: updateKeywordEntity,
remove: removeKeywordEntity,
} = useFieldArray({
control,
name: "keywordEntities",
});
// Auto-predict on text change
const debounceSetText = useCallback(
debounce((text: string) => {
setValue("text", text);
}, 400),
[setValue],
);
const { isLoading } = useQuery({
queryKey: ["nlp-prediction", currentText],
queryFn: async () => {
return await apiClient.predictNlp(currentText);
},
onSuccess: (prediction) => {
const predictedTraitEntities: INlpDatasetTraitEntity[] =
prediction.entities.filter((e) => allTraitEntities.has(e.entity));
const predictedKeywordEntities = prediction.entities.filter((e) =>
allKeywordEntities.has(e.entity),
) as INlpDatasetKeywordEntity[];
const predictedPatternEntities = prediction.entities.filter((e) =>
allPatternEntities.has(e.entity),
) as INlpDatasetKeywordEntity[];
const language = prediction.entities.find(
({ entity }) => entity === "language",
);
setValue("language", language?.value || "");
setValue("traitEntities", predictedTraitEntities);
setValue("keywordEntities", predictedKeywordEntities);
setPatternEntities(predictedPatternEntities);
},
enabled:
// Inbox sample update
sample?.type === "inbox" ||
// New sample
(!sample && !!currentText),
});
const findInsertIndex = (newItem: INlpDatasetKeywordEntity): number => {
const index = keywordEntities.findIndex(
(entity) => entity.start && newItem.start && entity.start > newItem.start,
);
return index === -1 ? keywordEntities.length : index;
};
const [selection, setSelection] = useState<{
value: string;
start: number;
end: number;
} | null>(null);
const onSubmitForm = (form: INlpSampleFormAttributes) => {
submitForm(form);
refetchAllEntities();
reset({
...defaultValues,
text: "",
});
};
useEffect(() => {
reset(defaultValues);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(defaultValues)]);
return (
<Box className="nlp-train" sx={{ position: "relative", p: 2 }}>
<form onSubmit={handleSubmit(onSubmitForm)}>
<ContentContainer>
<ContentItem
display="flex"
flexDirection="row"
justifyContent="space-between"
>
<Typography variant="h6" display="inline-block">
{t("title.nlp_train")}
</Typography>
<FormControl>
<FormLabel>{t("label.type")}</FormLabel>
<RadioGroup
row
defaultValue={
sample?.type === NlpSampleType.test
? NlpSampleType.test
: NlpSampleType.train
}
>
{Object.values(NlpSampleType)
.filter((type) => type !== "inbox")
.map((type, index) => (
<FormControlLabel
key={index}
value={type}
control={<Radio {...register("type")} />}
label={t(`label.${type}`)}
/>
))}
</RadioGroup>
</FormControl>
</ContentItem>
<ContentItem>
<Selectable
defaultValue={currentText}
keywordEntities={keywordEntities}
patternEntities={patternEntities}
placeholder={t("placeholder.nlp_sample_text")}
onSelect={(selection, start, end) => {
setSelection({
value: selection,
start,
end,
});
}}
onChange={({ text, entities }) => {
debounceSetText(text);
setValue(
"keywordEntities",
entities.map(({ entity, value, start, end }) => ({
entity,
value,
start,
end,
})),
);
setPatternEntities([]);
}}
loading={isLoading}
/>
</ContentItem>
<Box display="flex" flexDirection="column">
{/* Language selection */}
<ContentItem
display="flex"
flexDirection="row"
maxWidth="50%"
gap={2}
>
<Controller
name="language"
control={control}
render={({ field }) => {
const { onChange, ...rest } = field;
return (
<AutoCompleteEntitySelect<ILanguage, "title", false>
fullWidth={true}
autoFocus
searchFields={["title", "code"]}
entity={EntityType.LANGUAGE}
format={Format.BASIC}
labelKey="title"
idKey="code"
label={t("label.language")}
multiple={false}
{...field}
onChange={(_e, selected) => {
onChange(selected?.code);
}}
{...rest}
/>
);
}}
/>
</ContentItem>
{/* Trait entities */}
{traitEntities.map((traitEntity, index) => (
<ContentItem
key={traitEntity.id}
display="flex"
flexDirection="row"
maxWidth="50%"
gap={2}
>
<Controller
name={`traitEntities.${index}`}
rules={{ required: true }}
control={control}
render={({ field }) => {
const { onChange: _, value, ...rest } = field;
const options = (
allTraitEntities.get(traitEntity.entity)?.values || []
).map((v) => getNlpValueFromCache(v)!);
return (
<>
<AutoCompleteSelect<INlpValue, "value", false>
fullWidth={true}
options={options}
idKey="value"
labelKey="value"
label={value.entity}
multiple={false}
value={value.value}
onChange={(_e, selected, ..._) => {
updateTraitEntity(index, {
entity: value.entity,
value: selected?.value || "",
});
}}
{...rest}
/>
{value?.confidence &&
typeof value?.confidence === "number" && (
<Chip
sx={{ marginTop: 0.5 }}
variant="available"
label={`${(value?.confidence * 100).toFixed(
2,
)}% ${t("label.confidence")}`}
/>
)}
</>
);
}}
/>
</ContentItem>
))}
</Box>
{
/* Keyword entities */
}
<Box display="flex" flexDirection="column">
{keywordEntities.map((keywordEntity, index) => (
<ContentItem
key={keywordEntity.id}
display="flex"
maxWidth="50%"
gap={2}
>
<IconButton onClick={() => removeKeywordEntity(index)}>
<DeleteIcon />
</IconButton>
<Controller
name={`keywordEntities.${index}.entity`}
control={control}
render={({ field }) => {
const { onChange: _, ...rest } = field;
const options = [...allKeywordEntities.values()];
return (
<AutoCompleteSelect<INlpEntity, "name", false>
fullWidth={true}
options={options}
idKey="name"
labelKey="name"
label={t("label.nlp_entity")}
multiple={false}
onChange={(_e, selected, ..._) => {
updateKeywordEntity(index, {
...keywordEntities[index],
entity: selected?.name || "",
});
}}
{...rest}
/>
);
}}
/>
<Controller
name={`keywordEntities.${index}.value`}
control={control}
render={({ field }) => {
const { onChange: _, value, ...rest } = field;
const options = (
allKeywordEntities.get(keywordEntity.entity)?.values || []
).map((v) => getNlpValueFromCache(v)!);
return (
<AutoCompleteSelect<
INlpValue,
"value",
false,
false,
true
>
sx={{ width: "50%" }}
idKey="value"
labelKey="value"
label={t("label.value")}
multiple={false}
options={options}
value={value}
freeSolo={true}
getOptionLabel={(option) => {
return typeof option === "string"
? option
: option.value;
}}
onChange={(_e, selected, ..._) => {
selected &&
updateKeywordEntity(index, {
...keywordEntity,
value:
typeof selected === "string"
? selected
: selected.value,
});
}}
{...rest}
/>
);
}}
/>
</ContentItem>
))}
</Box>
</ContentContainer>
<ContentItem display="flex" justifyContent="space-between">
<Button
startIcon={<AddIcon />}
color="secondary"
variant="contained"
disabled={!selection?.value}
onClick={() => {
const newKeywordEntity = {
...selection,
entity: "",
} as INlpDatasetKeywordEntity;
const newIndex = findInsertIndex(newKeywordEntity);
selection && insertKeywordEntity(newIndex, newKeywordEntity);
setSelection(null);
}}
>
{!selection?.value
? t("button.select_some_text")
: t("button.add_nlp_entity", { 0: selection.value })}
</Button>
<Button
variant="contained"
startIcon={<Check />}
onClick={handleSubmit(onSubmitForm)}
disabled={
!(
currentText !== "" &&
currentType !== NlpSampleType.inbox &&
traitEntities.every((e) => e.value !== "") &&
keywordEntities.every((e) => e.value !== "")
)
}
type="submit"
>
{t("button.validate")}
</Button>
</ContentItem>
</form>
</Box>
);
};
NlpDatasetSample.displayName = "NlpTrain";
export default NlpDatasetSample;

View File

@ -13,22 +13,14 @@ import { useRouter } from "next/router";
import React from "react";
import { TabPanel } from "@/app-components/tabs/TabPanel";
import { useCreate } from "@/hooks/crud/useCreate";
import { useFind } from "@/hooks/crud/useFind";
import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate";
import { PageHeader } from "@/layout/content/PageHeader";
import { EntityType, Format } from "@/services/types";
import {
INlpDatasetSampleAttributes,
INlpSample,
INlpSampleFormAttributes,
INlpSampleFull,
} from "@/types/nlp-sample.types";
import NlpDatasetCounter from "./components/NlpDatasetCounter";
import NlpSample from "./components/NlpSample";
import NlpDatasetSample from "./components/NlpTrainForm";
import { NlpSampleForm } from "./components/NlpSampleForm";
import { NlpValues } from "./components/NlpValue";
const NlpEntity = dynamic(() => import("./components/NlpEntity"));
@ -61,28 +53,6 @@ export const Nlp = ({
);
};
const { t } = useTranslate();
const { toast } = useToast();
const { mutate: createSample } = useCreate<
EntityType.NLP_SAMPLE,
INlpDatasetSampleAttributes,
INlpSample,
INlpSampleFull
>(EntityType.NLP_SAMPLE, {
onError: () => {
toast.error(t("message.internal_server_error"));
},
onSuccess: () => {
toast.success(t("message.success_save"));
},
});
const onSubmitForm = (params: INlpSampleFormAttributes) => {
createSample({
text: params.text,
type: params.type,
entities: [...params.traitEntities, ...params.keywordEntities],
language: params.language,
});
};
return (
<Grid container gap={2} flexDirection="column">
@ -90,8 +60,8 @@ export const Nlp = ({
<Grid item xs={12}>
<Grid container flexDirection="row">
<Grid item xs={7}>
<Paper>
<NlpDatasetSample submitForm={onSubmitForm} />
<Paper sx={{ px: 3, py: 2 }}>
<NlpSampleForm data={{ defaultValues: null }} />
</Paper>
</Grid>
<Grid item xs={5} pl={2}>

View File

@ -162,8 +162,8 @@ export interface FormDialogProps
export interface FormButtonsProps {
onSubmit?: (e: BaseSyntheticEvent) => void;
onCancel?: () => void;
cancelButtonProps?: ButtonProps;
confirmButtonProps?: ButtonProps;
cancelButtonProps?: ButtonProps & { text?: string };
confirmButtonProps?: ButtonProps & { text?: string };
}
export type TPayload<D, P = unknown> = {

View File

@ -55,6 +55,8 @@
.sc-message--wrapper {
display: flex;
flex-direction: column;
overflow: hidden;
word-break: break-all;
.sc-message--text {
padding: 10px 20px;