"use client" import { useEffect, useState } from "react" import { Eye, EyeOff, Loader2, RotateCcw, Trash2, Zap, CheckCircle2, XCircle } from "lucide-react" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Label } from "@/components/ui/label" import { Switch } from "@/components/ui/switch" import { cn } from "@/lib/utils" import { useAISettings } from "./ai-settings-provider" import { DEFAULT_AI_SETTINGS, type AISettings } from "@/lib/ai/types" import { requestOpenAICompatible } from "@/lib/ai/client" import { validateForRequest } from "@/lib/ai/settings" type TestState = { status: "idle" | "loading" | "ok" | "error"; message: string } export function AISettingsDialog({ open, onOpenChange }: { open: boolean; onOpenChange: (o: boolean) => void }) { const { settings, setSettings } = useAISettings() const [draft, setDraft] = useState(settings) const [showKey, setShowKey] = useState(false) const [test, setTest] = useState({ status: "idle", message: "" }) useEffect(() => { if (open) { setDraft(settings) setTest({ status: "idle", message: "" }) } }, [open, settings]) function update(key: K, value: AISettings[K]) { setDraft((d) => ({ ...d, [key]: value })) } function handleSave() { setSettings(draft) onOpenChange(false) } function handleReset() { setDraft({ ...DEFAULT_AI_SETTINGS }) setTest({ status: "idle", message: "" }) } function handleClearKey() { update("apiKey", "") } async function handleTest() { const err = validateForRequest(draft) if (err) { setTest({ status: "error", message: err }) return } setTest({ status: "loading", message: "正在测试连接..." }) try { const reply = await requestOpenAICompatible({ baseUrl: draft.baseUrl, apiKey: draft.apiKey, model: draft.model, messages: [{ role: "user", content: "ping,请只回复 ok" }], temperature: 0, maxTokens: 16, }) setTest({ status: "ok", message: `连接成功 · 模型回复:${reply.slice(0, 40)}` }) } catch (e) { setTest({ status: "error", message: (e as Error).message }) } } return ( AI 设置 配置 OpenAI-compatible 接口。API Key 仅保存在本地浏览器,不会写入数据库。
update("baseUrl", e.target.value)} placeholder="https://api.openai.com/v1" className="bg-input/60 font-mono text-sm" />
update("apiKey", e.target.value)} placeholder="sk-..." className="bg-input/60 pr-10 font-mono text-sm" />
update("model", e.target.value)} placeholder="gpt-4o-mini / deepseek-chat / qwen-plus" className="bg-input/60 font-mono text-sm" />
update("temperature", Number(e.target.value))} className="bg-input/60 font-mono text-sm" />
update("maxTokens", Number(e.target.value))} className="bg-input/60 font-mono text-sm" />