mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-12 04:47:14 +00:00
feat(models): show LangBot Models pricing
This commit is contained in:
@@ -59,9 +59,11 @@ class SpaceModelAvailability(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
|
||||
model_id: str
|
||||
category: str | None = None
|
||||
input_credits: float | None = None
|
||||
output_credits: float | None = None
|
||||
availability: SpaceModelAvailability = pydantic.Field(default_factory=SpaceModelAvailability)
|
||||
|
||||
@@ -899,6 +899,8 @@ class TestSpaceServiceGetModelSelection:
|
||||
'uuid': 'embedding-model',
|
||||
'model_id': 'text-embedding',
|
||||
'category': 'embedding',
|
||||
'input_credits': 20,
|
||||
'output_credits': 40,
|
||||
},
|
||||
'availability': {'up': None, 'last_probed_at': None},
|
||||
}
|
||||
@@ -922,6 +924,8 @@ class TestSpaceServiceGetModelSelection:
|
||||
result = await service.get_model_selection()
|
||||
|
||||
assert result[0].category == 'embedding'
|
||||
assert result[0].input_credits == 20
|
||||
assert result[0].output_credits == 40
|
||||
assert result[0].availability.up is None
|
||||
session.get.assert_called_once_with(
|
||||
'https://space.langbot.app/api/v1/models/selection',
|
||||
|
||||
@@ -71,7 +71,7 @@ import { LANGBOT_MODELS_PROVIDER_REQUESTER } from '@/app/home/components/models-
|
||||
import ReasoningLevelPicker, {
|
||||
REASONING_LEVELS,
|
||||
} 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';
|
||||
|
||||
function hasUsableUuid<T extends { uuid?: string | null }>(
|
||||
@@ -162,26 +162,39 @@ export default function DynamicFormItemComponent({
|
||||
DynamicFormItemType.MODEL_FALLBACK_SELECTOR,
|
||||
].includes(config.type);
|
||||
const {
|
||||
availability: langbotModelAvailability,
|
||||
metadata: langbotModelMetadata,
|
||||
loaded: langbotModelAvailabilityLoaded,
|
||||
} = useLangBotModelAvailability(
|
||||
isModelSelector && !systemInfo.disable_models_service,
|
||||
);
|
||||
|
||||
const renderModelAvailability = (model: {
|
||||
const renderModelOption = (model: {
|
||||
uuid: string;
|
||||
name: string;
|
||||
abilities?: string[];
|
||||
provider?: { requester?: string };
|
||||
}) =>
|
||||
model.provider?.requester === LANGBOT_MODELS_PROVIDER_REQUESTER ? (
|
||||
<ModelAvailabilityIndicator
|
||||
availability={
|
||||
langbotModelAvailability[model.uuid] ??
|
||||
langbotModelAvailability[model.name]
|
||||
}
|
||||
show={langbotModelAvailabilityLoaded}
|
||||
/>
|
||||
) : null;
|
||||
}) => (
|
||||
<span className="grid w-full min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-3">
|
||||
<span className="inline-flex min-w-0 items-center gap-1">
|
||||
<span className="truncate">{model.name}</span>
|
||||
{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>
|
||||
{model.provider?.requester === LANGBOT_MODELS_PROVIDER_REQUESTER && (
|
||||
<LangBotModelMetadata
|
||||
metadata={
|
||||
langbotModelMetadata[model.uuid] ?? langbotModelMetadata[model.name]
|
||||
}
|
||||
loaded={langbotModelAvailabilityLoaded}
|
||||
compact
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
|
||||
const fetchLlmModels = () => {
|
||||
httpClient
|
||||
@@ -611,16 +624,7 @@ export default function DynamicFormItemComponent({
|
||||
<SelectLabel>{providerName}</SelectLabel>
|
||||
{models.map((model) => (
|
||||
<SelectItem key={model.uuid} value={model.uuid}>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
{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>
|
||||
{renderModelOption(model)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
@@ -716,16 +720,7 @@ export default function DynamicFormItemComponent({
|
||||
</SelectLabel>
|
||||
{models.map((model) => (
|
||||
<SelectItem key={model.uuid} value={model.uuid}>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
{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>
|
||||
{renderModelOption(model)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
@@ -814,8 +809,7 @@ export default function DynamicFormItemComponent({
|
||||
<SelectLabel>{providerName}</SelectLabel>
|
||||
{models.map((model) => (
|
||||
<SelectItem key={model.uuid} value={model.uuid}>
|
||||
{model.name}
|
||||
{renderModelAvailability(model)}
|
||||
{renderModelOption(model)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
@@ -907,8 +901,7 @@ export default function DynamicFormItemComponent({
|
||||
</SelectLabel>
|
||||
{models.map((model) => (
|
||||
<SelectItem key={model.uuid} value={model.uuid}>
|
||||
{model.name}
|
||||
{renderModelAvailability(model)}
|
||||
{renderModelOption(model)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
@@ -973,8 +966,7 @@ export default function DynamicFormItemComponent({
|
||||
<SelectLabel>{providerName}</SelectLabel>
|
||||
{models.map((model) => (
|
||||
<SelectItem key={model.uuid} value={model.uuid}>
|
||||
{model.name}
|
||||
{renderModelAvailability(model)}
|
||||
{renderModelOption(model)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
@@ -1092,16 +1084,7 @@ export default function DynamicFormItemComponent({
|
||||
<SelectLabel>{providerName}</SelectLabel>
|
||||
{models.map((model) => (
|
||||
<SelectItem key={model.uuid} value={model.uuid}>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
{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>
|
||||
{renderModelOption(model)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
@@ -1198,16 +1181,7 @@ export default function DynamicFormItemComponent({
|
||||
</SelectLabel>
|
||||
{models.map((model) => (
|
||||
<SelectItem key={model.uuid} value={model.uuid}>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
{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>
|
||||
{renderModelOption(model)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</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 type { LangBotModelAvailability } from '@/app/infra/entities/api';
|
||||
import type { LangBotModelAvailabilityItem } from '@/app/infra/entities/api';
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
|
||||
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 pendingRequest: Promise<AvailabilityMap> | null = null;
|
||||
let pendingRequest: Promise<ModelMetadataMap> | null = null;
|
||||
|
||||
async function loadAvailability(): Promise<AvailabilityMap> {
|
||||
if (cachedAvailability && Date.now() < cacheExpiresAt) {
|
||||
return cachedAvailability;
|
||||
async function loadMetadata(): Promise<ModelMetadataMap> {
|
||||
if (cachedMetadata && Date.now() < cacheExpiresAt) {
|
||||
return cachedMetadata;
|
||||
}
|
||||
if (pendingRequest) return pendingRequest;
|
||||
|
||||
pendingRequest = httpClient
|
||||
.getLangBotModelAvailability()
|
||||
.then((response) => {
|
||||
const next: AvailabilityMap = {};
|
||||
const next: ModelMetadataMap = {};
|
||||
for (const item of response.models) {
|
||||
next[item.uuid] = item.availability;
|
||||
next[item.model_id] = item.availability;
|
||||
next[item.uuid] = item;
|
||||
next[item.model_id] = item;
|
||||
}
|
||||
cachedAvailability = next;
|
||||
cachedMetadata = next;
|
||||
cacheExpiresAt = Date.now() + CACHE_TTL_MS;
|
||||
return next;
|
||||
})
|
||||
@@ -35,29 +35,29 @@ async function loadAvailability(): Promise<AvailabilityMap> {
|
||||
}
|
||||
|
||||
export function useLangBotModelAvailability(enabled = true) {
|
||||
const [availability, setAvailability] = useState<AvailabilityMap>(
|
||||
cachedAvailability ?? {},
|
||||
const [metadata, setMetadata] = useState<ModelMetadataMap>(
|
||||
cachedMetadata ?? {},
|
||||
);
|
||||
const [loaded, setLoaded] = useState(
|
||||
cachedAvailability !== null && Date.now() < cacheExpiresAt,
|
||||
cachedMetadata !== null && Date.now() < cacheExpiresAt,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
let active = true;
|
||||
loadAvailability()
|
||||
loadMetadata()
|
||||
.then((result) => {
|
||||
if (!active) return;
|
||||
setAvailability(result);
|
||||
setMetadata(result);
|
||||
setLoaded(true);
|
||||
})
|
||||
.catch(() => {
|
||||
// Availability is supplementary; model configuration remains usable.
|
||||
// Catalog metadata is supplementary; model configuration remains usable.
|
||||
});
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [enabled]);
|
||||
|
||||
return { availability, loaded };
|
||||
return { metadata, loaded };
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ export default function ModelsPanel({
|
||||
const canManage =
|
||||
currentWorkspace?.permissions.includes('provider_secret.manage') ?? false;
|
||||
const {
|
||||
availability: langbotModelAvailability,
|
||||
metadata: langbotModelMetadata,
|
||||
loaded: langbotModelAvailabilityLoaded,
|
||||
} = useLangBotModelAvailability(active && !systemInfo.disable_models_service);
|
||||
|
||||
@@ -559,7 +559,7 @@ export default function ModelsPanel({
|
||||
isWorkspaceOwner={currentWorkspace?.membership.role === 'owner'}
|
||||
ownerSpaceBound={spaceBilling?.owner_space_bound ?? false}
|
||||
spaceCredits={spaceBilling?.credits ?? null}
|
||||
modelAvailability={langbotModelAvailability}
|
||||
modelMetadata={langbotModelMetadata}
|
||||
modelAvailabilityLoaded={langbotModelAvailabilityLoaded}
|
||||
addModelPopoverOpen={addModelPopoverOpen}
|
||||
editModelPopoverOpen={editModelPopoverOpen}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
LLMModel,
|
||||
EmbeddingModel,
|
||||
LangBotModelAvailability,
|
||||
LangBotModelAvailabilityItem,
|
||||
ReasoningConfig,
|
||||
} from '@/app/infra/entities/api';
|
||||
import {
|
||||
@@ -25,14 +25,14 @@ import {
|
||||
} from '../types';
|
||||
import ExtraArgsEditor from './ExtraArgsEditor';
|
||||
import { userInfo } from '@/app/infra/http';
|
||||
import ModelAvailabilityIndicator from '../../model-availability/ModelAvailabilityIndicator';
|
||||
import LangBotModelMetadata from '../../model-availability/LangBotModelMetadata';
|
||||
|
||||
interface ModelItemProps {
|
||||
model: LLMModel | EmbeddingModel;
|
||||
canManage: boolean;
|
||||
modelType: ModelType;
|
||||
isLangBotModels: boolean;
|
||||
availability?: LangBotModelAvailability;
|
||||
metadata?: LangBotModelAvailabilityItem;
|
||||
availabilityLoaded: boolean;
|
||||
editModelPopoverOpen: string | null;
|
||||
deleteConfirmOpen: string | null;
|
||||
@@ -90,7 +90,7 @@ export default function ModelItem({
|
||||
canManage,
|
||||
modelType,
|
||||
isLangBotModels,
|
||||
availability,
|
||||
metadata,
|
||||
availabilityLoaded,
|
||||
editModelPopoverOpen,
|
||||
deleteConfirmOpen,
|
||||
@@ -203,14 +203,8 @@ export default function ModelItem({
|
||||
: '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>
|
||||
{isLangBotModels && (
|
||||
<ModelAvailabilityIndicator
|
||||
availability={availability}
|
||||
show={availabilityLoaded}
|
||||
/>
|
||||
)}
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{modelType === 'llm'
|
||||
? t('models.chat')
|
||||
@@ -237,6 +231,12 @@ export default function ModelItem({
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{isLangBotModels && (
|
||||
<LangBotModelMetadata
|
||||
metadata={metadata}
|
||||
loaded={availabilityLoaded}
|
||||
/>
|
||||
)}
|
||||
{canManage && !isLangBotModels && (
|
||||
<Popover
|
||||
open={isDeleteOpen}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { httpClient, systemInfo } from '@/app/infra/http/HttpClient';
|
||||
import {
|
||||
LangBotModelAvailability,
|
||||
LangBotModelAvailabilityItem,
|
||||
ModelProvider,
|
||||
ReasoningConfig,
|
||||
} from '@/app/infra/entities/api';
|
||||
@@ -51,7 +51,7 @@ interface ProviderCardProps {
|
||||
isWorkspaceOwner: boolean;
|
||||
ownerSpaceBound: boolean;
|
||||
spaceCredits: number | null;
|
||||
modelAvailability: Record<string, LangBotModelAvailability>;
|
||||
modelMetadata: Record<string, LangBotModelAvailabilityItem>;
|
||||
modelAvailabilityLoaded: boolean;
|
||||
// Popover states
|
||||
addModelPopoverOpen: string | null;
|
||||
@@ -121,7 +121,7 @@ export default function ProviderCard({
|
||||
isWorkspaceOwner,
|
||||
ownerSpaceBound,
|
||||
spaceCredits,
|
||||
modelAvailability,
|
||||
modelMetadata,
|
||||
modelAvailabilityLoaded,
|
||||
addModelPopoverOpen,
|
||||
editModelPopoverOpen,
|
||||
@@ -432,9 +432,8 @@ export default function ProviderCard({
|
||||
canManage={canManage}
|
||||
modelType="llm"
|
||||
isLangBotModels={isLangBotModels}
|
||||
availability={
|
||||
modelAvailability[model.uuid] ??
|
||||
modelAvailability[model.name]
|
||||
metadata={
|
||||
modelMetadata[model.uuid] ?? modelMetadata[model.name]
|
||||
}
|
||||
availabilityLoaded={modelAvailabilityLoaded}
|
||||
editModelPopoverOpen={editModelPopoverOpen}
|
||||
@@ -488,9 +487,8 @@ export default function ProviderCard({
|
||||
canManage={canManage}
|
||||
modelType="embedding"
|
||||
isLangBotModels={isLangBotModels}
|
||||
availability={
|
||||
modelAvailability[model.uuid] ??
|
||||
modelAvailability[model.name]
|
||||
metadata={
|
||||
modelMetadata[model.uuid] ?? modelMetadata[model.name]
|
||||
}
|
||||
availabilityLoaded={modelAvailabilityLoaded}
|
||||
editModelPopoverOpen={editModelPopoverOpen}
|
||||
@@ -542,9 +540,8 @@ export default function ProviderCard({
|
||||
canManage={canManage}
|
||||
modelType="rerank"
|
||||
isLangBotModels={isLangBotModels}
|
||||
availability={
|
||||
modelAvailability[model.uuid] ??
|
||||
modelAvailability[model.name]
|
||||
metadata={
|
||||
modelMetadata[model.uuid] ?? modelMetadata[model.name]
|
||||
}
|
||||
availabilityLoaded={modelAvailabilityLoaded}
|
||||
editModelPopoverOpen={editModelPopoverOpen}
|
||||
|
||||
@@ -169,6 +169,8 @@ export interface LangBotModelAvailabilityItem {
|
||||
uuid: string;
|
||||
model_id: string;
|
||||
category: string | null;
|
||||
input_credits: number | null;
|
||||
output_credits: number | null;
|
||||
availability: LangBotModelAvailability;
|
||||
}
|
||||
|
||||
|
||||
@@ -310,6 +310,14 @@ const enUS = {
|
||||
notChecked: 'No check result',
|
||||
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',
|
||||
spaceTrialTooltip:
|
||||
'Free trial credits available! Login with LangBot Account to access cloud models with zero configuration.',
|
||||
|
||||
@@ -312,6 +312,14 @@ const esES = {
|
||||
notChecked: 'Sin resultado de comprobación',
|
||||
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',
|
||||
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.',
|
||||
|
||||
@@ -315,6 +315,14 @@ const jaJP = {
|
||||
notChecked: 'チェック結果なし',
|
||||
lastChecked: '{{time}} にチェック',
|
||||
},
|
||||
pricing: {
|
||||
compact: '{{input}} / {{output}}',
|
||||
inline: '入力 {{input}} · 出力 {{output}}',
|
||||
title: '100万トークンあたりのクレジット',
|
||||
input: '入力:{{credits}} クレジット',
|
||||
output: '出力:{{credits}} クレジット',
|
||||
unavailable: '現在の価格なし',
|
||||
},
|
||||
langbotModels: 'LangBot モデル',
|
||||
spaceTrialTooltip:
|
||||
'無料トライアルクレジットが利用可能!LangBot アカウントでログインして、設定不要でクラウドモデルを使用できます。',
|
||||
|
||||
@@ -309,6 +309,14 @@ const ruRU = {
|
||||
notChecked: 'Нет результата проверки',
|
||||
lastChecked: 'Проверено {{time}}',
|
||||
},
|
||||
pricing: {
|
||||
compact: '{{input}} / {{output}}',
|
||||
inline: 'Ввод {{input}} · вывод {{output}}',
|
||||
title: 'Кредиты за 1 млн токенов',
|
||||
input: 'Ввод: {{credits}} кредитов',
|
||||
output: 'Вывод: {{credits}} кредитов',
|
||||
unavailable: 'Нет актуальной цены',
|
||||
},
|
||||
langbotModels: 'Модели LangBot',
|
||||
spaceTrialTooltip:
|
||||
'Доступны бесплатные пробные кредиты! Войдите с аккаунтом LangBot, чтобы получить доступ к облачным моделям без настройки.',
|
||||
|
||||
@@ -298,6 +298,14 @@ const thTH = {
|
||||
notChecked: 'ไม่มีผลการตรวจสอบ',
|
||||
lastChecked: 'ตรวจสอบเมื่อ {{time}}',
|
||||
},
|
||||
pricing: {
|
||||
compact: '{{input}} / {{output}}',
|
||||
inline: 'อินพุต {{input}} · เอาต์พุต {{output}}',
|
||||
title: 'เครดิตต่อ 1 ล้านโทเค็น',
|
||||
input: 'อินพุต: {{credits}} เครดิต',
|
||||
output: 'เอาต์พุต: {{credits}} เครดิต',
|
||||
unavailable: 'ไม่มีราคาปัจจุบัน',
|
||||
},
|
||||
langbotModels: 'โมเดล LangBot',
|
||||
spaceTrialTooltip:
|
||||
'มีเครดิตทดลองใช้งานฟรี! เข้าสู่ระบบด้วยบัญชี LangBot เพื่อเข้าถึงโมเดลคลาวด์โดยไม่ต้องตั้งค่า',
|
||||
|
||||
@@ -306,6 +306,14 @@ const viVN = {
|
||||
notChecked: 'Chưa có kết quả kiểm tra',
|
||||
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',
|
||||
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.',
|
||||
|
||||
@@ -296,6 +296,14 @@ const zhHans = {
|
||||
notChecked: '暂无检测结果',
|
||||
lastChecked: '检测于 {{time}}',
|
||||
},
|
||||
pricing: {
|
||||
compact: '{{input}} / {{output}}',
|
||||
inline: '输入 {{input}} · 输出 {{output}}',
|
||||
title: '每 1M tokens 消耗积分',
|
||||
input: '输入:{{credits}} 积分',
|
||||
output: '输出:{{credits}} 积分',
|
||||
unavailable: '暂无价格',
|
||||
},
|
||||
langbotModels: 'LangBot 模型',
|
||||
spaceTrialTooltip:
|
||||
'免费试用积分已就绪!通过 LangBot 账号登录即可零配置使用云端模型。',
|
||||
|
||||
@@ -287,6 +287,14 @@ const zhHant = {
|
||||
notChecked: '暫無檢測結果',
|
||||
lastChecked: '檢測於 {{time}}',
|
||||
},
|
||||
pricing: {
|
||||
compact: '{{input}} / {{output}}',
|
||||
inline: '輸入 {{input}} · 輸出 {{output}}',
|
||||
title: '每 1M tokens 消耗積分',
|
||||
input: '輸入:{{credits}} 積分',
|
||||
output: '輸出:{{credits}} 積分',
|
||||
unavailable: '暫無價格',
|
||||
},
|
||||
langbotModels: 'LangBot 模型',
|
||||
spaceTrialTooltip:
|
||||
'免費試用積分已就緒!使用 LangBot 帳號登入即可零設定使用雲端模型。',
|
||||
|
||||
Reference in New Issue
Block a user