Update debug drawer layout

This commit is contained in:
Xin Wang
2026-02-09 11:10:48 +08:00
parent ab8e3a82d9
commit c5ff3ea261

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { Plus, Search, Play, Copy, Trash2, Edit2, Mic, MessageSquare, Save, Video, PhoneOff, Camera, ArrowLeftRight, Send, Phone, MoreHorizontal, Rocket, AlertTriangle, PhoneCall, CameraOff, Image, Images, CloudSun, Calendar, TrendingUp, Coins, Wrench, Globe, Terminal, X, ClipboardCheck, Sparkles, Volume2, Timer, ChevronDown, ChevronLeft, ChevronRight, Link as LinkIcon, Database, Server, Zap, ExternalLink, Key, BrainCircuit, Ear, Book, Filter } from 'lucide-react'; import { Plus, Search, Play, Copy, Trash2, Edit2, Mic, MessageSquare, Save, Video, PhoneOff, Camera, ArrowLeftRight, Send, Phone, MoreHorizontal, Rocket, AlertTriangle, PhoneCall, CameraOff, Image, Images, CloudSun, Calendar, TrendingUp, Coins, Wrench, Globe, Terminal, X, ClipboardCheck, Sparkles, Volume2, Timer, ChevronDown, Link as LinkIcon, Database, Server, Zap, ExternalLink, Key, BrainCircuit, Ear, Book, Filter } from 'lucide-react';
import { Button, Input, Card, Badge, Drawer, Dialog } from '../components/UI'; import { Button, Input, Card, Badge, Drawer, Dialog } from '../components/UI';
import { ASRModel, Assistant, KnowledgeBase, LLMModel, TabValue, Voice } from '../types'; import { ASRModel, Assistant, KnowledgeBase, LLMModel, TabValue, Voice } from '../types';
import { createAssistant, deleteAssistant, fetchASRModels, fetchAssistantRuntimeConfig, fetchAssistants, fetchKnowledgeBases, fetchLLMModels, fetchVoices, updateAssistant as updateAssistantApi } from '../services/backendApi'; import { createAssistant, deleteAssistant, fetchASRModels, fetchAssistantRuntimeConfig, fetchAssistants, fetchKnowledgeBases, fetchLLMModels, fetchVoices, updateAssistant as updateAssistantApi } from '../services/backendApi';
@@ -1046,6 +1046,7 @@ export const DebugDrawer: React.FC<{
const [wsError, setWsError] = useState(''); const [wsError, setWsError] = useState('');
const [resolvedConfigOpen, setResolvedConfigOpen] = useState(false); const [resolvedConfigOpen, setResolvedConfigOpen] = useState(false);
const [resolvedConfigView, setResolvedConfigView] = useState<string>(''); const [resolvedConfigView, setResolvedConfigView] = useState<string>('');
const [settingsDrawerOpen, setSettingsDrawerOpen] = useState(false);
const [wsUrl, setWsUrl] = useState<string>(() => { const [wsUrl, setWsUrl] = useState<string>(() => {
const fromStorage = localStorage.getItem('debug_ws_url'); const fromStorage = localStorage.getItem('debug_ws_url');
if (fromStorage) return fromStorage; if (fromStorage) return fromStorage;
@@ -1089,6 +1090,7 @@ export const DebugDrawer: React.FC<{
void audioCtxRef.current.close(); void audioCtxRef.current.close();
audioCtxRef.current = null; audioCtxRef.current = null;
} }
setSettingsDrawerOpen(false);
setIsSwapped(false); setIsSwapped(false);
setCallStatus('idle'); setCallStatus('idle');
} }
@@ -1591,165 +1593,231 @@ export const DebugDrawer: React.FC<{
</div> </div>
); );
const settingsPanel = (
<div className="h-full border-r border-white/10 bg-background/95 backdrop-blur-md shadow-2xl p-3">
<div className="flex items-center justify-between mb-2">
<h3 className="text-xs font-black tracking-[0.15em] uppercase text-muted-foreground"></h3>
<Button size="icon" variant="ghost" className="h-7 w-7" onClick={() => setSettingsDrawerOpen(false)}>
<X className="h-3.5 w-3.5" />
</Button>
</div>
<div className="space-y-3">
<div className="space-y-1.5">
<label className="text-[10px] text-muted-foreground font-black tracking-widest uppercase block">WebSocket Endpoint</label>
<Input value={wsUrl} onChange={(e) => setWsUrl(e.target.value)} placeholder="ws://localhost:8000/ws" />
</div>
<div className="flex items-center justify-between gap-2">
<Badge variant="outline" className="text-xs">WS: {wsStatus}</Badge>
<label className="inline-flex items-center gap-1 text-xs text-muted-foreground px-2 py-1 rounded border border-white/10">
<input
type="checkbox"
checked={textTtsEnabled}
onChange={(e) => setTextTtsEnabled(e.target.checked)}
className="accent-primary"
/>
TTS
</label>
</div>
<div className="flex gap-2">
<Button size="sm" variant="secondary" onClick={() => ensureWsSession()} disabled={wsStatus === 'connecting'}>
Connect
</Button>
<Button size="sm" variant="ghost" onClick={closeWs}>
Disconnect
</Button>
</div>
{wsError && <p className="text-xs text-red-400">{wsError}</p>}
<div className="rounded-md border border-white/10 bg-black/30">
<button
className="w-full px-3 py-2 text-left text-xs text-muted-foreground hover:text-foreground flex items-center justify-between"
onClick={() => setResolvedConfigOpen((v) => !v)}
>
<span>Resolved Runtime Config</span>
<ChevronDown className={`h-3.5 w-3.5 transition-transform ${resolvedConfigOpen ? 'rotate-180' : ''}`} />
</button>
{resolvedConfigOpen && (
<pre className="px-3 pb-3 text-[11px] leading-5 text-cyan-100/90 whitespace-pre-wrap break-all max-h-64 overflow-auto">
{resolvedConfigView || 'Connect to load resolved config...'}
</pre>
)}
</div>
</div>
</div>
);
return ( return (
<>
<Drawer isOpen={isOpen} onClose={() => { handleHangup(); onClose(); }} title={`调试: ${assistant.name}`}> <Drawer isOpen={isOpen} onClose={() => { handleHangup(); onClose(); }} title={`调试: ${assistant.name}`}>
<div className="flex flex-col h-full"> <div className="relative flex flex-col h-full">
<div className="flex justify-center mb-4 bg-white/5 p-1 rounded-lg shrink-0"> <div className="flex items-center gap-2 mb-4 shrink-0">
{(['text', 'voice', 'video'] as const).map(m => ( <div className="flex-1 flex justify-center bg-white/5 p-1 rounded-lg">
<button key={m} className={`flex-1 py-1 text-sm rounded-md transition-all ${mode === m ? 'bg-primary text-primary-foreground shadow' : 'text-muted-foreground hover:bg-white/5'}`} onClick={() => setMode(m)}> {(['text', 'voice', 'video'] as const).map(m => (
{m === 'text' && <MessageSquare className="inline w-4 h-4 mr-1"/>} <button key={m} className={`flex-1 py-1 text-sm rounded-md transition-all ${mode === m ? 'bg-primary text-primary-foreground shadow' : 'text-muted-foreground hover:bg-white/5'}`} onClick={() => setMode(m)}>
{m === 'voice' && <Mic className="inline w-4 h-4 mr-1"/>} {m === 'text' && <MessageSquare className="inline w-4 h-4 mr-1" />}
{m === 'video' && <Video className="inline w-4 h-4 mr-1"/>} {m === 'voice' && <Mic className="inline w-4 h-4 mr-1" />}
{m === 'text' ? '文本' : m === 'voice' ? '语音' : '视频'} {m === 'video' && <Video className="inline w-4 h-4 mr-1" />}
</button> {m === 'text' ? '文本' : m === 'voice' ? '语音' : '视频'}
))} </button>
))}
</div>
</div> </div>
<div className="flex-1 min-h-0 flex"> <div className="flex-1 min-h-0 overflow-hidden flex flex-col">
<div className="flex-1 min-w-0 flex flex-col">
<div className="flex-1 overflow-hidden flex flex-col min-h-0 mb-4"> <div className="flex-1 overflow-hidden flex flex-col min-h-0 mb-4">
{mode === 'text' ? ( {mode === 'text' ? (
textSessionStarted ? ( textSessionStarted ? (
<div className="flex flex-col gap-2 h-full min-h-0"> <div className="flex-1 flex flex-col min-h-0 space-y-2">
<div className="shrink-0 rounded-md border border-white/10 bg-white/5 p-2 flex items-center gap-2"> <div className="flex flex-col h-full animate-in fade-in">
<Badge variant="outline" className="text-xs"> <div className="h-1/3 min-h-[150px] shrink-0 border border-white/5 rounded-md bg-black/20 flex flex-col items-center justify-center text-muted-foreground space-y-4 mb-2 relative overflow-hidden">
WS: {wsStatus} <div className="h-24 w-24 rounded-full bg-primary/10 flex items-center justify-center animate-pulse relative z-10">
</Badge> <MessageSquare className="h-10 w-10 text-primary" />
<Button size="sm" variant="ghost" onClick={closeWs}> </div>
Disconnect <p className="text-sm relative z-10">...</p>
</Button> </div>
{wsError && <span className="text-xs text-red-400 truncate">{wsError}</span>} <h4 className="text-xs font-medium text-muted-foreground px-1 mb-1 uppercase tracking-tight"></h4>
</div> <TranscriptionLog />
<TranscriptionLog /> </div>
</div> <Button variant="destructive" size="sm" className="w-full h-10 font-bold" onClick={closeWs}>
) : ( <PhoneOff className="mr-2 h-4 w-4" />
<div className="flex-1 flex flex-col items-center justify-center space-y-6 border border-white/5 rounded-xl bg-black/20 animate-in fade-in zoom-in-95 px-6"> </Button>
<div className="w-full max-w-xl space-y-3">
<label className="text-[10px] text-muted-foreground font-black tracking-widest uppercase block">WebSocket Endpoint</label>
<Input value={wsUrl} onChange={(e) => setWsUrl(e.target.value)} placeholder="ws://localhost:8000/ws" />
<div className="flex items-center gap-2">
<Badge variant="outline" className="text-xs">WS: {wsStatus}</Badge>
<label className="inline-flex items-center gap-1 text-xs text-muted-foreground px-2 py-1 rounded border border-white/10">
<input
type="checkbox"
checked={textTtsEnabled}
onChange={(e) => setTextTtsEnabled(e.target.checked)}
className="accent-primary"
/>
TTS
</label>
{wsError && <span className="text-xs text-red-400 truncate">{wsError}</span>}
</div> </div>
</div> ) : wsStatus === 'connecting' ? (
<Button <div className="flex-1 flex flex-col items-center justify-center space-y-6">
onClick={handleTextLaunch} <div className="h-24 w-24 rounded-full bg-primary/20 flex items-center justify-center animate-bounce">
disabled={wsStatus === 'connecting'} <MessageSquare className="h-10 w-10 text-primary" />
className="w-56 h-12 rounded-full bg-green-500 hover:bg-green-600 shadow-[0_0_20px_rgba(34,197,94,0.4)] text-base font-bold" </div>
> <div className="text-center">
<PhoneCall className="mr-2 h-5 w-5" /> <p className="text-primary font-mono text-sm tracking-widest animate-pulse">CONNECTING...</p>
</Button> <p className="text-xs text-muted-foreground mt-2"></p>
</div> </div>
) <Button onClick={closeWs} variant="destructive" className="rounded-full h-10 px-8"></Button>
</div>
) : (
<div className="flex-1 flex flex-col items-center justify-center space-y-6 border border-white/5 rounded-xl bg-black/20 animate-in fade-in zoom-in-95 px-6">
<div className="relative">
<div className="absolute inset-0 bg-primary/20 rounded-full blur-2xl animate-pulse"></div>
<div className="relative h-24 w-24 rounded-full bg-white/5 border border-white/10 flex items-center justify-center">
<MessageSquare className="h-10 w-10 text-muted-foreground" />
</div>
</div>
<div className="text-center">
<h3 className="text-lg font-bold text-white mb-1"></h3>
<p className="text-xs text-muted-foreground"></p>
</div>
<Button
onClick={handleTextLaunch}
disabled={wsStatus === 'connecting'}
className="w-48 h-12 rounded-full bg-green-500 hover:bg-green-600 shadow-[0_0_20px_rgba(34,197,94,0.4)] text-base font-bold"
>
<PhoneCall className="mr-2 h-5 w-5" />
</Button>
</div>
)
) : callStatus === 'idle' ? ( ) : callStatus === 'idle' ? (
<div className="flex-1 flex flex-col items-center justify-center space-y-6 border border-white/5 rounded-xl bg-black/20 animate-in fade-in zoom-in-95"> <div className="flex-1 flex flex-col items-center justify-center space-y-6 border border-white/5 rounded-xl bg-black/20 animate-in fade-in zoom-in-95">
<div className="relative"> <div className="relative">
<div className="absolute inset-0 bg-primary/20 rounded-full blur-2xl animate-pulse"></div> <div className="absolute inset-0 bg-primary/20 rounded-full blur-2xl animate-pulse"></div>
<div className="relative h-24 w-24 rounded-full bg-white/5 border border-white/10 flex items-center justify-center"> <div className="relative h-24 w-24 rounded-full bg-white/5 border border-white/10 flex items-center justify-center">
{mode === 'voice' ? <Mic className="h-10 w-10 text-muted-foreground" /> : <Video className="h-10 w-10 text-muted-foreground" />} {mode === 'voice' ? <Mic className="h-10 w-10 text-muted-foreground" /> : <Video className="h-10 w-10 text-muted-foreground" />}
</div> </div>
</div> </div>
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-bold text-white mb-1"></h3> <h3 className="text-lg font-bold text-white mb-1"></h3>
<p className="text-xs text-muted-foreground"></p> <p className="text-xs text-muted-foreground"></p>
</div> </div>
<Button onClick={handleCall} className="w-48 h-12 rounded-full bg-green-500 hover:bg-green-600 shadow-[0_0_20px_rgba(34,197,94,0.4)] text-base font-bold"> <Button onClick={handleCall} className="w-48 h-12 rounded-full bg-green-500 hover:bg-green-600 shadow-[0_0_20px_rgba(34,197,94,0.4)] text-base font-bold">
<PhoneCall className="mr-2 h-5 w-5" /> <PhoneCall className="mr-2 h-5 w-5" />
</Button> </Button>
</div> </div>
) : callStatus === 'calling' ? ( ) : callStatus === 'calling' ? (
<div className="flex-1 flex flex-col items-center justify-center space-y-6"> <div className="flex-1 flex flex-col items-center justify-center space-y-6">
<div className="h-24 w-24 rounded-full bg-primary/20 flex items-center justify-center animate-bounce"> <div className="h-24 w-24 rounded-full bg-primary/20 flex items-center justify-center animate-bounce">
<PhoneCall className="h-10 w-10 text-primary" /> <PhoneCall className="h-10 w-10 text-primary" />
</div> </div>
<div className="text-center"> <div className="text-center">
<p className="text-primary font-mono text-sm tracking-widest animate-pulse">CALLING...</p> <p className="text-primary font-mono text-sm tracking-widest animate-pulse">CALLING...</p>
<p className="text-xs text-muted-foreground mt-2"> AI </p> <p className="text-xs text-muted-foreground mt-2"> AI </p>
</div> </div>
<Button onClick={handleHangup} variant="destructive" className="rounded-full h-10 px-8"></Button> <Button onClick={handleHangup} variant="destructive" className="rounded-full h-10 px-8"></Button>
</div> </div>
) : ( ) : (
<div className="flex-1 flex flex-col min-h-0 space-y-2"> <div className="flex-1 flex flex-col min-h-0 space-y-2">
{mode === 'voice' ? ( {mode === 'voice' ? (
<div className="flex flex-col h-full animate-in fade-in"> <div className="flex flex-col h-full animate-in fade-in">
<div className="h-1/3 min-h-[150px] shrink-0 border border-white/5 rounded-md bg-black/20 flex flex-col items-center justify-center text-muted-foreground space-y-4 mb-2 relative overflow-hidden"> <div className="h-1/3 min-h-[150px] shrink-0 border border-white/5 rounded-md bg-black/20 flex flex-col items-center justify-center text-muted-foreground space-y-4 mb-2 relative overflow-hidden">
<div className="h-24 w-24 rounded-full bg-primary/10 flex items-center justify-center animate-pulse relative z-10"> <div className="h-24 w-24 rounded-full bg-primary/10 flex items-center justify-center animate-pulse relative z-10">
<Mic className="h-10 w-10 text-primary" /> <Mic className="h-10 w-10 text-primary" />
</div>
<p className="text-sm relative z-10">...</p>
</div> </div>
<h4 className="text-xs font-medium text-muted-foreground px-1 mb-1 uppercase tracking-tight"></h4> <p className="text-sm relative z-10">...</p>
<TranscriptionLog /> </div>
<h4 className="text-xs font-medium text-muted-foreground px-1 mb-1 uppercase tracking-tight"></h4>
<TranscriptionLog />
</div> </div>
) : ( ) : (
<div className="flex flex-col h-full space-y-2 animate-in fade-in"> <div className="flex flex-col h-full space-y-2 animate-in fade-in">
<div className="h-3/5 shrink-0 flex flex-col gap-2"> <div className="h-3/5 shrink-0 flex flex-col gap-2">
<div className="flex gap-2 shrink-0"> <div className="flex gap-2 shrink-0">
<select className="flex-1 text-xs bg-white/5 border border-white/10 rounded px-2 py-1 text-foreground" value={selectedCamera} onChange={e => setSelectedCamera(e.target.value)}> <select className="flex-1 text-xs bg-white/5 border border-white/10 rounded px-2 py-1 text-foreground" value={selectedCamera} onChange={e => setSelectedCamera(e.target.value)}>
{devices.filter(d => d.kind === 'videoinput').map(d => <option key={d.deviceId} value={d.deviceId}>{d.label || 'Camera'}</option>)} {devices.filter(d => d.kind === 'videoinput').map(d => <option key={d.deviceId} value={d.deviceId}>{d.label || 'Camera'}</option>)}
</select> </select>
<select className="flex-1 text-xs bg-white/5 border border-white/10 rounded px-2 py-1 text-foreground" value={selectedMic} onChange={e => setSelectedMic(e.target.value)}> <select className="flex-1 text-xs bg-white/5 border border-white/10 rounded px-2 py-1 text-foreground" value={selectedMic} onChange={e => setSelectedMic(e.target.value)}>
{devices.filter(d => d.kind === 'audioinput').map(d => <option key={d.deviceId} value={d.deviceId}>{d.label || 'Mic'}</option>)} {devices.filter(d => d.kind === 'audioinput').map(d => <option key={d.deviceId} value={d.deviceId}>{d.label || 'Mic'}</option>)}
</select> </select>
</div>
<div className="flex-1 relative rounded-lg overflow-hidden border border-white/10 bg-black min-h-0">
<div className="absolute inset-0">{isSwapped ? renderLocalVideo(false) : renderRemoteVideo(false)}</div>
<div className="absolute bottom-2 right-2 w-24 h-36 z-10">{isSwapped ? renderRemoteVideo(true) : renderLocalVideo(true)}</div>
<button className="absolute top-2 right-2 z-20 h-8 w-8 rounded-full bg-black/50 backdrop-blur flex items-center justify-center text-white border border-white/10 hover:bg-primary/80" onClick={() => setIsSwapped(!isSwapped)}><ArrowLeftRight className="h-3.5 w-3.5" /></button>
</div>
</div> </div>
<TranscriptionLog /> <div className="flex-1 relative rounded-lg overflow-hidden border border-white/10 bg-black min-h-0">
<div className="absolute inset-0">{isSwapped ? renderLocalVideo(false) : renderRemoteVideo(false)}</div>
<div className="absolute bottom-2 right-2 w-24 h-36 z-10">{isSwapped ? renderRemoteVideo(true) : renderLocalVideo(true)}</div>
<button className="absolute top-2 right-2 z-20 h-8 w-8 rounded-full bg-black/50 backdrop-blur flex items-center justify-center text-white border border-white/10 hover:bg-primary/80" onClick={() => setIsSwapped(!isSwapped)}><ArrowLeftRight className="h-3.5 w-3.5" /></button>
</div>
</div>
<TranscriptionLog />
</div> </div>
)} )}
<Button variant="destructive" size="sm" className="w-full h-10 font-bold" onClick={handleHangup}> <Button variant="destructive" size="sm" className="w-full h-10 font-bold" onClick={handleHangup}>
<PhoneOff className="mr-2 h-4 w-4" /> <PhoneOff className="mr-2 h-4 w-4" />
</Button> </Button>
</div> </div>
)} )}
</div> </div>
{(mode !== 'text' || textSessionStarted) && ( <div className="shrink-0 space-y-2">
<div className="shrink-0 space-y-2"> <div className="flex space-x-2">
<div className="flex space-x-2"> <Input
<Input value={inputText} onChange={e => setInputText(e.target.value)} placeholder={mode === 'text' ? "输入消息..." : "输入文本模拟交互..."} onKeyDown={e => e.key === 'Enter' && handleSend()} disabled={isLoading || (mode === 'text' ? !textSessionStarted : callStatus !== 'active')} className="flex-1" /> value={inputText}
<Button size="icon" onClick={handleSend} disabled={isLoading || (mode === 'text' ? !textSessionStarted : callStatus !== 'active')}><Send className="h-4 w-4" /></Button> onChange={e => setInputText(e.target.value)}
</div> placeholder={mode === 'text' && !textSessionStarted ? "请先发起呼叫后输入消息..." : (mode === 'text' ? "输入消息..." : "输入文本模拟交互...")}
onKeyDown={e => e.key === 'Enter' && handleSend()}
disabled={isLoading || (mode === 'text' ? !textSessionStarted : callStatus !== 'active')}
className="flex-1"
/>
<Button size="icon" onClick={handleSend} disabled={isLoading || (mode === 'text' ? !textSessionStarted : callStatus !== 'active')}><Send className="h-4 w-4" /></Button>
</div> </div>
)}
</div>
<div className="shrink-0 flex items-center px-1">
<button
className="h-10 w-7 rounded-md border border-white/10 bg-white/5 hover:bg-white/10 text-muted-foreground hover:text-foreground transition-colors flex items-center justify-center"
onClick={() => setResolvedConfigOpen((v) => !v)}
title={resolvedConfigOpen ? '收起运行时配置' : '展开运行时配置'}
>
{resolvedConfigOpen ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
</button>
</div>
<div className={`shrink-0 overflow-hidden transition-all duration-300 ease-out ${resolvedConfigOpen ? 'w-80 opacity-100 ml-2' : 'w-0 opacity-0 ml-0'}`}>
<div className="h-full rounded-xl border border-white/10 bg-black/20 flex flex-col">
<div className="px-3 py-2 border-b border-white/10 flex items-center justify-between">
<span className="text-xs text-muted-foreground uppercase tracking-wider">Resolved Runtime Config</span>
<Badge variant="outline" className="text-[10px]">read-only</Badge>
</div>
<pre className="flex-1 p-3 text-[11px] leading-5 text-cyan-100/90 whitespace-pre-wrap break-all overflow-auto">
{resolvedConfigView || 'Connect to load resolved config...'}
</pre>
</div> </div>
</div>
</div> </div>
</div> </div>
</Drawer> </Drawer>
{isOpen && (
<div className="fixed inset-y-0 z-[51] right-[min(100vw,32rem)]">
<button
className={`absolute inset-y-0 w-10 border-r border-white/10 bg-background/90 backdrop-blur-md text-muted-foreground hover:text-foreground hover:bg-background/95 transition-[right,color,background-color] duration-300 flex flex-col items-center justify-center gap-2 ${settingsDrawerOpen ? 'right-[min(78vw,28rem)]' : 'right-0'}`}
onClick={() => setSettingsDrawerOpen((v) => !v)}
title={settingsDrawerOpen ? '收起调试设置' : '展开调试设置'}
>
<span className={`text-[10px] font-mono tracking-tight ${settingsDrawerOpen ? 'text-primary animate-pulse' : 'opacity-90 animate-pulse'}`}>
{settingsDrawerOpen ? '>>' : '<<'}
</span>
<Wrench className="h-4 w-4" />
<span className="text-[10px] tracking-widest [writing-mode:vertical-rl] rotate-180"></span>
</button>
<div className="absolute inset-y-0 right-0 w-[78vw] max-w-md overflow-hidden pointer-events-none">
<div className={`h-full transition-transform duration-300 ease-out will-change-transform ${settingsDrawerOpen ? 'translate-x-0 pointer-events-auto' : 'translate-x-full pointer-events-none'}`}>
{settingsPanel}
</div>
</div>
</div>
)}
</>
); );
}; };