feat(ai): 统一 AI 配置入口到 /admin/ai-settings
## 新增 - 创建 /admin/ai-settings 统一配置页(AiProviderSettingsCard + AiUsageDashboard) - admin 侧边栏新增"AI 配置"菜单项(权限 AI_CONFIGURE,图标 Sparkles) - 新增 deleteAiProvider 数据访问层(事务删除 + 自动转移默认) - 新增 deleteAiProviderAction Server Action(Zod 校验 + 权限校验) - AiProviderSettingsCard 新增删除按钮(AlertDialog 确认 + destructive 变体) - 新增 i18n 翻译键(delete/deleteConfirm/deleteSuccess 等,zh-CN + en) ## 移除 - 从 /settings 移除 AI 标签页(原 VALID_TABS 含 "ai",现仅 4 标签页) - 从考试页面移除 AI 配置弹窗(Dialog + AiProviderSettingsCard 内嵌) - 从 ai-provider-selector.tsx 移除配置弹窗(managePanel/manageOpen props) - 移除 settings-view.tsx 中 canConfigureAi 逻辑和未使用 import ## 变更 - 考试页面"管理"按钮改为 Link 跳转到 /admin/ai-settings - ai-provider-selector.tsx"管理"按钮改为 Link 跳转到 /admin/ai-settings - exam-form.tsx 移除 providerDialogOpen/providerDialogKey 状态 - 修正架构文档 004 中 Action 命名(getAiProvidersAction → getAiProviderSummaries 等) ## 架构文档同步 - 004 更新 settings 模块章节(V3 标记/修正 Action 名称/新增 deleteAiProvider) - 005 新增 deleteAiProviderAction 节点 + /admin/ai-settings 路由
This commit is contained in:
@@ -4,14 +4,13 @@ import Link from "next/link"
|
||||
import { useRouter, useSearchParams } from "next/navigation"
|
||||
import { Suspense, type ReactNode } from "react"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { User, Palette, Lock, Bell, Sparkles } from "lucide-react"
|
||||
import { User, Palette, Lock, Bell } from "lucide-react"
|
||||
import { signOut } from "next-auth/react"
|
||||
|
||||
import { ThemePreferencesCard } from "@/modules/settings/components/theme-preferences-card"
|
||||
import { ProfileSettingsForm } from "@/modules/settings/components/profile-settings-form"
|
||||
import { PasswordChangeForm } from "@/modules/settings/components/password-change-form"
|
||||
import { NotificationPreferencesForm } from "@/modules/settings/components/notification-preferences-form"
|
||||
import { AiProviderSettingsCard } from "@/modules/settings/components/ai-provider-settings-card"
|
||||
import { SecurityCenterCard } from "@/modules/settings/components/security-center-card"
|
||||
import { SettingsSectionErrorBoundary } from "@/modules/settings/components/settings-section-error-boundary"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
@@ -31,8 +30,6 @@ import {
|
||||
} from "@/shared/components/ui/alert-dialog"
|
||||
import type { UserProfile } from "@/modules/users/data-access"
|
||||
import type { NotificationPreferences } from "@/modules/notifications/types"
|
||||
import { usePermission } from "@/shared/hooks/use-permission"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
|
||||
interface SettingsViewProps {
|
||||
/** 页面副标题描述(i18n 键) */
|
||||
@@ -49,7 +46,7 @@ interface SettingsViewProps {
|
||||
currentUserAgent?: string
|
||||
}
|
||||
|
||||
const VALID_TABS = ["general", "notifications", "appearance", "security", "ai"] as const
|
||||
const VALID_TABS = ["general", "notifications", "appearance", "security"] as const
|
||||
type TabValue = (typeof VALID_TABS)[number]
|
||||
|
||||
function isTabValue(value: string | null): value is TabValue {
|
||||
@@ -95,15 +92,12 @@ function SettingsViewInner({
|
||||
const t = useTranslations("settings")
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const { hasPermission } = usePermission()
|
||||
|
||||
const tabParam = searchParams.get("tab")
|
||||
const canConfigureAi = hasPermission(Permissions.AI_CONFIGURE)
|
||||
|
||||
// 解析 tab 参数,对无权限的 tab(如非管理员的 ai)回退到 general
|
||||
// 解析 tab 参数
|
||||
function resolveTab(value: string | null): TabValue {
|
||||
if (!isTabValue(value)) return "general"
|
||||
if (value === "ai" && !canConfigureAi) return "general"
|
||||
return value
|
||||
}
|
||||
const activeTab: TabValue = resolveTab(tabParam)
|
||||
@@ -151,12 +145,6 @@ function SettingsViewInner({
|
||||
<Lock className="h-4 w-4" />
|
||||
{t("tabs.security")}
|
||||
</TabsTrigger>
|
||||
{canConfigureAi ? (
|
||||
<TabsTrigger value="ai" className="gap-2">
|
||||
<Sparkles className="h-4 w-4" />
|
||||
{t("tabs.ai")}
|
||||
</TabsTrigger>
|
||||
) : null}
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="general" className="mt-6 space-y-6">
|
||||
@@ -223,16 +211,6 @@ function SettingsViewInner({
|
||||
</Suspense>
|
||||
</SettingsSectionErrorBoundary>
|
||||
</TabsContent>
|
||||
|
||||
{canConfigureAi ? (
|
||||
<TabsContent value="ai" className="mt-6 space-y-6">
|
||||
<SettingsSectionErrorBoundary>
|
||||
<Suspense fallback={<SettingsSectionSkeleton />}>
|
||||
<AiProviderSettingsCard />
|
||||
</Suspense>
|
||||
</SettingsSectionErrorBoundary>
|
||||
</TabsContent>
|
||||
) : null}
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user