mirror of
https://github.com/coaidev/coai.git
synced 2025-05-21 22:10:12 +09:00
feat: provide external display of model prices
This commit is contained in:
parent
d0b5c90df9
commit
b1eea1cac8
@ -5,11 +5,14 @@ export const nonBilling = "non-billing";
|
|||||||
export const defaultChargeType = tokenBilling;
|
export const defaultChargeType = tokenBilling;
|
||||||
export const chargeTypes = [nonBilling, timesBilling, tokenBilling];
|
export const chargeTypes = [nonBilling, timesBilling, tokenBilling];
|
||||||
|
|
||||||
export type ChargeProps = {
|
export type ChargeBaseProps = {
|
||||||
id: number;
|
|
||||||
models: string[];
|
|
||||||
type: string;
|
type: string;
|
||||||
anonymous: boolean;
|
anonymous: boolean;
|
||||||
input: number;
|
input: number;
|
||||||
output: number;
|
output: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ChargeProps = ChargeBaseProps & {
|
||||||
|
id: number;
|
||||||
|
models: string[];
|
||||||
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Conversation } from "./conversation.ts";
|
import { Conversation } from "./conversation.ts";
|
||||||
|
import { ChargeBaseProps } from "@/admin/charge.ts";
|
||||||
|
|
||||||
export type Message = {
|
export type Message = {
|
||||||
role: string;
|
role: string;
|
||||||
@ -19,6 +20,8 @@ export type Model = {
|
|||||||
high_context: boolean;
|
high_context: boolean;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
tag?: string[];
|
tag?: string[];
|
||||||
|
|
||||||
|
price?: ChargeBaseProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Id = number;
|
export type Id = number;
|
||||||
|
@ -247,6 +247,10 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
|
|
||||||
|
&.pro {
|
||||||
|
color: hsl(var(--gold)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
@ -37,11 +37,14 @@ function AppProvider() {
|
|||||||
const charge = await getApiCharge();
|
const charge = await getApiCharge();
|
||||||
|
|
||||||
market.forEach((item: Model) => {
|
market.forEach((item: Model) => {
|
||||||
const obj = charge.find((i: ChargeProps) => i.models.includes(item.id));
|
const instance = charge.find((i: ChargeProps) =>
|
||||||
if (!obj) return;
|
i.models.includes(item.id),
|
||||||
|
);
|
||||||
|
if (!instance) return;
|
||||||
|
|
||||||
item.free = obj.type === nonBilling;
|
item.free = instance.type === nonBilling;
|
||||||
item.auth = !item.free || !obj.anonymous;
|
item.auth = !item.free || !instance.anonymous;
|
||||||
|
item.price = { ...instance };
|
||||||
});
|
});
|
||||||
|
|
||||||
resetJsArray(supportModels, loadPreferenceModels(market));
|
resetJsArray(supportModels, loadPreferenceModels(market));
|
||||||
|
@ -3,11 +3,14 @@ import { Input } from "@/components/ui/input.tsx";
|
|||||||
import {
|
import {
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
Cloud,
|
||||||
|
DownloadCloud,
|
||||||
GripVertical,
|
GripVertical,
|
||||||
Link,
|
Link,
|
||||||
Plus,
|
Plus,
|
||||||
Search,
|
Search,
|
||||||
Trash2,
|
Trash2,
|
||||||
|
UploadCloud,
|
||||||
X,
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
@ -50,6 +53,12 @@ import { useMobile } from "@/utils/device.ts";
|
|||||||
import Tips from "@/components/Tips.tsx";
|
import Tips from "@/components/Tips.tsx";
|
||||||
import { includingModelFromPlan } from "@/conf/subscription.tsx";
|
import { includingModelFromPlan } from "@/conf/subscription.tsx";
|
||||||
import { subscriptionDataSelector } from "@/store/globals.ts";
|
import { subscriptionDataSelector } from "@/store/globals.ts";
|
||||||
|
import {
|
||||||
|
ChargeBaseProps,
|
||||||
|
nonBilling,
|
||||||
|
timesBilling,
|
||||||
|
tokenBilling,
|
||||||
|
} from "@/admin/charge.ts";
|
||||||
|
|
||||||
type SearchBarProps = {
|
type SearchBarProps = {
|
||||||
value: string;
|
value: string;
|
||||||
@ -97,6 +106,46 @@ type ModelProps = React.DetailedHTMLProps<
|
|||||||
forwardRef?: React.Ref<HTMLDivElement>;
|
forwardRef?: React.Ref<HTMLDivElement>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<span className={className}>
|
||||||
|
<Cloud className={`h-4 w-4 mr-1 translate-y-[1px]`} />
|
||||||
|
{t("tag.badges.non-billing")}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
case timesBilling:
|
||||||
|
return (
|
||||||
|
<span className={className}>
|
||||||
|
<Cloud className={`h-4 w-4 mr-1 translate-y-[1px]`} />
|
||||||
|
{t("tag.badges.times-billing", { price: output })}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
case tokenBilling:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className={className}>
|
||||||
|
<UploadCloud className={`h-4 w-4 mr-1 translate-y-[1px]`} />
|
||||||
|
{input.toFixed(2)} / 1k tokens
|
||||||
|
</span>
|
||||||
|
<span className={className}>
|
||||||
|
<DownloadCloud className={`h-4 w-4 mr-1 translate-y-[1px]`} />
|
||||||
|
{output.toFixed(2)} / 1k tokens
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function ModelItem({
|
function ModelItem({
|
||||||
model,
|
model,
|
||||||
className,
|
className,
|
||||||
@ -132,7 +181,10 @@ function ModelItem({
|
|||||||
return isUrl(model.avatar) ? model.avatar : `/icons/${model.avatar}`;
|
return isUrl(model.avatar) ? model.avatar : `/icons/${model.avatar}`;
|
||||||
}, [model]);
|
}, [model]);
|
||||||
|
|
||||||
const tags = useMemo((): string[] => getTags(model), [model]);
|
const tags = useMemo(
|
||||||
|
(): string[] => getTags(model).filter((tag) => tag !== "free"),
|
||||||
|
[model],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -199,6 +251,7 @@ function ModelItem({
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{model.price && <PriceTag {...model.price} pro={pro} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`grow`} />
|
<div className={`grow`} />
|
||||||
|
@ -56,6 +56,7 @@ export function parseOfflineModels(models: string): Model[] {
|
|||||||
high_context: item.high_context || false,
|
high_context: item.high_context || false,
|
||||||
avatar: item.avatar || "",
|
avatar: item.avatar || "",
|
||||||
tag: item.tag || [],
|
tag: item.tag || [],
|
||||||
|
price: item.price,
|
||||||
} as Model;
|
} as Model;
|
||||||
})
|
})
|
||||||
.filter((item): item is Model => item !== null);
|
.filter((item): item is Model => item !== null);
|
||||||
|
@ -98,7 +98,12 @@
|
|||||||
"image-generation": "绘图",
|
"image-generation": "绘图",
|
||||||
"multi-modal": "多模态",
|
"multi-modal": "多模态",
|
||||||
"fast": "快速",
|
"fast": "快速",
|
||||||
"english-model": "英文模型"
|
"english-model": "英文模型",
|
||||||
|
"badges": {
|
||||||
|
"non-billing": "免费",
|
||||||
|
"times-billing": "{{price}} / 次",
|
||||||
|
"token-billing": "输入 {{input}} / 1k tokens 输出 {{output}} / 1k tokens"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"market": {
|
"market": {
|
||||||
"title": "模型市场",
|
"title": "模型市场",
|
||||||
|
@ -46,7 +46,12 @@
|
|||||||
"image-generation": "Image Generation",
|
"image-generation": "Image Generation",
|
||||||
"multi-modal": "Multi Modal",
|
"multi-modal": "Multi Modal",
|
||||||
"fast": "Fast",
|
"fast": "Fast",
|
||||||
"english-model": "English Model"
|
"english-model": "English Model",
|
||||||
|
"badges": {
|
||||||
|
"non-billing": "FREE PASS",
|
||||||
|
"times-billing": "{{price}}/time",
|
||||||
|
"token-billing": "Input {{input}}/1k tokens Output {{output}}/1k tokens"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"market": {
|
"market": {
|
||||||
"title": "Model Market",
|
"title": "Model Market",
|
||||||
|
@ -46,7 +46,12 @@
|
|||||||
"image-generation": "画像生成",
|
"image-generation": "画像生成",
|
||||||
"multi-modal": "マルチモーダル",
|
"multi-modal": "マルチモーダル",
|
||||||
"fast": "高速",
|
"fast": "高速",
|
||||||
"english-model": "英語モデル"
|
"english-model": "英語モデル",
|
||||||
|
"badges": {
|
||||||
|
"non-billing": "無料",
|
||||||
|
"times-billing": "{{price }}/回",
|
||||||
|
"token-billing": "入力{{input }}/ 1 kトークン出力{{output }}/ 1 kトークン"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"market": {
|
"market": {
|
||||||
"title": "モデルマーケット",
|
"title": "モデルマーケット",
|
||||||
|
@ -46,7 +46,12 @@
|
|||||||
"image-generation": "Генерация изображений",
|
"image-generation": "Генерация изображений",
|
||||||
"multi-modal": "Мульти Модальный",
|
"multi-modal": "Мульти Модальный",
|
||||||
"fast": "Быстрый",
|
"fast": "Быстрый",
|
||||||
"english-model": "Английская модель"
|
"english-model": "Английская модель",
|
||||||
|
"badges": {
|
||||||
|
"non-billing": "Бесплатно",
|
||||||
|
"times-billing": "{{price}}/время",
|
||||||
|
"token-billing": "Ввод {{input}}/1k токенов Вывод {{output}}/1k токенов"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"market": {
|
"market": {
|
||||||
"title": "Рынок моделей",
|
"title": "Рынок моделей",
|
||||||
|
@ -679,11 +679,14 @@ function Market() {
|
|||||||
const charge = await getApiCharge();
|
const charge = await getApiCharge();
|
||||||
|
|
||||||
market.forEach((item: Model) => {
|
market.forEach((item: Model) => {
|
||||||
const obj = charge.find((i: ChargeProps) => i.models.includes(item.id));
|
const instance = charge.find((i: ChargeProps) =>
|
||||||
if (!obj) return;
|
i.models.includes(item.id),
|
||||||
|
);
|
||||||
|
if (!instance) return;
|
||||||
|
|
||||||
item.free = obj.type === nonBilling;
|
item.free = instance.type === nonBilling;
|
||||||
item.auth = !item.free || !obj.anonymous;
|
item.auth = !item.free || !instance.anonymous;
|
||||||
|
item.price = { ...instance };
|
||||||
});
|
});
|
||||||
|
|
||||||
resetJsArray(supportModels, loadPreferenceModels(market));
|
resetJsArray(supportModels, loadPreferenceModels(market));
|
||||||
|
Loading…
Reference in New Issue
Block a user