mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
fix(frontend): resolve file conflicts
This commit is contained in:
commit
fba71210a2
@ -10,6 +10,7 @@ import { HttpModule } from '@nestjs/axios';
|
|||||||
import { Global, Module } from '@nestjs/common';
|
import { Global, Module } from '@nestjs/common';
|
||||||
import { InjectDynamicProviders } from 'nestjs-dynamic-providers';
|
import { InjectDynamicProviders } from 'nestjs-dynamic-providers';
|
||||||
|
|
||||||
|
import { ChatModule } from '@/chat/chat.module';
|
||||||
import { CmsModule } from '@/cms/cms.module';
|
import { CmsModule } from '@/cms/cms.module';
|
||||||
import { NlpModule } from '@/nlp/nlp.module';
|
import { NlpModule } from '@/nlp/nlp.module';
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ import { HelperService } from './helper.service';
|
|||||||
'dist/.hexabot/custom/extensions/helpers/**/*.helper.js',
|
'dist/.hexabot/custom/extensions/helpers/**/*.helper.js',
|
||||||
)
|
)
|
||||||
@Module({
|
@Module({
|
||||||
imports: [HttpModule, NlpModule, CmsModule],
|
imports: [HttpModule, NlpModule, CmsModule, ChatModule],
|
||||||
controllers: [HelperController],
|
controllers: [HelperController],
|
||||||
providers: [HelperService],
|
providers: [HelperService],
|
||||||
exports: [HelperService],
|
exports: [HelperService],
|
||||||
|
@ -29,6 +29,7 @@ export const DialogFormButtons = ({
|
|||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
p="0.3rem 1rem"
|
p="0.3rem 1rem"
|
||||||
|
gap={1}
|
||||||
width="100%"
|
width="100%"
|
||||||
display="flex"
|
display="flex"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
@ -40,7 +41,7 @@ export const DialogFormButtons = ({
|
|||||||
startIcon={<CloseIcon />}
|
startIcon={<CloseIcon />}
|
||||||
{...cancelButtonProps}
|
{...cancelButtonProps}
|
||||||
>
|
>
|
||||||
{t(cancelButtonTitle)}
|
{cancelButtonProps?.text || t(cancelButtonTitle)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@ -48,7 +49,7 @@ export const DialogFormButtons = ({
|
|||||||
startIcon={<CheckIcon />}
|
startIcon={<CheckIcon />}
|
||||||
{...confirmButtonProps}
|
{...confirmButtonProps}
|
||||||
>
|
>
|
||||||
{t(confirmButtonTitle)}
|
{confirmButtonProps?.text || t(confirmButtonTitle)}
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
|
@ -25,18 +25,19 @@ import {
|
|||||||
|
|
||||||
const SelectableBox = styled(Box)({
|
const SelectableBox = styled(Box)({
|
||||||
position: "relative",
|
position: "relative",
|
||||||
height: "30px",
|
|
||||||
marginBottom: "1rem",
|
marginBottom: "1rem",
|
||||||
"& .highlight, & .editable": {
|
"& .highlight, & .editable": {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 0,
|
top: 0,
|
||||||
display: "block",
|
display: "block",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
padding: "4px",
|
padding: "0 4px",
|
||||||
|
lineHeight: 1.5,
|
||||||
|
whiteSpaceCollapse: "preserve",
|
||||||
},
|
},
|
||||||
"& .editable": {
|
"& .editable": {
|
||||||
|
position: "relative",
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
padding: "0px 4px",
|
|
||||||
color: "#000",
|
color: "#000",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -169,10 +170,10 @@ const Selectable: FC<SelectableProps> = ({
|
|||||||
) {
|
) {
|
||||||
const inputContainer = editableRef.current;
|
const inputContainer = editableRef.current;
|
||||||
let substring: string = "";
|
let substring: string = "";
|
||||||
let input: HTMLInputElement | null = null;
|
let input: HTMLTextAreaElement | null = null;
|
||||||
|
|
||||||
if (inputContainer) {
|
if (inputContainer) {
|
||||||
input = inputContainer.querySelector("input");
|
input = inputContainer.querySelector("textarea");
|
||||||
|
|
||||||
if (
|
if (
|
||||||
input &&
|
input &&
|
||||||
@ -267,6 +268,7 @@ const Selectable: FC<SelectableProps> = ({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<Input
|
<Input
|
||||||
|
multiline
|
||||||
ref={editableRef}
|
ref={editableRef}
|
||||||
className="editable"
|
className="editable"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
@ -184,7 +184,6 @@ export default function NlpSample() {
|
|||||||
{ defaultValues: data },
|
{ defaultValues: data },
|
||||||
{
|
{
|
||||||
maxWidth: "md",
|
maxWidth: "md",
|
||||||
hasButtons: false,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -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).
|
* 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 { useUpdate } from "@/hooks/crud/useUpdate";
|
||||||
|
import { useApiClient } from "@/hooks/useApiClient";
|
||||||
|
import { useNlp } from "@/hooks/useNlp";
|
||||||
import { useToast } from "@/hooks/useToast";
|
import { useToast } from "@/hooks/useToast";
|
||||||
import { useTranslate } from "@/hooks/useTranslate";
|
import { useTranslate } from "@/hooks/useTranslate";
|
||||||
import { EntityType } from "@/services/types";
|
import { EntityType, Format } from "@/services/types";
|
||||||
import { ComponentFormProps } from "@/types/common/dialogs.types";
|
|
||||||
import {
|
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,
|
INlpDatasetSample,
|
||||||
|
INlpDatasetSampleAttributes,
|
||||||
|
INlpDatasetTraitEntity,
|
||||||
|
INlpSample,
|
||||||
INlpSampleFormAttributes,
|
INlpSampleFormAttributes,
|
||||||
|
INlpSampleFull,
|
||||||
|
NlpSampleType,
|
||||||
} from "@/types/nlp-sample.types";
|
} from "@/types/nlp-sample.types";
|
||||||
|
import { INlpValue } from "@/types/nlp-value.types";
|
||||||
import NlpDatasetSample from "./NlpTrainForm";
|
|
||||||
|
|
||||||
export const NlpSampleForm: FC<ComponentFormProps<INlpDatasetSample>> = ({
|
export const NlpSampleForm: FC<ComponentFormProps<INlpDatasetSample>> = ({
|
||||||
data: { defaultValues: nlpDatasetSample },
|
data: { defaultValues: nlpDatasetSample },
|
||||||
@ -28,15 +64,134 @@ export const NlpSampleForm: FC<ComponentFormProps<INlpDatasetSample>> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslate();
|
const { t } = useTranslate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { mutate: updateSample } = useUpdate(EntityType.NLP_SAMPLE, {
|
const options = {
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast.error(t("message.internal_server_error"));
|
toast.error(t("message.internal_server_error"));
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t("message.success_save"));
|
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) {
|
if (nlpDatasetSample?.id) {
|
||||||
updateSample(
|
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 (
|
return (
|
||||||
<Wrapper onSubmit={() => {}} {...WrapperProps}>
|
<Wrapper
|
||||||
<NlpDatasetSample
|
onSubmit={() => {}}
|
||||||
sample={nlpDatasetSample || undefined}
|
{...WrapperProps}
|
||||||
submitForm={onSubmitForm}
|
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>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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;
|
|
@ -13,22 +13,14 @@ import { useRouter } from "next/router";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { TabPanel } from "@/app-components/tabs/TabPanel";
|
import { TabPanel } from "@/app-components/tabs/TabPanel";
|
||||||
import { useCreate } from "@/hooks/crud/useCreate";
|
|
||||||
import { useFind } from "@/hooks/crud/useFind";
|
import { useFind } from "@/hooks/crud/useFind";
|
||||||
import { useToast } from "@/hooks/useToast";
|
|
||||||
import { useTranslate } from "@/hooks/useTranslate";
|
import { useTranslate } from "@/hooks/useTranslate";
|
||||||
import { PageHeader } from "@/layout/content/PageHeader";
|
import { PageHeader } from "@/layout/content/PageHeader";
|
||||||
import { EntityType, Format } from "@/services/types";
|
import { EntityType, Format } from "@/services/types";
|
||||||
import {
|
|
||||||
INlpDatasetSampleAttributes,
|
|
||||||
INlpSample,
|
|
||||||
INlpSampleFormAttributes,
|
|
||||||
INlpSampleFull,
|
|
||||||
} from "@/types/nlp-sample.types";
|
|
||||||
|
|
||||||
import NlpDatasetCounter from "./components/NlpDatasetCounter";
|
import NlpDatasetCounter from "./components/NlpDatasetCounter";
|
||||||
import NlpSample from "./components/NlpSample";
|
import NlpSample from "./components/NlpSample";
|
||||||
import NlpDatasetSample from "./components/NlpTrainForm";
|
import { NlpSampleForm } from "./components/NlpSampleForm";
|
||||||
import { NlpValues } from "./components/NlpValue";
|
import { NlpValues } from "./components/NlpValue";
|
||||||
|
|
||||||
const NlpEntity = dynamic(() => import("./components/NlpEntity"));
|
const NlpEntity = dynamic(() => import("./components/NlpEntity"));
|
||||||
@ -61,28 +53,6 @@ export const Nlp = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
const { t } = useTranslate();
|
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 (
|
return (
|
||||||
<Grid container gap={2} flexDirection="column">
|
<Grid container gap={2} flexDirection="column">
|
||||||
@ -90,8 +60,8 @@ export const Nlp = ({
|
|||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Grid container flexDirection="row">
|
<Grid container flexDirection="row">
|
||||||
<Grid item xs={7}>
|
<Grid item xs={7}>
|
||||||
<Paper>
|
<Paper sx={{ px: 3, py: 2 }}>
|
||||||
<NlpDatasetSample submitForm={onSubmitForm} />
|
<NlpSampleForm data={{ defaultValues: null }} />
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={5} pl={2}>
|
<Grid item xs={5} pl={2}>
|
||||||
|
@ -162,8 +162,8 @@ export interface FormDialogProps
|
|||||||
export interface FormButtonsProps {
|
export interface FormButtonsProps {
|
||||||
onSubmit?: (e: BaseSyntheticEvent) => void;
|
onSubmit?: (e: BaseSyntheticEvent) => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
cancelButtonProps?: ButtonProps;
|
cancelButtonProps?: ButtonProps & { text?: string };
|
||||||
confirmButtonProps?: ButtonProps;
|
confirmButtonProps?: ButtonProps & { text?: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TPayload<D, P = unknown> = {
|
export type TPayload<D, P = unknown> = {
|
||||||
|
@ -55,6 +55,8 @@
|
|||||||
.sc-message--wrapper {
|
.sc-message--wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
word-break: break-all;
|
||||||
|
|
||||||
.sc-message--text {
|
.sc-message--text {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
|
Loading…
Reference in New Issue
Block a user