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:
Sean Hatfield 2026-01-28 16:58:45 -08:00 committed by GitHub
parent fdeb7b9acf
commit 49a2b8f6f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 448 additions and 481 deletions

View File

@ -6,6 +6,7 @@ import { TagsInput } from "react-tag-input-component";
import { Info, Warning } from "@phosphor-icons/react";
import { Tooltip } from "react-tooltip";
import { useTranslation } from "react-i18next";
import Toggle from "@/components/lib/Toggle";
const DEFAULT_BRANCHES = ["main", "master"];
export default function GitlabOptions() {
@ -128,32 +129,18 @@ export default function GitlabOptions() {
</p>
</div>
<div className="flex items-center gap-x-2 mb-3">
<label className="relative inline-flex cursor-pointer items-center">
<input
type="checkbox"
name="fetchIssues"
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>
<Toggle
name="fetchIssues"
size="md"
label={t("connectors.gitlab.fetch_issues")}
/>
</div>
<div className="flex items-center gap-x-2">
<label className="relative inline-flex cursor-pointer items-center">
<input
type="checkbox"
name="fetchWikis"
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>
<Toggle
name="fetchWikis"
size="md"
label="Fetch Wikis as Documents"
/>
</div>
</div>
<GitLabBranchSelection

View File

@ -11,6 +11,7 @@ import { useTranslation } from "react-i18next";
import { useState, useEffect } from "react";
import { Tooltip } from "react-tooltip";
import { safeJsonParse } from "@/utils/request";
import Toggle from "@/components/lib/Toggle";
import {
USERNAME_MIN_LENGTH,
USERNAME_MAX_LENGTH,
@ -294,10 +295,9 @@ function AutoSubmitPreference() {
setAutoSubmitSttInput(settings.autoSubmitSttInput ?? true);
}, []);
const handleChange = (e) => {
const newValue = e.target.checked;
setAutoSubmitSttInput(newValue);
Appearance.updateSettings({ autoSubmitSttInput: newValue });
const handleChange = (checked) => {
setAutoSubmitSttInput(checked);
Appearance.updateSettings({ autoSubmitSttInput: checked });
};
return (
@ -317,19 +317,7 @@ function AutoSubmitPreference() {
<Info size={16} weight="bold" className="text-white" />
</div>
</div>
<div className="flex items-center gap-x-4">
<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>
<Toggle size="lg" enabled={autoSubmitSttInput} onChange={handleChange} />
<Tooltip
id="auto-submit-info"
place="bottom"
@ -352,10 +340,9 @@ function AutoSpeakPreference() {
);
}, []);
const handleChange = (e) => {
const newValue = e.target.checked;
setAutoPlayAssistantTtsResponse(newValue);
Appearance.updateSettings({ autoPlayAssistantTtsResponse: newValue });
const handleChange = (checked) => {
setAutoPlayAssistantTtsResponse(checked);
Appearance.updateSettings({ autoPlayAssistantTtsResponse: checked });
};
return (
@ -375,19 +362,11 @@ function AutoSpeakPreference() {
<Info size={16} weight="bold" className="text-white" />
</div>
</div>
<div className="flex items-center gap-x-4">
<label className="relative inline-flex cursor-pointer items-center">
<input
id="autoSpeak"
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>
<Toggle
size="lg"
enabled={autoPlayAssistantTtsResponse}
onChange={handleChange}
/>
<Tooltip
id="auto-speak-info"
place="bottom"

View 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>
);
}

View File

@ -11,6 +11,7 @@ import {
BracketsCurly,
} from "@phosphor-icons/react";
import { Tooltip } from "react-tooltip";
import Toggle from "@/components/lib/Toggle";
import StartNode from "../nodes/StartNode";
import ApiCallNode from "../nodes/ApiCallNode";
import WebsiteNode from "../nodes/WebsiteNode";
@ -172,32 +173,20 @@ export default function BlockList({
return (
<div className="space-y-4">
{renderBlockConfigContent(block, props)}
<div className="flex justify-between items-center pt-4 border-t border-white/10">
<div>
<label className="block text-sm font-medium text-theme-text-primary">
Direct Output
</label>
<p className="text-xs text-theme-text-secondary">
The output of this block will be returned directly to the chat.
<br />
This will prevent any further tool calls from being executed.
</p>
</div>
<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 className="pt-4 border-t border-white/10">
<Toggle
size="md"
variant="horizontal"
label="Direct Output"
description="The output of this block will be returned directly to the chat. This will prevent any further tool calls from being executed."
enabled={props.config.directOutput || false}
onChange={(checked) =>
props.onConfigChange({
...props.config,
directOutput: checked,
})
}
/>
</div>
</div>
);

View File

@ -1,5 +1,4 @@
import { Info } from "@phosphor-icons/react";
import React from "react";
import Toggle from "@/components/lib/Toggle";
export default function WebScrapingNode({
config,
@ -72,35 +71,16 @@ export default function WebScrapingNode({
</div>
)}
<div className="flex justify-between items-center">
<div className="flex flex-row items-center gap-x-1 mb-2">
<label className="block text-sm font-medium text-theme-text-primary">
Content Summarization
</label>
<Info
size={16}
className="text-theme-text-secondary cursor-pointer"
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>
<Toggle
size="md"
variant="horizontal"
label="Content Summarization"
hint="content-summarization-tooltip"
enabled={config.enableSummarization ?? true}
onChange={(checked) =>
onConfigChange({ ...config, enableSummarization: checked })
}
/>
<div>
<label className="block text-sm font-medium text-theme-text-primary mb-2">
Result Variable

View File

@ -4,6 +4,7 @@ import showToast from "@/utils/toast";
import { FlowArrow, Gear } from "@phosphor-icons/react";
import { useNavigate } from "react-router-dom";
import paths from "@/utils/paths";
import Toggle from "@/components/lib/Toggle";
function ManageFlowMenu({ flow, onDelete }) {
const [open, setOpen] = useState(false);
@ -97,22 +98,17 @@ export default function FlowPanel({ flow, toggleFlow, onDelete }) {
<>
<div className="p-2">
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
<div className="flex items-center gap-x-2">
<FlowArrow size={24} weight="bold" className="text-white" />
<label htmlFor="name" className="text-white text-md font-bold">
{flow.name}
</label>
<label className="border-none relative inline-flex items-center ml-auto cursor-pointer">
<input
type="checkbox"
className="peer sr-only"
checked={isActive}
onChange={handleToggle}
/>
<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 className="flex w-full justify-between items-center">
<div className="flex items-center gap-x-2">
<FlowArrow size={24} weight="bold" className="text-white" />
<label htmlFor="name" className="text-white text-md font-bold">
{flow.name}
</label>
</div>
<div className="flex items-center gap-x-2">
<Toggle size="lg" enabled={isActive} onChange={handleToggle} />
<ManageFlowMenu flow={flow} onDelete={onDelete} />
</div>
</div>
<p className="whitespace-pre-wrap text-white text-opacity-60 text-xs font-medium py-1.5">
{flow.description || "No description provided"}

View File

@ -1,5 +1,6 @@
import React from "react";
import { DefaultBadge } from "../Badges/default";
import Toggle from "@/components/lib/Toggle";
export default function DefaultSkillPanel({
title,
@ -29,18 +30,11 @@ export default function DefaultSkillPanel({
</label>
<DefaultBadge title={title} />
</div>
<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>
<span className="ml-3 text-sm font-medium"></span>
</label>
<Toggle
size="lg"
enabled={enabled}
onChange={() => toggleSkill(skill)}
/>
</div>
<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">

View File

@ -1,4 +1,5 @@
import React from "react";
import Toggle from "@/components/lib/Toggle";
export default function GenericSkillPanel({
title,
@ -13,34 +14,27 @@ export default function GenericSkillPanel({
return (
<div className="p-2">
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
<div className="flex items-center gap-x-2">
{icon &&
React.createElement(icon, {
size: 24,
color: "var(--theme-text-primary)",
weight: "bold",
})}
<label
htmlFor="name"
className="text-theme-text-primary text-md font-bold"
>
{title}
</label>
<label
className={`border-none relative inline-flex items-center ml-auto ${
disabled ? "cursor-not-allowed" : "cursor-pointer"
}`}
>
<input
type="checkbox"
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 className="flex w-full justify-between items-center">
<div className="flex items-center gap-x-2">
{icon &&
React.createElement(icon, {
size: 24,
color: "var(--theme-text-primary)",
weight: "bold",
})}
<label
htmlFor="name"
className="text-theme-text-primary text-md font-bold"
>
{title}
</label>
</div>
<Toggle
size="lg"
enabled={enabled}
disabled={disabled}
onChange={() => toggleSkill(skill)}
/>
</div>
<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">

View File

@ -3,6 +3,7 @@ import showToast from "@/utils/toast";
import { Gear, Plug } from "@phosphor-icons/react";
import { useEffect, useState, useRef } from "react";
import { sentenceCase } from "text-case";
import Toggle from "@/components/lib/Toggle";
/**
* Converts setup_args to inputs for the form builder
@ -110,25 +111,24 @@ export default function ImportedSkillConfig({
<>
<div className="p-2">
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
<div className="flex items-center gap-x-2">
<Plug size={24} weight="bold" className="text-white" />
<label htmlFor="name" className="text-white text-md font-bold">
{sentenceCase(config.name)}
</label>
<label className="border-none relative inline-flex items-center ml-auto cursor-pointer">
<input
type="checkbox"
className="peer sr-only"
checked={config.active}
onChange={() => toggleSkill()}
<div className="flex w-full justify-between items-center">
<div className="flex items-center gap-x-2">
<Plug size={24} weight="bold" className="text-white" />
<label htmlFor="name" className="text-white text-md font-bold">
{sentenceCase(config.name)}
</label>
</div>
<div className="flex items-center gap-x-2">
<Toggle
size="lg"
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>
<span className="ml-3 text-sm font-medium"></span>
</label>
<ManageSkillMenu
config={config}
setImportedSkills={setImportedSkills}
/>
<ManageSkillMenu
config={config}
setImportedSkills={setImportedSkills}
/>
</div>
</div>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
{config.description} by{" "}

View File

@ -5,6 +5,7 @@ import { WarningOctagon, X } from "@phosphor-icons/react";
import { DB_LOGOS } from "./DBConnection";
import System from "@/models/system";
import showToast from "@/utils/toast";
import Toggle from "@/components/lib/Toggle";
function assembleConnectionString({
engine,
@ -288,21 +289,13 @@ export default function NewSQLConnection({
)}
{engine === "sql-server" && (
<div className="flex items-center justify-between">
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
name="encrypt"
value="true"
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>
<Toggle
name="encrypt"
value="true"
size="md"
label="Enable Encryption"
enabled={config.encrypt}
/>
)}
<p className="text-theme-text-secondary text-sm">

View File

@ -5,6 +5,7 @@ import NewSQLConnection from "./NewConnectionModal";
import { useModal } from "@/hooks/useModal";
import SQLAgentImage from "@/media/agents/sql-agent.png";
import Admin from "@/models/admin";
import Toggle from "@/components/lib/Toggle";
export default function AgentSQLConnectorSelection({
skill,
@ -36,28 +37,25 @@ export default function AgentSQLConnectorSelection({
<>
<div className="p-2">
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
<div className="flex items-center gap-x-2">
<Database
size={24}
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="flex w-full justify-between items-center">
<div className="flex items-center gap-x-2">
<Database
size={24}
color="var(--theme-text-primary)"
weight="bold"
/>
<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>
<label
htmlFor="name"
className="text-theme-text-primary text-md font-bold"
>
SQL Agent
</label>
</div>
<Toggle
size="lg"
enabled={enabled}
onChange={() => toggleSkill(skill)}
/>
</div>
<img
src={SQLAgentImage}

View File

@ -17,6 +17,7 @@ import {
X,
ListMagnifyingGlass,
} from "@phosphor-icons/react";
import Toggle from "@/components/lib/Toggle";
import SearchProviderItem from "./SearchProviderItem";
import WebSearchImage from "@/media/agents/scrape-websites.png";
import {
@ -170,28 +171,25 @@ export default function AgentWebSearchSelection({
return (
<div className="p-2">
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
<div className="flex items-center gap-x-2">
<ListMagnifyingGlass
size={24}
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="flex w-full justify-between items-center">
<div className="flex items-center gap-x-2">
<ListMagnifyingGlass
size={24}
color="var(--theme-text-primary)"
weight="bold"
/>
<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>
<label
htmlFor="name"
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>
<img
src={WebSearchImage}

View File

@ -4,6 +4,7 @@ import showToast from "@/utils/toast";
import { ArrowSquareOut } from "@phosphor-icons/react";
import { useState } from "react";
import { Link } from "react-router-dom";
import Toggle from "@/components/lib/Toggle";
export default function LiveSyncToggle({ enabled = false, onToggle }) {
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">
Automatic Document Content Sync
</h2>
<label className="relative inline-flex cursor-pointer items-center">
<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>
<Toggle size="lg" enabled={status} onChange={toggleFeatureFlag} />
</div>
<div className="flex flex-col space-y-4">
<p className="text-theme-text-secondary text-sm">

View File

@ -11,6 +11,7 @@ import NewUserModal from "./NewUserModal";
import { useModal } from "@/hooks/useModal";
import ModalWrapper from "@/components/ModalWrapper";
import CTAButton from "@/components/lib/CTAButton";
import Toggle from "@/components/lib/Toggle";
export default function AdminUsers() {
const { isOpen, openModal, closeModal } = useModal();
@ -147,31 +148,19 @@ export function MessageLimitInput({ enabled, limit, updateState, role }) {
if (role === "admin") return null;
return (
<div className="mt-4 mb-8">
<div className="flex flex-col gap-y-1">
<div className="flex items-center gap-x-2">
<h2 className="text-base leading-6 font-bold text-white">
Limit messages per day
</h2>
<label className="relative inline-flex cursor-pointer items-center">
<input
type="checkbox"
checked={enabled}
onChange={(e) => {
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>
<Toggle
size="md"
variant="horizontal"
label="Limit messages per day"
description="Restrict this user to a number of successful queries or chats within a 24 hour window."
enabled={enabled}
onChange={(checked) => {
updateState((prev) => ({
...prev,
enabled: checked,
}));
}}
/>
{enabled && (
<div className="mt-4">
<label className="text-white text-sm font-semibold block mb-4">

View File

@ -3,6 +3,7 @@ import { X } from "@phosphor-icons/react";
import Workspace from "@/models/workspace";
import { TagsInput } from "react-tag-input-component";
import Embed from "@/models/embed";
import Toggle from "@/components/lib/Toggle";
export function enforceSubmissionSchema(form) {
const data = {};
@ -343,23 +344,14 @@ export const BooleanInput = ({ name, title, hint, defaultValue = null }) => {
const [status, setStatus] = useState(defaultValue ?? false);
return (
<div>
<div className="flex flex-col mb-2">
<label htmlFor={name} className="block text-sm font-medium text-white">
{title}
</label>
<p className="text-theme-text-secondary text-xs">{hint}</p>
</div>
<label className="relative inline-flex cursor-pointer items-center">
<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>
<Toggle
name={name}
size="md"
variant="horizontal"
label={title}
description={hint}
enabled={status}
onChange={(checked) => setStatus(checked)}
/>
);
};

View File

@ -6,6 +6,7 @@ import System from "@/models/system";
import PreLoader from "@/components/Preloader";
import { useTranslation } from "react-i18next";
import ProviderPrivacy from "@/components/ProviderPrivacy";
import Toggle from "@/components/lib/Toggle";
export default function PrivacyAndDataHandling() {
const [settings, setSettings] = useState({});
@ -80,18 +81,13 @@ function TelemetryLogs({ settings }) {
<div className="space-y-6 flex h-full w-full">
<div className="w-full flex flex-col gap-y-4">
<div className="">
<label className="mb-2.5 block font-medium text-theme-text-primary">
{t("privacy.anonymous")}
</label>
<label className="relative inline-flex cursor-pointer items-center">
<input
type="checkbox"
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>
<Toggle
size="lg"
className="mb-4"
label={t("privacy.anonymous")}
enabled={telemetry}
onChange={toggleTelemetry}
/>
</div>
</div>
</div>

View File

@ -8,6 +8,7 @@ import { AUTH_TIMESTAMP, AUTH_TOKEN, AUTH_USER } from "@/utils/constants";
import PreLoader from "@/components/Preloader";
import CTAButton from "@/components/lib/CTAButton";
import { useTranslation } from "react-i18next";
import Toggle from "@/components/lib/Toggle";
import {
USERNAME_MIN_LENGTH,
USERNAME_MAX_LENGTH,
@ -125,26 +126,19 @@ function MultiUserMode() {
<div className="flex items-start justify-between px-6 py-4"></div>
<div className="space-y-6 flex h-full w-full">
<div className="w-full flex flex-col gap-y-4">
<div className="">
<label className="text-white text-sm font-semibold block mb-3">
{multiUserModeEnabled
? t("security.multiuser.enable.is-enable")
: t("security.multiuser.enable.enable")}
</label>
<label className="relative inline-flex cursor-pointer items-center">
<input
type="checkbox"
onClick={() => setUseMultiUserMode(!useMultiUserMode)}
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>
{multiUserModeEnabled ? (
<p className="text-white text-sm font-semibold">
{t("security.multiuser.enable.is-enable")}
</p>
) : (
<Toggle
size="lg"
className="mb-4"
label={t("security.multiuser.enable.enable")}
enabled={useMultiUserMode}
onChange={(checked) => setUseMultiUserMode(checked)}
/>
)}
{useMultiUserMode && (
<div className="w-full flex flex-col gap-y-2 my-5">
<div className="w-80">
@ -307,21 +301,13 @@ function PasswordProtection() {
<div className="flex items-start justify-between px-6 py-4"></div>
<div className="space-y-6 flex h-full w-full">
<div className="w-full flex flex-col gap-y-4">
<div className="">
<label className="text-white text-sm font-semibold block mb-3">
{t("security.password.title")}
</label>
<label className="relative inline-flex cursor-pointer items-center">
<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>
<Toggle
size="lg"
className="mb-4"
label={t("security.password.title")}
enabled={usePassword}
onChange={(checked) => setUsePassword(checked)}
/>
{usePassword && (
<div className="w-full flex flex-col gap-y-2 my-5">
<div className="mt-4 w-80">

View File

@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react";
import Appearance from "@/models/appearance";
import { useTranslation } from "react-i18next";
import Toggle from "@/components/lib/Toggle";
export default function AutoSpeak() {
const [saving, setSaving] = useState(false);
@ -8,15 +9,14 @@ export default function AutoSpeak() {
useState(false);
const { t } = useTranslation();
const handleChange = async (e) => {
const newValue = e.target.checked;
setAutoPlayAssistantTtsResponse(newValue);
const handleChange = async (checked) => {
setAutoPlayAssistantTtsResponse(checked);
setSaving(true);
try {
Appearance.updateSettings({ autoPlayAssistantTtsResponse: newValue });
Appearance.updateSettings({ autoPlayAssistantTtsResponse: checked });
} catch (error) {
console.error("Failed to update appearance settings:", error);
setAutoPlayAssistantTtsResponse(!newValue);
setAutoPlayAssistantTtsResponse(!checked);
}
setSaving(false);
};
@ -32,28 +32,16 @@ export default function AutoSpeak() {
}, []);
return (
<div className="flex flex-col gap-y-0.5 my-4">
<p className="text-sm leading-6 font-semibold text-white">
{t("customization.chat.auto_speak.title")}
</p>
<p className="text-xs text-white/60">
{t("customization.chat.auto_speak.description")}
</p>
<div className="flex items-center gap-x-4">
<label className="relative inline-flex cursor-pointer items-center">
<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 className="my-4">
<Toggle
size="md"
variant="horizontal"
enabled={autoPlayAssistantTtsResponse}
onChange={handleChange}
disabled={saving}
label={t("customization.chat.auto_speak.title")}
description={t("customization.chat.auto_speak.description")}
/>
</div>
);
}

View File

@ -1,21 +1,21 @@
import React, { useState, useEffect } from "react";
import Appearance from "@/models/appearance";
import { useTranslation } from "react-i18next";
import Toggle from "@/components/lib/Toggle";
export default function AutoSubmit() {
const [saving, setSaving] = useState(false);
const [autoSubmitSttInput, setAutoSubmitSttInput] = useState(true);
const { t } = useTranslation();
const handleChange = async (e) => {
const newValue = e.target.checked;
setAutoSubmitSttInput(newValue);
const handleChange = async (checked) => {
setAutoSubmitSttInput(checked);
setSaving(true);
try {
Appearance.updateSettings({ autoSubmitSttInput: newValue });
Appearance.updateSettings({ autoSubmitSttInput: checked });
} catch (error) {
console.error("Failed to update appearance settings:", error);
setAutoSubmitSttInput(!newValue);
setAutoSubmitSttInput(!checked);
}
setSaving(false);
};
@ -29,28 +29,16 @@ export default function AutoSubmit() {
}, []);
return (
<div className="flex flex-col gap-y-0.5 my-4">
<p className="text-sm leading-6 font-semibold text-white">
{t("customization.chat.auto_submit.title")}
</p>
<p className="text-xs text-white/60">
{t("customization.chat.auto_submit.description")}
</p>
<div className="flex items-center gap-x-4">
<label className="relative inline-flex cursor-pointer items-center">
<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 className="my-4">
<Toggle
size="md"
variant="horizontal"
enabled={autoSubmitSttInput}
onChange={handleChange}
disabled={saving}
label={t("customization.chat.auto_submit.title")}
description={t("customization.chat.auto_submit.description")}
/>
</div>
);
}

View File

@ -1,21 +1,21 @@
import React, { useState, useEffect } from "react";
import Appearance from "@/models/appearance";
import { useTranslation } from "react-i18next";
import Toggle from "@/components/lib/Toggle";
export default function ChatRenderHTML() {
const { t } = useTranslation();
const [saving, setSaving] = useState(false);
const [renderHTML, setRenderHTML] = useState(false);
const handleChange = async (e) => {
const newValue = e.target.checked;
setRenderHTML(newValue);
const handleChange = async (checked) => {
setRenderHTML(checked);
setSaving(true);
try {
Appearance.updateSettings({ renderHTML: newValue });
Appearance.updateSettings({ renderHTML: checked });
} catch (error) {
console.error("Failed to update appearance settings:", error);
setRenderHTML(!newValue);
setRenderHTML(!checked);
}
setSaving(false);
};
@ -29,28 +29,16 @@ export default function ChatRenderHTML() {
}, []);
return (
<div className="flex flex-col gap-y-0.5 my-4">
<p className="text-sm leading-6 font-semibold text-white">
{t("customization.items.render-html.title")}
</p>
<p className="text-xs text-white/60 w-1/2 whitespace-pre-line">
{t("customization.items.render-html.description")}
</p>
<div className="flex items-center gap-x-4 pt-1">
<label className="relative inline-flex cursor-pointer items-center">
<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 className="my-4">
<Toggle
size="md"
variant="horizontal"
enabled={renderHTML}
onChange={handleChange}
disabled={saving}
label={t("customization.items.render-html.title")}
description={t("customization.items.render-html.description")}
/>
</div>
);
}

View File

@ -1,21 +1,21 @@
import React, { useState, useEffect } from "react";
import Appearance from "@/models/appearance";
import { useTranslation } from "react-i18next";
import Toggle from "@/components/lib/Toggle";
export default function ShowScrollbar() {
const { t } = useTranslation();
const [saving, setSaving] = useState(false);
const [showScrollbar, setShowScrollbar] = useState(false);
const handleChange = async (e) => {
const newValue = e.target.checked;
setShowScrollbar(newValue);
const handleChange = async (checked) => {
setShowScrollbar(checked);
setSaving(true);
try {
Appearance.updateSettings({ showScrollbar: newValue });
Appearance.updateSettings({ showScrollbar: checked });
} catch (error) {
console.error("Failed to update appearance settings:", error);
setShowScrollbar(!newValue);
setShowScrollbar(!checked);
}
setSaving(false);
};
@ -29,28 +29,16 @@ export default function ShowScrollbar() {
}, []);
return (
<div className="flex flex-col gap-y-0.5 my-4">
<p className="text-sm leading-6 font-semibold text-white">
{t("customization.items.show-scrollbar.title")}
</p>
<p className="text-xs text-white/60">
{t("customization.items.show-scrollbar.description")}
</p>
<div className="flex items-center gap-x-4">
<label className="relative inline-flex cursor-pointer items-center">
<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 className="my-4">
<Toggle
size="md"
variant="horizontal"
enabled={showScrollbar}
onChange={handleChange}
disabled={saving}
label={t("customization.items.show-scrollbar.title")}
description={t("customization.items.show-scrollbar.description")}
/>
</div>
);
}

View File

@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react";
import React, { useState } from "react";
import Appearance from "@/models/appearance";
import { useTranslation } from "react-i18next";
import Toggle from "@/components/lib/Toggle";
export default function SpellCheck() {
const { t } = useTranslation();
@ -9,42 +10,29 @@ export default function SpellCheck() {
Appearance.get("enableSpellCheck")
);
const handleChange = async (e) => {
const newValue = e.target.checked;
setEnableSpellCheck(newValue);
const handleChange = async (checked) => {
setEnableSpellCheck(checked);
setSaving(true);
try {
Appearance.set("enableSpellCheck", newValue);
Appearance.set("enableSpellCheck", checked);
} catch (error) {
console.error("Failed to update appearance settings:", error);
setEnableSpellCheck(!newValue);
setEnableSpellCheck(!checked);
}
setSaving(false);
};
return (
<div className="flex flex-col gap-y-0.5 my-4">
<p className="text-sm leading-6 font-semibold text-white">
{t("customization.chat.spellcheck.title")}
</p>
<p className="text-xs text-white/60">
{t("customization.chat.spellcheck.description")}
</p>
<div className="flex items-center gap-x-4">
<label className="relative inline-flex cursor-pointer items-center">
<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 className="my-4">
<Toggle
size="md"
variant="horizontal"
enabled={enableSpellCheck}
onChange={handleChange}
disabled={saving}
label={t("customization.chat.spellcheck.title")}
description={t("customization.chat.spellcheck.description")}
/>
</div>
);
}