"use client" import { useEffect, useState } from "react" import { X, Maximize2, Minimize2, Sparkles, Code2, Loader2 } from "lucide-react" import { toast } from "sonner" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { cn } from "@/lib/utils" 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 { Badge } from "@/components/ui/badge" import { Switch } from "@/components/ui/switch" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import type { Difficulty, KnowledgeItem, KnowledgeItemInput, Mastery } from "@/lib/types" import { CATEGORIES, DIFFICULTIES, DIFFICULTY_META, MASTERY_LEVELS, MASTERY_META, emptyItemInput, } from "@/lib/types" import { useAISettings } from "@/components/ai/ai-settings-provider" import { requestOpenAICompatible } from "@/lib/ai/client" import { validateForRequest } from "@/lib/ai/settings" import { buildCodeGenPrompt, buildOptimizePrompt } from "@/lib/ai/prompts" // 通过对象定义 value 与展示文案,下方循环渲染,便于维护 const CATEGORY_OPTIONS = CATEGORIES.map((c) => ({ value: c, label: c })) const DIFFICULTY_OPTIONS = DIFFICULTIES.map((d) => ({ value: d, label: DIFFICULTY_META[d].label })) const MASTERY_OPTIONS = MASTERY_LEVELS.map((m) => ({ value: m, label: MASTERY_META[m].label })) export function ItemDialog({ open, onOpenChange, initial, onSubmit, }: { open: boolean onOpenChange: (open: boolean) => void initial: KnowledgeItem | null onSubmit: (input: KnowledgeItemInput) => void | Promise }) { const { settings } = useAISettings() const [form, setForm] = useState(emptyItemInput()) const [tagInput, setTagInput] = useState("") const [submitting, setSubmitting] = useState(false) const [fullscreen, setFullscreen] = useState(false) const [optimizing, setOptimizing] = useState(false) const [genCode, setGenCode] = useState(false) useEffect(() => { if (!open) return if (initial) { setForm({ title: initial.title, summary: initial.summary ?? "", content: initial.content ?? "", code_snippet: initial.code_snippet ?? "", tags: initial.tags ?? [], category: initial.category, difficulty: initial.difficulty, mastery: initial.mastery, is_favorite: initial.is_favorite, source_url: initial.source_url ?? "", notes: initial.notes ?? "", }) } else { setForm(emptyItemInput()) } setTagInput("") }, [open, initial]) function update(key: K, value: KnowledgeItemInput[K]) { setForm((f) => ({ ...f, [key]: value })) } function addTag() { const t = tagInput.trim().replace(/^#/, "") if (t && !form.tags.includes(t)) update("tags", [...form.tags, t]) setTagInput("") } async function submit() { if (!form.title.trim()) return setSubmitting(true) await onSubmit({ ...form, title: form.title.trim() }) setSubmitting(false) } async function handleOptimize() { const err = validateForRequest(settings) if (err) return toast.error(err) if (!form.title.trim()) return toast.error("请先填写标题") setOptimizing(true) try { const content = await requestOpenAICompatible({ baseUrl: settings.baseUrl, apiKey: settings.apiKey, model: settings.model, customPrompt: buildOptimizePrompt({ title: form.title, summary: form.summary, content: form.content }), messages: [], temperature: settings.temperature, maxTokens: settings.maxTokens, }) const cleaned = content.replace(/```(?:json)?/gi, "").trim() try { const parsed = JSON.parse(cleaned) setForm((f) => ({ ...f, summary: typeof parsed.summary === "string" ? parsed.summary : f.summary, content: typeof parsed.content === "string" ? parsed.content : f.content, })) toast.success("已用 AI 优化内容") } catch { // not JSON: treat whole thing as content setForm((f) => ({ ...f, content: cleaned })) toast.success("已用 AI 优化内容") } } catch (e) { toast.error((e as Error).message) } finally { setOptimizing(false) } } async function handleGenerateCode() { const err = validateForRequest(settings) if (err) return toast.error(err) if (!form.title.trim()) return toast.error("请先填写标题") setGenCode(true) try { const content = await requestOpenAICompatible({ baseUrl: settings.baseUrl, apiKey: settings.apiKey, model: settings.model, customPrompt: buildCodeGenPrompt({ title: form.title, summary: form.summary, content: form.content }), messages: [], temperature: settings.temperature, maxTokens: settings.maxTokens, }) const code = content.replace(/```[a-z]*\n?/gi, "").replace(/```$/g, "").trim() setForm((f) => ({ ...f, code_snippet: code })) toast.success("已生成代码示例") } catch (e) { toast.error((e as Error).message) } finally { setGenCode(false) } } return ( {initial ? "编辑知识点" : "新增知识点"} 记录前端核心概念、代码片段与个人笔记。
update("title", e.target.value)} placeholder="例如:事件循环 Event Loop" />
update("summary", e.target.value)} placeholder="简短描述这个知识点" />