Toggle switch component (#4890)
* replace all toggle switches with toggle component * add variant, label, and description support for toggle component * refactor Toggle with subcomponents and JSDoc * use checked value from Toggle onChange callback * replace missed inline toggles with Toggle component * fix jsdoc to use optional props --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
parent
fdeb7b9acf
commit
49a2b8f6f3
@ -6,6 +6,7 @@ import { TagsInput } from "react-tag-input-component";
|
|||||||
import { Info, Warning } from "@phosphor-icons/react";
|
import { Info, Warning } from "@phosphor-icons/react";
|
||||||
import { Tooltip } from "react-tooltip";
|
import { Tooltip } from "react-tooltip";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
const DEFAULT_BRANCHES = ["main", "master"];
|
const DEFAULT_BRANCHES = ["main", "master"];
|
||||||
export default function GitlabOptions() {
|
export default function GitlabOptions() {
|
||||||
@ -128,32 +129,18 @@ export default function GitlabOptions() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-x-2 mb-3">
|
<div className="flex items-center gap-x-2 mb-3">
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
<Toggle
|
||||||
<input
|
name="fetchIssues"
|
||||||
type="checkbox"
|
size="md"
|
||||||
name="fetchIssues"
|
label={t("connectors.gitlab.fetch_issues")}
|
||||||
value={true}
|
/>
|
||||||
className="peer sr-only"
|
|
||||||
/>
|
|
||||||
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
<span className="ml-3 text-sm font-medium text-white">
|
|
||||||
{t("connectors.gitlab.fetch_issues")}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-x-2">
|
<div className="flex items-center gap-x-2">
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
<Toggle
|
||||||
<input
|
name="fetchWikis"
|
||||||
type="checkbox"
|
size="md"
|
||||||
name="fetchWikis"
|
label="Fetch Wikis as Documents"
|
||||||
value={true}
|
/>
|
||||||
className="peer sr-only"
|
|
||||||
/>
|
|
||||||
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
<span className="ml-3 text-sm font-medium text-white">
|
|
||||||
Fetch Wikis as Documents
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<GitLabBranchSelection
|
<GitLabBranchSelection
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Tooltip } from "react-tooltip";
|
import { Tooltip } from "react-tooltip";
|
||||||
import { safeJsonParse } from "@/utils/request";
|
import { safeJsonParse } from "@/utils/request";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
import {
|
import {
|
||||||
USERNAME_MIN_LENGTH,
|
USERNAME_MIN_LENGTH,
|
||||||
USERNAME_MAX_LENGTH,
|
USERNAME_MAX_LENGTH,
|
||||||
@ -294,10 +295,9 @@ function AutoSubmitPreference() {
|
|||||||
setAutoSubmitSttInput(settings.autoSubmitSttInput ?? true);
|
setAutoSubmitSttInput(settings.autoSubmitSttInput ?? true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (checked) => {
|
||||||
const newValue = e.target.checked;
|
setAutoSubmitSttInput(checked);
|
||||||
setAutoSubmitSttInput(newValue);
|
Appearance.updateSettings({ autoSubmitSttInput: checked });
|
||||||
Appearance.updateSettings({ autoSubmitSttInput: newValue });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -317,19 +317,7 @@ function AutoSubmitPreference() {
|
|||||||
<Info size={16} weight="bold" className="text-white" />
|
<Info size={16} weight="bold" className="text-white" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-x-4">
|
<Toggle size="lg" enabled={autoSubmitSttInput} onChange={handleChange} />
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
|
||||||
<input
|
|
||||||
id="autoSubmit"
|
|
||||||
type="checkbox"
|
|
||||||
name="autoSubmit"
|
|
||||||
checked={autoSubmitSttInput}
|
|
||||||
onChange={handleChange}
|
|
||||||
className="peer sr-only"
|
|
||||||
/>
|
|
||||||
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
id="auto-submit-info"
|
id="auto-submit-info"
|
||||||
place="bottom"
|
place="bottom"
|
||||||
@ -352,10 +340,9 @@ function AutoSpeakPreference() {
|
|||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (checked) => {
|
||||||
const newValue = e.target.checked;
|
setAutoPlayAssistantTtsResponse(checked);
|
||||||
setAutoPlayAssistantTtsResponse(newValue);
|
Appearance.updateSettings({ autoPlayAssistantTtsResponse: checked });
|
||||||
Appearance.updateSettings({ autoPlayAssistantTtsResponse: newValue });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -375,19 +362,11 @@ function AutoSpeakPreference() {
|
|||||||
<Info size={16} weight="bold" className="text-white" />
|
<Info size={16} weight="bold" className="text-white" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-x-4">
|
<Toggle
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
size="lg"
|
||||||
<input
|
enabled={autoPlayAssistantTtsResponse}
|
||||||
id="autoSpeak"
|
onChange={handleChange}
|
||||||
type="checkbox"
|
/>
|
||||||
name="autoSpeak"
|
|
||||||
checked={autoPlayAssistantTtsResponse}
|
|
||||||
onChange={handleChange}
|
|
||||||
className="peer sr-only"
|
|
||||||
/>
|
|
||||||
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
id="auto-speak-info"
|
id="auto-speak-info"
|
||||||
place="bottom"
|
place="bottom"
|
||||||
|
|||||||
163
frontend/src/components/lib/Toggle/index.jsx
Normal file
163
frontend/src/components/lib/Toggle/index.jsx
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import { Info } from "@phosphor-icons/react";
|
||||||
|
|
||||||
|
const TOGGLE_STYLES = {
|
||||||
|
sm: "h-[12px] w-[20px] after:h-[8px] after:w-[8px] after:top-[2px] after:left-[2px] peer-checked:after:translate-x-full",
|
||||||
|
md: "h-[16px] w-[28px] after:h-[12px] after:w-[12px] after:top-[2px] after:left-[2px] peer-checked:after:translate-x-full",
|
||||||
|
lg: "h-[19px] w-[36px] after:h-[15px] after:w-[15px] after:top-[2px] after:left-[2px] peer-checked:after:translate-x-[17px]",
|
||||||
|
};
|
||||||
|
|
||||||
|
const LABEL_STYLES = {
|
||||||
|
sm: {
|
||||||
|
label: "text-[12px] leading-[10px] font-medium mt-[1.5px]",
|
||||||
|
description: "text-[10px] leading-[16px] font-normal",
|
||||||
|
gap: "gap-[2px]",
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
label: "text-[14px] leading-[18px] font-medium -mt-[2px]",
|
||||||
|
description: "text-[12px] leading-[16px] font-normal",
|
||||||
|
gap: "gap-[2px]",
|
||||||
|
},
|
||||||
|
lg: {
|
||||||
|
label: "text-[16px] leading-[14px] font-medium mt-[2.5px]",
|
||||||
|
description: "text-[14px] leading-[24px] font-normal",
|
||||||
|
gap: "gap-[2px]",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} props - Component props
|
||||||
|
* @param {string} [props.className] - Additional CSS classes
|
||||||
|
* @param {boolean} [props.enabled] - Controlled checked state
|
||||||
|
* @param {(checked: boolean) => void} [props.onChange] - Change handler receiving new checked state
|
||||||
|
* @param {boolean} [props.disabled=false] - Whether toggle is disabled
|
||||||
|
* @param {"sm" | "md" | "lg"} [props.size="sm"] - Toggle size
|
||||||
|
* @param {string} [props.name] - Input name for form submission
|
||||||
|
* @param {string} [props.label] - Label text next to toggle
|
||||||
|
* @param {string} [props.description] - Description text below label
|
||||||
|
* @param {"default" | "horizontal"} [props.variant="default"] - Layout variant
|
||||||
|
* @param {string} [props.hint] - Tooltip ID for info icon hint next to label
|
||||||
|
* @param {string} [props.value] - Input value for form submission
|
||||||
|
*/
|
||||||
|
export default function Toggle({
|
||||||
|
className,
|
||||||
|
enabled,
|
||||||
|
onChange,
|
||||||
|
disabled = false,
|
||||||
|
size = "sm",
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
variant = "default",
|
||||||
|
hint,
|
||||||
|
value,
|
||||||
|
}) {
|
||||||
|
const inputProps =
|
||||||
|
enabled !== undefined
|
||||||
|
? { checked: enabled, onChange: (e) => onChange?.(e.target.checked) }
|
||||||
|
: { defaultChecked: false };
|
||||||
|
|
||||||
|
const labelStyles = LABEL_STYLES[size] || LABEL_STYLES.sm;
|
||||||
|
|
||||||
|
if (variant === "horizontal") {
|
||||||
|
return (
|
||||||
|
<label
|
||||||
|
className={`flex items-start justify-between max-w-[700px] ${disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"} ${className ?? ""}`}
|
||||||
|
>
|
||||||
|
<TextContent
|
||||||
|
label={label}
|
||||||
|
description={description}
|
||||||
|
labelStyles={labelStyles}
|
||||||
|
hint={hint}
|
||||||
|
/>
|
||||||
|
<div className="shrink-0 ml-4">
|
||||||
|
<ToggleSwitch
|
||||||
|
name={name}
|
||||||
|
disabled={disabled}
|
||||||
|
size={size}
|
||||||
|
inputProps={inputProps}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label
|
||||||
|
className={`inline-flex items-start ${disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"} ${className ?? ""}`}
|
||||||
|
>
|
||||||
|
<ToggleSwitch
|
||||||
|
name={name}
|
||||||
|
disabled={disabled}
|
||||||
|
size={size}
|
||||||
|
inputProps={inputProps}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
{(label || description) && (
|
||||||
|
<div className="ml-3">
|
||||||
|
<TextContent
|
||||||
|
label={label}
|
||||||
|
description={description}
|
||||||
|
labelStyles={labelStyles}
|
||||||
|
hint={hint}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToggleSwitch({ name, disabled, size, inputProps, value }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name={name}
|
||||||
|
disabled={disabled}
|
||||||
|
className="peer sr-only"
|
||||||
|
value={value}
|
||||||
|
{...inputProps}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
relative shrink-0 peer pointer-events-none rounded-full
|
||||||
|
${TOGGLE_STYLES[size] || TOGGLE_STYLES.sm}
|
||||||
|
after:absolute after:rounded-full after:bg-white
|
||||||
|
after:transition-all after:content-['']
|
||||||
|
peer-focus:ring-2
|
||||||
|
bg-zinc-500 light:bg-zinc-300 peer-focus:ring-zinc-700 light:peer-focus:bg-green-100 light:peer-focus:ring-green-200
|
||||||
|
peer-checked:bg-green-400 peer-checked:peer-focus:bg-green-300 peer-checked:peer-focus:ring-green-900 light:peer-checked:peer-focus:bg-green-300 light:peer-checked:peer-focus:ring-green-200
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TextContent({ label, description, labelStyles = {}, hint }) {
|
||||||
|
if (!label && !description) return null;
|
||||||
|
return (
|
||||||
|
<div className={`flex flex-col ${labelStyles.gap}`}>
|
||||||
|
{label && (
|
||||||
|
<span
|
||||||
|
className={`flex items-center gap-x-1 text-white light:text-slate-950 ${labelStyles.label}`}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
{hint && (
|
||||||
|
<Info
|
||||||
|
size={14}
|
||||||
|
className="text-theme-text-secondary cursor-pointer"
|
||||||
|
data-tooltip-id={hint}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{description && (
|
||||||
|
<span
|
||||||
|
className={`text-zinc-400 light:text-zinc-600 ${labelStyles.description}`}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -11,6 +11,7 @@ import {
|
|||||||
BracketsCurly,
|
BracketsCurly,
|
||||||
} from "@phosphor-icons/react";
|
} from "@phosphor-icons/react";
|
||||||
import { Tooltip } from "react-tooltip";
|
import { Tooltip } from "react-tooltip";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
import StartNode from "../nodes/StartNode";
|
import StartNode from "../nodes/StartNode";
|
||||||
import ApiCallNode from "../nodes/ApiCallNode";
|
import ApiCallNode from "../nodes/ApiCallNode";
|
||||||
import WebsiteNode from "../nodes/WebsiteNode";
|
import WebsiteNode from "../nodes/WebsiteNode";
|
||||||
@ -172,32 +173,20 @@ export default function BlockList({
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{renderBlockConfigContent(block, props)}
|
{renderBlockConfigContent(block, props)}
|
||||||
<div className="flex justify-between items-center pt-4 border-t border-white/10">
|
<div className="pt-4 border-t border-white/10">
|
||||||
<div>
|
<Toggle
|
||||||
<label className="block text-sm font-medium text-theme-text-primary">
|
size="md"
|
||||||
Direct Output
|
variant="horizontal"
|
||||||
</label>
|
label="Direct Output"
|
||||||
<p className="text-xs text-theme-text-secondary">
|
description="The output of this block will be returned directly to the chat. This will prevent any further tool calls from being executed."
|
||||||
The output of this block will be returned directly to the chat.
|
enabled={props.config.directOutput || false}
|
||||||
<br />
|
onChange={(checked) =>
|
||||||
This will prevent any further tool calls from being executed.
|
props.onConfigChange({
|
||||||
</p>
|
...props.config,
|
||||||
</div>
|
directOutput: checked,
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
})
|
||||||
<input
|
}
|
||||||
type="checkbox"
|
/>
|
||||||
checked={props.config.directOutput || false}
|
|
||||||
onChange={(e) =>
|
|
||||||
props.onConfigChange({
|
|
||||||
...props.config,
|
|
||||||
directOutput: e.target.checked,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="peer sr-only"
|
|
||||||
aria-label="Toggle direct output"
|
|
||||||
/>
|
|
||||||
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Info } from "@phosphor-icons/react";
|
import Toggle from "@/components/lib/Toggle";
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export default function WebScrapingNode({
|
export default function WebScrapingNode({
|
||||||
config,
|
config,
|
||||||
@ -72,35 +71,16 @@ export default function WebScrapingNode({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex justify-between items-center">
|
<Toggle
|
||||||
<div className="flex flex-row items-center gap-x-1 mb-2">
|
size="md"
|
||||||
<label className="block text-sm font-medium text-theme-text-primary">
|
variant="horizontal"
|
||||||
Content Summarization
|
label="Content Summarization"
|
||||||
</label>
|
hint="content-summarization-tooltip"
|
||||||
<Info
|
enabled={config.enableSummarization ?? true}
|
||||||
size={16}
|
onChange={(checked) =>
|
||||||
className="text-theme-text-secondary cursor-pointer"
|
onConfigChange({ ...config, enableSummarization: checked })
|
||||||
data-tooltip-id="content-summarization-tooltip"
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 mb-2">
|
|
||||||
<label className="relative inline-flex items-center cursor-pointer">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={config.enableSummarization ?? true}
|
|
||||||
onChange={(e) =>
|
|
||||||
onConfigChange({
|
|
||||||
...config,
|
|
||||||
enableSummarization: e.target.checked,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="sr-only peer"
|
|
||||||
aria-label="Toggle content summarization"
|
|
||||||
/>
|
|
||||||
<div className="w-11 h-6 bg-theme-settings-input-bg peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-button/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-button"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-theme-text-primary mb-2">
|
<label className="block text-sm font-medium text-theme-text-primary mb-2">
|
||||||
Result Variable
|
Result Variable
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import showToast from "@/utils/toast";
|
|||||||
import { FlowArrow, Gear } from "@phosphor-icons/react";
|
import { FlowArrow, Gear } from "@phosphor-icons/react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import paths from "@/utils/paths";
|
import paths from "@/utils/paths";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
function ManageFlowMenu({ flow, onDelete }) {
|
function ManageFlowMenu({ flow, onDelete }) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@ -97,22 +98,17 @@ export default function FlowPanel({ flow, toggleFlow, onDelete }) {
|
|||||||
<>
|
<>
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
||||||
<div className="flex items-center gap-x-2">
|
<div className="flex w-full justify-between items-center">
|
||||||
<FlowArrow size={24} weight="bold" className="text-white" />
|
<div className="flex items-center gap-x-2">
|
||||||
<label htmlFor="name" className="text-white text-md font-bold">
|
<FlowArrow size={24} weight="bold" className="text-white" />
|
||||||
{flow.name}
|
<label htmlFor="name" className="text-white text-md font-bold">
|
||||||
</label>
|
{flow.name}
|
||||||
<label className="border-none relative inline-flex items-center ml-auto cursor-pointer">
|
</label>
|
||||||
<input
|
</div>
|
||||||
type="checkbox"
|
<div className="flex items-center gap-x-2">
|
||||||
className="peer sr-only"
|
<Toggle size="lg" enabled={isActive} onChange={handleToggle} />
|
||||||
checked={isActive}
|
<ManageFlowMenu flow={flow} onDelete={onDelete} />
|
||||||
onChange={handleToggle}
|
</div>
|
||||||
/>
|
|
||||||
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
<span className="ml-3 text-sm font-medium"></span>
|
|
||||||
</label>
|
|
||||||
<ManageFlowMenu flow={flow} onDelete={onDelete} />
|
|
||||||
</div>
|
</div>
|
||||||
<p className="whitespace-pre-wrap text-white text-opacity-60 text-xs font-medium py-1.5">
|
<p className="whitespace-pre-wrap text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||||
{flow.description || "No description provided"}
|
{flow.description || "No description provided"}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { DefaultBadge } from "../Badges/default";
|
import { DefaultBadge } from "../Badges/default";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
export default function DefaultSkillPanel({
|
export default function DefaultSkillPanel({
|
||||||
title,
|
title,
|
||||||
@ -29,18 +30,11 @@ export default function DefaultSkillPanel({
|
|||||||
</label>
|
</label>
|
||||||
<DefaultBadge title={title} />
|
<DefaultBadge title={title} />
|
||||||
</div>
|
</div>
|
||||||
<label
|
<Toggle
|
||||||
className={`border-none relative inline-flex items-center ml-auto cursor-pointer`}
|
size="lg"
|
||||||
>
|
enabled={enabled}
|
||||||
<input
|
onChange={() => toggleSkill(skill)}
|
||||||
type="checkbox"
|
/>
|
||||||
className="peer sr-only"
|
|
||||||
checked={enabled}
|
|
||||||
onChange={() => toggleSkill(skill)}
|
|
||||||
/>
|
|
||||||
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
<span className="ml-3 text-sm font-medium"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<img src={image} alt={title} className="w-full rounded-md" />
|
<img src={image} alt={title} className="w-full rounded-md" />
|
||||||
<p className="text-theme-text-secondary text-opacity-60 text-xs font-medium py-1.5">
|
<p className="text-theme-text-secondary text-opacity-60 text-xs font-medium py-1.5">
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
export default function GenericSkillPanel({
|
export default function GenericSkillPanel({
|
||||||
title,
|
title,
|
||||||
@ -13,34 +14,27 @@ export default function GenericSkillPanel({
|
|||||||
return (
|
return (
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
||||||
<div className="flex items-center gap-x-2">
|
<div className="flex w-full justify-between items-center">
|
||||||
{icon &&
|
<div className="flex items-center gap-x-2">
|
||||||
React.createElement(icon, {
|
{icon &&
|
||||||
size: 24,
|
React.createElement(icon, {
|
||||||
color: "var(--theme-text-primary)",
|
size: 24,
|
||||||
weight: "bold",
|
color: "var(--theme-text-primary)",
|
||||||
})}
|
weight: "bold",
|
||||||
<label
|
})}
|
||||||
htmlFor="name"
|
<label
|
||||||
className="text-theme-text-primary text-md font-bold"
|
htmlFor="name"
|
||||||
>
|
className="text-theme-text-primary text-md font-bold"
|
||||||
{title}
|
>
|
||||||
</label>
|
{title}
|
||||||
<label
|
</label>
|
||||||
className={`border-none relative inline-flex items-center ml-auto ${
|
</div>
|
||||||
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
<Toggle
|
||||||
}`}
|
size="lg"
|
||||||
>
|
enabled={enabled}
|
||||||
<input
|
disabled={disabled}
|
||||||
type="checkbox"
|
onChange={() => toggleSkill(skill)}
|
||||||
disabled={disabled}
|
/>
|
||||||
className="peer sr-only"
|
|
||||||
checked={enabled}
|
|
||||||
onChange={() => toggleSkill(skill)}
|
|
||||||
/>
|
|
||||||
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
<span className="ml-3 text-sm font-medium"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<img src={image} alt={title} className="w-full rounded-md" />
|
<img src={image} alt={title} className="w-full rounded-md" />
|
||||||
<p className="text-theme-text-secondary text-opacity-60 text-xs font-medium py-1.5">
|
<p className="text-theme-text-secondary text-opacity-60 text-xs font-medium py-1.5">
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import showToast from "@/utils/toast";
|
|||||||
import { Gear, Plug } from "@phosphor-icons/react";
|
import { Gear, Plug } from "@phosphor-icons/react";
|
||||||
import { useEffect, useState, useRef } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import { sentenceCase } from "text-case";
|
import { sentenceCase } from "text-case";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts setup_args to inputs for the form builder
|
* Converts setup_args to inputs for the form builder
|
||||||
@ -110,25 +111,24 @@ export default function ImportedSkillConfig({
|
|||||||
<>
|
<>
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
||||||
<div className="flex items-center gap-x-2">
|
<div className="flex w-full justify-between items-center">
|
||||||
<Plug size={24} weight="bold" className="text-white" />
|
<div className="flex items-center gap-x-2">
|
||||||
<label htmlFor="name" className="text-white text-md font-bold">
|
<Plug size={24} weight="bold" className="text-white" />
|
||||||
{sentenceCase(config.name)}
|
<label htmlFor="name" className="text-white text-md font-bold">
|
||||||
</label>
|
{sentenceCase(config.name)}
|
||||||
<label className="border-none relative inline-flex items-center ml-auto cursor-pointer">
|
</label>
|
||||||
<input
|
</div>
|
||||||
type="checkbox"
|
<div className="flex items-center gap-x-2">
|
||||||
className="peer sr-only"
|
<Toggle
|
||||||
checked={config.active}
|
size="lg"
|
||||||
onChange={() => toggleSkill()}
|
enabled={config.active}
|
||||||
|
onChange={toggleSkill}
|
||||||
/>
|
/>
|
||||||
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
<ManageSkillMenu
|
||||||
<span className="ml-3 text-sm font-medium"></span>
|
config={config}
|
||||||
</label>
|
setImportedSkills={setImportedSkills}
|
||||||
<ManageSkillMenu
|
/>
|
||||||
config={config}
|
</div>
|
||||||
setImportedSkills={setImportedSkills}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||||
{config.description} by{" "}
|
{config.description} by{" "}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { WarningOctagon, X } from "@phosphor-icons/react";
|
|||||||
import { DB_LOGOS } from "./DBConnection";
|
import { DB_LOGOS } from "./DBConnection";
|
||||||
import System from "@/models/system";
|
import System from "@/models/system";
|
||||||
import showToast from "@/utils/toast";
|
import showToast from "@/utils/toast";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
function assembleConnectionString({
|
function assembleConnectionString({
|
||||||
engine,
|
engine,
|
||||||
@ -288,21 +289,13 @@ export default function NewSQLConnection({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{engine === "sql-server" && (
|
{engine === "sql-server" && (
|
||||||
<div className="flex items-center justify-between">
|
<Toggle
|
||||||
<label className="relative inline-flex items-center cursor-pointer">
|
name="encrypt"
|
||||||
<input
|
value="true"
|
||||||
type="checkbox"
|
size="md"
|
||||||
name="encrypt"
|
label="Enable Encryption"
|
||||||
value="true"
|
enabled={config.encrypt}
|
||||||
className="sr-only peer"
|
/>
|
||||||
checked={config.encrypt}
|
|
||||||
/>
|
|
||||||
<div className="w-11 h-6 bg-theme-settings-input-bg peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
|
|
||||||
<span className="ml-3 text-sm font-medium text-white">
|
|
||||||
Enable Encryption
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="text-theme-text-secondary text-sm">
|
<p className="text-theme-text-secondary text-sm">
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import NewSQLConnection from "./NewConnectionModal";
|
|||||||
import { useModal } from "@/hooks/useModal";
|
import { useModal } from "@/hooks/useModal";
|
||||||
import SQLAgentImage from "@/media/agents/sql-agent.png";
|
import SQLAgentImage from "@/media/agents/sql-agent.png";
|
||||||
import Admin from "@/models/admin";
|
import Admin from "@/models/admin";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
export default function AgentSQLConnectorSelection({
|
export default function AgentSQLConnectorSelection({
|
||||||
skill,
|
skill,
|
||||||
@ -36,28 +37,25 @@ export default function AgentSQLConnectorSelection({
|
|||||||
<>
|
<>
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
||||||
<div className="flex items-center gap-x-2">
|
<div className="flex w-full justify-between items-center">
|
||||||
<Database
|
<div className="flex items-center gap-x-2">
|
||||||
size={24}
|
<Database
|
||||||
color="var(--theme-text-primary)"
|
size={24}
|
||||||
weight="bold"
|
color="var(--theme-text-primary)"
|
||||||
/>
|
weight="bold"
|
||||||
<label
|
|
||||||
htmlFor="name"
|
|
||||||
className="text-theme-text-primary text-md font-bold"
|
|
||||||
>
|
|
||||||
SQL Agent
|
|
||||||
</label>
|
|
||||||
<label className="border-none relative inline-flex items-center ml-auto cursor-pointer">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="peer sr-only"
|
|
||||||
checked={enabled}
|
|
||||||
onChange={() => toggleSkill(skill)}
|
|
||||||
/>
|
/>
|
||||||
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
<label
|
||||||
<span className="ml-3 text-sm font-medium"></span>
|
htmlFor="name"
|
||||||
</label>
|
className="text-theme-text-primary text-md font-bold"
|
||||||
|
>
|
||||||
|
SQL Agent
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<Toggle
|
||||||
|
size="lg"
|
||||||
|
enabled={enabled}
|
||||||
|
onChange={() => toggleSkill(skill)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
src={SQLAgentImage}
|
src={SQLAgentImage}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
X,
|
X,
|
||||||
ListMagnifyingGlass,
|
ListMagnifyingGlass,
|
||||||
} from "@phosphor-icons/react";
|
} from "@phosphor-icons/react";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
import SearchProviderItem from "./SearchProviderItem";
|
import SearchProviderItem from "./SearchProviderItem";
|
||||||
import WebSearchImage from "@/media/agents/scrape-websites.png";
|
import WebSearchImage from "@/media/agents/scrape-websites.png";
|
||||||
import {
|
import {
|
||||||
@ -170,28 +171,25 @@ export default function AgentWebSearchSelection({
|
|||||||
return (
|
return (
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
||||||
<div className="flex items-center gap-x-2">
|
<div className="flex w-full justify-between items-center">
|
||||||
<ListMagnifyingGlass
|
<div className="flex items-center gap-x-2">
|
||||||
size={24}
|
<ListMagnifyingGlass
|
||||||
color="var(--theme-text-primary)"
|
size={24}
|
||||||
weight="bold"
|
color="var(--theme-text-primary)"
|
||||||
/>
|
weight="bold"
|
||||||
<label
|
|
||||||
htmlFor="name"
|
|
||||||
className="text-theme-text-primary text-md font-bold"
|
|
||||||
>
|
|
||||||
Live web search and browsing
|
|
||||||
</label>
|
|
||||||
<label className="border-none relative inline-flex items-center ml-auto cursor-pointer">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="peer sr-only"
|
|
||||||
checked={enabled}
|
|
||||||
onChange={() => toggleSkill(skill)}
|
|
||||||
/>
|
/>
|
||||||
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
<label
|
||||||
<span className="ml-3 text-sm font-medium"></span>
|
htmlFor="name"
|
||||||
</label>
|
className="text-theme-text-primary text-md font-bold"
|
||||||
|
>
|
||||||
|
Live web search and browsing
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<Toggle
|
||||||
|
size="lg"
|
||||||
|
enabled={enabled}
|
||||||
|
onChange={() => toggleSkill(skill)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
src={WebSearchImage}
|
src={WebSearchImage}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import showToast from "@/utils/toast";
|
|||||||
import { ArrowSquareOut } from "@phosphor-icons/react";
|
import { ArrowSquareOut } from "@phosphor-icons/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
export default function LiveSyncToggle({ enabled = false, onToggle }) {
|
export default function LiveSyncToggle({ enabled = false, onToggle }) {
|
||||||
const [status, setStatus] = useState(enabled);
|
const [status, setStatus] = useState(enabled);
|
||||||
@ -36,15 +37,7 @@ export default function LiveSyncToggle({ enabled = false, onToggle }) {
|
|||||||
<h2 className="text-theme-text-primary text-md font-bold">
|
<h2 className="text-theme-text-primary text-md font-bold">
|
||||||
Automatic Document Content Sync
|
Automatic Document Content Sync
|
||||||
</h2>
|
</h2>
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
<Toggle size="lg" enabled={status} onChange={toggleFeatureFlag} />
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
onClick={toggleFeatureFlag}
|
|
||||||
checked={status}
|
|
||||||
className="peer sr-only pointer-events-none"
|
|
||||||
/>
|
|
||||||
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
<p className="text-theme-text-secondary text-sm">
|
<p className="text-theme-text-secondary text-sm">
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import NewUserModal from "./NewUserModal";
|
|||||||
import { useModal } from "@/hooks/useModal";
|
import { useModal } from "@/hooks/useModal";
|
||||||
import ModalWrapper from "@/components/ModalWrapper";
|
import ModalWrapper from "@/components/ModalWrapper";
|
||||||
import CTAButton from "@/components/lib/CTAButton";
|
import CTAButton from "@/components/lib/CTAButton";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
export default function AdminUsers() {
|
export default function AdminUsers() {
|
||||||
const { isOpen, openModal, closeModal } = useModal();
|
const { isOpen, openModal, closeModal } = useModal();
|
||||||
@ -147,31 +148,19 @@ export function MessageLimitInput({ enabled, limit, updateState, role }) {
|
|||||||
if (role === "admin") return null;
|
if (role === "admin") return null;
|
||||||
return (
|
return (
|
||||||
<div className="mt-4 mb-8">
|
<div className="mt-4 mb-8">
|
||||||
<div className="flex flex-col gap-y-1">
|
<Toggle
|
||||||
<div className="flex items-center gap-x-2">
|
size="md"
|
||||||
<h2 className="text-base leading-6 font-bold text-white">
|
variant="horizontal"
|
||||||
Limit messages per day
|
label="Limit messages per day"
|
||||||
</h2>
|
description="Restrict this user to a number of successful queries or chats within a 24 hour window."
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
enabled={enabled}
|
||||||
<input
|
onChange={(checked) => {
|
||||||
type="checkbox"
|
updateState((prev) => ({
|
||||||
checked={enabled}
|
...prev,
|
||||||
onChange={(e) => {
|
enabled: checked,
|
||||||
updateState((prev) => ({
|
}));
|
||||||
...prev,
|
}}
|
||||||
enabled: e.target.checked,
|
/>
|
||||||
}));
|
|
||||||
}}
|
|
||||||
className="peer sr-only"
|
|
||||||
/>
|
|
||||||
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs leading-[18px] font-base text-white/60">
|
|
||||||
Restrict this user to a number of successful queries or chats within a
|
|
||||||
24 hour window.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{enabled && (
|
{enabled && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<label className="text-white text-sm font-semibold block mb-4">
|
<label className="text-white text-sm font-semibold block mb-4">
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { X } from "@phosphor-icons/react";
|
|||||||
import Workspace from "@/models/workspace";
|
import Workspace from "@/models/workspace";
|
||||||
import { TagsInput } from "react-tag-input-component";
|
import { TagsInput } from "react-tag-input-component";
|
||||||
import Embed from "@/models/embed";
|
import Embed from "@/models/embed";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
export function enforceSubmissionSchema(form) {
|
export function enforceSubmissionSchema(form) {
|
||||||
const data = {};
|
const data = {};
|
||||||
@ -343,23 +344,14 @@ export const BooleanInput = ({ name, title, hint, defaultValue = null }) => {
|
|||||||
const [status, setStatus] = useState(defaultValue ?? false);
|
const [status, setStatus] = useState(defaultValue ?? false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Toggle
|
||||||
<div className="flex flex-col mb-2">
|
name={name}
|
||||||
<label htmlFor={name} className="block text-sm font-medium text-white">
|
size="md"
|
||||||
{title}
|
variant="horizontal"
|
||||||
</label>
|
label={title}
|
||||||
<p className="text-theme-text-secondary text-xs">{hint}</p>
|
description={hint}
|
||||||
</div>
|
enabled={status}
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
onChange={(checked) => setStatus(checked)}
|
||||||
<input
|
/>
|
||||||
name={name}
|
|
||||||
type="checkbox"
|
|
||||||
onClick={() => setStatus(!status)}
|
|
||||||
checked={status}
|
|
||||||
className="peer sr-only pointer-events-none"
|
|
||||||
/>
|
|
||||||
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import System from "@/models/system";
|
|||||||
import PreLoader from "@/components/Preloader";
|
import PreLoader from "@/components/Preloader";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import ProviderPrivacy from "@/components/ProviderPrivacy";
|
import ProviderPrivacy from "@/components/ProviderPrivacy";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
export default function PrivacyAndDataHandling() {
|
export default function PrivacyAndDataHandling() {
|
||||||
const [settings, setSettings] = useState({});
|
const [settings, setSettings] = useState({});
|
||||||
@ -80,18 +81,13 @@ function TelemetryLogs({ settings }) {
|
|||||||
<div className="space-y-6 flex h-full w-full">
|
<div className="space-y-6 flex h-full w-full">
|
||||||
<div className="w-full flex flex-col gap-y-4">
|
<div className="w-full flex flex-col gap-y-4">
|
||||||
<div className="">
|
<div className="">
|
||||||
<label className="mb-2.5 block font-medium text-theme-text-primary">
|
<Toggle
|
||||||
{t("privacy.anonymous")}
|
size="lg"
|
||||||
</label>
|
className="mb-4"
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
label={t("privacy.anonymous")}
|
||||||
<input
|
enabled={telemetry}
|
||||||
type="checkbox"
|
onChange={toggleTelemetry}
|
||||||
onClick={toggleTelemetry}
|
/>
|
||||||
checked={telemetry}
|
|
||||||
className="peer sr-only pointer-events-none"
|
|
||||||
/>
|
|
||||||
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { AUTH_TIMESTAMP, AUTH_TOKEN, AUTH_USER } from "@/utils/constants";
|
|||||||
import PreLoader from "@/components/Preloader";
|
import PreLoader from "@/components/Preloader";
|
||||||
import CTAButton from "@/components/lib/CTAButton";
|
import CTAButton from "@/components/lib/CTAButton";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
import {
|
import {
|
||||||
USERNAME_MIN_LENGTH,
|
USERNAME_MIN_LENGTH,
|
||||||
USERNAME_MAX_LENGTH,
|
USERNAME_MAX_LENGTH,
|
||||||
@ -125,26 +126,19 @@ function MultiUserMode() {
|
|||||||
<div className="flex items-start justify-between px-6 py-4"></div>
|
<div className="flex items-start justify-between px-6 py-4"></div>
|
||||||
<div className="space-y-6 flex h-full w-full">
|
<div className="space-y-6 flex h-full w-full">
|
||||||
<div className="w-full flex flex-col gap-y-4">
|
<div className="w-full flex flex-col gap-y-4">
|
||||||
<div className="">
|
{multiUserModeEnabled ? (
|
||||||
<label className="text-white text-sm font-semibold block mb-3">
|
<p className="text-white text-sm font-semibold">
|
||||||
{multiUserModeEnabled
|
{t("security.multiuser.enable.is-enable")}
|
||||||
? t("security.multiuser.enable.is-enable")
|
</p>
|
||||||
: t("security.multiuser.enable.enable")}
|
) : (
|
||||||
</label>
|
<Toggle
|
||||||
|
size="lg"
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
className="mb-4"
|
||||||
<input
|
label={t("security.multiuser.enable.enable")}
|
||||||
type="checkbox"
|
enabled={useMultiUserMode}
|
||||||
onClick={() => setUseMultiUserMode(!useMultiUserMode)}
|
onChange={(checked) => setUseMultiUserMode(checked)}
|
||||||
defaultChecked={useMultiUserMode}
|
/>
|
||||||
className="peer sr-only pointer-events-none"
|
)}
|
||||||
/>
|
|
||||||
<div
|
|
||||||
hidden={multiUserModeEnabled}
|
|
||||||
className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{useMultiUserMode && (
|
{useMultiUserMode && (
|
||||||
<div className="w-full flex flex-col gap-y-2 my-5">
|
<div className="w-full flex flex-col gap-y-2 my-5">
|
||||||
<div className="w-80">
|
<div className="w-80">
|
||||||
@ -307,21 +301,13 @@ function PasswordProtection() {
|
|||||||
<div className="flex items-start justify-between px-6 py-4"></div>
|
<div className="flex items-start justify-between px-6 py-4"></div>
|
||||||
<div className="space-y-6 flex h-full w-full">
|
<div className="space-y-6 flex h-full w-full">
|
||||||
<div className="w-full flex flex-col gap-y-4">
|
<div className="w-full flex flex-col gap-y-4">
|
||||||
<div className="">
|
<Toggle
|
||||||
<label className="text-white text-sm font-semibold block mb-3">
|
size="lg"
|
||||||
{t("security.password.title")}
|
className="mb-4"
|
||||||
</label>
|
label={t("security.password.title")}
|
||||||
|
enabled={usePassword}
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
onChange={(checked) => setUsePassword(checked)}
|
||||||
<input
|
/>
|
||||||
type="checkbox"
|
|
||||||
onClick={() => setUsePassword(!usePassword)}
|
|
||||||
defaultChecked={usePassword}
|
|
||||||
className="peer sr-only pointer-events-none"
|
|
||||||
/>
|
|
||||||
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{usePassword && (
|
{usePassword && (
|
||||||
<div className="w-full flex flex-col gap-y-2 my-5">
|
<div className="w-full flex flex-col gap-y-2 my-5">
|
||||||
<div className="mt-4 w-80">
|
<div className="mt-4 w-80">
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import Appearance from "@/models/appearance";
|
import Appearance from "@/models/appearance";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
export default function AutoSpeak() {
|
export default function AutoSpeak() {
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
@ -8,15 +9,14 @@ export default function AutoSpeak() {
|
|||||||
useState(false);
|
useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChange = async (e) => {
|
const handleChange = async (checked) => {
|
||||||
const newValue = e.target.checked;
|
setAutoPlayAssistantTtsResponse(checked);
|
||||||
setAutoPlayAssistantTtsResponse(newValue);
|
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
Appearance.updateSettings({ autoPlayAssistantTtsResponse: newValue });
|
Appearance.updateSettings({ autoPlayAssistantTtsResponse: checked });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update appearance settings:", error);
|
console.error("Failed to update appearance settings:", error);
|
||||||
setAutoPlayAssistantTtsResponse(!newValue);
|
setAutoPlayAssistantTtsResponse(!checked);
|
||||||
}
|
}
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
};
|
};
|
||||||
@ -32,28 +32,16 @@ export default function AutoSpeak() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-y-0.5 my-4">
|
<div className="my-4">
|
||||||
<p className="text-sm leading-6 font-semibold text-white">
|
<Toggle
|
||||||
{t("customization.chat.auto_speak.title")}
|
size="md"
|
||||||
</p>
|
variant="horizontal"
|
||||||
<p className="text-xs text-white/60">
|
enabled={autoPlayAssistantTtsResponse}
|
||||||
{t("customization.chat.auto_speak.description")}
|
onChange={handleChange}
|
||||||
</p>
|
disabled={saving}
|
||||||
<div className="flex items-center gap-x-4">
|
label={t("customization.chat.auto_speak.title")}
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
description={t("customization.chat.auto_speak.description")}
|
||||||
<input
|
/>
|
||||||
id="auto_speak"
|
|
||||||
type="checkbox"
|
|
||||||
name="auto_speak"
|
|
||||||
value="yes"
|
|
||||||
checked={autoPlayAssistantTtsResponse}
|
|
||||||
onChange={handleChange}
|
|
||||||
disabled={saving}
|
|
||||||
className="peer sr-only"
|
|
||||||
/>
|
|
||||||
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import Appearance from "@/models/appearance";
|
import Appearance from "@/models/appearance";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
export default function AutoSubmit() {
|
export default function AutoSubmit() {
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [autoSubmitSttInput, setAutoSubmitSttInput] = useState(true);
|
const [autoSubmitSttInput, setAutoSubmitSttInput] = useState(true);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChange = async (e) => {
|
const handleChange = async (checked) => {
|
||||||
const newValue = e.target.checked;
|
setAutoSubmitSttInput(checked);
|
||||||
setAutoSubmitSttInput(newValue);
|
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
Appearance.updateSettings({ autoSubmitSttInput: newValue });
|
Appearance.updateSettings({ autoSubmitSttInput: checked });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update appearance settings:", error);
|
console.error("Failed to update appearance settings:", error);
|
||||||
setAutoSubmitSttInput(!newValue);
|
setAutoSubmitSttInput(!checked);
|
||||||
}
|
}
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
};
|
};
|
||||||
@ -29,28 +29,16 @@ export default function AutoSubmit() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-y-0.5 my-4">
|
<div className="my-4">
|
||||||
<p className="text-sm leading-6 font-semibold text-white">
|
<Toggle
|
||||||
{t("customization.chat.auto_submit.title")}
|
size="md"
|
||||||
</p>
|
variant="horizontal"
|
||||||
<p className="text-xs text-white/60">
|
enabled={autoSubmitSttInput}
|
||||||
{t("customization.chat.auto_submit.description")}
|
onChange={handleChange}
|
||||||
</p>
|
disabled={saving}
|
||||||
<div className="flex items-center gap-x-4">
|
label={t("customization.chat.auto_submit.title")}
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
description={t("customization.chat.auto_submit.description")}
|
||||||
<input
|
/>
|
||||||
id="auto_submit"
|
|
||||||
type="checkbox"
|
|
||||||
name="auto_submit"
|
|
||||||
value="yes"
|
|
||||||
checked={autoSubmitSttInput}
|
|
||||||
onChange={handleChange}
|
|
||||||
disabled={saving}
|
|
||||||
className="peer sr-only"
|
|
||||||
/>
|
|
||||||
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import Appearance from "@/models/appearance";
|
import Appearance from "@/models/appearance";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
export default function ChatRenderHTML() {
|
export default function ChatRenderHTML() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [renderHTML, setRenderHTML] = useState(false);
|
const [renderHTML, setRenderHTML] = useState(false);
|
||||||
|
|
||||||
const handleChange = async (e) => {
|
const handleChange = async (checked) => {
|
||||||
const newValue = e.target.checked;
|
setRenderHTML(checked);
|
||||||
setRenderHTML(newValue);
|
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
Appearance.updateSettings({ renderHTML: newValue });
|
Appearance.updateSettings({ renderHTML: checked });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update appearance settings:", error);
|
console.error("Failed to update appearance settings:", error);
|
||||||
setRenderHTML(!newValue);
|
setRenderHTML(!checked);
|
||||||
}
|
}
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
};
|
};
|
||||||
@ -29,28 +29,16 @@ export default function ChatRenderHTML() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-y-0.5 my-4">
|
<div className="my-4">
|
||||||
<p className="text-sm leading-6 font-semibold text-white">
|
<Toggle
|
||||||
{t("customization.items.render-html.title")}
|
size="md"
|
||||||
</p>
|
variant="horizontal"
|
||||||
<p className="text-xs text-white/60 w-1/2 whitespace-pre-line">
|
enabled={renderHTML}
|
||||||
{t("customization.items.render-html.description")}
|
onChange={handleChange}
|
||||||
</p>
|
disabled={saving}
|
||||||
<div className="flex items-center gap-x-4 pt-1">
|
label={t("customization.items.render-html.title")}
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
description={t("customization.items.render-html.description")}
|
||||||
<input
|
/>
|
||||||
id="render_html"
|
|
||||||
type="checkbox"
|
|
||||||
name="render_html"
|
|
||||||
value="yes"
|
|
||||||
checked={renderHTML}
|
|
||||||
onChange={handleChange}
|
|
||||||
disabled={saving}
|
|
||||||
className="peer sr-only"
|
|
||||||
/>
|
|
||||||
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import Appearance from "@/models/appearance";
|
import Appearance from "@/models/appearance";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
export default function ShowScrollbar() {
|
export default function ShowScrollbar() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [showScrollbar, setShowScrollbar] = useState(false);
|
const [showScrollbar, setShowScrollbar] = useState(false);
|
||||||
|
|
||||||
const handleChange = async (e) => {
|
const handleChange = async (checked) => {
|
||||||
const newValue = e.target.checked;
|
setShowScrollbar(checked);
|
||||||
setShowScrollbar(newValue);
|
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
Appearance.updateSettings({ showScrollbar: newValue });
|
Appearance.updateSettings({ showScrollbar: checked });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update appearance settings:", error);
|
console.error("Failed to update appearance settings:", error);
|
||||||
setShowScrollbar(!newValue);
|
setShowScrollbar(!checked);
|
||||||
}
|
}
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
};
|
};
|
||||||
@ -29,28 +29,16 @@ export default function ShowScrollbar() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-y-0.5 my-4">
|
<div className="my-4">
|
||||||
<p className="text-sm leading-6 font-semibold text-white">
|
<Toggle
|
||||||
{t("customization.items.show-scrollbar.title")}
|
size="md"
|
||||||
</p>
|
variant="horizontal"
|
||||||
<p className="text-xs text-white/60">
|
enabled={showScrollbar}
|
||||||
{t("customization.items.show-scrollbar.description")}
|
onChange={handleChange}
|
||||||
</p>
|
disabled={saving}
|
||||||
<div className="flex items-center gap-x-4">
|
label={t("customization.items.show-scrollbar.title")}
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
description={t("customization.items.show-scrollbar.description")}
|
||||||
<input
|
/>
|
||||||
id="show_scrollbar"
|
|
||||||
type="checkbox"
|
|
||||||
name="show_scrollbar"
|
|
||||||
value="yes"
|
|
||||||
checked={showScrollbar}
|
|
||||||
onChange={handleChange}
|
|
||||||
disabled={saving}
|
|
||||||
className="peer sr-only"
|
|
||||||
/>
|
|
||||||
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState } from "react";
|
||||||
import Appearance from "@/models/appearance";
|
import Appearance from "@/models/appearance";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Toggle from "@/components/lib/Toggle";
|
||||||
|
|
||||||
export default function SpellCheck() {
|
export default function SpellCheck() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -9,42 +10,29 @@ export default function SpellCheck() {
|
|||||||
Appearance.get("enableSpellCheck")
|
Appearance.get("enableSpellCheck")
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = async (e) => {
|
const handleChange = async (checked) => {
|
||||||
const newValue = e.target.checked;
|
setEnableSpellCheck(checked);
|
||||||
setEnableSpellCheck(newValue);
|
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
Appearance.set("enableSpellCheck", newValue);
|
Appearance.set("enableSpellCheck", checked);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update appearance settings:", error);
|
console.error("Failed to update appearance settings:", error);
|
||||||
setEnableSpellCheck(!newValue);
|
setEnableSpellCheck(!checked);
|
||||||
}
|
}
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-y-0.5 my-4">
|
<div className="my-4">
|
||||||
<p className="text-sm leading-6 font-semibold text-white">
|
<Toggle
|
||||||
{t("customization.chat.spellcheck.title")}
|
size="md"
|
||||||
</p>
|
variant="horizontal"
|
||||||
<p className="text-xs text-white/60">
|
enabled={enableSpellCheck}
|
||||||
{t("customization.chat.spellcheck.description")}
|
onChange={handleChange}
|
||||||
</p>
|
disabled={saving}
|
||||||
<div className="flex items-center gap-x-4">
|
label={t("customization.chat.spellcheck.title")}
|
||||||
<label className="relative inline-flex cursor-pointer items-center">
|
description={t("customization.chat.spellcheck.description")}
|
||||||
<input
|
/>
|
||||||
id="spellcheck"
|
|
||||||
type="checkbox"
|
|
||||||
name="spellcheck"
|
|
||||||
value="yes"
|
|
||||||
checked={enableSpellCheck}
|
|
||||||
onChange={handleChange}
|
|
||||||
disabled={saving}
|
|
||||||
className="peer sr-only"
|
|
||||||
/>
|
|
||||||
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user