feat: brainstorm sessions, PDF document Q&A, embedding fixes, and UI improvements
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 7s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 7s
- Add brainstorm feature with collaborative canvas, AI idea generation, live cursors, playback, and export - Add PDF upload/extraction/ingestion pipeline with pgvector document search (RAG) - Add document Q&A overlay with streaming chat and PDF preview - Add note attachments UI with status polling, grid layout, and auto-scroll - Add task extraction AI tool and agent executor improvements - Fix NoteEmbedding missing updatedAt column, re-index 66 notes with 1536-dim embeddings - Fix brainstorm 'Create Note' button: add success toast and redirect to created note - Fix memory echo notification infinite polling - Fix chat route to always include document_search tool - Add brainstorm i18n keys across all 14 locales - Add socket server for real-time brainstorm collaboration - Add hierarchical notebook selector and organize notebook dialog improvements - Add sidebar brainstorm section with session management - Update prisma schema with brainstorm tables, attachments, and document chunks
This commit is contained in:
316
memento-note/hooks/use-brainstorm.ts
Normal file
316
memento-note/hooks/use-brainstorm.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
'use client'
|
||||
|
||||
import { useQueryClient, useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { queryKeys } from '@/lib/query-keys'
|
||||
import type { BrainstormSession, BrainstormSessionListItem } from '@/types/brainstorm'
|
||||
|
||||
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()
|
||||
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()
|
||||
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()
|
||||
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,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user