From f79d02130741de83b236a7b142fbaa8e5abca61e Mon Sep 17 00:00:00 2001 From: gouki Date: Sat, 1 Aug 2026 23:59:30 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9EAI=E8=B4=A6=E5=8F=B7?= =?UTF-8?q?=E8=81=9A=E5=90=88=E8=A7=86=E5=9B=BE=EF=BC=88=E6=8C=89=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E5=88=86=E7=BB=84+=E4=BD=99=E9=A2=9D=E5=90=88?= =?UTF-8?q?=E8=AE=A1+=E9=A2=84=E8=AD=A6+=E4=B8=80=E9=94=AE=E5=8A=A0?= =?UTF-8?q?=E5=8F=B7=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/js/app.js | 84 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 5 deletions(-) diff --git a/static/js/app.js b/static/js/app.js index 8665e35..a1d4623 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -83,6 +83,17 @@ function openAssetCreate(presetType) { if (presetType) form.asset_type = presetType; store.assetModal = { show: true, editing: null, form }; } +function openAICreate(provider) { + const form = emptyAssetForm(); + form.asset_type = 'ai_agent'; + if (provider) { + form.provider = provider; + form.ai_detail.provider = provider; + const p = store.providers.find(x => x.slug === provider); + if (p) form.provider_id = p.id; + } + store.assetModal = { show: true, editing: null, form }; +} function openAssetEdit(a) { const form = emptyAssetForm(); ['name', 'asset_type', 'provider', 'provider_id', 'renewal_cycle', 'account', 'expiry_date', 'auto_renew', 'cost', 'currency', 'status', 'is_archived', 'remark'].forEach(k => { form[k] = a[k]; }); @@ -406,6 +417,66 @@ const DomainsView = { }, }; +const AIAccountsView = { + template: ` +
+
+
共 {{ totalAccounts }} 个账号 · 总余额 {{ totalBalance }}
+ +
+
+
+
+ {{ g.provider }} + {{ g.accounts.length }} 个账号 +
+
+ 余额合计 {{ g.totalBalance }} + +
+
+
+
+
+ {{ a.name }} + {{ a.ai_detail.plan }} +
+
+ 余额 {{ fmtBal(a.ai_detail && a.ai_detail.balance) }}{{ a.ai_detail && a.ai_detail.balance !== null && a.ai_detail.balance !== undefined ? ' ' + a.ai_detail.currency : '' }} + 用量 {{ a.ai_detail.monthly_usage }}/{{ a.ai_detail.monthly_limit ?? '∞' }} + +
+
+
+
+
暂无 AI 账号,点右上角「新增账号」添加(同一平台可添加多个)
+
`, + setup() { + const aiList = () => store.assets.filter(a => a.asset_type === 'ai_agent'); + const groups = computed(() => { + const map = {}; + for (const a of aiList()) { + const prov = (a.ai_detail && a.ai_detail.provider) || a.provider || '其他'; + if (!map[prov]) map[prov] = { provider: prov, accounts: [], totalBalance: 0 }; + map[prov].accounts.push(a); + const bal = a.ai_detail && a.ai_detail.balance; + if (bal) map[prov].totalBalance += Number(bal); + } + return Object.values(map).map(g => ({ ...g, totalBalance: Math.round(g.totalBalance * 100) / 100 })).sort((x, y) => y.totalBalance - x.totalBalance); + }); + const totalAccounts = computed(() => aiList().length); + const totalBalance = computed(() => Math.round(aiList().reduce((s, a) => s + (a.ai_detail && a.ai_detail.balance ? Number(a.ai_detail.balance) : 0), 0) * 100) / 100); + function balanceClass(b) { + if (b === null || b === undefined) return 'text-slate-400'; + if (Number(b) < 10) return 'text-red-600 dark:text-red-400 font-semibold'; + if (Number(b) < 50) return 'text-amber-600 dark:text-amber-400'; + return 'text-emerald-600 dark:text-emerald-400'; + } + function fmtBal(b) { return (b === null || b === undefined) ? '—' : b; } + return { store, groups, totalAccounts, totalBalance, balanceClass, fmtBal, add: openAICreate, addAny: () => openAICreate(''), edit: openAssetEdit }; + }, +}; + const SettingsView = { template: `
@@ -621,17 +692,19 @@ const AppRoot = { { key: 'assets', label: '资产', icon: '📦' }, { key: 'servers', label: '服务器', icon: '🖥️' }, { key: 'domains', label: '域名', icon: '🌐' }, + { key: 'ai', label: 'AI账号', icon: '🤖' }, { key: 'providers', label: '平台', icon: '🏢' }, { key: 'settings', label: '设置', icon: '⚙️' }, ]; - const viewMap = { dashboard: 'dashboard-view', assets: 'assets-view', servers: 'servers-view', domains: 'domains-view', providers: 'providers-view', settings: 'settings-view' }; + const viewMap = { dashboard: 'dashboard-view', assets: 'assets-view', servers: 'servers-view', domains: 'domains-view', ai: 'ai-view', providers: 'providers-view', settings: 'settings-view' }; const currentNav = computed(() => navs.find(n => n.key === store.view) || navs[0]); const viewComponent = computed(() => viewMap[store.view] || 'dashboard-view'); - function go(key) { store.view = key; if (key !== 'servers') store.serverMetrics = {}; } + function go(key) { store.view = key; if (key !== 'servers') store.serverMetrics = {}; if (key === 'ai') { store.filterType = ''; store.filterStatus = ''; store.search = ''; loadAssets(); } } function quickAdd() { - const preset = store.view === 'domains' ? 'domain' : (store.view === 'providers' ? null : 'vps'); - if (store.view === 'providers') openProviderCreate(); - else openAssetCreate(preset); + if (store.view === 'providers') return openProviderCreate(); + if (store.view === 'ai') return openAICreate(''); + const preset = store.view === 'domains' ? 'domain' : 'vps'; + openAssetCreate(preset); } function toggleDark() { store.dark = !store.dark; applyDark(); } return { store, appName, navs, currentNav, viewComponent, go, quickAdd, toggleDark, reload: loadAssets }; @@ -645,6 +718,7 @@ app.component('assets-view', AssetsView); app.component('providers-view', ProvidersView); app.component('servers-view', ServersView); app.component('domains-view', DomainsView); +app.component('ai-view', AIAccountsView); app.component('settings-view', SettingsView); applyDark(); loadAll();