168 lines
5.1 KiB
TypeScript
168 lines
5.1 KiB
TypeScript
'use client';
|
|
import { useChatStore } from '../store';
|
|
import { useState } from 'react';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import { Button } from '@/components/ui/button';
|
|
import { cn } from '@/lib/utils';
|
|
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
|
|
|
|
type VoiceGender = 'all' | 'male' | 'female';
|
|
|
|
type VoiceActorItem = {
|
|
id: number;
|
|
name: string;
|
|
description: string;
|
|
avatarUrl: string;
|
|
gender: 'male' | 'female';
|
|
};
|
|
|
|
export default function VoiceActor() {
|
|
const setSideBar = useChatStore((store) => store.setSideBar);
|
|
|
|
// 语音演员列表(静态数据)
|
|
const voiceActors: VoiceActorItem[] = [
|
|
{
|
|
id: 1,
|
|
name: 'Voice Actor 1',
|
|
description: 'Have a role-playing conversation with AI',
|
|
avatarUrl: 'https://i.pravatar.cc/150?img=1',
|
|
gender: 'female',
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'Voice Actor 2',
|
|
description: 'Have a role-playing conversation with AI',
|
|
avatarUrl: 'https://i.pravatar.cc/150?img=2',
|
|
gender: 'female',
|
|
},
|
|
{
|
|
id: 3,
|
|
name: 'Voice Actor 3',
|
|
description: 'Have a role-playing conversation with AI',
|
|
avatarUrl: 'https://i.pravatar.cc/150?img=3',
|
|
gender: 'male',
|
|
},
|
|
{
|
|
id: 4,
|
|
name: 'Voice Actor 4',
|
|
description: 'Have a role-playing conversation with AI',
|
|
avatarUrl: 'https://i.pravatar.cc/150?img=4',
|
|
gender: 'female',
|
|
},
|
|
{
|
|
id: 5,
|
|
name: 'Voice Actor 5',
|
|
description: 'Have a role-playing conversation with AI',
|
|
avatarUrl: 'https://i.pravatar.cc/150?img=5',
|
|
gender: 'male',
|
|
},
|
|
{
|
|
id: 6,
|
|
name: 'Voice Actor 6',
|
|
description: 'Have a role-playing conversation with AI',
|
|
avatarUrl: 'https://i.pravatar.cc/150?img=6',
|
|
gender: 'female',
|
|
},
|
|
{
|
|
id: 7,
|
|
name: 'Voice Actor 7',
|
|
description: 'Have a role-playing conversation with AI',
|
|
avatarUrl: 'https://i.pravatar.cc/150?img=7',
|
|
gender: 'male',
|
|
},
|
|
];
|
|
|
|
const [selectedGender, setSelectedGender] = useState<VoiceGender>('all');
|
|
const [selectedActorId, setSelectedActorId] = useState(1);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
// 根据性别过滤演员列表
|
|
const filteredActors = voiceActors.filter((actor) => {
|
|
if (selectedGender === 'all') return true;
|
|
return actor.gender === selectedGender;
|
|
});
|
|
|
|
const handleConfirm = async () => {
|
|
setLoading(true);
|
|
try {
|
|
// TODO: 调用实际的 API 保存语音演员设置
|
|
// await updateVoiceActor({ voiceActorId: selectedActorId })
|
|
console.log('Selected voice actor:', selectedActorId);
|
|
|
|
// 模拟延迟
|
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
|
|
setSideBar('profile');
|
|
} catch (error) {
|
|
console.error(error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex h-full flex-col">
|
|
{/* Gender Tabs */}
|
|
<div className="mb-6 flex gap-6">
|
|
{[
|
|
{ value: 'all' as const, label: 'All' },
|
|
{ value: 'male' as const, label: 'Male' },
|
|
{ value: 'female' as const, label: 'Female' },
|
|
].map((tab) => (
|
|
<button
|
|
key={tab.value}
|
|
className={cn(
|
|
'txt-title-s relative pb-2 transition-colors',
|
|
selectedGender === tab.value ? 'text-txt-primary-normal' : 'text-txt-secondary-normal'
|
|
)}
|
|
onClick={() => setSelectedGender(tab.value)}
|
|
>
|
|
{tab.label}
|
|
{selectedGender === tab.value && (
|
|
<div className="bg-primary-normal absolute bottom-0 left-0 right-0 h-0.5 rounded-full" />
|
|
)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Voice Actor List */}
|
|
<div className="flex-1 overflow-y-auto">
|
|
<div className="flex flex-col gap-3">
|
|
{filteredActors.map((actor) => (
|
|
<div
|
|
key={actor.id}
|
|
className={cn(
|
|
'bg-surface-element-normal flex cursor-pointer items-center gap-3 rounded-lg p-4 transition-colors',
|
|
selectedActorId === actor.id && 'bg-surface-element-hover'
|
|
)}
|
|
onClick={() => setSelectedActorId(actor.id)}
|
|
>
|
|
<Avatar className="h-14 w-14">
|
|
<AvatarImage src={actor.avatarUrl} alt={actor.name} />
|
|
<AvatarFallback>{actor.name.charAt(0)}</AvatarFallback>
|
|
</Avatar>
|
|
|
|
<div className="flex-1">
|
|
<div className="txt-title-s text-txt-primary-normal">{actor.name}</div>
|
|
<div className="txt-body-s text-txt-secondary-normal mt-1">{actor.description}</div>
|
|
</div>
|
|
|
|
<Checkbox shape="round" checked={selectedActorId === actor.id} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Footer Buttons */}
|
|
{/* <div className="mt-6 flex justify-end gap-3">
|
|
<Button variant="tertiary" size="large" onClick={() => setSideBar('profile')}>
|
|
Cancel
|
|
</Button>
|
|
<Button size="large" variant="primary" loading={loading} onClick={handleConfirm}>
|
|
Select
|
|
</Button>
|
|
</div> */}
|
|
</div>
|
|
);
|
|
}
|