mirror of
https://github.com/hexastack/hexabot
synced 2024-12-02 09:05:06 +00:00
feat: enhance fieldset
This commit is contained in:
parent
eba7308e16
commit
1da3a3d9ba
@ -232,7 +232,7 @@
|
|||||||
"capture_context_vars": "Capture context variables?",
|
"capture_context_vars": "Capture context variables?",
|
||||||
"block_event_type": "Type of event",
|
"block_event_type": "Type of event",
|
||||||
"patterns": "Patterns",
|
"patterns": "Patterns",
|
||||||
"no_patterns": "- No patterns -",
|
"no_patterns": "- No triggers -",
|
||||||
"text_patterns": " Text Patterns",
|
"text_patterns": " Text Patterns",
|
||||||
"triggers": "Triggers",
|
"triggers": "Triggers",
|
||||||
"payloads": "Payloads",
|
"payloads": "Payloads",
|
||||||
@ -558,7 +558,7 @@
|
|||||||
"media_library": "Media Library",
|
"media_library": "Media Library",
|
||||||
"manage_roles": "Manage Roles",
|
"manage_roles": "Manage Roles",
|
||||||
"connect_with_sso": "Connect with SSO",
|
"connect_with_sso": "Connect with SSO",
|
||||||
"add_pattern": "Add pattern",
|
"add_pattern": "New Trigger",
|
||||||
"mark_as_default": "Mark as Default",
|
"mark_as_default": "Mark as Default",
|
||||||
"toggle": "Toggle button"
|
"toggle": "Toggle button"
|
||||||
},
|
},
|
||||||
|
@ -559,7 +559,7 @@
|
|||||||
"media_library": "Bibliothéque Media",
|
"media_library": "Bibliothéque Media",
|
||||||
"manage_roles": "Gérer les rôles",
|
"manage_roles": "Gérer les rôles",
|
||||||
"connect_with_sso": "Se connecter avec SSO",
|
"connect_with_sso": "Se connecter avec SSO",
|
||||||
"add_pattern": "Ajouter un motif",
|
"add_pattern": "Ajouter un déclencheur",
|
||||||
"mark_as_default": "Par Défaut",
|
"mark_as_default": "Par Défaut",
|
||||||
"toggle": "Bouton de bascule"
|
"toggle": "Bouton de bascule"
|
||||||
},
|
},
|
||||||
|
96
frontend/src/app-components/buttons/DropdownButton.tsx
Normal file
96
frontend/src/app-components/buttons/DropdownButton.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024 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 { ArrowDropDown } from "@mui/icons-material";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
List,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Popover,
|
||||||
|
SxProps,
|
||||||
|
Theme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
export interface DropdownButtonAction {
|
||||||
|
icon: React.ReactNode;
|
||||||
|
name: string;
|
||||||
|
defaultValue: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddPatternProps {
|
||||||
|
actions: DropdownButtonAction[];
|
||||||
|
onClick: (action: DropdownButtonAction) => void;
|
||||||
|
label?: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
sx?: SxProps<Theme> | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DropdownButton: React.FC<AddPatternProps> = ({
|
||||||
|
actions,
|
||||||
|
onClick,
|
||||||
|
label = "Add",
|
||||||
|
icon,
|
||||||
|
sx,
|
||||||
|
}) => {
|
||||||
|
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
||||||
|
const handleOpen = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
const handleClose = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
const handleAddFieldset = (action: DropdownButtonAction) => {
|
||||||
|
onClick(action);
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
const open = Boolean(anchorEl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={sx}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleOpen}
|
||||||
|
startIcon={icon}
|
||||||
|
endIcon={<ArrowDropDown />}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
<Popover
|
||||||
|
open={open}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
onClose={handleClose}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "bottom",
|
||||||
|
horizontal: "left",
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: "top",
|
||||||
|
horizontal: "left",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<List>
|
||||||
|
{actions.map((action, index) => (
|
||||||
|
<ListItemButton
|
||||||
|
key={index}
|
||||||
|
onClick={() => handleAddFieldset(action)}
|
||||||
|
>
|
||||||
|
<ListItemIcon>{action.icon}</ListItemIcon>
|
||||||
|
<ListItemText primary={action.name} />
|
||||||
|
</ListItemButton>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</Popover>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DropdownButton;
|
@ -117,6 +117,7 @@ const NlpPatternSelect = (
|
|||||||
<Autocomplete
|
<Autocomplete
|
||||||
ref={ref}
|
ref={ref}
|
||||||
size="medium"
|
size="medium"
|
||||||
|
fullWidth={true}
|
||||||
disabled={options.length === 0}
|
disabled={options.length === 0}
|
||||||
value={defaultValue}
|
value={defaultValue}
|
||||||
multiple={true}
|
multiple={true}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* 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 { Grid, MenuItem, TextFieldProps } from "@mui/material";
|
import { Box, TextFieldProps } from "@mui/material";
|
||||||
import { FC, useEffect, useState } from "react";
|
import { FC, useEffect, useState } from "react";
|
||||||
import { RegisterOptions, useFormContext } from "react-hook-form";
|
import { RegisterOptions, useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
@ -25,6 +25,7 @@ import {
|
|||||||
|
|
||||||
import { PostbackInput } from "./PostbackInput";
|
import { PostbackInput } from "./PostbackInput";
|
||||||
|
|
||||||
|
|
||||||
const isRegex = (str: Pattern) => {
|
const isRegex = (str: Pattern) => {
|
||||||
return typeof str === "string" && str.startsWith("/") && str.endsWith("/");
|
return typeof str === "string" && str.startsWith("/") && str.endsWith("/");
|
||||||
};
|
};
|
||||||
@ -65,16 +66,9 @@ const PatternInput: FC<PatternInputProps> = ({
|
|||||||
register,
|
register,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useFormContext<IBlockAttributes>();
|
} = useFormContext<IBlockAttributes>();
|
||||||
// const getNlpEntityFromCache = useGetFromCache(EntityType.NLP_ENTITY);
|
|
||||||
const [pattern, setPattern] = useState<Pattern>(value);
|
const [pattern, setPattern] = useState<Pattern>(value);
|
||||||
const [patternType, setPatternType] = useState<PatternType>(getType(value));
|
const patternType = getType(value);
|
||||||
const isPostbackType = ["payload", "content", "menu"].includes(patternType);
|
const isPostbackType = ["payload", "content", "menu"].includes(patternType);
|
||||||
const types = [
|
|
||||||
{ value: "text", label: t("label.match_sound") },
|
|
||||||
{ value: "regex", label: t("label.regex") },
|
|
||||||
{ value: "payload", label: t("label.postback") },
|
|
||||||
{ value: "nlp", label: t("label.nlp") },
|
|
||||||
];
|
|
||||||
const registerInput = (
|
const registerInput = (
|
||||||
errorMessage: string,
|
errorMessage: string,
|
||||||
idx: number,
|
idx: number,
|
||||||
@ -100,53 +94,15 @@ const PatternInput: FC<PatternInputProps> = ({
|
|||||||
}, [pattern]);
|
}, [pattern]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box display="flex" flexGrow={1}>
|
||||||
<Grid item xs={2}>
|
{patternType === "nlp" && (
|
||||||
<Input
|
<NlpPatternSelect
|
||||||
select
|
patterns={pattern as NlpPattern[]}
|
||||||
label={t("label.type")}
|
onChange={setPattern}
|
||||||
value={isPostbackType ? "payload" : patternType}
|
/>
|
||||||
onChange={(e) => {
|
)}
|
||||||
const selected = e.target.value as PatternType;
|
|
||||||
|
|
||||||
switch (selected) {
|
{isPostbackType ? (
|
||||||
case "regex": {
|
|
||||||
setPattern("//");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "nlp": {
|
|
||||||
setPattern([]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "menu":
|
|
||||||
case "content":
|
|
||||||
case "payload": {
|
|
||||||
setPattern(null);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
setPattern("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setPatternType(selected);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{types.map((item) => (
|
|
||||||
<MenuItem key={item.value} value={item.value}>
|
|
||||||
{item.label}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Input>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={9}>
|
|
||||||
{patternType === "nlp" && (
|
|
||||||
<NlpPatternSelect
|
|
||||||
patterns={pattern as NlpPattern[]}
|
|
||||||
onChange={setPattern}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isPostbackType ? (
|
|
||||||
<PostbackInput
|
<PostbackInput
|
||||||
onChange={(payload) => {
|
onChange={(payload) => {
|
||||||
payload && setPattern(payload);
|
payload && setPattern(payload);
|
||||||
@ -154,40 +110,39 @@ const PatternInput: FC<PatternInputProps> = ({
|
|||||||
defaultValue={pattern as PayloadPattern}
|
defaultValue={pattern as PayloadPattern}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{typeof value === "string" && patternType === "regex" ? (
|
{typeof value === "string" && patternType === "regex" ? (
|
||||||
<RegexInput
|
<RegexInput
|
||||||
{...registerInput(t("message.regex_is_empty"), idx, {
|
{...registerInput(t("message.regex_is_empty"), idx, {
|
||||||
validate: (pattern) => {
|
validate: (pattern) => {
|
||||||
try {
|
try {
|
||||||
const parsedPattern = new RegExp(pattern.slice(1, -1));
|
const parsedPattern = new RegExp(pattern.slice(1, -1));
|
||||||
|
|
||||||
if (String(parsedPattern) !== pattern) {
|
if (String(parsedPattern) !== pattern) {
|
||||||
throw t("message.regex_is_invalid");
|
throw t("message.regex_is_invalid");
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (_e) {
|
|
||||||
return t("message.regex_is_invalid");
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
setValueAs: (v) => (isRegex(v) ? v : `/${v}/`),
|
return true;
|
||||||
})}
|
} catch (_e) {
|
||||||
label={t("label.regex")}
|
return t("message.regex_is_invalid");
|
||||||
value={value.slice(1, -1)}
|
}
|
||||||
onChange={(v) => onChange(v)}
|
},
|
||||||
required
|
setValueAs: (v) => (isRegex(v) ? v : `/${v}/`),
|
||||||
/>
|
})}
|
||||||
) : null}
|
label={t("label.regex")}
|
||||||
{typeof value === "string" && patternType === "text" ? (
|
value={value.slice(1, -1)}
|
||||||
<Input
|
onChange={(v) => onChange(v)}
|
||||||
{...(getInputProps ? getInputProps(idx) : null)}
|
required
|
||||||
label={t("label.text")}
|
/>
|
||||||
value={value}
|
) : null}
|
||||||
onChange={(e) => onChange(e.target.value)}
|
{typeof value === "string" && patternType === "text" ? (
|
||||||
/>
|
<Input
|
||||||
) : null}
|
{...(getInputProps ? getInputProps(idx) : null)}
|
||||||
</Grid>
|
label={t("label.text")}
|
||||||
</>
|
value={value}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,12 +6,21 @@
|
|||||||
* 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 AddIcon from "@mui/icons-material/Add";
|
import {
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
Abc,
|
||||||
import { Box, Button, Grid, IconButton, styled } from "@mui/material";
|
Add,
|
||||||
import { FC, Fragment, useEffect, useState } from "react";
|
Mouse,
|
||||||
|
PsychologyAlt,
|
||||||
|
RemoveCircleOutline,
|
||||||
|
Spellcheck,
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
import { Box, IconButton, styled } from "@mui/material";
|
||||||
|
import { FC, useEffect, useState } from "react";
|
||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
import DropdownButton, {
|
||||||
|
DropdownButtonAction,
|
||||||
|
} from "@/app-components/buttons/DropdownButton";
|
||||||
import { useTranslate } from "@/hooks/useTranslate";
|
import { useTranslate } from "@/hooks/useTranslate";
|
||||||
import { Pattern } from "@/types/block.types";
|
import { Pattern } from "@/types/block.types";
|
||||||
import { SXStyleOptions } from "@/utils/SXStyleOptions";
|
import { SXStyleOptions } from "@/utils/SXStyleOptions";
|
||||||
@ -21,11 +30,6 @@ import { getInputControls } from "../../utils/inputControls";
|
|||||||
|
|
||||||
import PatternInput from "./PatternInput";
|
import PatternInput from "./PatternInput";
|
||||||
|
|
||||||
type PatternsInputProps = {
|
|
||||||
value: Pattern[];
|
|
||||||
onChange: (patterns: Pattern[]) => void;
|
|
||||||
minInput: number;
|
|
||||||
};
|
|
||||||
const StyledNoPatternsDiv = styled("div")(
|
const StyledNoPatternsDiv = styled("div")(
|
||||||
SXStyleOptions({
|
SXStyleOptions({
|
||||||
color: "grey.500",
|
color: "grey.500",
|
||||||
@ -35,6 +39,19 @@ const StyledNoPatternsDiv = styled("div")(
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
const actions: DropdownButtonAction[] = [
|
||||||
|
{ icon: <Spellcheck />, name: "Exact Match", defaultValue: "" },
|
||||||
|
{ icon: <Abc />, name: "Pattern Match", defaultValue: "//" },
|
||||||
|
{ icon: <PsychologyAlt />, name: "Intent Match", defaultValue: [] },
|
||||||
|
{ icon: <Mouse />, name: "Interaction", defaultValue: {} },
|
||||||
|
];
|
||||||
|
|
||||||
|
type PatternsInputProps = {
|
||||||
|
value: Pattern[];
|
||||||
|
onChange: (patterns: Pattern[]) => void;
|
||||||
|
minInput: number;
|
||||||
|
};
|
||||||
|
|
||||||
const PatternsInput: FC<PatternsInputProps> = ({ value, onChange }) => {
|
const PatternsInput: FC<PatternsInputProps> = ({ value, onChange }) => {
|
||||||
const { t } = useTranslate();
|
const { t } = useTranslate();
|
||||||
const [patterns, setPatterns] = useState<ValueWithId<Pattern>[]>(
|
const [patterns, setPatterns] = useState<ValueWithId<Pattern>[]>(
|
||||||
@ -44,8 +61,8 @@ const PatternsInput: FC<PatternsInputProps> = ({ value, onChange }) => {
|
|||||||
register,
|
register,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useFormContext<any>();
|
} = useFormContext<any>();
|
||||||
const addInput = () => {
|
const addInput = (defaultValue: Pattern) => {
|
||||||
setPatterns([...patterns, createValueWithId<Pattern>("")]);
|
setPatterns([...patterns, createValueWithId<Pattern>(defaultValue)]);
|
||||||
};
|
};
|
||||||
const removeInput = (index: number) => {
|
const removeInput = (index: number) => {
|
||||||
const updatedPatterns = [...patterns];
|
const updatedPatterns = [...patterns];
|
||||||
@ -64,18 +81,13 @@ const PatternsInput: FC<PatternsInputProps> = ({ value, onChange }) => {
|
|||||||
}, [patterns]);
|
}, [patterns]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box display="flex" flexDirection="column">
|
||||||
<Grid container spacing={2}>
|
<Box display="flex" flexDirection="column">
|
||||||
{patterns.length == 0 ? (
|
{patterns.length == 0 ? (
|
||||||
<StyledNoPatternsDiv>{t("label.no_patterns")}</StyledNoPatternsDiv>
|
<StyledNoPatternsDiv>{t("label.no_patterns")}</StyledNoPatternsDiv>
|
||||||
) : (
|
) : (
|
||||||
patterns.map(({ value, id }, idx) => (
|
patterns.map(({ value, id }, idx) => (
|
||||||
<Fragment key={id}>
|
<Box display="flex" mt={2} key={id}>
|
||||||
<Grid item xs={1}>
|
|
||||||
<IconButton onClick={() => removeInput(idx)}>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Grid>
|
|
||||||
<PatternInput
|
<PatternInput
|
||||||
idx={idx}
|
idx={idx}
|
||||||
value={value}
|
value={value}
|
||||||
@ -87,19 +99,20 @@ const PatternsInput: FC<PatternsInputProps> = ({ value, onChange }) => {
|
|||||||
t("message.text_is_required"),
|
t("message.text_is_required"),
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
<IconButton size="small" color="error" onClick={() => removeInput(idx)}>
|
||||||
|
<RemoveCircleOutline />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Box>
|
||||||
<Button
|
<DropdownButton
|
||||||
variant="contained"
|
sx={{ alignSelf: "end", marginTop: 2 }}
|
||||||
color="primary"
|
label={t("button.add_pattern")}
|
||||||
onClick={addInput}
|
actions={actions}
|
||||||
startIcon={<AddIcon />}
|
onClick={(action) => addInput(action.defaultValue as Pattern)}
|
||||||
sx={{ marginTop: 2, float: "right" }}
|
icon={<Add />}
|
||||||
>
|
/>
|
||||||
{t("button.add_pattern")}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user