import { App, Button, Select, Switch, Tag } from "antd"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { Eye, Pencil, Plus, RefreshCw, Trash2 } from "lucide-react"; import { useState } from "react"; import { PromptSourceEditorDrawer } from "./prompt-source-editor-drawer"; import { PromptSourceContentModal } from "./prompt-source-content-modal"; import { fetchPromptSourceStatuses, refreshAllSources, refreshSource } from "@/services/api/prompts"; import { PROMPT_SOURCE_INTERVAL_OPTIONS, usePromptSourceStore } from "@/stores/use-prompt-source-store"; import type { PromptSource } from "@/services/api/prompt-source-presets"; const STATUS_QUERY_KEY = ["prompt-source-statuses"]; export function ConfigPromptSources() { const { message, modal } = App.useApp(); const queryClient = useQueryClient(); const sources = usePromptSourceStore((state) => state.sources); const schedule = usePromptSourceStore((state) => state.schedule); const addSource = usePromptSourceStore((state) => state.addSource); const saveSource = usePromptSourceStore((state) => state.saveSource); const removeSource = usePromptSourceStore((state) => state.removeSource); const toggleSource = usePromptSourceStore((state) => state.toggleSource); const updateSchedule = usePromptSourceStore((state) => state.updateSchedule); const statusQuery = useQuery({ queryKey: STATUS_QUERY_KEY, queryFn: fetchPromptSourceStatuses }); const [editingSource, setEditingSource] = useState(null); const [viewingId, setViewingId] = useState(""); const [refreshingId, setRefreshingId] = useState(""); const [refreshingAll, setRefreshingAll] = useState(false); const viewingSource = sources.find((item) => item.id === viewingId) || null; const invalidatePrompts = async () => { await Promise.all([ queryClient.invalidateQueries({ queryKey: ["prompts"] }), queryClient.invalidateQueries({ queryKey: ["side-panel-prompts"] }), queryClient.invalidateQueries({ queryKey: STATUS_QUERY_KEY }), ]); }; const handleSave = (source: PromptSource) => { saveSource(source); void invalidatePrompts(); }; const handleDelete = (source: PromptSource) => { modal.confirm({ title: `删除「${source.name}」?`, content: "来源配置会被移除,已经加入我的资产的内容不受影响。", okText: "删除", okButtonProps: { danger: true }, cancelText: "取消", onOk: async () => { removeSource(source.id); await invalidatePrompts(); }, }); }; const handleRefreshOne = async (source: PromptSource) => { setRefreshingId(source.id); try { const result = await refreshSource(source.id); await invalidatePrompts(); message.success(`「${source.name}」已更新 ${result.count} 条`); } catch (error) { await queryClient.invalidateQueries({ queryKey: STATUS_QUERY_KEY }); message.error(error instanceof Error ? error.message : "更新失败,已保留旧缓存"); } finally { setRefreshingId(""); } }; const handleRefreshAll = async () => { setRefreshingAll(true); try { const result = await refreshAllSources(); updateSchedule("lastFetchedAt", new Date().toISOString()); await invalidatePrompts(); if (result.failureCount) message.warning(`更新完成:${result.successCount} 个成功,${result.failureCount} 个失败,失败来源已保留旧缓存`); else message.success(`已更新 ${result.successCount} 个来源,共 ${result.total} 条`); } catch (error) { message.error(error instanceof Error ? error.message : "更新失败"); } finally { setRefreshingAll(false); } }; return (
{sources.map((source) => { const status = statusQuery.data?.[source.id]; return (
{ toggleSource(source.id, checked); void invalidatePrompts(); }} />
{source.name} {source.builtIn ? 内置 : null}
{source.homepage || source.url} {status?.count ?? 0} 条 {status?.lastError ? 失败 : status?.lastSuccessAt ? 正常 : 未同步} {status?.lastSuccessAt ? `上次成功 ${formatTime(status.lastSuccessAt)}` : "尚未拉取"}
{!source.builtIn ? : null} {!source.builtIn ? : null}
); })}
定时拉取
拉取周期