mirror of
https://github.com/supabase/supabase.git
synced 2026-09-06 09:59:03 +08:00
## Summary - record server divergence when an assistant changes a notebook with local unsaved edits - clear the session-only marker after a successful save or notebook removal - cover update, delete, saved eviction, and lifecycle behavior ## Verification - pnpm --dir apps/studio exec vitest run state/notebooks/notebooks-state.test.ts lib/ai/notebook-cache-invalidation.test.ts - pnpm --dir apps/studio exec eslint state/notebooks/notebooks-state.ts state/notebooks/notebooks-state.test.ts lib/ai/notebook-cache-invalidation.ts lib/ai/notebook-cache-invalidation.test.ts - pnpm --dir apps/studio typecheck Stacked on the approval-warning PR for FE-4255. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved handling of server changes to notebooks with unsaved local edits. * Server updates and deletions are now tracked as divergences instead of being silently skipped. * Divergence indicators are cleared when changes are saved or notebooks are removed. * Unrelated notebook changes no longer create false conflicts. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
82 lines
2.4 KiB
TypeScript
82 lines
2.4 KiB
TypeScript
import type { QueryClient } from '@tanstack/react-query'
|
|
import type { ToolUIPart, UIMessage } from 'ai'
|
|
|
|
import { notebookToolOutputSchema } from '@/components/ui/AIAssistantPanel/Message.utils'
|
|
import { contentKeys } from '@/data/content/keys'
|
|
import { evictNotebookFromCaches } from '@/data/content/notebooks/notebook-cache'
|
|
import { notebooksState } from '@/state/notebooks/notebooks-state'
|
|
|
|
export type NotebookCacheEffect =
|
|
| { _tag: 'upserted'; toolCallId: string; id: string }
|
|
| { _tag: 'deleted'; toolCallId: string; id: string }
|
|
|
|
const NOTEBOOK_MUTATION_TOOL_TYPES = new Set([
|
|
'tool-create_notebook',
|
|
'tool-update_notebook',
|
|
'tool-delete_notebook',
|
|
])
|
|
|
|
function isNotebookMutationPart(part: UIMessage['parts'][number]): part is ToolUIPart {
|
|
return NOTEBOOK_MUTATION_TOOL_TYPES.has(part.type)
|
|
}
|
|
|
|
export function collectNotebookCacheEffects(
|
|
messages: Array<UIMessage>,
|
|
processed: ReadonlySet<string>
|
|
): Array<NotebookCacheEffect> {
|
|
const effects: Array<NotebookCacheEffect> = []
|
|
|
|
for (const message of messages) {
|
|
if (message.role !== 'assistant') continue
|
|
|
|
for (const part of message.parts ?? []) {
|
|
if (!isNotebookMutationPart(part)) continue
|
|
if (part.state !== 'output-available') continue
|
|
if (processed.has(part.toolCallId)) continue
|
|
|
|
const result = notebookToolOutputSchema.safeParse(part.output)
|
|
if (!result.success) continue
|
|
|
|
effects.push({
|
|
_tag: part.type === 'tool-delete_notebook' ? 'deleted' : 'upserted',
|
|
toolCallId: part.toolCallId,
|
|
id: result.data.id,
|
|
})
|
|
}
|
|
}
|
|
|
|
return effects
|
|
}
|
|
|
|
export async function applyNotebookCacheEffects({
|
|
queryClient,
|
|
projectRef,
|
|
effects,
|
|
}: {
|
|
queryClient: QueryClient
|
|
projectRef: string
|
|
effects: Array<NotebookCacheEffect>
|
|
}): Promise<void> {
|
|
if (effects.length === 0) return
|
|
|
|
await Promise.all([
|
|
queryClient.invalidateQueries({ queryKey: contentKeys.allContentLists(projectRef) }),
|
|
queryClient.invalidateQueries({ queryKey: contentKeys.infiniteList(projectRef) }),
|
|
])
|
|
|
|
await Promise.all(
|
|
effects.map((effect) => {
|
|
const stateNotebook = notebooksState.notebooks[effect.id]
|
|
if (stateNotebook && stateNotebook.status !== 'saved') {
|
|
notebooksState.markServerDivergence({
|
|
id: effect.id,
|
|
type: effect._tag === 'deleted' ? 'deleted' : 'updated',
|
|
})
|
|
return
|
|
}
|
|
|
|
return evictNotebookFromCaches({ queryClient, projectRef, id: effect.id })
|
|
})
|
|
)
|
|
}
|