feat(models): show LangBot Models pricing

This commit is contained in:
RockChinQ
2026-09-12 00:53:47 +08:00
parent a3509b3626
commit 33b4035140
17 changed files with 233 additions and 103 deletions
+3 -1
View File
@@ -59,9 +59,11 @@ class SpaceModelAvailability(pydantic.BaseModel):
class SpaceModelSelection(pydantic.BaseModel): class SpaceModelSelection(pydantic.BaseModel):
"""Model identity and the latest persisted probe from Space.""" """Model identity, pricing, and latest persisted probe from Space."""
uuid: str uuid: str
model_id: str model_id: str
category: str | None = None category: str | None = None
input_credits: float | None = None
output_credits: float | None = None
availability: SpaceModelAvailability = pydantic.Field(default_factory=SpaceModelAvailability) availability: SpaceModelAvailability = pydantic.Field(default_factory=SpaceModelAvailability)
@@ -899,6 +899,8 @@ class TestSpaceServiceGetModelSelection:
'uuid': 'embedding-model', 'uuid': 'embedding-model',
'model_id': 'text-embedding', 'model_id': 'text-embedding',
'category': 'embedding', 'category': 'embedding',
'input_credits': 20,
'output_credits': 40,
}, },
'availability': {'up': None, 'last_probed_at': None}, 'availability': {'up': None, 'last_probed_at': None},
} }
@@ -922,6 +924,8 @@ class TestSpaceServiceGetModelSelection:
result = await service.get_model_selection() result = await service.get_model_selection()
assert result[0].category == 'embedding' assert result[0].category == 'embedding'
assert result[0].input_credits == 20
assert result[0].output_credits == 40
assert result[0].availability.up is None assert result[0].availability.up is None
session.get.assert_called_once_with( session.get.assert_called_once_with(
'https://space.langbot.app/api/v1/models/selection', 'https://space.langbot.app/api/v1/models/selection',
@@ -71,7 +71,7 @@ import { LANGBOT_MODELS_PROVIDER_REQUESTER } from '@/app/home/components/models-
import ReasoningLevelPicker, { import ReasoningLevelPicker, {
REASONING_LEVELS, REASONING_LEVELS,
} from '@/app/home/components/reasoning/ReasoningLevelPicker'; } from '@/app/home/components/reasoning/ReasoningLevelPicker';
import ModelAvailabilityIndicator from '@/app/home/components/model-availability/ModelAvailabilityIndicator'; import LangBotModelMetadata from '@/app/home/components/model-availability/LangBotModelMetadata';
import { useLangBotModelAvailability } from '@/app/home/components/model-availability/useLangBotModelAvailability'; import { useLangBotModelAvailability } from '@/app/home/components/model-availability/useLangBotModelAvailability';
function hasUsableUuid<T extends { uuid?: string | null }>( function hasUsableUuid<T extends { uuid?: string | null }>(
@@ -162,26 +162,39 @@ export default function DynamicFormItemComponent({
DynamicFormItemType.MODEL_FALLBACK_SELECTOR, DynamicFormItemType.MODEL_FALLBACK_SELECTOR,
].includes(config.type); ].includes(config.type);
const { const {
availability: langbotModelAvailability, metadata: langbotModelMetadata,
loaded: langbotModelAvailabilityLoaded, loaded: langbotModelAvailabilityLoaded,
} = useLangBotModelAvailability( } = useLangBotModelAvailability(
isModelSelector && !systemInfo.disable_models_service, isModelSelector && !systemInfo.disable_models_service,
); );
const renderModelAvailability = (model: { const renderModelOption = (model: {
uuid: string; uuid: string;
name: string; name: string;
abilities?: string[];
provider?: { requester?: string }; provider?: { requester?: string };
}) => }) => (
model.provider?.requester === LANGBOT_MODELS_PROVIDER_REQUESTER ? ( <span className="grid w-full min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-3">
<ModelAvailabilityIndicator <span className="inline-flex min-w-0 items-center gap-1">
availability={ <span className="truncate">{model.name}</span>
langbotModelAvailability[model.uuid] ?? {model.abilities?.includes('vision') && (
langbotModelAvailability[model.name] <Eye className="h-3 w-3 text-muted-foreground" />
} )}
show={langbotModelAvailabilityLoaded} {model.abilities?.includes('func_call') && (
/> <Wrench className="h-3 w-3 text-muted-foreground" />
) : null; )}
</span>
{model.provider?.requester === LANGBOT_MODELS_PROVIDER_REQUESTER && (
<LangBotModelMetadata
metadata={
langbotModelMetadata[model.uuid] ?? langbotModelMetadata[model.name]
}
loaded={langbotModelAvailabilityLoaded}
compact
/>
)}
</span>
);
const fetchLlmModels = () => { const fetchLlmModels = () => {
httpClient httpClient
@@ -611,16 +624,7 @@ export default function DynamicFormItemComponent({
<SelectLabel>{providerName}</SelectLabel> <SelectLabel>{providerName}</SelectLabel>
{models.map((model) => ( {models.map((model) => (
<SelectItem key={model.uuid} value={model.uuid}> <SelectItem key={model.uuid} value={model.uuid}>
<span className="inline-flex items-center gap-1"> {renderModelOption(model)}
{model.name}
{renderModelAvailability(model)}
{model.abilities?.includes('vision') && (
<Eye className="h-3 w-3 text-muted-foreground" />
)}
{model.abilities?.includes('func_call') && (
<Wrench className="h-3 w-3 text-muted-foreground" />
)}
</span>
</SelectItem> </SelectItem>
))} ))}
</SelectGroup> </SelectGroup>
@@ -716,16 +720,7 @@ export default function DynamicFormItemComponent({
</SelectLabel> </SelectLabel>
{models.map((model) => ( {models.map((model) => (
<SelectItem key={model.uuid} value={model.uuid}> <SelectItem key={model.uuid} value={model.uuid}>
<span className="inline-flex items-center gap-1"> {renderModelOption(model)}
{model.name}
{renderModelAvailability(model)}
{model.abilities?.includes('vision') && (
<Eye className="h-3 w-3 text-muted-foreground" />
)}
{model.abilities?.includes('func_call') && (
<Wrench className="h-3 w-3 text-muted-foreground" />
)}
</span>
</SelectItem> </SelectItem>
))} ))}
</SelectGroup> </SelectGroup>
@@ -814,8 +809,7 @@ export default function DynamicFormItemComponent({
<SelectLabel>{providerName}</SelectLabel> <SelectLabel>{providerName}</SelectLabel>
{models.map((model) => ( {models.map((model) => (
<SelectItem key={model.uuid} value={model.uuid}> <SelectItem key={model.uuid} value={model.uuid}>
{model.name} {renderModelOption(model)}
{renderModelAvailability(model)}
</SelectItem> </SelectItem>
))} ))}
</SelectGroup> </SelectGroup>
@@ -907,8 +901,7 @@ export default function DynamicFormItemComponent({
</SelectLabel> </SelectLabel>
{models.map((model) => ( {models.map((model) => (
<SelectItem key={model.uuid} value={model.uuid}> <SelectItem key={model.uuid} value={model.uuid}>
{model.name} {renderModelOption(model)}
{renderModelAvailability(model)}
</SelectItem> </SelectItem>
))} ))}
</SelectGroup> </SelectGroup>
@@ -973,8 +966,7 @@ export default function DynamicFormItemComponent({
<SelectLabel>{providerName}</SelectLabel> <SelectLabel>{providerName}</SelectLabel>
{models.map((model) => ( {models.map((model) => (
<SelectItem key={model.uuid} value={model.uuid}> <SelectItem key={model.uuid} value={model.uuid}>
{model.name} {renderModelOption(model)}
{renderModelAvailability(model)}
</SelectItem> </SelectItem>
))} ))}
</SelectGroup> </SelectGroup>
@@ -1092,16 +1084,7 @@ export default function DynamicFormItemComponent({
<SelectLabel>{providerName}</SelectLabel> <SelectLabel>{providerName}</SelectLabel>
{models.map((model) => ( {models.map((model) => (
<SelectItem key={model.uuid} value={model.uuid}> <SelectItem key={model.uuid} value={model.uuid}>
<span className="inline-flex items-center gap-1"> {renderModelOption(model)}
{model.name}
{renderModelAvailability(model)}
{model.abilities?.includes('vision') && (
<Eye className="h-3 w-3 text-muted-foreground" />
)}
{model.abilities?.includes('func_call') && (
<Wrench className="h-3 w-3 text-muted-foreground" />
)}
</span>
</SelectItem> </SelectItem>
))} ))}
</SelectGroup> </SelectGroup>
@@ -1198,16 +1181,7 @@ export default function DynamicFormItemComponent({
</SelectLabel> </SelectLabel>
{models.map((model) => ( {models.map((model) => (
<SelectItem key={model.uuid} value={model.uuid}> <SelectItem key={model.uuid} value={model.uuid}>
<span className="inline-flex items-center gap-1"> {renderModelOption(model)}
{model.name}
{renderModelAvailability(model)}
{model.abilities?.includes('vision') && (
<Eye className="h-3 w-3 text-muted-foreground" />
)}
{model.abilities?.includes('func_call') && (
<Wrench className="h-3 w-3 text-muted-foreground" />
)}
</span>
</SelectItem> </SelectItem>
))} ))}
</SelectGroup> </SelectGroup>
@@ -0,0 +1,87 @@
import type { LangBotModelAvailabilityItem } from '@/app/infra/entities/api';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { Coins } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import ModelAvailabilityIndicator from './ModelAvailabilityIndicator';
interface LangBotModelMetadataProps {
metadata?: LangBotModelAvailabilityItem;
loaded: boolean;
compact?: boolean;
}
function formatCredits(value: number, locale: string): string {
if (value >= 1000) {
const thousands = value / 1000;
return `${thousands.toFixed(thousands >= 10 ? 0 : 1).replace(/\.0$/, '')}K`;
}
return new Intl.NumberFormat(locale, {
maximumFractionDigits: 2,
}).format(value);
}
export default function LangBotModelMetadata({
metadata,
loaded,
compact = false,
}: LangBotModelMetadataProps) {
const { t, i18n } = useTranslation();
if (!loaded) return null;
const inputCredits = metadata?.input_credits;
const outputCredits = metadata?.output_credits;
const hasPricing = inputCredits != null && outputCredits != null;
const input =
inputCredits != null ? formatCredits(inputCredits, i18n.language) : '';
const output =
outputCredits != null ? formatCredits(outputCredits, i18n.language) : '';
return (
<span className="ml-auto inline-flex shrink-0 items-center gap-2 pl-3">
<Tooltip>
<TooltipTrigger asChild>
<span
className="inline-flex items-center gap-1 text-xs tabular-nums text-muted-foreground"
onMouseDown={(event) => event.preventDefault()}
>
<Coins className="size-3" />
{hasPricing
? compact
? t('models.pricing.compact', { input, output })
: t('models.pricing.inline', { input, output })
: compact
? '—'
: t('models.pricing.unavailable')}
</span>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-64">
{hasPricing ? (
<div className="space-y-0.5">
<p>{t('models.pricing.title')}</p>
<p className="text-xs text-muted-foreground">
{t('models.pricing.input', {
credits: inputCredits,
})}
</p>
<p className="text-xs text-muted-foreground">
{t('models.pricing.output', {
credits: outputCredits,
})}
</p>
</div>
) : (
<p>{t('models.pricing.unavailable')}</p>
)}
</TooltipContent>
</Tooltip>
<ModelAvailabilityIndicator
availability={metadata?.availability}
show={loaded}
/>
</span>
);
}
@@ -1,30 +1,30 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import type { LangBotModelAvailability } from '@/app/infra/entities/api'; import type { LangBotModelAvailabilityItem } from '@/app/infra/entities/api';
import { httpClient } from '@/app/infra/http/HttpClient'; import { httpClient } from '@/app/infra/http/HttpClient';
const CACHE_TTL_MS = 60_000; const CACHE_TTL_MS = 60_000;
type AvailabilityMap = Record<string, LangBotModelAvailability>; type ModelMetadataMap = Record<string, LangBotModelAvailabilityItem>;
let cachedAvailability: AvailabilityMap | null = null; let cachedMetadata: ModelMetadataMap | null = null;
let cacheExpiresAt = 0; let cacheExpiresAt = 0;
let pendingRequest: Promise<AvailabilityMap> | null = null; let pendingRequest: Promise<ModelMetadataMap> | null = null;
async function loadAvailability(): Promise<AvailabilityMap> { async function loadMetadata(): Promise<ModelMetadataMap> {
if (cachedAvailability && Date.now() < cacheExpiresAt) { if (cachedMetadata && Date.now() < cacheExpiresAt) {
return cachedAvailability; return cachedMetadata;
} }
if (pendingRequest) return pendingRequest; if (pendingRequest) return pendingRequest;
pendingRequest = httpClient pendingRequest = httpClient
.getLangBotModelAvailability() .getLangBotModelAvailability()
.then((response) => { .then((response) => {
const next: AvailabilityMap = {}; const next: ModelMetadataMap = {};
for (const item of response.models) { for (const item of response.models) {
next[item.uuid] = item.availability; next[item.uuid] = item;
next[item.model_id] = item.availability; next[item.model_id] = item;
} }
cachedAvailability = next; cachedMetadata = next;
cacheExpiresAt = Date.now() + CACHE_TTL_MS; cacheExpiresAt = Date.now() + CACHE_TTL_MS;
return next; return next;
}) })
@@ -35,29 +35,29 @@ async function loadAvailability(): Promise<AvailabilityMap> {
} }
export function useLangBotModelAvailability(enabled = true) { export function useLangBotModelAvailability(enabled = true) {
const [availability, setAvailability] = useState<AvailabilityMap>( const [metadata, setMetadata] = useState<ModelMetadataMap>(
cachedAvailability ?? {}, cachedMetadata ?? {},
); );
const [loaded, setLoaded] = useState( const [loaded, setLoaded] = useState(
cachedAvailability !== null && Date.now() < cacheExpiresAt, cachedMetadata !== null && Date.now() < cacheExpiresAt,
); );
useEffect(() => { useEffect(() => {
if (!enabled) return; if (!enabled) return;
let active = true; let active = true;
loadAvailability() loadMetadata()
.then((result) => { .then((result) => {
if (!active) return; if (!active) return;
setAvailability(result); setMetadata(result);
setLoaded(true); setLoaded(true);
}) })
.catch(() => { .catch(() => {
// Availability is supplementary; model configuration remains usable. // Catalog metadata is supplementary; model configuration remains usable.
}); });
return () => { return () => {
active = false; active = false;
}; };
}, [enabled]); }, [enabled]);
return { availability, loaded }; return { metadata, loaded };
} }
@@ -91,7 +91,7 @@ export default function ModelsPanel({
const canManage = const canManage =
currentWorkspace?.permissions.includes('provider_secret.manage') ?? false; currentWorkspace?.permissions.includes('provider_secret.manage') ?? false;
const { const {
availability: langbotModelAvailability, metadata: langbotModelMetadata,
loaded: langbotModelAvailabilityLoaded, loaded: langbotModelAvailabilityLoaded,
} = useLangBotModelAvailability(active && !systemInfo.disable_models_service); } = useLangBotModelAvailability(active && !systemInfo.disable_models_service);
@@ -559,7 +559,7 @@ export default function ModelsPanel({
isWorkspaceOwner={currentWorkspace?.membership.role === 'owner'} isWorkspaceOwner={currentWorkspace?.membership.role === 'owner'}
ownerSpaceBound={spaceBilling?.owner_space_bound ?? false} ownerSpaceBound={spaceBilling?.owner_space_bound ?? false}
spaceCredits={spaceBilling?.credits ?? null} spaceCredits={spaceBilling?.credits ?? null}
modelAvailability={langbotModelAvailability} modelMetadata={langbotModelMetadata}
modelAvailabilityLoaded={langbotModelAvailabilityLoaded} modelAvailabilityLoaded={langbotModelAvailabilityLoaded}
addModelPopoverOpen={addModelPopoverOpen} addModelPopoverOpen={addModelPopoverOpen}
editModelPopoverOpen={editModelPopoverOpen} editModelPopoverOpen={editModelPopoverOpen}
@@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next';
import { import {
LLMModel, LLMModel,
EmbeddingModel, EmbeddingModel,
LangBotModelAvailability, LangBotModelAvailabilityItem,
ReasoningConfig, ReasoningConfig,
} from '@/app/infra/entities/api'; } from '@/app/infra/entities/api';
import { import {
@@ -25,14 +25,14 @@ import {
} from '../types'; } from '../types';
import ExtraArgsEditor from './ExtraArgsEditor'; import ExtraArgsEditor from './ExtraArgsEditor';
import { userInfo } from '@/app/infra/http'; import { userInfo } from '@/app/infra/http';
import ModelAvailabilityIndicator from '../../model-availability/ModelAvailabilityIndicator'; import LangBotModelMetadata from '../../model-availability/LangBotModelMetadata';
interface ModelItemProps { interface ModelItemProps {
model: LLMModel | EmbeddingModel; model: LLMModel | EmbeddingModel;
canManage: boolean; canManage: boolean;
modelType: ModelType; modelType: ModelType;
isLangBotModels: boolean; isLangBotModels: boolean;
availability?: LangBotModelAvailability; metadata?: LangBotModelAvailabilityItem;
availabilityLoaded: boolean; availabilityLoaded: boolean;
editModelPopoverOpen: string | null; editModelPopoverOpen: string | null;
deleteConfirmOpen: string | null; deleteConfirmOpen: string | null;
@@ -90,7 +90,7 @@ export default function ModelItem({
canManage, canManage,
modelType, modelType,
isLangBotModels, isLangBotModels,
availability, metadata,
availabilityLoaded, availabilityLoaded,
editModelPopoverOpen, editModelPopoverOpen,
deleteConfirmOpen, deleteConfirmOpen,
@@ -203,14 +203,8 @@ export default function ModelItem({
: 'hover:bg-accent cursor-pointer' : 'hover:bg-accent cursor-pointer'
}`} }`}
> >
<div className="flex items-center gap-2 flex-wrap"> <div className="flex min-w-0 items-center gap-2 flex-wrap">
<span className="text-sm font-medium">{model.name}</span> <span className="text-sm font-medium">{model.name}</span>
{isLangBotModels && (
<ModelAvailabilityIndicator
availability={availability}
show={availabilityLoaded}
/>
)}
<Badge variant="secondary" className="text-xs"> <Badge variant="secondary" className="text-xs">
{modelType === 'llm' {modelType === 'llm'
? t('models.chat') ? t('models.chat')
@@ -237,6 +231,12 @@ export default function ModelItem({
</Badge> </Badge>
)} )}
</div> </div>
{isLangBotModels && (
<LangBotModelMetadata
metadata={metadata}
loaded={availabilityLoaded}
/>
)}
{canManage && !isLangBotModels && ( {canManage && !isLangBotModels && (
<Popover <Popover
open={isDeleteOpen} open={isDeleteOpen}
@@ -10,7 +10,7 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { httpClient, systemInfo } from '@/app/infra/http/HttpClient'; import { httpClient, systemInfo } from '@/app/infra/http/HttpClient';
import { import {
LangBotModelAvailability, LangBotModelAvailabilityItem,
ModelProvider, ModelProvider,
ReasoningConfig, ReasoningConfig,
} from '@/app/infra/entities/api'; } from '@/app/infra/entities/api';
@@ -51,7 +51,7 @@ interface ProviderCardProps {
isWorkspaceOwner: boolean; isWorkspaceOwner: boolean;
ownerSpaceBound: boolean; ownerSpaceBound: boolean;
spaceCredits: number | null; spaceCredits: number | null;
modelAvailability: Record<string, LangBotModelAvailability>; modelMetadata: Record<string, LangBotModelAvailabilityItem>;
modelAvailabilityLoaded: boolean; modelAvailabilityLoaded: boolean;
// Popover states // Popover states
addModelPopoverOpen: string | null; addModelPopoverOpen: string | null;
@@ -121,7 +121,7 @@ export default function ProviderCard({
isWorkspaceOwner, isWorkspaceOwner,
ownerSpaceBound, ownerSpaceBound,
spaceCredits, spaceCredits,
modelAvailability, modelMetadata,
modelAvailabilityLoaded, modelAvailabilityLoaded,
addModelPopoverOpen, addModelPopoverOpen,
editModelPopoverOpen, editModelPopoverOpen,
@@ -432,9 +432,8 @@ export default function ProviderCard({
canManage={canManage} canManage={canManage}
modelType="llm" modelType="llm"
isLangBotModels={isLangBotModels} isLangBotModels={isLangBotModels}
availability={ metadata={
modelAvailability[model.uuid] ?? modelMetadata[model.uuid] ?? modelMetadata[model.name]
modelAvailability[model.name]
} }
availabilityLoaded={modelAvailabilityLoaded} availabilityLoaded={modelAvailabilityLoaded}
editModelPopoverOpen={editModelPopoverOpen} editModelPopoverOpen={editModelPopoverOpen}
@@ -488,9 +487,8 @@ export default function ProviderCard({
canManage={canManage} canManage={canManage}
modelType="embedding" modelType="embedding"
isLangBotModels={isLangBotModels} isLangBotModels={isLangBotModels}
availability={ metadata={
modelAvailability[model.uuid] ?? modelMetadata[model.uuid] ?? modelMetadata[model.name]
modelAvailability[model.name]
} }
availabilityLoaded={modelAvailabilityLoaded} availabilityLoaded={modelAvailabilityLoaded}
editModelPopoverOpen={editModelPopoverOpen} editModelPopoverOpen={editModelPopoverOpen}
@@ -542,9 +540,8 @@ export default function ProviderCard({
canManage={canManage} canManage={canManage}
modelType="rerank" modelType="rerank"
isLangBotModels={isLangBotModels} isLangBotModels={isLangBotModels}
availability={ metadata={
modelAvailability[model.uuid] ?? modelMetadata[model.uuid] ?? modelMetadata[model.name]
modelAvailability[model.name]
} }
availabilityLoaded={modelAvailabilityLoaded} availabilityLoaded={modelAvailabilityLoaded}
editModelPopoverOpen={editModelPopoverOpen} editModelPopoverOpen={editModelPopoverOpen}
+2
View File
@@ -169,6 +169,8 @@ export interface LangBotModelAvailabilityItem {
uuid: string; uuid: string;
model_id: string; model_id: string;
category: string | null; category: string | null;
input_credits: number | null;
output_credits: number | null;
availability: LangBotModelAvailability; availability: LangBotModelAvailability;
} }
+8
View File
@@ -310,6 +310,14 @@ const enUS = {
notChecked: 'No check result', notChecked: 'No check result',
lastChecked: 'Checked {{time}}', lastChecked: 'Checked {{time}}',
}, },
pricing: {
compact: '{{input}} / {{output}}',
inline: '{{input}} input · {{output}} output',
title: 'Credits per 1M tokens',
input: 'Input: {{credits}} credits',
output: 'Output: {{credits}} credits',
unavailable: 'No current price',
},
langbotModels: 'LangBot Models', langbotModels: 'LangBot Models',
spaceTrialTooltip: spaceTrialTooltip:
'Free trial credits available! Login with LangBot Account to access cloud models with zero configuration.', 'Free trial credits available! Login with LangBot Account to access cloud models with zero configuration.',
+8
View File
@@ -312,6 +312,14 @@ const esES = {
notChecked: 'Sin resultado de comprobación', notChecked: 'Sin resultado de comprobación',
lastChecked: 'Comprobado {{time}}', lastChecked: 'Comprobado {{time}}',
}, },
pricing: {
compact: '{{input}} / {{output}}',
inline: 'Entrada {{input}} · salida {{output}}',
title: 'Créditos por 1 M de tokens',
input: 'Entrada: {{credits}} créditos',
output: 'Salida: {{credits}} créditos',
unavailable: 'Sin precio actual',
},
langbotModels: 'Modelos LangBot', langbotModels: 'Modelos LangBot',
spaceTrialTooltip: spaceTrialTooltip:
'¡Créditos de prueba gratuitos disponibles! Inicia sesión con una cuenta de LangBot para acceder a modelos en la nube sin configuración.', '¡Créditos de prueba gratuitos disponibles! Inicia sesión con una cuenta de LangBot para acceder a modelos en la nube sin configuración.',
+8
View File
@@ -315,6 +315,14 @@ const jaJP = {
notChecked: 'チェック結果なし', notChecked: 'チェック結果なし',
lastChecked: '{{time}} にチェック', lastChecked: '{{time}} にチェック',
}, },
pricing: {
compact: '{{input}} / {{output}}',
inline: '入力 {{input}} · 出力 {{output}}',
title: '100万トークンあたりのクレジット',
input: '入力:{{credits}} クレジット',
output: '出力:{{credits}} クレジット',
unavailable: '現在の価格なし',
},
langbotModels: 'LangBot モデル', langbotModels: 'LangBot モデル',
spaceTrialTooltip: spaceTrialTooltip:
'無料トライアルクレジットが利用可能!LangBot アカウントでログインして、設定不要でクラウドモデルを使用できます。', '無料トライアルクレジットが利用可能!LangBot アカウントでログインして、設定不要でクラウドモデルを使用できます。',
+8
View File
@@ -309,6 +309,14 @@ const ruRU = {
notChecked: 'Нет результата проверки', notChecked: 'Нет результата проверки',
lastChecked: 'Проверено {{time}}', lastChecked: 'Проверено {{time}}',
}, },
pricing: {
compact: '{{input}} / {{output}}',
inline: 'Ввод {{input}} · вывод {{output}}',
title: 'Кредиты за 1 млн токенов',
input: 'Ввод: {{credits}} кредитов',
output: 'Вывод: {{credits}} кредитов',
unavailable: 'Нет актуальной цены',
},
langbotModels: 'Модели LangBot', langbotModels: 'Модели LangBot',
spaceTrialTooltip: spaceTrialTooltip:
'Доступны бесплатные пробные кредиты! Войдите с аккаунтом LangBot, чтобы получить доступ к облачным моделям без настройки.', 'Доступны бесплатные пробные кредиты! Войдите с аккаунтом LangBot, чтобы получить доступ к облачным моделям без настройки.',
+8
View File
@@ -298,6 +298,14 @@ const thTH = {
notChecked: 'ไม่มีผลการตรวจสอบ', notChecked: 'ไม่มีผลการตรวจสอบ',
lastChecked: 'ตรวจสอบเมื่อ {{time}}', lastChecked: 'ตรวจสอบเมื่อ {{time}}',
}, },
pricing: {
compact: '{{input}} / {{output}}',
inline: 'อินพุต {{input}} · เอาต์พุต {{output}}',
title: 'เครดิตต่อ 1 ล้านโทเค็น',
input: 'อินพุต: {{credits}} เครดิต',
output: 'เอาต์พุต: {{credits}} เครดิต',
unavailable: 'ไม่มีราคาปัจจุบัน',
},
langbotModels: 'โมเดล LangBot', langbotModels: 'โมเดล LangBot',
spaceTrialTooltip: spaceTrialTooltip:
'มีเครดิตทดลองใช้งานฟรี! เข้าสู่ระบบด้วยบัญชี LangBot เพื่อเข้าถึงโมเดลคลาวด์โดยไม่ต้องตั้งค่า', 'มีเครดิตทดลองใช้งานฟรี! เข้าสู่ระบบด้วยบัญชี LangBot เพื่อเข้าถึงโมเดลคลาวด์โดยไม่ต้องตั้งค่า',
+8
View File
@@ -306,6 +306,14 @@ const viVN = {
notChecked: 'Chưa có kết quả kiểm tra', notChecked: 'Chưa có kết quả kiểm tra',
lastChecked: 'Đã kiểm tra {{time}}', lastChecked: 'Đã kiểm tra {{time}}',
}, },
pricing: {
compact: '{{input}} / {{output}}',
inline: 'Đầu vào {{input}} · đầu ra {{output}}',
title: 'Tín dụng trên 1 triệu token',
input: 'Đầu vào: {{credits}} tín dụng',
output: 'Đầu ra: {{credits}} tín dụng',
unavailable: 'Chưa có giá hiện tại',
},
langbotModels: 'Mô hình LangBot', langbotModels: 'Mô hình LangBot',
spaceTrialTooltip: spaceTrialTooltip:
'Có tín dụng dùng thử miễn phí! Đăng nhập bằng tài khoản LangBot để truy cập mô hình đám mây không cần cấu hình.', 'Có tín dụng dùng thử miễn phí! Đăng nhập bằng tài khoản LangBot để truy cập mô hình đám mây không cần cấu hình.',
+8
View File
@@ -296,6 +296,14 @@ const zhHans = {
notChecked: '暂无检测结果', notChecked: '暂无检测结果',
lastChecked: '检测于 {{time}}', lastChecked: '检测于 {{time}}',
}, },
pricing: {
compact: '{{input}} / {{output}}',
inline: '输入 {{input}} · 输出 {{output}}',
title: '每 1M tokens 消耗积分',
input: '输入:{{credits}} 积分',
output: '输出:{{credits}} 积分',
unavailable: '暂无价格',
},
langbotModels: 'LangBot 模型', langbotModels: 'LangBot 模型',
spaceTrialTooltip: spaceTrialTooltip:
'免费试用积分已就绪!通过 LangBot 账号登录即可零配置使用云端模型。', '免费试用积分已就绪!通过 LangBot 账号登录即可零配置使用云端模型。',
+8
View File
@@ -287,6 +287,14 @@ const zhHant = {
notChecked: '暫無檢測結果', notChecked: '暫無檢測結果',
lastChecked: '檢測於 {{time}}', lastChecked: '檢測於 {{time}}',
}, },
pricing: {
compact: '{{input}} / {{output}}',
inline: '輸入 {{input}} · 輸出 {{output}}',
title: '每 1M tokens 消耗積分',
input: '輸入:{{credits}} 積分',
output: '輸出:{{credits}} 積分',
unavailable: '暫無價格',
},
langbotModels: 'LangBot 模型', langbotModels: 'LangBot 模型',
spaceTrialTooltip: spaceTrialTooltip:
'免費試用積分已就緒!使用 LangBot 帳號登入即可零設定使用雲端模型。', '免費試用積分已就緒!使用 LangBot 帳號登入即可零設定使用雲端模型。',