feat(ollama): 按模型请求刷新云端用量

将 Ollama Cloud settings HTML 自动抓取从固定间隔轮询改为请求驱动的
trailing debounce + max-wait:无新请求不再抓取,连续请求最晚在
max-wait 强制刷新;失败退避仍优先于活动 due。新增 debounce_minutes
(默认 1),interval_minutes 保留为最长等待兼容字段。
This commit is contained in:
alfadb
2026-07-25 15:37:05 +08:00
parent 43d4bae246
commit 0f72b7dca7
17 changed files with 504 additions and 122 deletions
@@ -38,7 +38,7 @@ describe('admin Ollama Cloud usage API', () => {
})
it('uses dedicated global settings endpoints', async () => {
const settings = { enabled: false, interval_minutes: 60 }
const settings = { enabled: false, interval_minutes: 60, debounce_minutes: 1 }
get.mockResolvedValueOnce({ data: settings })
put.mockResolvedValueOnce({ data: settings })
@@ -335,11 +335,13 @@ export default {
},
ollamaCloudUsage: {
title: 'Ollama Cloud Usage Refresh',
description: 'Periodically refresh official Ollama settings-page usage for individually opted-in accounts. Disabled by default.',
description: 'Refresh official Ollama settings-page usage driven by model requests for individually opted-in accounts. Disabled by default. Idle accounts are not polled.',
enabled: 'Enable global automatic refresh',
enabledHint: 'Only accounts with a stored browser session and their own automatic refresh switch enabled are refreshed. Manual refresh remains available.',
intervalMinutes: 'Refresh interval (minutes)',
intervalHint: 'Range: 15–1440 minutes. Failures use bounded exponential backoff.',
enabledHint: 'Only accounts with a stored browser session and their own automatic refresh switch enabled are refreshed, and only after subsequent model requests. Manual refresh remains available.',
intervalMinutes: 'Max wait while requests continue (minutes)',
intervalHint: 'Range: 15–1440 minutes. When continuous requests keep sliding the debounce, force a refresh after this wait.',
debounceMinutes: 'Quiet period after last request (minutes)',
debounceHint: 'Range: 1–60 minutes. Refresh after the latest model request has been quiet for this long.',
saved: 'Ollama Cloud usage refresh settings saved',
saveFailed: 'Failed to save Ollama Cloud usage refresh settings'
},
@@ -328,11 +328,13 @@ export default {
},
ollamaCloudUsage: {
title: 'Ollama Cloud 用量刷新',
description: '定期刷新账号在 Ollama 官方设置页展示的用量;默认关闭。',
description: '在模型请求驱动下刷新账号在 Ollama 官方设置页展示的用量;默认关闭。无新请求时不会自动抓取。',
enabled: '启用全局自动刷新',
enabledHint: '仅刷新已保存浏览器会话且账号自身也开启自动刷新的账号;手动刷新不受影响。',
intervalMinutes: '刷新周期(分钟)',
intervalHint: '范围 15–1440 分钟。失败后按有上限的指数退避重试。',
enabledHint: '仅刷新已保存浏览器会话且账号自身也开启自动刷新的账号;需有后续模型请求才会触发。手动刷新不受影响。',
intervalMinutes: '持续请求最长等待(分钟)',
intervalHint: '范围 15–1440 分钟。请求持续不断导致 debounce 一直后移时,最晚在此时间强制刷新。',
debounceMinutes: '请求安静等待(分钟)',
debounceHint: '范围 1–60 分钟。最后一次模型请求安静满此时长后再抓取用量。',
saved: 'Ollama Cloud 用量刷新设置已保存',
saveFailed: '保存 Ollama Cloud 用量刷新设置失败'
},
+3
View File
@@ -1023,7 +1023,10 @@ export interface OllamaCloudUsageState {
export interface OllamaCloudUsageSettings {
enabled: boolean
/** Max wait while model requests keep arriving (minutes). */
interval_minutes: number
/** Trailing quiet period after the latest model request (minutes). */
debounce_minutes: number
}
export interface Account {
+38 -17
View File
@@ -4220,23 +4220,43 @@
data-testid="ollama-cloud-usage-global-enabled"
/>
</div>
<div v-if="ollamaCloudUsageForm.enabled" class="border-t border-gray-100 pt-4 dark:border-dark-700">
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300" for="ollama-cloud-usage-interval">
{{ t("admin.settings.ollamaCloudUsage.intervalMinutes") }}
</label>
<input
id="ollama-cloud-usage-interval"
v-model.number="ollamaCloudUsageForm.interval_minutes"
type="number"
min="15"
max="1440"
class="input w-32"
data-testid="ollama-cloud-usage-global-interval"
@keydown.enter.prevent="saveOllamaCloudUsageSettings"
/>
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
{{ t("admin.settings.ollamaCloudUsage.intervalHint") }}
</p>
<div v-if="ollamaCloudUsageForm.enabled" class="space-y-4 border-t border-gray-100 pt-4 dark:border-dark-700">
<div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300" for="ollama-cloud-usage-debounce">
{{ t("admin.settings.ollamaCloudUsage.debounceMinutes") }}
</label>
<input
id="ollama-cloud-usage-debounce"
v-model.number="ollamaCloudUsageForm.debounce_minutes"
type="number"
min="1"
max="60"
class="input w-32"
data-testid="ollama-cloud-usage-global-debounce"
@keydown.enter.prevent="saveOllamaCloudUsageSettings"
/>
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
{{ t("admin.settings.ollamaCloudUsage.debounceHint") }}
</p>
</div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300" for="ollama-cloud-usage-interval">
{{ t("admin.settings.ollamaCloudUsage.intervalMinutes") }}
</label>
<input
id="ollama-cloud-usage-interval"
v-model.number="ollamaCloudUsageForm.interval_minutes"
type="number"
min="15"
max="1440"
class="input w-32"
data-testid="ollama-cloud-usage-global-interval"
@keydown.enter.prevent="saveOllamaCloudUsageSettings"
/>
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
{{ t("admin.settings.ollamaCloudUsage.intervalHint") }}
</p>
</div>
</div>
<div class="flex justify-end border-t border-gray-100 pt-4 dark:border-dark-700">
<button
@@ -7934,6 +7954,7 @@ const ollamaCloudUsageSaving = ref(false);
const ollamaCloudUsageForm = reactive({
enabled: false,
interval_minutes: 60,
debounce_minutes: 1,
});
// Overload Cooldown (529) 状态
@@ -50,6 +50,7 @@ const {
getOllamaCloudUsageSettings: vi.fn().mockResolvedValue({
enabled: false,
interval_minutes: 60,
debounce_minutes: 1,
}),
updateOllamaCloudUsageSettings: vi.fn().mockImplementation(async (payload) => payload),
getGroups: vi.fn(),
@@ -647,6 +648,7 @@ describe("admin SettingsView payment visible method controls", () => {
getOllamaCloudUsageSettings.mockResolvedValue({
enabled: false,
interval_minutes: 60,
debounce_minutes: 1,
});
updateOllamaCloudUsageSettings.mockImplementation(async (payload) => payload);
getGroups.mockResolvedValue([]);
@@ -1007,6 +1009,7 @@ describe("admin SettingsView payment visible method controls", () => {
expect(card.find('[data-testid="ollama-cloud-usage-global-interval"]').exists()).toBe(false);
await card.get('[data-testid="ollama-cloud-usage-global-enabled"]').setValue(true);
await card.get('[data-testid="ollama-cloud-usage-global-debounce"]').setValue(3);
await card.get('[data-testid="ollama-cloud-usage-global-interval"]').setValue(90);
await card.get('[data-testid="ollama-cloud-usage-global-save"]').trigger("click");
await flushPromises();
@@ -1014,6 +1017,7 @@ describe("admin SettingsView payment visible method controls", () => {
expect(updateOllamaCloudUsageSettings).toHaveBeenCalledWith({
enabled: true,
interval_minutes: 90,
debounce_minutes: 3,
});
});