Merge pull request #6388 from alfadb/feature/ollama-usage-cn-platforms

feat(ollama): 用量窗口支持挂在国产三家平台下的 Ollama Cloud 账号
This commit is contained in:
Wesley Liddick
2026-08-31 11:16:25 +08:00
committed by GitHub
12 changed files with 390 additions and 15 deletions
@@ -431,7 +431,16 @@
<!-- CN providers (Kimi / Zhipu / DeepSeek): coding-plan quota or payg balance -->
<template v-else-if="account.platform === 'kimi' || account.platform === 'zhipu' || account.platform === 'deepseek'">
<div class="space-y-1">
<!-- 挂在 CN 平台下的 Ollama Cloud 账号(资格由后端下发 eligible):用量由
Ollama 用量窗口负责。这类账号不是国产厂商订阅,CN 的额度/余额探测端点由
base_url 衍生,对 ollama.com 会被后端出站 URL 白名单拒绝,渲染出来只会
给用户一行探测报错,因此不再渲染 CN 子单元格与占位符。 -->
<OllamaCloudUsageCell
v-if="account.ollama_cloud_usage?.eligible"
:account="account"
@updated="handleOllamaCloudUsageUpdated"
/>
<div v-else class="space-y-1">
<!-- 子单元格各自按 模式×平台 判定可见;两者都不可见时(智谱 payg 无公开
余额端点、coding 探测也不适用)才回落到占位符。 -->
<div
@@ -54,6 +54,40 @@ function makeAccount(overrides: Partial<Account>): Account {
}
}
function makeOllamaUsage(accountId: number, overrides: Partial<NonNullable<Account['ollama_cloud_usage']>> = {}) {
return {
account_id: accountId,
eligible: true,
configured: true,
auto_refresh_enabled: true,
encryption_key_configured: true,
snapshot: {
status: 'ok' as const,
last_attempt_at: '2026-07-23T00:00:00Z',
next_refresh_at: '2026-07-23T01:00:00Z',
data: {
five_hour: { used_percent: 12 },
seven_day: { used_percent: 34 }
}
},
...overrides,
}
}
// CN 平台 Ollama Cloud 用例共用的子组件 stub:按 data-test 断言渲染与否
const cnUsageCellStubs = {
OllamaCloudUsageCell: {
props: ['account'],
template: '<div data-test="embedded-ollama">ollama</div>'
},
CNProviderQuotaCell: {
template: '<div data-test="cn-quota-cell" />'
},
CNProviderBalanceCell: {
template: '<div data-test="cn-balance-cell" />'
}
}
describe('AccountUsageCell', () => {
beforeEach(() => {
getUsage.mockReset()
@@ -120,6 +154,92 @@ describe('AccountUsageCell', () => {
expect(updatedAccount?.ollama_cloud_usage?.auto_refresh_enabled).toBe(false)
})
it.each(['kimi', 'zhipu', 'deepseek'] as const)(
'%s apikey 账号 Ollama Cloud eligible 时渲染 Ollama 用量单元格并跳过 CN 子单元格',
async (platform) => {
const wrapper = mount(AccountUsageCell, {
props: {
account: makeAccount({
id: 9002,
platform,
type: 'apikey',
credentials: { account_mode: 'coding' },
ollama_cloud_usage: makeOllamaUsage(9002)
})
},
global: {
stubs: { ...cnUsageCellStubs, UsageProgressBar: true, AccountQuotaInfo: true }
}
})
await flushPromises()
expect(wrapper.find('[data-test="embedded-ollama"]').exists()).toBe(true)
expect(wrapper.find('[data-test="cn-quota-cell"]').exists()).toBe(false)
expect(wrapper.find('[data-test="cn-balance-cell"]').exists()).toBe(false)
expect(wrapper.find('div[title="admin.accounts.cnProviders.noBalanceEndpoint"]').exists()).toBe(false)
expect(getUsage).not.toHaveBeenCalled()
}
)
it('CN 平台 Ollama Cloud eligible 账号的用量更新经 account-updated 透传', async () => {
const wrapper = mount(AccountUsageCell, {
props: {
account: makeAccount({
id: 9003,
platform: 'kimi',
type: 'apikey',
credentials: { account_mode: 'coding' },
ollama_cloud_usage: makeOllamaUsage(9003)
})
},
global: {
stubs: {
...cnUsageCellStubs,
OllamaCloudUsageCell: {
props: ['account'],
emits: ['updated'],
template: '<button data-test="embedded-ollama" @click="$emit(\'updated\', { ...account.ollama_cloud_usage, auto_refresh_enabled: false })" />'
},
UsageProgressBar: true,
AccountQuotaInfo: true
}
}
})
await wrapper.get('[data-test="embedded-ollama"]').trigger('click')
const updatedAccount = wrapper.emitted<Account[]>('account-updated')?.[0]?.[0]
expect(updatedAccount?.id).toBe(9003)
expect(updatedAccount?.ollama_cloud_usage?.auto_refresh_enabled).toBe(false)
})
it.each([
{ name: '无 ollama_cloud_usage', usage: undefined },
{ name: 'eligible=false', usage: makeOllamaUsage(9004, { eligible: false }) }
])('普通 kimi apikey 账号($name)仍渲染 CN 子单元格', async ({ usage }) => {
const wrapper = mount(AccountUsageCell, {
props: {
account: makeAccount({
id: 9004,
platform: 'kimi',
type: 'apikey',
credentials: { account_mode: 'coding' },
ollama_cloud_usage: usage
})
},
global: {
stubs: { ...cnUsageCellStubs, UsageProgressBar: true, AccountQuotaInfo: true }
}
})
await flushPromises()
expect(wrapper.find('[data-test="cn-quota-cell"]').exists()).toBe(true)
expect(wrapper.find('[data-test="cn-balance-cell"]').exists()).toBe(true)
expect(wrapper.find('[data-test="embedded-ollama"]').exists()).toBe(false)
})
it('Antigravity 图片用量会聚合新旧 image 模型', async () => {
getUsage.mockResolvedValue({
antigravity_quota: {