mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-08 18:35:35 +08:00
175 lines
9.0 KiB
JavaScript
175 lines
9.0 KiB
JavaScript
import {
|
|
ChevronDown,
|
|
MessageSquare,
|
|
Cpu,
|
|
Search as SearchIcon,
|
|
Terminal,
|
|
Zap,
|
|
ToggleLeft,
|
|
ToggleRight
|
|
} from 'lucide-react'
|
|
import clsx from 'clsx'
|
|
|
|
export default function ConfigPanel({
|
|
t,
|
|
configExpanded,
|
|
setConfigExpanded,
|
|
models,
|
|
model,
|
|
setModel,
|
|
streamingMode,
|
|
setStreamingMode,
|
|
selectedAccount,
|
|
setSelectedAccount,
|
|
accounts,
|
|
resolveAccountIdentifier,
|
|
apiKey,
|
|
setApiKey,
|
|
config,
|
|
customKeyActive,
|
|
customKeyManaged,
|
|
}) {
|
|
const iconMap = {
|
|
MessageSquare,
|
|
Cpu,
|
|
SearchIcon,
|
|
Terminal,
|
|
Zap,
|
|
ToggleLeft,
|
|
ToggleRight,
|
|
}
|
|
|
|
return (
|
|
<div className={clsx(
|
|
"lg:col-span-3 flex flex-col transition-all duration-300 ease-in-out z-20",
|
|
configExpanded ? "h-auto" : "h-14 lg:h-full"
|
|
)}>
|
|
<div className="bg-card border border-border rounded-xl flex flex-col h-full shadow-sm">
|
|
<button
|
|
onClick={() => setConfigExpanded(!configExpanded)}
|
|
className="lg:hidden flex items-center justify-between p-4 w-full bg-muted/20 hover:bg-muted/30 transition-colors"
|
|
>
|
|
<div className="flex items-center gap-2.5 font-medium text-sm text-foreground">
|
|
<div className="p-1.5 rounded-md bg-transparent text-foreground">
|
|
<Terminal className="w-4 h-4" />
|
|
</div>
|
|
<span>{t('apiTester.config')}</span>
|
|
</div>
|
|
<div className={clsx("transition-transform duration-300 text-muted-foreground", configExpanded ? "rotate-180" : "") }>
|
|
<ChevronDown className="w-4 h-4" />
|
|
</div>
|
|
</button>
|
|
|
|
<div className={clsx(
|
|
"p-4 space-y-6 overflow-y-auto custom-scrollbar flex-1",
|
|
!configExpanded && "hidden lg:block"
|
|
)}>
|
|
<div className="space-y-3">
|
|
<label className="text-[11px] font-semibold text-muted-foreground uppercase tracking-wider ml-0.5">{t('apiTester.modelLabel')}</label>
|
|
<div className="grid grid-cols-1 gap-2">
|
|
{models.map(m => {
|
|
const Icon = iconMap[m.icon] || MessageSquare
|
|
return (
|
|
<button
|
|
key={m.id}
|
|
onClick={() => setModel(m.id)}
|
|
className={clsx(
|
|
"group relative flex items-start gap-3 p-3 rounded-lg border text-left transition-all duration-200",
|
|
model === m.id
|
|
? "bg-secondary border-primary/50 shadow-sm"
|
|
: "bg-transparent border-transparent hover:bg-muted"
|
|
)}
|
|
>
|
|
<div className={clsx(
|
|
"p-1.5 rounded-md shrink-0 transition-colors",
|
|
model === m.id ? m.color : "text-muted-foreground group-hover:text-foreground"
|
|
)}>
|
|
<Icon className="w-4 h-4" />
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<div className={clsx("font-medium text-sm", model === m.id ? "text-foreground" : "text-foreground/80") }>
|
|
{m.name}
|
|
</div>
|
|
<div className="text-[11px] text-muted-foreground mt-0.5">{m.desc}</div>
|
|
</div>
|
|
{model === m.id && (
|
|
<div className={clsx("absolute top-3 right-3", m.color)}>
|
|
<div className="w-1.5 h-1.5 rounded-full bg-current" />
|
|
</div>
|
|
)}
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-[11px] font-semibold text-muted-foreground uppercase tracking-wider ml-0.5">{t('apiTester.streamMode')}</label>
|
|
<button
|
|
onClick={() => setStreamingMode(!streamingMode)}
|
|
className={clsx(
|
|
"w-full flex items-center justify-between px-3 py-2 rounded-lg border transition-all duration-200",
|
|
streamingMode
|
|
? "bg-primary/10 border-primary/50 text-foreground"
|
|
: "bg-background border-border text-muted-foreground hover:bg-muted/50"
|
|
)}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
<div className={clsx("p-1.5 rounded-md", streamingMode ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground")}>
|
|
<Zap className="w-4 h-4" />
|
|
</div>
|
|
<span className="text-sm font-medium">{t('apiTester.streamMode')}</span>
|
|
</div>
|
|
{streamingMode ? <ToggleRight className="w-5 h-5 text-primary" /> : <ToggleLeft className="w-5 h-5 text-muted-foreground" />}
|
|
</button>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-[11px] font-semibold text-muted-foreground uppercase tracking-wider ml-0.5">{t('apiTester.accountSelector')}</label>
|
|
<div className="relative">
|
|
<select
|
|
className="w-full h-10 pl-3 pr-8 bg-secondary border border-border rounded-lg text-sm appearance-none focus:outline-none focus:ring-1 focus:ring-ring focus:border-ring transition-all cursor-pointer hover:bg-muted"
|
|
value={selectedAccount}
|
|
onChange={e => setSelectedAccount(e.target.value)}
|
|
>
|
|
<option value="" className="bg-popover text-popover-foreground">{t('apiTester.autoRandom')}</option>
|
|
{accounts.map((acc, i) => {
|
|
const id = resolveAccountIdentifier(acc)
|
|
if (!id) return null
|
|
return (
|
|
<option key={i} value={id} className="bg-popover text-popover-foreground">
|
|
👤 {id}
|
|
</option>
|
|
)
|
|
})}
|
|
</select>
|
|
<ChevronDown className="absolute right-2.5 top-3 w-4 h-4 text-muted-foreground pointer-events-none" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-[11px] font-semibold text-muted-foreground uppercase tracking-wider ml-0.5">{t('apiTester.apiKeyOptional')}</label>
|
|
<input
|
|
type="text"
|
|
autoComplete="off"
|
|
spellCheck={false}
|
|
className="w-full h-10 px-3 bg-muted/30 border border-border rounded-lg text-sm font-mono placeholder:text-muted-foreground/40 focus:outline-none focus:ring-1 focus:ring-ring focus:border-ring transition-all"
|
|
placeholder={config.keys?.[0] ? t('apiTester.apiKeyDefault', { suffix: config.keys[0].slice(-6) }) : t('apiTester.apiKeyPlaceholder')}
|
|
value={apiKey}
|
|
onChange={e => setApiKey(e.target.value)}
|
|
/>
|
|
{customKeyActive && (
|
|
<p className={clsx(
|
|
"text-[11px] mt-1",
|
|
customKeyManaged ? "text-emerald-600" : "text-amber-600"
|
|
)}>
|
|
{customKeyManaged ? t('apiTester.modeManaged') : t('apiTester.modeDirect')}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|