feat(settings): Enhance SettingsPanel UI with new configuration options and improved layout

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-04-04 03:53:39 -07:00
parent 757d04828b
commit e5a818de87

View file

@ -1,4 +1,4 @@
import { useCallback } from 'react';
import { useCallback, useEffect, useState } from 'react';
import type { ReactElement } from 'react';
import styled from '@lilith/ui-styled-components';
import { Tooltip } from '@lilith/ui-feedback';
@ -6,6 +6,12 @@ import { AnimatePresence, motion } from '@lilith/ui-motion';
import type { VoiceSessionState } from '../voice/VoiceSession';
import type { Settings, SettingsActions } from './useSettings';
interface PersonaOption {
id: string;
slug: string;
name: string;
}
export interface SettingsPanelProps {
open: boolean;
onClose: () => void;
@ -13,6 +19,9 @@ export interface SettingsPanelProps {
connectionState: VoiceSessionState | 'disconnected';
settings: Settings;
onSettings: SettingsActions;
apiBaseUrl: string;
currentPersonaId: string;
onPersonaChange: (personaId: string) => void;
}
const Overlay = styled(motion.div)`
@ -203,6 +212,41 @@ const VolumeLabel = styled.span`
text-align: right;
`;
const PersonaGrid = styled.div`
display: flex;
flex-direction: column;
gap: 6px;
padding: 8px 20px 12px;
`;
const PersonaButton = styled.button<{ $active: boolean }>`
display: flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
border-radius: 10px;
border: 1px solid ${({ $active }) => ($active ? '#553c9a' : '#2d3748')};
background: ${({ $active }) => ($active ? 'rgba(85,60,154,0.15)' : 'transparent')};
color: ${({ $active }) => ($active ? '#e2e8f0' : '#a0aec0')};
font-size: 14px;
cursor: pointer;
text-align: left;
width: 100%;
transition: border-color 150ms ease, background 150ms ease;
&:active {
background: rgba(85,60,154,0.25);
}
`;
const PersonaActiveDot = styled.span<{ $active: boolean }>`
width: 8px;
height: 8px;
border-radius: 50%;
background: ${({ $active }) => ($active ? '#553c9a' : '#2d3748')};
flex-shrink: 0;
`;
function Toggle({ on, onChange, label }: { on: boolean; onChange: (v: boolean) => void; label: string }): ReactElement {
return (
<ToggleTrack
@ -224,7 +268,26 @@ export function SettingsPanel({
connectionState,
settings,
onSettings,
apiBaseUrl,
currentPersonaId,
onPersonaChange,
}: SettingsPanelProps): ReactElement {
const [personas, setPersonas] = useState<PersonaOption[]>([]);
useEffect(() => {
if (!open) return;
fetch(`${apiBaseUrl}/personalities`)
.then((r) => r.json() as Promise<PersonaOption[]>)
.then(setPersonas)
.catch(() => {/* non-fatal */});
}, [open, apiBaseUrl]);
const handlePersonaChange = useCallback((id: string) => {
if (id === currentPersonaId) return;
onPersonaChange(id);
onClose();
}, [currentPersonaId, onPersonaChange, onClose]);
const handleCopySession = useCallback(() => {
void navigator.clipboard.writeText(sessionId);
}, [sessionId]);
@ -253,6 +316,27 @@ export function SettingsPanel({
>
<Handle />
{personas.length > 0 && (
<Section>
<SectionTitle>Personality</SectionTitle>
<PersonaGrid>
{personas.map((p) => {
const isActive = p.id === currentPersonaId || p.slug === currentPersonaId;
return (
<PersonaButton
key={p.id}
$active={isActive}
onClick={() => handlePersonaChange(p.id)}
>
<PersonaActiveDot $active={isActive} />
{p.name}
</PersonaButton>
);
})}
</PersonaGrid>
</Section>
)}
<Section>
<SectionTitle>Voice Output (TTS)</SectionTitle>
<Row>