import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group.tsx"; import { Label } from "@/components/ui/label.tsx"; import { ChargeProps, chargeTypes, defaultChargeType, nonBilling, timesBilling, tokenBilling, } from "@/admin/charge.ts"; import { useTranslation } from "react-i18next"; import { Input } from "@/components/ui/input.tsx"; import { useMemo, useReducer, useState } from "react"; import { Button } from "@/components/ui/button.tsx"; import { AlertCircle, Cloud, DownloadCloud, Eraser, EyeOff, Minus, PencilLine, Plus, RotateCw, Search, Settings2, Trash, UploadCloud, } from "lucide-react"; import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu.tsx"; import { Command, CommandInput, CommandItem, CommandList, } from "@/components/ui/command.tsx"; import { channelModels } from "@/admin/channel.ts"; import { toastState } from "@/admin/utils.ts"; import { Switch } from "@/components/ui/switch.tsx"; import { NumberInput } from "@/components/ui/number-input.tsx"; import { Table, TableBody, TableCell, TableHeader, TableRow, } from "@/components/ui/table.tsx"; import OperationAction from "@/components/OperationAction.tsx"; import { Badge } from "@/components/ui/badge.tsx"; import { useToast } from "@/components/ui/use-toast"; import { deleteCharge, listCharge, setCharge } from "@/admin/api/charge.ts"; import { useEffectAsync } from "@/utils/hook.ts"; import { cn } from "@/components/ui/lib/utils.ts"; import { allModels } from "@/conf.ts"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert.tsx"; import Tips from "@/components/Tips.tsx"; const initialState: ChargeProps = { id: -1, type: defaultChargeType, models: [], anonymous: false, input: 0, output: 0, }; function reducer(state: ChargeProps, action: any): ChargeProps { switch (action.type) { case "set": return { ...action.payload }; case "set-models": return { ...state, models: action.payload }; case "add-model": const model = action.payload.trim(); if (model.length === 0 || state.models.includes(model)) return state; return { ...state, models: [...state.models, model] }; case "remove-model": return { ...state, models: state.models.filter((model) => model !== action.payload), }; case "set-type": return { ...state, type: action.payload }; case "set-anonymous": return { ...state, anonymous: action.payload }; case "set-input": return { ...state, input: action.payload }; case "set-output": return { ...state, output: action.payload }; case "clear": return initialState; case "clear-param": return { ...initialState, id: state.id }; default: return state; } } function preflight(state: ChargeProps): ChargeProps { state.models = state.models .map((model) => model.trim()) .filter((model) => model.length > 0); switch (state.type) { case nonBilling: state.input = 0; state.output = 0; break; case timesBilling: state.input = 0; state.anonymous = false; break; case tokenBilling: state.anonymous = false; break; } if (state.input < 0) state.input = 0; if (state.output < 0) state.output = 0; return state; } type ChargeAlertProps = { models: string[]; }; function ChargeAlert({ models }: ChargeAlertProps) { const { t } = useTranslation(); return ( models.length > 0 && (

{t("admin.charge.unused-model")}

{models.map((model, index) => (
{model}
))}
) ); } type ChargeEditorProps = { form: ChargeProps; dispatch: (action: any) => void; onRefresh: () => void; usedModels: string[]; }; function ChargeEditor({ form, dispatch, onRefresh, usedModels, }: ChargeEditorProps) { const { t } = useTranslation(); const { toast } = useToast(); const [model, setModel] = useState(""); const unusedModels = useMemo(() => { return channelModels.filter( (model) => !form.models.includes(model) && !usedModels.includes(model) && model.trim() !== "", ); }, [form.models, usedModels]); const disabled = useMemo(() => { return form.models.length === 0; }, [form.models]); const [loading, setLoading] = useState(false); async function post() { const resp = await setCharge(preflight({ ...form })); toastState(toast, t, resp, true); if (resp.status) clear(); onRefresh(); } function clear() { dispatch({ type: "clear" }); setModel(""); } return (
dispatch({ type: "set-type", payload: value }) } className={`flex flex-row gap-5 whitespace-nowrap flex-wrap`} > {chargeTypes.map((chargeType, index) => (
))}
setModel(e.target.value)} placeholder={t("admin.channels.model")} onKeyDown={(e) => { if (e.key === "Enter") { dispatch({ type: "add-model", payload: model }); setModel(""); } }} /> {unusedModels.map((model, idx) => ( dispatch({ type: "add-model", payload: value }) } className={`px-2`} > {model} ))}
{form.models.map((model, index) => (
))}
{form.type === nonBilling && (
dispatch({ type: "set-anonymous", payload: checked }) } />
)} {form.type === timesBilling && (
dispatch({ type: "set-output", payload: value }) } acceptNegative={false} className={`w-20`} min={0} max={99999} />
)} {form.type === tokenBilling && (
dispatch({ type: "set-input", payload: value }) } acceptNegative={false} className={`w-20`} min={0} max={99999} />
dispatch({ type: "set-output", payload: value }) } acceptNegative={false} className={`w-20`} min={0} max={99999} />
)}
ID {form.id === -1 ? ( ) : ( {form.id} )}
); } type ChargeTableProps = { data: ChargeProps[]; dispatch: (action: any) => void; onRefresh: () => void; loading: boolean; }; function ChargeTable({ data, dispatch, onRefresh, loading }: ChargeTableProps) { const { t } = useTranslation(); const { toast } = useToast(); return (
{t("admin.charge.id")} {t("admin.charge.type")} {t("admin.charge.model")} {t("admin.charge.input")} {t("admin.charge.output")} {t("admin.charge.support-anonymous")} {t("admin.charge.action")} {data.map((charge, idx) => ( {charge.id} {charge.type.split("-")[0]}
{charge.models.join("\n")}
{charge.input === 0 ? 0 : charge.input.toFixed(3)} {charge.output === 0 ? 0 : charge.output.toFixed(3)} {t(String(charge.anonymous))}
{ const props: ChargeProps = { ...charge }; dispatch({ type: "set", payload: props }); }} > { const resp = await deleteCharge(charge.id); toastState(toast, t, resp, true); onRefresh(); }} >
))}
); } function ChargeWidget() { const { t } = useTranslation(); const { toast } = useToast(); const [data, setData] = useState([]); const [form, dispatch] = useReducer(reducer, initialState); const [loading, setLoading] = useState(false); const usedModels = useMemo((): string[] => { return data.flatMap((charge) => charge.models); }, [data]); const unusedModels = useMemo(() => { if (loading) return []; return allModels.filter( (model) => !usedModels.includes(model) && model.trim() !== "", ); }, [loading, allModels, usedModels]); async function refresh() { setLoading(true); const resp = await listCharge(); setLoading(false); toastState(toast, t, resp); setData(resp.data); } useEffectAsync(refresh, []); return (
); } export default ChargeWidget;