import { Table, TableBody, TableCell, TableHeader, TableRow, } from "@/components/ui/table.tsx"; import { Badge } from "@/components/ui/badge.tsx"; import { Activity, Check, Plus, RotateCw, Settings2, Trash, X, } from "lucide-react"; import { Button } from "@/components/ui/button.tsx"; import OperationAction from "@/components/OperationAction.tsx"; import { Dispatch, useEffect, useMemo, useState } from "react"; import { Channel, getShortChannelType } from "@/admin/channel.ts"; import { toastState } from "@/api/common.ts"; import { useTranslation } from "react-i18next"; import { useEffectAsync } from "@/utils/hook.ts"; import { activateChannel, deactivateChannel, deleteChannel, listChannel, } from "@/admin/api/channel.ts"; import { useToast } from "@/components/ui/use-toast.ts"; import { cn } from "@/components/ui/lib/utils.ts"; import { getApiModels } from "@/api/v1.ts"; import { getHostName } from "@/utils/base.ts"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog.tsx"; import { Label } from "@/components/ui/label.tsx"; import { Input } from "@/components/ui/input.tsx"; import { DialogClose } from "@radix-ui/react-dialog"; type ChannelTableProps = { display: boolean; dispatch: Dispatch; setId: (id: number) => void; setEnabled: (enabled: boolean) => void; }; type TypeBadgeProps = { type: string; }; function TypeBadge({ type }: TypeBadgeProps) { const content = useMemo(() => getShortChannelType(type), [type]); return ( {content || type} ); } type SyncDialogProps = { dispatch: Dispatch; open: boolean; setOpen: (open: boolean) => void; }; function SyncDialog({ dispatch, open, setOpen }: SyncDialogProps) { const { t } = useTranslation(); const { toast } = useToast(); const [endpoint, setEndpoint] = useState("https://api.openai.com"); const [secret, setSecret] = useState(""); const submit = async (endpoint: string): Promise => { endpoint = endpoint.trim(); endpoint.endsWith("/") && (endpoint = endpoint.slice(0, -1)); const resp = await getApiModels(secret, { endpoint }); toastState(toast, t, resp, true); if (!resp.status) return false; const name = getHostName(endpoint).replace(/\./g, "-"); const data: Channel = { id: -1, name, type: "openai", models: resp.data, priority: 0, weight: 1, retry: 1, secret, endpoint, mapper: "", state: true, group: [], proxy: { proxy: "", proxy_type: 0, username: "", password: "" }, }; dispatch({ type: "set", value: data }); return true; }; return ( <> {t("admin.channels.joint")}
setEndpoint(e.target.value)} placeholder={t("admin.channels.upstream-endpoint-placeholder")} />
setSecret(e.target.value)} placeholder={t("admin.channels.sync-secret-placeholder")} />
); } function ChannelTable({ display, dispatch, setId, setEnabled, }: ChannelTableProps) { const { t } = useTranslation(); const { toast } = useToast(); const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [open, setOpen] = useState(false); const refresh = async () => { setLoading(true); const resp = await listChannel(); setLoading(false); if (!resp.status) toastState(toast, t, resp); else setData(resp.data); }; useEffectAsync(refresh, []); useEffectAsync(refresh, [display]); useEffect(() => { if (display) setId(-1); }, [display]); return ( display && (
{ dispatch(action); setEnabled(true); setId(-1); }} />
{t("admin.channels.id")} {t("admin.channels.name")} {t("admin.channels.type")} {t("admin.channels.priority")} {t("admin.channels.weight")} {t("admin.channels.state")} {t("admin.channels.action")} {(data || []).map((chan, idx) => ( #{chan.id} {chan.name} {chan.priority} {chan.weight} {chan.state ? ( ) : ( )} { setEnabled(true); setId(chan.id); }} > {chan.state ? ( { const resp = await deactivateChannel(chan.id); toastState(toast, t, resp, true); await refresh(); }} > ) : ( { const resp = await activateChannel(chan.id); toastState(toast, t, resp, true); await refresh(); }} > )} { const resp = await deleteChannel(chan.id); toastState(toast, t, resp, true); await refresh(); }} > ))}
) ); } export default ChannelTable;