Settings: Migrate to Tabs

This commit is contained in:
Enrico Ros
2023-05-22 02:11:19 -07:00
parent 7c74821ffb
commit b6871accac
5 changed files with 252 additions and 256 deletions
+26 -11
View File
@@ -1,26 +1,28 @@
import * as React from 'react';
import { Box, Button, Divider } from '@mui/joy';
import { Button, Divider, Tab, TabList, TabPanel, Tabs } from '@mui/joy';
import BuildCircleIcon from '@mui/icons-material/BuildCircle';
import { ElevenlabsSettings } from '~/modules/elevenlabs/ElevenlabsSettings';
import { GoodModal } from '~/common/components/GoodModal';
import { ProdiaSettings } from '~/modules/prodia/ProdiaSettings';
import { SearchSettings } from '~/modules/google/SearchSettings';
import { GoodModal } from '~/common/components/GoodModal';
import { useUIStateStore } from '~/common/state/store-ui';
import { UISettings } from './UISettings';
/**
* Component that allows the User to modify the application settings,
* persisted on the client via localStorage.
*/
export function SettingsModal() {
// external state
const { settingsOpen, closeSettings, openModelsSetup } = useUIStateStore();
const { settingsOpenTab, closeSettings, openModelsSetup } = useUIStateStore();
return (
<GoodModal title={`Preferences`} open={settingsOpen} onClose={closeSettings}
<GoodModal title={`Preferences`} open={!!settingsOpenTab} onClose={closeSettings}
startButton={
<Button variant='plain' color='info' onClick={openModelsSetup} startDecorator={<BuildCircleIcon />}>
Models
@@ -30,17 +32,30 @@ export function SettingsModal() {
<Divider />
<Box>
<Tabs aria-label='Settings tabbed menu' defaultValue={settingsOpenTab} sx={{ borderRadius: 'lg' }}>
<TabList variant='soft' color='neutral' sx={{ mb: 2 /* gap: 3, minus 0.5 for the Tabs-gap, minus 0.5 for perception */ }}>
<Tab value={1}>UI</Tab>
<Tab value={2}>Draw</Tab>
<Tab value={3}>Speak</Tab>
<Tab value={4}>Search</Tab>
</TabList>
<UISettings />
<TabPanel value={1} sx={{ p: 'var(--Tabs-gap)' }}>
<UISettings />
</TabPanel>
<ElevenlabsSettings />
<TabPanel value={2} sx={{ p: 'var(--Tabs-gap)' }}>
<ProdiaSettings />
</TabPanel>
<ProdiaSettings />
<TabPanel value={3} sx={{ p: 'var(--Tabs-gap)' }}>
<ElevenlabsSettings />
</TabPanel>
<SearchSettings />
</Box>
<TabPanel value={4} sx={{ p: 'var(--Tabs-gap)' }}>
<SearchSettings />
</TabPanel>
</Tabs>
<Divider />
+5 -5
View File
@@ -8,8 +8,8 @@ import { DLLMId } from '~/modules/llms/llm.types';
interface UIStateStore {
settingsOpen: boolean;
openSettings: () => void;
settingsOpenTab: number; // 0: closed, 1..N: tab index
openSettings: (tab?: number) => void;
closeSettings: () => void;
modelsSetupOpen: boolean;
@@ -25,9 +25,9 @@ interface UIStateStore {
export const useUIStateStore = create<UIStateStore>()(
(set) => ({
settingsOpen: false,
closeSettings: () => set({ settingsOpen: false }),
openSettings: () => set({ settingsOpen: true }),
settingsOpenTab: 0,
openSettings: (tab?: number) => set({ settingsOpenTab: tab || 1 }),
closeSettings: () => set({ settingsOpenTab: 0 }),
modelsSetupOpen: false,
openModelsSetup: () => set({ modelsSetupOpen: true }),
+49 -70
View File
@@ -1,16 +1,13 @@
import * as React from 'react';
import { shallow } from 'zustand/shallow';
import { Box, CircularProgress, FormControl, FormHelperText, FormLabel, IconButton, Input, Option, Radio, RadioGroup, Select, Stack } from '@mui/joy';
import KeyIcon from '@mui/icons-material/Key';
import { Box, CircularProgress, FormControl, FormHelperText, FormLabel, Option, Radio, RadioGroup, Select, Stack } from '@mui/joy';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import RecordVoiceOverIcon from '@mui/icons-material/RecordVoiceOver';
import VisibilityIcon from '@mui/icons-material/Visibility';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import { apiQuery } from '~/modules/trpc/trpc.client';
import { Section } from '~/common/components/Section';
import { FormInputKey } from '~/common/components/FormInputKey';
import { settingsCol1Width, settingsGap } from '~/common/theme';
import { isElevenLabsEnabled, requireUserKeyElevenLabs } from './elevenlabs.client';
@@ -18,9 +15,6 @@ import { useElevenlabsStore } from './store-elevenlabs';
export function ElevenlabsSettings() {
// state
const [showApiKeyValue, setShowApiKeyValue] = React.useState(false);
// external state
const { apiKey, setApiKey, voiceId, setVoiceId, autoSpeak, setAutoSpeak } = useElevenlabsStore(state => ({
apiKey: state.elevenLabsApiKey, setApiKey: state.setElevenLabsApiKey,
@@ -36,76 +30,61 @@ export function ElevenlabsSettings() {
staleTime: 1000 * 60 * 5, // 5 minutes
});
const handleToggleApiKeyVisibility = () => setShowApiKeyValue(!showApiKeyValue);
const handleApiKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => setApiKey(e.target.value);
const handleVoiceChange = (e: any, value: string | null) => setVoiceId(value || '');
const handleAutoSpeakChange = (e: React.ChangeEvent<HTMLInputElement>) => setAutoSpeak((e.target.value || 'off') as 'off' | 'firstLine');
return (
<Section title='📢 Voice Generation' collapsible collapsed>
<Stack direction='column' sx={{ gap: settingsGap, mt: -0.8 }}>
<Stack direction='column' sx={{ gap: settingsGap }}>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<Box>
<FormLabel sx={{ minWidth: settingsCol1Width }}>
ElevenLabs API Key
</FormLabel>
<FormHelperText>
{requiresKey ? '(required)' : '(optional)'}
</FormHelperText>
</Box>
<Input
variant='outlined' type={showApiKeyValue ? 'text' : 'password'} placeholder={requiresKey ? 'required' : '...'} error={!isValidKey}
value={apiKey} onChange={handleApiKeyChange}
startDecorator={<KeyIcon />}
endDecorator={!!apiKey && (
<IconButton variant='plain' color='neutral' onClick={handleToggleApiKeyVisibility}>
{showApiKeyValue ? <VisibilityIcon /> : <VisibilityOffIcon />}
</IconButton>
)}
slotProps={{ input: { sx: { width: '100%' } } }}
sx={{ width: '100%' }}
/>
</FormControl>
<FormHelperText>
📢 Hear AI responses, even in your own voice
</FormHelperText>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<FormLabel sx={{ minWidth: settingsCol1Width }}>
Assistant voice
</FormLabel>
<Select
variant='outlined' placeholder={isValidKey ? 'Select a voice' : 'Enter API Key'}
value={voiceId} onChange={handleVoiceChange}
startDecorator={<RecordVoiceOverIcon />}
endDecorator={isValidKey && loadingVoices && <CircularProgress size='sm' />}
indicator={<KeyboardArrowDownIcon />}
slotProps={{
root: { sx: { width: '100%' } },
indicator: { sx: { opacity: 0.5 } },
}}
>
{voicesData && voicesData.voices?.map(voice => (
<Option key={voice.id} value={voice.id}>
{voice.name}
</Option>
))}
</Select>
</FormControl>
<FormInputKey
label='ElevenLabs API Key' required={requiresKey} isVisible={false}
rightLabel={requiresKey ? '(required)' : '✔️ already set in server'}
value={apiKey} onChange={setApiKey}
// value={oaiKey} onChange={value => updateSetup({ oaiKey: value })}
// required={!hasServerKeyOpenAI} isError={keyError}
placeholder='sk-...'
/>
<FormControl orientation='horizontal' sx={{ alignItems: 'center', justifyContent: 'space-between' }}>
<Box>
<FormLabel>Speak responses</FormLabel>
<FormHelperText>{autoSpeak === 'off' ? 'Off' : 'Just the first line'}</FormHelperText>
</Box>
<RadioGroup orientation='horizontal' value={autoSpeak} onChange={handleAutoSpeakChange}>
<Radio value='off' label='Off' />
<Radio value='firstLine' label='Beginning' />
</RadioGroup>
</FormControl>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<FormLabel sx={{ minWidth: settingsCol1Width }}>
Assistant Voice
</FormLabel>
<Select
variant='outlined' placeholder={isValidKey ? 'Select a voice' : 'Enter valid API Key'}
value={voiceId} onChange={handleVoiceChange}
startDecorator={<RecordVoiceOverIcon />}
endDecorator={isValidKey && loadingVoices && <CircularProgress size='sm' />}
indicator={<KeyboardArrowDownIcon />}
slotProps={{
root: { sx: { width: '100%' } },
indicator: { sx: { opacity: 0.5 } },
}}
>
{voicesData && voicesData.voices?.map(voice => (
<Option key={voice.id} value={voice.id}>
{voice.name}
</Option>
))}
</Select>
</FormControl>
</Stack>
</Section>
<FormControl orientation='horizontal' sx={{ alignItems: 'center', justifyContent: 'space-between' }}>
<Box sx={{ minWidth: settingsCol1Width }}>
<FormLabel>Speak Responses</FormLabel>
<FormHelperText>{autoSpeak === 'off' ? 'Off' : 'First paragraph'}</FormHelperText>
</Box>
<RadioGroup orientation='horizontal' value={autoSpeak} onChange={handleAutoSpeakChange}>
<Radio disabled={!voicesData?.voices} value='off' label='Off' />
<Radio disabled={!voicesData?.voices} value='firstLine' label='Start' />
<Radio disabled={true} value='all' label='Full' />
</RadioGroup>
</FormControl>
</Stack>
);
}
+44 -43
View File
@@ -7,7 +7,6 @@ import SearchIcon from '@mui/icons-material/Search';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import { Link } from '~/common/components/Link';
import { Section } from '~/common/components/Section';
import { settingsCol1Width, settingsGap } from '~/common/theme';
import { isValidGoogleCloudApiKey, isValidGoogleCseId, requireUserKeyGoogleCse } from './search.client';
@@ -30,50 +29,52 @@ export function SearchSettings() {
const handleCseIdChange = (e: React.ChangeEvent<HTMLInputElement>) => setGoogleCSEId(e.target.value);
return (
<Section title='🔍 Google Search' collapsible collapsed disclaimer='EXPERIMENTAL. When configured, the UI will have the option to use Google to search for fresher websites' sx={{ mt: 2 }}>
<Stack direction='column' sx={{ gap: settingsGap, mt: -0.8 }}>
<Stack direction='column' sx={{ gap: settingsGap }}>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<Box>
<Tooltip title='Create your Google Cloud "API Key Credential" and enter it here'>
<FormLabel sx={{ minWidth: settingsCol1Width }}>
Google Cloud API Key
</FormLabel>
</Tooltip>
<FormHelperText>
<Link href='https://console.cloud.google.com/apis/credentials' noLinkStyle target='_blank'>Create one here</Link>
</FormHelperText>
</Box>
<Input
variant='outlined' placeholder={requiresKeys ? 'missing' : '...'} error={!isValidKey}
value={googleCloudApiKey} onChange={handleGoogleApiKeyChange}
startDecorator={<KeyIcon />}
slotProps={{ input: { sx: { width: '100%' } } }}
sx={{ width: '100%' }}
/>
</FormControl>
<FormHelperText>
🔍 Helps in locating up-to-date websites. Bring your own Google Keys.
</FormHelperText>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<Box>
<Tooltip title='Create your Google "Programmable Search Engine" and enter its ID here'>
<FormLabel sx={{ minWidth: settingsCol1Width }}>
Google CSE ID <InfoOutlinedIcon sx={{ mx: 0.5 }} />
</FormLabel>
</Tooltip>
<FormHelperText>
<Link href='https://programmablesearchengine.google.com/' noLinkStyle target='_blank'>Get it here</Link>
</FormHelperText>
</Box>
<Input
variant='outlined' placeholder={requiresKeys ? 'missing' : '...'} error={!isValidId}
value={googleCSEId} onChange={handleCseIdChange}
startDecorator={<SearchIcon />}
slotProps={{ input: { sx: { width: '100%' } } }}
sx={{ width: '100%' }}
/>
</FormControl>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<Box>
<Tooltip title='Create your Google Cloud "API Key Credential" and enter it here'>
<FormLabel sx={{ minWidth: settingsCol1Width }}>
Google Cloud API Key
</FormLabel>
</Tooltip>
<FormHelperText>
<Link href='https://console.cloud.google.com/apis/credentials' noLinkStyle target='_blank'>Create one here</Link>
</FormHelperText>
</Box>
<Input
variant='outlined' placeholder={requiresKeys ? 'missing' : '...'} error={!isValidKey}
value={googleCloudApiKey} onChange={handleGoogleApiKeyChange}
startDecorator={<KeyIcon />}
slotProps={{ input: { sx: { width: '100%' } } }}
sx={{ width: '100%' }}
/>
</FormControl>
</Stack>
</Section>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<Box>
<Tooltip title='Create your Google "Programmable Search Engine" and enter its ID here'>
<FormLabel sx={{ minWidth: settingsCol1Width }}>
Google CSE ID <InfoOutlinedIcon sx={{ mx: 0.5 }} />
</FormLabel>
</Tooltip>
<FormHelperText>
<Link href='https://programmablesearchengine.google.com/' noLinkStyle target='_blank'>Get it here</Link>
</FormHelperText>
</Box>
<Input
variant='outlined' placeholder={requiresKeys ? 'missing' : '...'} error={!isValidId}
value={googleCSEId} onChange={handleCseIdChange}
startDecorator={<SearchIcon />}
slotProps={{ input: { sx: { width: '100%' } } }}
sx={{ width: '100%' }}
/>
</FormControl>
</Stack>
);
}
+128 -127
View File
@@ -10,8 +10,6 @@ import VisibilityIcon from '@mui/icons-material/Visibility';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import { apiQuery } from '~/modules/trpc/trpc.client';
import { Section } from '~/common/components/Section';
import { settingsGap } from '~/common/theme';
import { isValidProdiaApiKey, prodiaDefaultModelId, requireUserKeyProdia } from './prodia.client';
@@ -50,139 +48,142 @@ export function ProdiaSettings() {
const colWidth = 150;
return (
<Section title='🎨 Image Generation' collapsible collapsed disclaimer='Supported image generators: Prodia.com' sx={{ mt: 2 }}>
<Stack direction='column' sx={{ gap: settingsGap, mt: -0.8 }}>
<Stack direction='column' sx={{ gap: settingsGap }}>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<Box>
<FormLabel sx={{ minWidth: colWidth }}>
Prodia API Key
</FormLabel>
<FormHelperText>
{requiresKey ? '(required)' : '(optional)'}
</FormHelperText>
</Box>
<Input
variant='outlined' type={showApiKeyValue ? 'text' : 'password'} placeholder={requiresKey ? 'required' : '...'} error={!isValidKey}
value={apiKey} onChange={handleApiKeyChange}
startDecorator={<KeyIcon />}
endDecorator={!!apiKey && (
<IconButton variant='plain' color='neutral' onClick={handleToggleApiKeyVisibility}>
{showApiKeyValue ? <VisibilityIcon /> : <VisibilityOffIcon />}
</IconButton>
)}
slotProps={{ input: { sx: { width: '100%' } } }}
sx={{ width: '100%' }}
/>
</FormControl>
<FormHelperText>
🎨 Turn text into pictures and /imagine. Bring your own Prodia.com API Key.
</FormHelperText>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<Box>
<FormLabel sx={{ minWidth: colWidth }}>
Diffusion Model
Prodia API Key
</FormLabel>
<Select
variant='outlined' placeholder={isValidKey ? 'Select a model' : 'Enter API Key'}
value={modelId || prodiaDefaultModelId} onChange={handleModelChange}
startDecorator={<FormatPaintIcon />}
endDecorator={isValidKey && loadingModels && <CircularProgress size='sm' />}
indicator={<KeyboardArrowDownIcon />}
slotProps={{
root: { sx: { width: '100%' } },
indicator: { sx: { opacity: 0.5 } },
}}
>
{modelsData && modelsData.models?.map((model, idx) => (
<Option key={'prodia-model-' + idx} value={model.id}>
{model.label}
</Option>
))}
</Select>
</FormControl>
<FormHelperText>
{requiresKey ? '(required)' : '(optional)'}
</FormHelperText>
</Box>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<Box>
<Tooltip title='Avoid these image traits: comma-separated names & adjectives that you want the images to Not have. Example: ugly, blurry, malformed'>
<FormLabel sx={{ minWidth: colWidth }}>
Negative Prompt <InfoOutlinedIcon sx={{ mx: 0.5 }} />
</FormLabel>
</Tooltip>
<FormHelperText>
{negativePrompt ? 'Custom' : 'Not set'}
</FormHelperText>
</Box>
<Input
aria-label='Image Generation Negative Prompt'
variant='outlined' placeholder='ugly, blurry, ...'
value={negativePrompt} onChange={(e) => setNegativePrompt(e.target.value)}
slotProps={{ input: { sx: { width: '100%' } } }}
sx={{ width: '100%' }}
/>
</FormControl>
<Input
variant='outlined' type={showApiKeyValue ? 'text' : 'password'} placeholder={requiresKey ? 'required' : '...'} error={!isValidKey}
value={apiKey} onChange={handleApiKeyChange}
startDecorator={<KeyIcon />}
endDecorator={!!apiKey && (
<IconButton variant='plain' color='neutral' onClick={handleToggleApiKeyVisibility}>
{showApiKeyValue ? <VisibilityIcon /> : <VisibilityOffIcon />}
</IconButton>
)}
slotProps={{ input: { sx: { width: '100%' } } }}
sx={{ width: '100%' }}
/>
</FormControl>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<Box>
<Tooltip title='More steps boost image detail & quality but risk oversaturation and cost increase. Start from 20 steps, and increase gradually. Defaults to 25.'>
<FormLabel sx={{ minWidth: colWidth }}>
Diffusion Steps <InfoOutlinedIcon sx={{ mx: 0.5 }} />
</FormLabel>
</Tooltip>
<FormHelperText>
{steps === 25 ? 'Default' : steps > 30 ? (steps > 40 ? 'May be unnecessary' : 'More detail') : steps <= 15 ? 'Less detail' : 'Balanced'}
</FormHelperText>
</Box>
<Slider
aria-label='Image Generation steps' valueLabelDisplay='auto'
value={steps} onChange={(e, value) => setSteps(value as number)}
min={10} max={50} step={1} defaultValue={25}
sx={{ width: '100%' }}
/>
</FormControl>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<FormLabel sx={{ minWidth: colWidth }}>
Diffusion Model
</FormLabel>
<Select
variant='outlined' placeholder={isValidKey ? 'Select a model' : 'Enter API Key'}
value={modelId || prodiaDefaultModelId} onChange={handleModelChange}
startDecorator={<FormatPaintIcon />}
endDecorator={isValidKey && loadingModels && <CircularProgress size='sm' />}
indicator={<KeyboardArrowDownIcon />}
slotProps={{
root: { sx: { width: '100%' } },
indicator: { sx: { opacity: 0.5 } },
}}
>
{modelsData && modelsData.models?.map((model, idx) => (
<Option key={'prodia-model-' + idx} value={model.id}>
{model.label}
</Option>
))}
</Select>
</FormControl>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<Box>
<Tooltip title='Adjust the prompt intensity for generation. Low values deviate, high values overfit. Default: 7 - a balanced start.'>
<FormLabel sx={{ minWidth: colWidth }}>
Cfg-Scale <InfoOutlinedIcon sx={{ mx: 0.5 }} />
</FormLabel>
</Tooltip>
<FormHelperText>
{cfgScale === 7 ? 'Default' : cfgScale >= 9 ? (cfgScale >= 12 ? 'Heavy guidance' : 'Intense guidance') : cfgScale <= 5 ? 'More freedom' : 'Balanced'}
</FormHelperText>
</Box>
<Slider
aria-label='Image Generation Guidance' valueLabelDisplay='auto'
value={cfgScale} onChange={(e, value) => setCfgScale(value as number)}
min={1} max={15} step={0.5} defaultValue={7}
sx={{ width: '100%' }}
/>
</FormControl>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<Box>
<Tooltip title='Avoid these image traits: comma-separated names & adjectives that you want the images to Not have. Example: ugly, blurry, malformed'>
<FormLabel sx={{ minWidth: colWidth }}>
Negative Prompt <InfoOutlinedIcon sx={{ mx: 0.5 }} />
</FormLabel>
</Tooltip>
<FormHelperText>
{negativePrompt ? 'Custom' : 'Not set'}
</FormHelperText>
</Box>
<Input
aria-label='Image Generation Negative Prompt'
variant='outlined' placeholder='ugly, blurry, ...'
value={negativePrompt} onChange={(e) => setNegativePrompt(e.target.value)}
slotProps={{ input: { sx: { width: '100%' } } }}
sx={{ width: '100%' }}
/>
</FormControl>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<Box>
<Tooltip title='Set value for reproducible images. Different by default.'>
<FormLabel sx={{ minWidth: colWidth }}>
Noise Seed <InfoOutlinedIcon sx={{ mx: 0.5 }} />
</FormLabel>
</Tooltip>
<FormHelperText>
{seed ? 'Custom' : 'Random'}
</FormHelperText>
</Box>
<Input
aria-label='Image Generation Seed'
variant='outlined' placeholder='Random'
value={seed || ''} onChange={(e) => setSeed(e.target.value || '')}
slotProps={{
input: {
type: 'number',
sx: { width: '100%' },
},
}}
sx={{ width: '100%' }}
/>
</FormControl>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<Box>
<Tooltip title='More steps boost image detail & quality but risk oversaturation and cost increase. Start from 20 steps, and increase gradually. Defaults to 25.'>
<FormLabel sx={{ minWidth: colWidth }}>
Diffusion Steps <InfoOutlinedIcon sx={{ mx: 0.5 }} />
</FormLabel>
</Tooltip>
<FormHelperText>
{steps === 25 ? 'Default' : steps > 30 ? (steps > 40 ? 'May be unnecessary' : 'More detail') : steps <= 15 ? 'Less detail' : 'Balanced'}
</FormHelperText>
</Box>
<Slider
aria-label='Image Generation steps' valueLabelDisplay='auto'
value={steps} onChange={(e, value) => setSteps(value as number)}
min={10} max={50} step={1} defaultValue={25}
sx={{ width: '100%' }}
/>
</FormControl>
</Stack>
</Section>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<Box>
<Tooltip title='Adjust the prompt intensity for generation. Low values deviate, high values overfit. Default: 7 - a balanced start.'>
<FormLabel sx={{ minWidth: colWidth }}>
Cfg-Scale <InfoOutlinedIcon sx={{ mx: 0.5 }} />
</FormLabel>
</Tooltip>
<FormHelperText>
{cfgScale === 7 ? 'Default' : cfgScale >= 9 ? (cfgScale >= 12 ? 'Heavy guidance' : 'Intense guidance') : cfgScale <= 5 ? 'More freedom' : 'Balanced'}
</FormHelperText>
</Box>
<Slider
aria-label='Image Generation Guidance' valueLabelDisplay='auto'
value={cfgScale} onChange={(e, value) => setCfgScale(value as number)}
min={1} max={15} step={0.5} defaultValue={7}
sx={{ width: '100%' }}
/>
</FormControl>
<FormControl orientation='horizontal' sx={{ justifyContent: 'space-between' }}>
<Box>
<Tooltip title='Set value for reproducible images. Different by default.'>
<FormLabel sx={{ minWidth: colWidth }}>
Noise Seed <InfoOutlinedIcon sx={{ mx: 0.5 }} />
</FormLabel>
</Tooltip>
<FormHelperText>
{seed ? 'Custom' : 'Random'}
</FormHelperText>
</Box>
<Input
aria-label='Image Generation Seed'
variant='outlined' placeholder='Random'
value={seed || ''} onChange={(e) => setSeed(e.target.value || '')}
slotProps={{
input: {
type: 'number',
sx: { width: '100%' },
},
}}
sx={{ width: '100%' }}
/>
</FormControl>
</Stack>
);
}