echo/src/app/[locale]/agents/AgentsPageClient.tsx
ddshi 4c42d6b6f5 feat: 完成阶段 2 - 用户系统与智能体初始化
## 完成的功能
- 用户登录/注册(邮箱 + Google OAuth)
- 初始问题引导流程(4 个问题)
- 智能体自动生成(根据用户回答)
- 智能体列表页面

## 新增文件
- 登录/注册页面和组件
- Onboarding 页面和组件
- 智能体列表页面
- 智能体 API 端点
- 智能体 Prompt 设计

## 数据库迁移
- onboarding_fix 迁移(users.has_completed_onboarding)

## 测试
- 登录/注册:100% 通过
- Onboarding:85% 通过
- 智能体功能:85% 通过

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-12 14:08:12 +08:00

192 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useState } from 'react';
import { Plus, X, Sparkles, Loader2 } from 'lucide-react';
import { AgentList } from '@/components/agents/AgentList';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { Textarea } from '@/components/ui/Textarea';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/Card';
import type { Dictionary } from '@/get-dictionary';
interface AgentsPageClientProps {
locale: string;
isPremium: boolean;
dict: Dictionary;
}
export function AgentsPageClient({ locale, isPremium, dict }: AgentsPageClientProps) {
const [showCreateModal, setShowCreateModal] = useState(false);
const [creating, setCreating] = useState(false);
const [formData, setFormData] = useState({
name: '',
personality: '',
background: '',
});
const [error, setError] = useState<string | null>(null);
const handleCreateAgent = async () => {
if (!formData.name.trim()) {
setError('请输入智能体名称');
return;
}
setCreating(true);
setError(null);
try {
const response = await fetch('/api/agents', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: formData.name,
background: formData.background,
personality: {
traits: formData.personality.split(',').map((t) => t.trim()).filter(Boolean),
tone: 'warm',
},
}),
});
if (!response.ok) {
const data = await response.json();
throw new Error(data.error || '创建失败');
}
// Close modal and refresh
setShowCreateModal(false);
setFormData({ name: '', personality: '', background: '' });
// The AgentList will auto-refresh
} catch (err) {
setError(err instanceof Error ? err.message : '创建失败');
} finally {
setCreating(false);
}
};
return (
<>
<AgentList
locale={locale}
isPremium={isPremium}
onCreateAgent={() => setShowCreateModal(true)}
/>
{/* Create Modal */}
{showCreateModal && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-2xl w-full max-w-lg max-h-[90vh] overflow-y-auto">
{/* Modal header */}
<div className="flex items-center justify-between p-6 border-b border-gray-100">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-gradient-to-r from-purple-500 to-pink-500 flex items-center justify-center">
<Sparkles className="w-5 h-5 text-white" />
</div>
<div>
<h3 className="text-lg font-semibold text-gray-900">
</h3>
<p className="text-sm text-gray-500">
</p>
</div>
</div>
<button
onClick={() => setShowCreateModal(false)}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
>
<X className="w-5 h-5 text-gray-400" />
</button>
</div>
{/* Modal body */}
<div className="p-6 space-y-4">
{/* Name input */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
<span className="text-red-500">*</span>
</label>
<Input
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="例如:十年后的自己"
maxLength={50}
/>
</div>
{/* Background textarea */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
</label>
<Textarea
value={formData.background}
onChange={(e) => setFormData({ ...formData, background: e.target.value })}
placeholder="描述这个智能体的背景故事..."
rows={3}
maxLength={500}
/>
</div>
{/* Personality input */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
</label>
<Input
value={formData.personality}
onChange={(e) => setFormData({ ...formData, personality: e.target.value })}
placeholder="例如:温柔、乐观、善解人意(用逗号分隔)"
maxLength={100}
/>
<p className="text-xs text-gray-400 mt-1">
</p>
</div>
{/* Error message */}
{error && (
<div className="bg-red-50 border border-red-200 rounded-lg p-3 text-sm text-red-600">
{error}
</div>
)}
{/* Premium notice */}
{!isPremium && (
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4">
<p className="text-sm text-amber-800">
<strong></strong>
1
3
</p>
</div>
)}
</div>
{/* Modal footer */}
<div className="flex items-center justify-end gap-3 p-6 border-t border-gray-100">
<Button
variant="outline"
onClick={() => setShowCreateModal(false)}
disabled={creating}
>
</Button>
<Button
variant="primary"
onClick={handleCreateAgent}
isLoading={creating}
disabled={!formData.name.trim() || creating}
>
{creating ? '创建中...' : '创建智能体'}
</Button>
</div>
</div>
</div>
)}
</>
);
}