Files
Momento/memento-note/hooks/use-brainstorm.ts
Antigravity 5728452b4a
Some checks failed
CI / Lint, Test & Build (push) Failing after 17s
CI / Deploy production (on server) (push) Has been skipped
feat: add slides generation tool with multiple slide types
- Add slides.tool.ts with support for title, bullets, chart, stats, table, cards, timeline, quote, comparison, equation, image, summary slide types
- Chart types: bar, horizontal-bar, line, donut, radar
- Integrate with agent executor and canvas system
- Add multilingual support (en/fr)
- Various UI improvements and bug fixes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 17:18:48 +00:00

397 lines
12 KiB
TypeScript

'use client'
import { useQueryClient, useMutation, useQuery } from '@tanstack/react-query'
import { queryKeys } from '@/lib/query-keys'
import {
BrainstormQuotaError,
type BrainstormQuotaPayload,
} from '@/lib/brainstorm-quota-client'
import type { BrainstormSession, BrainstormSessionListItem } from '@/types/brainstorm'
function parseBrainstormQuota(res: Response, data: BrainstormQuotaPayload & Record<string, unknown>) {
if (res.status === 402 && data?.error === 'QUOTA_EXCEEDED') {
throw new BrainstormQuotaError(data, 'QUOTA_EXCEEDED')
}
}
/** i18n key for host-pays quota toasts (Story 3.4). */
export function brainstormQuotaMessageKey(err: unknown): string | null {
if (err instanceof BrainstormQuotaError) {
return err.isGuestActor ? 'brainstorm.quotaGuest' : 'brainstorm.quotaHost'
}
return null
}
export interface CreateBrainstormResult {
session: BrainstormSession
contextSummary?: {
support: number
tension: number
extension: number
}
}
export function useBrainstormSessions() {
return useQuery({
queryKey: queryKeys.brainstormSessions(),
queryFn: async (): Promise<BrainstormSessionListItem[]> => {
const res = await fetch('/api/brainstorm', { credentials: 'include' })
const data = await res.json()
return data.data || []
},
})
}
export function useSharedBrainstormSessions() {
return useQuery({
queryKey: queryKeys.brainstormSharedSessions(),
queryFn: async (): Promise<BrainstormSessionListItem[]> => {
const res = await fetch('/api/brainstorm/shared', { credentials: 'include' })
const data = await res.json()
return data.data || []
},
})
}
export interface BrainstormSessionMeta {
role: 'owner' | 'editor' | 'viewer' | 'guest' | 'none'
canEdit: boolean
}
export interface BrainstormSessionResult {
session: BrainstormSession
meta?: BrainstormSessionMeta
}
export function useBrainstormSession(sessionId: string | null) {
return useQuery({
queryKey: queryKeys.brainstormSession(sessionId || ''),
queryFn: async (): Promise<BrainstormSessionResult | null> => {
if (!sessionId) return null
const res = await fetch(`/api/brainstorm/${sessionId}`, { credentials: 'include' })
const data = await res.json()
if (!data.data) return null
return {
session: data.data,
meta: data._meta || undefined,
}
},
enabled: !!sessionId,
})
}
export function useCreateBrainstorm() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (input: {
seedIdea: string
sourceNoteId?: string
contextNoteIds?: string[]
locale?: string
}): Promise<CreateBrainstormResult> => {
const res = await fetch('/api/brainstorm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(input),
})
const data = await res.json()
parseBrainstormQuota(res, data)
if (!data.success) throw new Error(data.error || 'Failed to create brainstorm')
return { session: data.data, contextSummary: data.contextSummary }
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSessions() })
},
})
}
export function useExpandIdea(sessionId: string) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (input: { ideaId: string; locale?: string }): Promise<BrainstormSession> => {
const res = await fetch(`/api/brainstorm/${sessionId}/expand`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(input),
})
const data = await res.json()
parseBrainstormQuota(res, data)
if (!data.success) throw new Error(data.error || 'Failed to expand idea')
return data.data
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSession(sessionId) })
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSessions() })
},
})
}
export function useDismissIdea(sessionId: string) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (ideaId: string) => {
const res = await fetch(`/api/brainstorm/${sessionId}/dismiss`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ ideaId }),
})
const data = await res.json()
if (!data.success) throw new Error(data.error || 'Failed to dismiss idea')
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSession(sessionId) })
},
})
}
export function useConvertIdea(sessionId: string) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (ideaId: string) => {
const res = await fetch(`/api/brainstorm/${sessionId}/convert`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ ideaId }),
})
const data = await res.json()
if (!data.success) throw new Error(data.error || 'Failed to convert idea')
return data.data
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSession(sessionId) })
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSessions() })
},
})
}
export function useExportBrainstorm(sessionId: string) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async () => {
const res = await fetch(`/api/brainstorm/${sessionId}/export`, {
method: 'POST',
credentials: 'include',
})
const data = await res.json()
if (!data.success) throw new Error(data.error || 'Failed to export brainstorm')
return data.data
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSession(sessionId) })
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSessions() })
},
})
}
export function useFinalizeBrainstorm(sessionId: string) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async () => {
const res = await fetch(`/api/brainstorm/${sessionId}/finalize`, {
method: 'POST',
credentials: 'include',
})
const data = await res.json()
if (!data.success) throw new Error(data.error || 'Failed to finalize brainstorm')
return data.impact
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSessions() })
},
})
}
export function useDeleteBrainstorm() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (sessionId: string) => {
const res = await fetch(`/api/brainstorm/${sessionId}`, {
method: 'DELETE',
credentials: 'include',
})
const data = await res.json()
if (!data.success) throw new Error(data.error || 'Failed to delete brainstorm')
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSessions() })
},
})
}
export function useInviteParticipant(sessionId: string) {
return useMutation({
mutationFn: async (input: { role: 'editor' | 'viewer'; expiresInHours?: number; email?: string }) => {
const res = await fetch(`/api/brainstorm/${sessionId}/invite`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(input),
})
const data = await res.json()
if (!data.success) throw new Error(data.error || 'Failed to create invite')
return data
},
})
}
export function useJoinBrainstorm() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (token: string) => {
const res = await fetch(`/api/brainstorm/join`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ token }),
})
const data = await res.json()
if (!data.success) throw new Error(data.error || 'Failed to join')
return data
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSessions() })
},
})
}
export function useAddManualIdea(sessionId: string) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (input: { title: string; description?: string; parentIdeaId?: string; locale?: string }) => {
const res = await fetch(`/api/brainstorm/${sessionId}/manual-idea`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(input),
})
const data = await res.json()
parseBrainstormQuota(res, data)
if (!data.success) throw new Error(data.error || 'Failed to add idea')
return data.data
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSession(sessionId) })
},
})
}
export function useBrainstormActivity(sessionId: string | null) {
return useQuery({
queryKey: ['brainstorm', 'activity', sessionId],
queryFn: async () => {
if (!sessionId) return []
const res = await fetch(`/api/brainstorm/${sessionId}/activity`, { credentials: 'include' })
const data = await res.json()
return data.data || []
},
enabled: !!sessionId,
refetchInterval: 10000,
})
}
export function useUpdateBrainstormSettings(sessionId: string) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (input: { isPublic?: boolean; guestCanEdit?: boolean }) => {
const res = await fetch(`/api/brainstorm/${sessionId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(input),
})
const data = await res.json()
if (!data.success) throw new Error(data.error || 'Failed to update settings')
return data.data
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSession(sessionId) })
},
})
}
export function useBrainstormSnapshots(sessionId: string | null) {
return useQuery({
queryKey: ['brainstorm', 'snapshots', sessionId],
queryFn: async () => {
if (!sessionId) return []
const res = await fetch(`/api/brainstorm/${sessionId}/snapshots`, { credentials: 'include' })
const data = await res.json()
return data.data || []
},
enabled: !!sessionId,
})
}
export function useStarIdea(sessionId: string) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (ideaId: string) => {
const res = await fetch(`/api/brainstorm/${sessionId}/star`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ ideaId }),
})
const data = await res.json()
if (!data.success) throw new Error(data.error || 'Failed to star idea')
return data.data as { ideaId: string; isStarred: boolean }
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSession(sessionId) })
},
})
}
export function useSummarizeBrainstorm(sessionId: string) {
return useMutation({
mutationFn: async (locale?: string): Promise<string> => {
const res = await fetch(`/api/brainstorm/${sessionId}/summarize`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ locale }),
})
const data = await res.json()
if (!data.success) throw new Error(data.error || 'Failed to summarize')
return data.data.summary as string
},
})
}
export function useRenameBrainstormSession(sessionId: string) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (seedIdea: string) => {
const res = await fetch(`/api/brainstorm/${sessionId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ seedIdea }),
})
const data = await res.json()
if (!data.success) throw new Error(data.error || 'Failed to rename session')
return data.data
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSession(sessionId) })
queryClient.invalidateQueries({ queryKey: queryKeys.brainstormSessions() })
},
})
}