Initial commit: Pixel AI comic/video creation platform

- FastAPI backend with SQLModel, Alembic migrations, AgentScope agents
- Next.js 15 frontend with React 19, Tailwind, Zustand, React Flow
- Multi-provider AI system (DashScope, Kling, MiniMax, Volcengine, OpenAI, etc.)
- All HTTP clients migrated from sync requests to async httpx
- Admin-managed API keys via environment variables
- SSRF vulnerability fixed in ensure_url()
This commit is contained in:
张鹏
2026-04-29 01:20:12 +08:00
commit f9f4560459
808 changed files with 151724 additions and 0 deletions

View File

@@ -0,0 +1,336 @@
"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import {
PageContainer,
PageHeader,
PageCard,
} from "@/components/admin/common";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Skeleton } from "@/components/ui/skeleton";
import {
Save,
LayoutGrid,
Settings,
Users,
Mail,
Database,
Shield,
} from "lucide-react";
import {
useAdminSettings,
useUpdateAdminSettings,
SystemSettings,
} from "@/lib/hooks/admin/useAdminSettings";
export default function AdminSettingsPage() {
const { data: settings, isLoading } = useAdminSettings();
const updateMutation = useUpdateAdminSettings();
const [formData, setFormData] = useState<Partial<SystemSettings>>({});
useEffect(() => {
if (settings) {
setFormData(settings);
}
}, [settings]);
const handleSave = () => {
updateMutation.mutate(formData);
};
const handleChange = (field: keyof SystemSettings, value: unknown) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
if (isLoading) {
return (
<PageContainer>
<div className="flex items-center justify-between">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-10 w-24" />
</div>
<div className="space-y-4">
<Skeleton className="h-96" />
</div>
</PageContainer>
);
}
return (
<PageContainer>
<PageHeader
title="系统设置"
description="管理系统的全局配置"
actions={
<div className="flex items-center gap-2">
<Button
onClick={handleSave}
disabled={updateMutation.isPending}
className="gap-2"
>
<Save className="h-4 w-4" />
{updateMutation.isPending ? "保存中..." : "保存设置"}
</Button>
<Button variant="outline" size="icon" asChild>
<Link href="/admin/dashboard">
<LayoutGrid className="h-4 w-4" />
</Link>
</Button>
</div>
}
/>
<Tabs defaultValue="general" className="space-y-6">
<TabsList className="grid w-full grid-cols-4 lg:w-fit">
<TabsTrigger value="general" className="gap-2">
<Settings className="h-4 w-4" />
</TabsTrigger>
<TabsTrigger value="users" className="gap-2">
<Users className="h-4 w-4" />
</TabsTrigger>
<TabsTrigger value="email" className="gap-2">
<Mail className="h-4 w-4" />
</TabsTrigger>
<TabsTrigger value="system" className="gap-2">
<Database className="h-4 w-4" />
</TabsTrigger>
</TabsList>
<TabsContent value="general" className="space-y-6">
<PageCard
title="基本设置"
icon={<Settings className="h-5 w-5" />}
description="配置系统的基本信息"
>
<div className="space-y-6">
<div className="space-y-2">
<Label htmlFor="site_name"></Label>
<Input
id="site_name"
value={formData.site_name || ""}
onChange={(e) => handleChange("site_name", e.target.value)}
placeholder="Pixel Studio"
/>
</div>
<div className="space-y-2">
<Label htmlFor="site_description"></Label>
<Textarea
id="site_description"
value={formData.site_description || ""}
onChange={(e) =>
handleChange("site_description", e.target.value)
}
placeholder="AI Comic and Video Creation Platform"
rows={3}
/>
</div>
</div>
</PageCard>
</TabsContent>
<TabsContent value="users" className="space-y-6">
<PageCard
title="用户设置"
icon={<Users className="h-5 w-5" />}
description="配置用户注册和权限相关设置"
>
<div className="space-y-6">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label></Label>
<p className="text-sm text-muted-foreground">
</p>
</div>
<Switch
checked={formData.allow_registration ?? true}
onCheckedChange={(checked) =>
handleChange("allow_registration", checked)
}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label></Label>
<p className="text-sm text-muted-foreground">
使
</p>
</div>
<Switch
checked={formData.require_email_verification ?? false}
onCheckedChange={(checked) =>
handleChange("require_email_verification", checked)
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="max_projects">
(0)
</Label>
<Input
id="max_projects"
type="number"
value={formData.max_projects_per_user || 0}
onChange={(e) =>
handleChange(
"max_projects_per_user",
parseInt(e.target.value) || 0
)
}
min={0}
/>
</div>
<div className="space-y-2">
<Label htmlFor="max_storage">
(MB) (0)
</Label>
<Input
id="max_storage"
type="number"
value={formData.max_storage_per_user_mb || 0}
onChange={(e) =>
handleChange(
"max_storage_per_user_mb",
parseInt(e.target.value) || 0
)
}
min={0}
/>
</div>
</div>
</PageCard>
</TabsContent>
<TabsContent value="email" className="space-y-6">
<PageCard
title="邮件设置"
icon={<Mail className="h-5 w-5" />}
description="配置邮件通知相关设置"
>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label></Label>
<p className="text-sm text-muted-foreground">
</p>
</div>
<Switch
checked={formData.email_notifications_enabled ?? true}
onCheckedChange={(checked) =>
handleChange("email_notifications_enabled", checked)
}
/>
</div>
</PageCard>
</TabsContent>
<TabsContent value="system" className="space-y-6">
<PageCard
title="系统维护"
icon={<Shield className="h-5 w-5" />}
description="配置系统维护和清理相关设置"
>
<div className="space-y-6">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label></Label>
<p className="text-sm text-muted-foreground">
访
</p>
</div>
<Switch
checked={formData.maintenance_mode ?? false}
onCheckedChange={(checked) =>
handleChange("maintenance_mode", checked)
}
/>
</div>
{formData.maintenance_mode && (
<div className="space-y-2">
<Label htmlFor="maintenance_message"></Label>
<Textarea
id="maintenance_message"
value={formData.maintenance_message || ""}
onChange={(e) =>
handleChange("maintenance_message", e.target.value)
}
placeholder="系统正在维护中,请稍后再试..."
rows={2}
/>
</div>
)}
<div className="space-y-2">
<Label htmlFor="generation_timeout">
()
</Label>
<Input
id="generation_timeout"
type="number"
value={formData.default_generation_timeout || 300}
onChange={(e) =>
handleChange(
"default_generation_timeout",
parseInt(e.target.value) || 300
)
}
min={60}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label></Label>
<p className="text-sm text-muted-foreground">
</p>
</div>
<Switch
checked={formData.auto_cleanup_enabled ?? true}
onCheckedChange={(checked) =>
handleChange("auto_cleanup_enabled", checked)
}
/>
</div>
{formData.auto_cleanup_enabled && (
<div className="space-y-2">
<Label htmlFor="cleanup_days">
()
</Label>
<Input
id="cleanup_days"
type="number"
value={formData.auto_cleanup_days || 30}
onChange={(e) =>
handleChange(
"auto_cleanup_days",
parseInt(e.target.value) || 30
)
}
min={1}
/>
</div>
)}
</div>
</PageCard>
</TabsContent>
</Tabs>
</PageContainer>
);
}