mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-02 03:55:55 +00:00
feat: add endpoint for retrieving user space credits and implement caching mechanism in UserService
This commit is contained in:
@@ -9,12 +9,11 @@ import {
|
||||
ChevronRight,
|
||||
Trash2,
|
||||
Settings,
|
||||
Sparkles,
|
||||
LogIn,
|
||||
Eye,
|
||||
Wrench,
|
||||
} from 'lucide-react';
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import { httpClient, systemInfo } from '@/app/infra/http/HttpClient';
|
||||
import {
|
||||
LLMModel,
|
||||
EmbeddingModel,
|
||||
@@ -46,6 +45,7 @@ import { Badge } from '@/components/ui/badge';
|
||||
import LLMForm from './component/llm-form/LLMForm';
|
||||
import EmbeddingForm from './component/embedding-form/EmbeddingForm';
|
||||
import ProviderForm from './component/provider-form/ProviderForm';
|
||||
import langbotIcon from '@/app/assets/langbot-logo.webp';
|
||||
|
||||
interface ModelsDialogProps {
|
||||
open: boolean;
|
||||
@@ -62,7 +62,7 @@ export default function ModelsDialog({
|
||||
|
||||
const [providers, setProviders] = useState<ModelProvider[]>([]);
|
||||
const [accountType, setAccountType] = useState<'local' | 'space'>('local');
|
||||
const [spaceBalance] = useState<number | null>(null);
|
||||
const [spaceCredits, setSpaceCredits] = useState<number | null>(null);
|
||||
|
||||
// Expanded providers and their models
|
||||
const [expandedProviders, setExpandedProviders] = useState<Set<string>>(
|
||||
@@ -103,6 +103,10 @@ export default function ModelsDialog({
|
||||
try {
|
||||
const userInfo = await httpClient.getUserInfo();
|
||||
setAccountType(userInfo.account_type);
|
||||
if (userInfo.account_type === 'space') {
|
||||
const creditsInfo = await httpClient.getSpaceCredits();
|
||||
setSpaceCredits(creditsInfo.credits);
|
||||
}
|
||||
} catch {
|
||||
setAccountType('local');
|
||||
}
|
||||
@@ -279,8 +283,12 @@ export default function ModelsDialog({
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 flex-1">
|
||||
{isLangBotModels ? (
|
||||
<div className="p-2 bg-gradient-to-br from-purple-500 to-blue-500 rounded-lg">
|
||||
<Sparkles className="h-5 w-5 text-white" />
|
||||
<div className="w-9 h-9 rounded-lg overflow-hidden flex-shrink-0">
|
||||
<img
|
||||
src={langbotIcon.src}
|
||||
alt="LangBot"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<img
|
||||
@@ -332,11 +340,29 @@ export default function ModelsDialog({
|
||||
{t('models.loginWithSpace')}
|
||||
</Button>
|
||||
)}
|
||||
{isLangBotModels && accountType === 'space' && (
|
||||
<Badge variant="secondary">
|
||||
{t('models.balance')}: {spaceBalance ?? '--'}
|
||||
</Badge>
|
||||
)}
|
||||
{isLangBotModels &&
|
||||
accountType === 'space' &&
|
||||
spaceCredits !== null && (
|
||||
<div className="flex items-center gap-1 border rounded-md px-2 h-8 text-sm mr-2">
|
||||
<span>
|
||||
{(spaceCredits / 5000).toFixed(2)} {t('models.credits')}
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-5 w-5"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
window.open(
|
||||
`${systemInfo.cloud_service_url}/billing`,
|
||||
'_blank',
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{!isLangBotModels && (
|
||||
<>
|
||||
<Button
|
||||
@@ -480,8 +506,12 @@ export default function ModelsDialog({
|
||||
<CardHeader className="p-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-2 bg-gradient-to-br from-purple-500 to-blue-500 rounded-lg">
|
||||
<Sparkles className="h-5 w-5 text-white" />
|
||||
<div className="w-9 h-9 rounded-lg overflow-hidden flex-shrink-0">
|
||||
<img
|
||||
src={langbotIcon.src}
|
||||
alt="LangBot"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-base">
|
||||
@@ -492,11 +522,32 @@ export default function ModelsDialog({
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{accountType !== 'space' && (
|
||||
{accountType !== 'space' ? (
|
||||
<Button variant="outline" size="sm" onClick={handleSpaceLogin}>
|
||||
<LogIn className="h-4 w-4 mr-1" />
|
||||
{t('models.loginWithSpace')}
|
||||
</Button>
|
||||
) : (
|
||||
spaceCredits !== null && (
|
||||
<div className="flex items-center gap-1 border rounded-md px-2 h-8 text-sm">
|
||||
<span>
|
||||
{(spaceCredits / 5000).toFixed(2)} {t('models.credits')}
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-5 w-5"
|
||||
onClick={() =>
|
||||
window.open(
|
||||
`${systemInfo.cloud_service_url}/billing`,
|
||||
'_blank',
|
||||
)
|
||||
}
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
@@ -531,12 +582,15 @@ export default function ModelsDialog({
|
||||
<DialogTitle>{t('models.title')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex-1 flex flex-col overflow-hidden px-6 pb-6 mt-4">
|
||||
<div className="flex-1 flex flex-col overflow-hidden px-6 pb-6 mt-0">
|
||||
{/* Fixed LangBot Models Card */}
|
||||
<div className="flex-shrink-0">{renderLangBotModelsCard()}</div>
|
||||
|
||||
{/* Add Model Button */}
|
||||
<div className="flex-shrink-0 mb-3 flex justify-end">
|
||||
<div className="flex-shrink-0 mb-3 flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{t('models.providerCount', { count: otherProviders.length })}
|
||||
</span>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="sm" variant="outline">
|
||||
|
||||
@@ -731,6 +731,10 @@ export class BackendClient extends BaseHttpClient {
|
||||
return this.get('/api/v1/user/info');
|
||||
}
|
||||
|
||||
public getSpaceCredits(): Promise<{ credits: number | null }> {
|
||||
return this.get('/api/v1/user/space-credits');
|
||||
}
|
||||
|
||||
public getAccountInfo(): Promise<{
|
||||
initialized: boolean;
|
||||
account_type?: 'local' | 'space';
|
||||
|
||||
@@ -189,6 +189,7 @@ const enUS = {
|
||||
spaceModelReadOnly: 'Space models are read-only',
|
||||
noSpaceModels: 'No Space models. Click Sync to fetch models from Space.',
|
||||
noLocalModels: 'No local models. Click Create to add a model.',
|
||||
providerCount: '{{count}} providers',
|
||||
// New keys for provider-based structure
|
||||
addModel: 'Add Model',
|
||||
addLLMModel: 'Add LLM Model',
|
||||
@@ -197,17 +198,17 @@ const enUS = {
|
||||
existingProvider: 'Existing Provider',
|
||||
newProvider: 'New Provider',
|
||||
selectProvider: 'Select Provider',
|
||||
requester: 'Requester',
|
||||
selectRequester: 'Select Requester',
|
||||
requester: 'Provider Type',
|
||||
selectRequester: 'Select Provider Type',
|
||||
langbotModelsDescription: 'Cloud models powered by LangBot Space',
|
||||
balance: 'Balance',
|
||||
credits: 'Credits',
|
||||
loginWithSpace: 'Login with Space',
|
||||
loginToUseModels: 'Login with Space to use cloud models',
|
||||
noModels: 'No models configured',
|
||||
editProvider: 'Edit Provider',
|
||||
providerName: 'Provider Name',
|
||||
providerNameRequired: 'Provider name is required',
|
||||
requesterRequired: 'Requester is required',
|
||||
requesterRequired: 'Provider type is required',
|
||||
providerSaved: 'Provider saved',
|
||||
providerCreated: 'Provider created',
|
||||
providerSaveError: 'Failed to save provider: ',
|
||||
|
||||
@@ -195,6 +195,7 @@ const jaJP = {
|
||||
'Space モデルがありません。同期ボタンをクリックして Space からモデルを取得してください。',
|
||||
noLocalModels:
|
||||
'ローカルモデルがありません。作成ボタンをクリックしてモデルを追加してください。',
|
||||
providerCount: '{{count}} 件のプロバイダー',
|
||||
addModel: 'モデルを追加',
|
||||
addLLMModel: 'LLMモデルを追加',
|
||||
addEmbeddingModel: '埋め込みモデルを追加',
|
||||
@@ -202,17 +203,17 @@ const jaJP = {
|
||||
existingProvider: '既存のプロバイダー',
|
||||
newProvider: '新規プロバイダー',
|
||||
selectProvider: 'プロバイダーを選択',
|
||||
requester: 'リクエスター',
|
||||
selectRequester: 'リクエスターを選択',
|
||||
requester: 'プロバイダータイプ',
|
||||
selectRequester: 'プロバイダータイプを選択',
|
||||
langbotModelsDescription: 'LangBot Space が提供するクラウドモデル',
|
||||
balance: '残高',
|
||||
credits: 'クレジット',
|
||||
loginWithSpace: 'Space でログイン',
|
||||
loginToUseModels: 'Space でログインしてクラウドモデルを使用',
|
||||
noModels: 'モデルがありません',
|
||||
editProvider: 'プロバイダーを編集',
|
||||
providerName: 'プロバイダー名',
|
||||
providerNameRequired: 'プロバイダー名は必須です',
|
||||
requesterRequired: 'リクエスターは必須です',
|
||||
requesterRequired: 'プロバイダータイプは必須です',
|
||||
providerSaved: 'プロバイダーを保存しました',
|
||||
providerCreated: 'プロバイダーを作成しました',
|
||||
providerSaveError: 'プロバイダーの保存に失敗しました:',
|
||||
|
||||
@@ -182,6 +182,7 @@ const zhHans = {
|
||||
spaceModelReadOnly: 'Space 模型为只读',
|
||||
noSpaceModels: '暂无 Space 模型。点击同步按钮从 Space 获取模型。',
|
||||
noLocalModels: '暂无本地模型。点击创建按钮添加模型。',
|
||||
providerCount: '共 {{count}} 个供应商',
|
||||
// 供应商结构新增键
|
||||
addModel: '添加模型',
|
||||
addLLMModel: '添加对话模型',
|
||||
@@ -190,17 +191,17 @@ const zhHans = {
|
||||
existingProvider: '已有供应商',
|
||||
newProvider: '新建供应商',
|
||||
selectProvider: '选择供应商',
|
||||
requester: '请求器',
|
||||
selectRequester: '选择请求器',
|
||||
requester: '供应商类型',
|
||||
selectRequester: '选择供应商类型',
|
||||
langbotModelsDescription: 'LangBot Space 提供的云端模型',
|
||||
balance: '余额',
|
||||
credits: '积分',
|
||||
loginWithSpace: '通过 Space 登录',
|
||||
loginToUseModels: '通过 Space 登录以使用云端模型',
|
||||
noModels: '暂无模型',
|
||||
editProvider: '编辑供应商',
|
||||
providerName: '供应商名称',
|
||||
providerNameRequired: '供应商名称不能为空',
|
||||
requesterRequired: '请求器不能为空',
|
||||
requesterRequired: '供应商类型不能为空',
|
||||
providerSaved: '供应商已保存',
|
||||
providerCreated: '供应商已创建',
|
||||
providerSaveError: '保存供应商失败:',
|
||||
|
||||
@@ -182,6 +182,7 @@ const zhHant = {
|
||||
spaceModelReadOnly: 'Space 模型為唯讀',
|
||||
noSpaceModels: '暫無 Space 模型。點擊同步按鈕從 Space 取得模型。',
|
||||
noLocalModels: '暫無本地模型。點擊建立按鈕新增模型。',
|
||||
providerCount: '共 {{count}} 個供應商',
|
||||
addModel: '新增模型',
|
||||
addLLMModel: '新增對話模型',
|
||||
addEmbeddingModel: '新增嵌入模型',
|
||||
@@ -189,17 +190,17 @@ const zhHant = {
|
||||
existingProvider: '現有供應商',
|
||||
newProvider: '新供應商',
|
||||
selectProvider: '選擇供應商',
|
||||
requester: '請求器',
|
||||
selectRequester: '選擇請求器',
|
||||
requester: '供應商類型',
|
||||
selectRequester: '選擇供應商類型',
|
||||
langbotModelsDescription: '由 LangBot Space 提供的雲端模型',
|
||||
balance: '餘額',
|
||||
credits: '積分',
|
||||
loginWithSpace: '使用 Space 登入',
|
||||
loginToUseModels: '使用 Space 登入以使用雲端模型',
|
||||
noModels: '暫無模型',
|
||||
editProvider: '編輯供應商',
|
||||
providerName: '供應商名稱',
|
||||
providerNameRequired: '供應商名稱不能為空',
|
||||
requesterRequired: '請求器不能為空',
|
||||
requesterRequired: '供應商類型不能為空',
|
||||
providerSaved: '供應商已儲存',
|
||||
providerCreated: '供應商已建立',
|
||||
providerSaveError: '儲存供應商失敗:',
|
||||
|
||||
Reference in New Issue
Block a user