mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-11 14:10:15 -07:00
Settings: Migrate to Tabs
This commit is contained in:
@@ -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 />
|
||||
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user