From 51d32fa96d07862adf13c267507330d7f145284d Mon Sep 17 00:00:00 2001 From: liangxu Date: Tue, 12 May 2026 01:54:12 +0800 Subject: [PATCH] fix(admin-web): invalidate workspace cache after every AI-points-consuming call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Top-nav quota chip (会员到期 / 生成文章 / AI 点数) only refreshes when its query is invalidated. Several mutations and stream-based flows that debit points or article quota on the server were not invalidating it, so users had to refresh the page to see updated balances. - TemplateWizardView: invalidate `['workspace']` inside `endAssistTask()`, covering analyze / title / outline assist tasks in one place. - FreeCreateView: invalidate after `articlesApi.create` success. - GenerateTaskDrawer: add `['workspace']` to the instant-generate `Promise.all` alongside the existing `articles` / `instantTasks` keys. - KolPromptEditor: invalidate in the `finally` of both `runAiAssistStream` (generate) and `generateSelectionAiPreview` (optimize) so abort / error paths also refresh. - ArticleEditorCanvas: bring in `useQueryClient` and invalidate in the selection-optimize stream `finally`. Together with the existing invalidations in TemplateWizard.generate, ImitationGenerate, KolGenerate, and article-regenerate, every quota- debiting call site now feeds the cache refresh. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/admin-web/src/components/ArticleEditorCanvas.vue | 3 +++ apps/admin-web/src/components/GenerateTaskDrawer.vue | 1 + apps/admin-web/src/components/kol/KolPromptEditor.vue | 2 ++ apps/admin-web/src/views/FreeCreateView.vue | 1 + apps/admin-web/src/views/TemplateWizardView.vue | 1 + 5 files changed, 8 insertions(+) diff --git a/apps/admin-web/src/components/ArticleEditorCanvas.vue b/apps/admin-web/src/components/ArticleEditorCanvas.vue index 79e56a9..d301a80 100644 --- a/apps/admin-web/src/components/ArticleEditorCanvas.vue +++ b/apps/admin-web/src/components/ArticleEditorCanvas.vue @@ -57,6 +57,7 @@ import { } from '@milkdown/kit/prose/state' import { Decoration, DecorationSet, type EditorView } from '@milkdown/kit/prose/view' import { callCommand, replaceAll } from '@milkdown/kit/utils' +import { useQueryClient } from '@tanstack/vue-query' import { message } from 'ant-design-vue' // NOTE: import each common stylesheet explicitly instead of the aggregate // `theme/common/style.css`. The aggregate transitively `@import`s `latex.css`, @@ -121,6 +122,7 @@ const emit = defineEmits<{ }>() const { t } = useI18n() +const queryClient = useQueryClient() const crepeRef = shallowRef(null) const syncingExternalMarkdown = ref(false) const editorRootRef = ref(null) @@ -1481,6 +1483,7 @@ async function generateAiOptimizePreview(): Promise { if (aiOptimizeAbortController === controller) { aiOptimizeAbortController = null } + void queryClient.invalidateQueries({ queryKey: ['workspace'] }) } } diff --git a/apps/admin-web/src/components/GenerateTaskDrawer.vue b/apps/admin-web/src/components/GenerateTaskDrawer.vue index d65b0de..6a393e8 100644 --- a/apps/admin-web/src/components/GenerateTaskDrawer.vue +++ b/apps/admin-web/src/components/GenerateTaskDrawer.vue @@ -173,6 +173,7 @@ const instantGenerateMutation = useMutation({ await Promise.all([ queryClient.invalidateQueries({ queryKey: ['articles'] }), queryClient.invalidateQueries({ queryKey: ['instantTasks'] }), + queryClient.invalidateQueries({ queryKey: ['workspace'] }), ]) }, onError: (error) => message.error(formatError(error)), diff --git a/apps/admin-web/src/components/kol/KolPromptEditor.vue b/apps/admin-web/src/components/kol/KolPromptEditor.vue index e74115f..c5de98f 100644 --- a/apps/admin-web/src/components/kol/KolPromptEditor.vue +++ b/apps/admin-web/src/components/kol/KolPromptEditor.vue @@ -275,6 +275,7 @@ async function runAiAssistStream(payload: KolAssistRequest) { if (aiAssistAbortController === controller) { aiAssistAbortController = null } + void queryClient.invalidateQueries({ queryKey: ['workspace'] }) } } @@ -411,6 +412,7 @@ async function generateSelectionAiPreview() { if (selectionAiAbortController === controller) { selectionAiAbortController = null } + void queryClient.invalidateQueries({ queryKey: ['workspace'] }) } } diff --git a/apps/admin-web/src/views/FreeCreateView.vue b/apps/admin-web/src/views/FreeCreateView.vue index 4454d85..13b2dd5 100644 --- a/apps/admin-web/src/views/FreeCreateView.vue +++ b/apps/admin-web/src/views/FreeCreateView.vue @@ -95,6 +95,7 @@ const deleteMutation = useMutation({ const createMutation = useMutation({ mutationFn: () => articlesApi.create({}), onSuccess: (data) => { + void queryClient.invalidateQueries({ queryKey: ['workspace'] }) void router.push({ name: 'article-editor', params: { id: String(data.id) } }) }, onError: (error) => { diff --git a/apps/admin-web/src/views/TemplateWizardView.vue b/apps/admin-web/src/views/TemplateWizardView.vue index 7638ebc..7edf042 100644 --- a/apps/admin-web/src/views/TemplateWizardView.vue +++ b/apps/admin-web/src/views/TemplateWizardView.vue @@ -839,6 +839,7 @@ function beginAssistTask(kind: 'analyze' | 'title' | 'outline'): void { } function endAssistTask(): void { + void queryClient.invalidateQueries({ queryKey: ['workspace'] }) window.setTimeout(() => { assistModalOpen.value = false assistBusy.value = false