import { useTranslation } from "react-i18next"; import { Input } from "@/components/ui/input.tsx"; import { ChevronLeft, ChevronRight, Cloud, DownloadCloud, GripVertical, Link, Plus, Search, Trash2, UploadCloud, X, } from "lucide-react"; import React, { useMemo, useState } from "react"; import { isUrl, splitList } from "@/utils/base.ts"; import { Model } from "@/api/types.tsx"; import { useDispatch, useSelector } from "react-redux"; import { addModelList, closeMarket, removeModelList, selectModel, selectModelList, selectSupportModels, setModel, setSupportModels, } from "@/store/chat.ts"; import { Button } from "@/components/ui/button.tsx"; import { levelSelector } from "@/store/subscription.ts"; import { teenagerSelector } from "@/store/package.ts"; import { ToastAction } from "@/components/ui/toast.tsx"; import { selectAuthenticated } from "@/store/auth.ts"; import { useToast } from "@/components/ui/use-toast.ts"; import { docsEndpoint } from "@/conf/env.ts"; import { goAuth } from "@/utils/app.ts"; import { DragDropContext, Droppable, Draggable, DropResult, } from "react-beautiful-dnd"; import { savePreferenceModels } from "@/conf/storage.ts"; import { cn } from "@/components/ui/lib/utils.ts"; import { Badge } from "@/components/ui/badge.tsx"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip.tsx"; import { useMobile } from "@/utils/device.ts"; import Tips from "@/components/Tips.tsx"; import { includingModelFromPlan } from "@/conf/subscription.tsx"; import { subscriptionDataSelector } from "@/store/globals.ts"; import { ChargeBaseProps, nonBilling, timesBilling, tokenBilling, } from "@/admin/charge.ts"; import { ScrollArea } from "@/components/ui/scroll-area.tsx"; type SearchBarProps = { value: string; onChange: (value: string) => void; }; function getTags(model: Model): string[] { let raw = model.tag || []; if (model.free && !raw.includes("free")) raw = ["free", ...raw]; if (model.high_context && !raw.includes("high-context")) raw = ["high-context", ...raw]; return raw; } function SearchBar({ value, onChange }: SearchBarProps) { const { t } = useTranslation(); return (
onChange(e.target.value)} /> 0 && "active")} onClick={() => onChange("")} />
); } type ModelProps = React.DetailedHTMLProps< React.HTMLAttributes, HTMLDivElement > & { model: Model; className?: string; style?: React.CSSProperties; forwardRef?: React.Ref; }; type PriceTagProps = ChargeBaseProps & { pro: boolean; }; function PriceTag({ type, input, output, pro }: PriceTagProps) { const { t } = useTranslation(); const className = cn("flex flex-row tag-item", pro && "pro"); switch (type) { case nonBilling: return ( {t("tag.badges.non-billing")} ); case timesBilling: return ( {t("tag.badges.times-billing", { price: output })} ); case tokenBilling: return ( <> {input.toFixed(2)} / 1k tokens {output.toFixed(2)} / 1k tokens ); } } function ModelItem({ model, className, style, forwardRef, ...props }: ModelProps) { const { t } = useTranslation(); const dispatch = useDispatch(); const { toast } = useToast(); const list = useSelector(selectModelList); const current = useSelector(selectModel); const mobile = useMobile(); const level = useSelector(levelSelector); const student = useSelector(teenagerSelector); const auth = useSelector(selectAuthenticated); const subscriptionData = useSelector(subscriptionDataSelector); const state = useMemo(() => { if (current === model.id) return 0; if (list.includes(model.id)) return 1; return 2; }, [model, current, list]); const pro = useMemo(() => { return includingModelFromPlan(subscriptionData, level, model.id); }, [subscriptionData, model, level, student]); const avatar = useMemo(() => { return isUrl(model.avatar) ? model.avatar : `/icons/${model.avatar}`; }, [model]); const tags = useMemo( (): string[] => getTags(model).filter((tag) => tag !== "free"), [model], ); return (
{ dispatch(addModelList(model.id)); if (!auth && model.auth) { toast({ title: t("login-require"), action: ( {t("login")} ), }); return; } dispatch(setModel(model.id)); dispatch(closeMarket()); }} > {model.name}

{model.name}

{mobile ? (

{t("market.model-api")}

{model.id}
) : ( {model.id} {t("market.model-api")} )}
{model.description && (

{model.description}

)}
{tags.map((tag, index) => { return ( {t(`tag.${tag}`)} ); })} {model.price && }
); } type MarketPlaceProps = { search: string; }; function MarketPlace({ search }: MarketPlaceProps) { const { t } = useTranslation(); const select = useSelector(selectModel); const supportModels = useSelector(selectSupportModels); const dispatch = useDispatch(); const models = useMemo(() => { if (search.length === 0) return supportModels; // fuzzy search const raw = splitList(search.toLowerCase(), [" ", ",", ";", "-"]); return supportModels.filter((model) => { const name = model.name.toLowerCase(); const tag = getTags(model); const tag_name = tag.join(" ").toLowerCase(); const tag_translated_name = tag .map((item) => t(`tag.${item}`)) .join(" ") .toLowerCase(); const id = model.id.toLowerCase(); return raw.every( (item) => name.includes(item) || tag_name.includes(item) || tag_translated_name.includes(item) || id.includes(item), ); }); }, [supportModels, search]); const queryIndex = (id: number) => { const model = models[id]; if (!model) return -1; return supportModels.findIndex((item) => item.id === model.id); }; const onDragEnd = (result: DropResult) => { const { destination, source } = result; if ( !destination || destination.index === source.index || destination.index === -1 ) return; const from = queryIndex(source.index); const to = queryIndex(destination.index); if (from === -1 || to === -1) return; const list = [...supportModels]; const [removed] = list.splice(from, 1); list.splice(to, 0, removed); dispatch(setSupportModels(list)); savePreferenceModels(list); }; return ( {(provided) => (
{models.map((model, index) => ( {(provided) => ( )} ))} {provided.placeholder}
)}
); } function MarketHeader() { const { t } = useTranslation(); const dispatch = useDispatch(); return (

{t("market.explore")}

); } function MarketFooter() { const { t } = useTranslation(); return (
{t("pricing")}
); } function ModelMarket() { const [search, setSearch] = useState(""); return (
); } export default ModelMarket;