feat: i18n 翻译覆盖 + DB 动态数据 + 模型选择器 API
This commit is contained in:
@@ -14,25 +14,63 @@ interface Message {
|
||||
|
||||
const AVAILABLE_TOOLS_DESC = `可用工具列表(需要执行操作时,返回 JSON:{"tool":"工具名","params":{...},"description":"简述"}):
|
||||
|
||||
- **get-dashboard**: 获取仪表盘概览数据(用户数、课程数、内容数、提示词数、订单数)
|
||||
- **list-users**: 列出用户,可按关键词搜索(参数: search, page, pageSize)
|
||||
- **get-user**: 查看用户详情(参数: id)
|
||||
- **update-user-status**: 修改用户状态(参数: id, status: ACTIVE|INACTIVE|BANNED)
|
||||
- **list-orders**: 查看最近订单列表
|
||||
- **get-analytics-overview**: 获取数据分析概览(用户增长、收入、趋势)
|
||||
- **list-comments**: 查看评论(参数: status: PENDING_REVIEW|PUBLISHED|REJECTED)
|
||||
- **approve-comment**: 通过评论(参数: id)
|
||||
- **reject-comment**: 拒绝评论(参数: id, reason?)
|
||||
- **list-banners**: 查看所有Banner
|
||||
- **list-notifications**: 查看系统通知
|
||||
- **list-config**: 查看系统配置
|
||||
- **list-roles**: 查看管理角色
|
||||
- **list-admins**: 查看管理员
|
||||
- **get-enterprise-orgs**: 查看企业版组织
|
||||
- **toggle-course-status**: 切换课程上下架(参数: id)
|
||||
- **toggle-content-status**: 切换内容上下架(参数: id)
|
||||
- **toggle-prompt-status**: 切换提示词上下架(参数: id)
|
||||
- **navigate**: 跳转到某个管理页面(参数: path — 如 /admin/users, /admin/orders, /admin/analytics, /admin/enterprise, /admin/operations/banners, /admin/operations/notifications, /admin/settings/roles, /admin/settings/config, /admin/comments, /admin/courses, /admin/prompts, /admin/contents, /admin/tools)
|
||||
=== 查看 ===
|
||||
- **get-dashboard**: 仪表盘概览
|
||||
- **get-analytics-overview**: 数据分析概览
|
||||
- **list-users**: 用户列表(search?, page?, pageSize?)
|
||||
- **get-user**: 用户详情(id)
|
||||
- **list-orders**: 订单列表
|
||||
- **list-courses**: 课程列表 / **get-course**: 课程详情(id)
|
||||
- **list-contents**: 内容列表
|
||||
- **list-prompts**: 提示词列表
|
||||
- **list-comments**: 评论列表(status?)
|
||||
- **list-banners**: Banner列表
|
||||
- **list-notifications**: 通知列表
|
||||
- **list-config**: 配置列表
|
||||
- **list-roles**: 角色列表
|
||||
- **list-admins**: 管理员列表
|
||||
- **get-enterprise-orgs**: 企业组织列表
|
||||
|
||||
=== 创建 ===
|
||||
- **create-course**: 创建课程(title必填, description?, price?, isFree?, status?)
|
||||
- **create-content**: 创建内容(title必填, summary?, content?, tags?, status?)
|
||||
- **create-prompt**: 创建提示词(title必填, content必填, description?, tags?, status?)
|
||||
- **create-banner**: 创建Banner(title必填, image必填, link?, position?, sortOrder?)
|
||||
- **create-notification**: 创建通知(title必填, content?, link?, userId?)
|
||||
- **create-role**: 创建角色(name必填, description?, permissions?)
|
||||
- **create-admin**: 创建管理员(username必填, password必填, nickname?, roleId?)
|
||||
- **create-organization**: 创建企业组织(name必填, description?, contactName?, contactPhone?)
|
||||
|
||||
=== 修改 ===
|
||||
- **update-user**: 修改用户(id, nickname?, phone?, email?, status?, memberPlan?, sandboxDaily?)
|
||||
- **update-user-status**: 修改用户状态(id, status: ACTIVE|INACTIVE|BANNED)
|
||||
- **update-course**: 修改课程(id, title?, description?, price?, isFree?, status?)
|
||||
- **update-content**: 修改内容(id, title?, summary?, content?, tags?, status?)
|
||||
- **update-prompt**: 修改提示词(id, title?, content?, description?, tags?, status?)
|
||||
- **update-banner**: 修改Banner(id, title?, image?, link?, sortOrder?)
|
||||
- **update-config**: 修改配置(key必填, value必填, category?, description?)
|
||||
- **update-role**: 修改角色(id, name?, description?, permissions?)
|
||||
- **update-admin**: 修改管理员(id, nickname?, roleId?, password?)
|
||||
- **update-organization**: 修改企业组织(id, name?, description?, contactName?, contactPhone?)
|
||||
- **toggle-course-status**: 切换课程上下架(id)
|
||||
- **toggle-content-status**: 切换内容上下架(id)
|
||||
- **toggle-prompt-status**: 切换提示词上下架(id)
|
||||
|
||||
=== 删除 ===
|
||||
- **delete-user**: 删除用户(id)
|
||||
- **delete-course**: 删除课程(id)
|
||||
- **delete-content**: 删除内容(id)
|
||||
- **delete-prompt**: 删除提示词(id)
|
||||
- **delete-banner**: 删除Banner(id)
|
||||
- **delete-notification**: 删除通知(id)
|
||||
- **delete-role**: 禁用角色(id)
|
||||
- **delete-admin**: 禁用管理员(id)
|
||||
- **delete-organization**: 删除组织(id)
|
||||
- **add-org-member**: 添加组织成员(organizationId, userId, role?)
|
||||
- **remove-org-member**: 移除组织成员(organizationId, userId)
|
||||
|
||||
=== 导航 ===
|
||||
- **navigate**: 跳转到页面(path: /admin/users等)
|
||||
|
||||
当用户请求执行操作时,先调用对应工具。工具执行完毕后会用自然语言总结结果。`;
|
||||
|
||||
|
||||
@@ -1,35 +1,37 @@
|
||||
import Link from 'next/link';
|
||||
import { useT } from '@/i18n';
|
||||
|
||||
export function Footer() {
|
||||
const t = useT();
|
||||
return (
|
||||
<footer className="border-t border-border bg-muted/30">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 md:py-16">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
|
||||
<div className="col-span-2 md:col-span-1">
|
||||
<h3 className="text-lg font-bold bg-gradient-to-r from-brand-600 to-brand-400 bg-clip-text text-transparent mb-4">宇之然 AI</h3>
|
||||
<p className="text-sm text-muted-foreground">让每个人都能用好 AI</p>
|
||||
<p className="text-sm text-muted-foreground">{t.footer.tagline}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold mb-3">探索</h4>
|
||||
<h4 className="text-sm font-semibold mb-3">{t.footer.explore}</h4>
|
||||
<ul className="space-y-2.5">
|
||||
<li><Link href="/courses" className="text-sm text-muted-foreground hover:text-foreground transition-colors">专题</Link></li>
|
||||
<li><Link href="/sandbox" className="text-sm text-muted-foreground hover:text-foreground transition-colors">AI 沙盒</Link></li>
|
||||
<li><Link href="/prompts" className="text-sm text-muted-foreground hover:text-foreground transition-colors">提示词库</Link></li>
|
||||
<li><Link href="/models" className="text-sm text-muted-foreground hover:text-foreground transition-colors">模型百科</Link></li>
|
||||
<li><Link href="/tools" className="text-sm text-muted-foreground hover:text-foreground transition-colors">AI 工具</Link></li>
|
||||
<li><Link href="/courses" className="text-sm text-muted-foreground hover:text-foreground transition-colors">{t.nav.courses}</Link></li>
|
||||
<li><Link href="/sandbox" className="text-sm text-muted-foreground hover:text-foreground transition-colors">{t.nav.sandbox}</Link></li>
|
||||
<li><Link href="/prompts" className="text-sm text-muted-foreground hover:text-foreground transition-colors">{t.nav.prompts}</Link></li>
|
||||
<li><Link href="/models" className="text-sm text-muted-foreground hover:text-foreground transition-colors">{t.footer.models}</Link></li>
|
||||
<li><Link href="/tools" className="text-sm text-muted-foreground hover:text-foreground transition-colors">{t.footer.aiTools}</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold mb-3">关于</h4>
|
||||
<h4 className="text-sm font-semibold mb-3">{t.footer.about}</h4>
|
||||
<ul className="space-y-2.5">
|
||||
<li><Link href="/about" className="text-sm text-muted-foreground hover:text-foreground transition-colors">关于我们</Link></li>
|
||||
<li><Link href="/privacy" className="text-sm text-muted-foreground hover:text-foreground transition-colors">隐私政策</Link></li>
|
||||
<li><Link href="/terms" className="text-sm text-muted-foreground hover:text-foreground transition-colors">服务协议</Link></li>
|
||||
<li><Link href="/ai-agreement" className="text-sm text-muted-foreground hover:text-foreground transition-colors">AI 服务协议</Link></li>
|
||||
<li><Link href="/about" className="text-sm text-muted-foreground hover:text-foreground transition-colors">{t.footer.aboutUs}</Link></li>
|
||||
<li><Link href="/privacy" className="text-sm text-muted-foreground hover:text-foreground transition-colors">{t.footer.privacy}</Link></li>
|
||||
<li><Link href="/terms" className="text-sm text-muted-foreground hover:text-foreground transition-colors">{t.footer.terms}</Link></li>
|
||||
<li><Link href="/ai-agreement" className="text-sm text-muted-foreground hover:text-foreground transition-colors">{t.footer.aiAgreement}</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold mb-3">联系方式</h4>
|
||||
<h4 className="text-sm font-semibold mb-3">{t.footer.contact}</h4>
|
||||
<ul className="space-y-2.5">
|
||||
<li className="text-sm text-muted-foreground">邮箱:contact@yuzhiran.com</li>
|
||||
<li className="text-sm text-muted-foreground">北京宇之然科技中心</li>
|
||||
@@ -38,7 +40,7 @@ export function Footer() {
|
||||
</div>
|
||||
<div className="mt-10 pt-8 border-t border-border">
|
||||
<div className="flex flex-col md:flex-row items-center justify-between gap-2 text-xs text-muted-foreground">
|
||||
<p>© {new Date().getFullYear()} 北京宇之然科技中心 版权所有</p>
|
||||
<p>{t.footer.copyright.replace('{year}', String(new Date().getFullYear()))}</p>
|
||||
<p>
|
||||
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer" className="hover:text-foreground transition-colors">
|
||||
ICP 备案号:京ICP备XXXXXXXX号
|
||||
|
||||
@@ -9,19 +9,7 @@ import { ThemeToggle } from '@/components/ui/theme-toggle';
|
||||
import { Search, Menu, X, Bell, Languages } from 'lucide-react';
|
||||
import { apiFetch } from '@/lib/auth';
|
||||
import { useAuth } from '@/lib/auth-context';
|
||||
import { useLang } from '@/i18n';
|
||||
|
||||
const navItems = [
|
||||
{ href: '/', label: '首页' },
|
||||
{ href: '/courses', label: '专题' },
|
||||
{ href: '/sandbox', label: '沙盒' },
|
||||
{ href: '/skills', label: '技能' },
|
||||
{ href: '/models', label: '模型' },
|
||||
{ href: '/prompts', label: '提示词' },
|
||||
{ href: '/contents', label: '文章' },
|
||||
{ href: '/tools', label: 'AI 工具' },
|
||||
{ href: '/community', label: '社区' },
|
||||
];
|
||||
import { useLang, useT } from '@/i18n';
|
||||
|
||||
function NotificationBellComponent() {
|
||||
const [count, setCount] = useState(0);
|
||||
@@ -63,6 +51,19 @@ export function Header() {
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const { isLoggedIn, logout } = useAuth();
|
||||
const { lang, setLang } = useLang();
|
||||
const t = useT();
|
||||
|
||||
const navItems = [
|
||||
{ href: '/', label: t.nav.home },
|
||||
{ href: '/courses', label: t.nav.courses },
|
||||
{ href: '/sandbox', label: t.nav.sandbox },
|
||||
{ href: '/skills', label: t.discover?.skills || '技能' },
|
||||
{ href: '/models', label: t.discover?.models || '模型' },
|
||||
{ href: '/prompts', label: t.nav.prompts },
|
||||
{ href: '/contents', label: t.discover?.articles || '文章' },
|
||||
{ href: '/tools', label: t.nav.tools },
|
||||
{ href: '/community', label: t.nav.community },
|
||||
];
|
||||
|
||||
function isActive(href: string) {
|
||||
if (href === '/') return pathname === '/';
|
||||
@@ -112,7 +113,7 @@ export function Header() {
|
||||
<Input
|
||||
name="q"
|
||||
type="text"
|
||||
placeholder="搜索..."
|
||||
placeholder={t.common.search}
|
||||
className="w-36 lg:w-48 pl-8 h-9 text-sm bg-muted/50 border-0 focus-visible:ring-1"
|
||||
/>
|
||||
</form>
|
||||
@@ -128,23 +129,23 @@ export function Header() {
|
||||
<>
|
||||
<NotificationBellComponent />
|
||||
<Button variant="default" size="sm" asChild>
|
||||
<Link href="/dashboard">控制台</Link>
|
||||
<Link href="/dashboard">{t.nav?.my || '控制台'}</Link>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => { logout(); window.location.href = '/'; }}
|
||||
>
|
||||
退出
|
||||
{t.common.logout}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
<Link href="/auth">登录</Link>
|
||||
<Link href="/auth">{t.common.login}</Link>
|
||||
</Button>
|
||||
<Button variant="default" size="sm" asChild>
|
||||
<Link href="/auth?tab=register">注册</Link>
|
||||
<Link href="/auth?tab=register">{t.common.register}</Link>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
@@ -187,19 +188,19 @@ export function Header() {
|
||||
{isLoggedIn ? (
|
||||
<>
|
||||
<Button className="w-full" size="sm" asChild>
|
||||
<Link href="/dashboard" onClick={() => setMobileOpen(false)}>控制台</Link>
|
||||
<Link href="/dashboard" onClick={() => setMobileOpen(false)}>{t.nav?.my || '控制台'}</Link>
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" className="w-full" onClick={() => { logout(); window.location.href = '/'; }}>
|
||||
退出
|
||||
{t.common.logout}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button variant="outline" className="w-full" size="sm" asChild>
|
||||
<Link href="/auth" onClick={() => setMobileOpen(false)}>登录</Link>
|
||||
<Link href="/auth" onClick={() => setMobileOpen(false)}>{t.common.login}</Link>
|
||||
</Button>
|
||||
<Button className="w-full" size="sm" asChild>
|
||||
<Link href="/auth?tab=register" onClick={() => setMobileOpen(false)}>注册</Link>
|
||||
<Link href="/auth?tab=register" onClick={() => setMobileOpen(false)}>{t.common.register}</Link>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { AVAILABLE_MODELS, type ModelOption } from '@/lib/models';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { AVAILABLE_MODELS as FALLBACK_MODELS, type ModelOption } from '@/lib/models';
|
||||
import { API_BASE } from '@/lib/config';
|
||||
|
||||
interface ModelSelectorProps {
|
||||
value: string;
|
||||
@@ -11,7 +12,25 @@ interface ModelSelectorProps {
|
||||
|
||||
export function ModelSelector({ value, onChange, className = '' }: ModelSelectorProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const selected = AVAILABLE_MODELS.find(m => m.id === value) || AVAILABLE_MODELS[0];
|
||||
const [models, setModels] = useState<ModelOption[]>(FALLBACK_MODELS);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`${API_BASE}/public/models`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.items?.length) {
|
||||
setModels(data.items.map((m: any) => ({
|
||||
id: m.id,
|
||||
label: m.name,
|
||||
provider: m.provider,
|
||||
desc: m.description || '',
|
||||
})));
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
const selected = models.find(m => m.id === value) || models[0];
|
||||
|
||||
return (
|
||||
<div className={`relative ${className}`}>
|
||||
@@ -27,7 +46,7 @@ export function ModelSelector({ value, onChange, className = '' }: ModelSelector
|
||||
<>
|
||||
<div className="fixed inset-0 z-10" onClick={() => setOpen(false)} />
|
||||
<div className="absolute right-0 top-full mt-1 z-20 w-64 bg-card border border-border rounded-xl shadow-lg overflow-hidden">
|
||||
{AVAILABLE_MODELS.map(m => (
|
||||
{models.map(m => (
|
||||
<button key={m.id} onClick={() => { onChange(m.id); setOpen(false); }}
|
||||
className={`w-full text-left px-4 py-3 hover:bg-accent transition-colors flex items-center gap-3 ${m.id === value ? 'bg-accent' : ''}`}>
|
||||
<span className={`w-2 h-2 rounded-full shrink-0 ${m.id === value ? 'bg-brand-600' : 'bg-muted-foreground/30'}`} />
|
||||
|
||||
Reference in New Issue
Block a user