All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 5s
- Fix useBrainstormSocket: stable guestId via useRef, remove setState in cleanup - Fix GhostCursor: direct DOM manipulation via refs, no useState re-renders - Fix all SQL embedding queries: add ::vector cast on text columns - Fix embedding truncation to 15000 chars (under 8192 token limit) - Fix NoteEmbedding INSERT: remove non-existent updatedAt column - Fix billing page: show all quota stats in grid instead of single metric - Fix usage meter: accordion expand/collapse, per-feature detail - Fix semantic search: rebuild 103 note embeddings, ::vector cast on vectorSearch - Fix brainstorm expand/manual-idea/create: ::vector cast on embedding SQL
110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useRef, useState, useCallback } from 'react'
|
|
import { io, Socket } from 'socket.io-client'
|
|
|
|
export interface PresenceUser {
|
|
userId: string
|
|
name: string
|
|
cursor: { x: number; y: number } | null
|
|
color: string
|
|
}
|
|
|
|
export interface ActivityEvent {
|
|
action: string
|
|
userId: string
|
|
userName: string
|
|
details: any
|
|
}
|
|
|
|
const SOCKET_URL = process.env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3001'
|
|
|
|
export function useBrainstormSocket(
|
|
sessionId: string | null,
|
|
userId: string | null,
|
|
userName: string | null,
|
|
onIdeaMoved?: (data: { ideaId: string; positionX: number; positionY: number }) => void
|
|
) {
|
|
const socketRef = useRef<Socket | null>(null)
|
|
const onIdeaMovedRef = useRef(onIdeaMoved)
|
|
onIdeaMovedRef.current = onIdeaMoved
|
|
const [others, setOthers] = useState<PresenceUser[]>([])
|
|
const [activities, setActivities] = useState<ActivityEvent[]>([])
|
|
const [aiProcessingNodeId, setAiProcessingNodeId] = useState<string | null>(null)
|
|
|
|
const guestIdRef = useRef<string | null>(null)
|
|
if (!guestIdRef.current && !userId) {
|
|
guestIdRef.current = `guest_${Math.random().toString(36).slice(2, 10)}`
|
|
}
|
|
const effectiveUserId = userId || guestIdRef.current!
|
|
|
|
useEffect(() => {
|
|
if (!sessionId) return
|
|
|
|
const socket = io(SOCKET_URL, {
|
|
auth: {
|
|
userId: effectiveUserId,
|
|
sessionId,
|
|
name: userName || 'Guest',
|
|
isGuest: !userId,
|
|
},
|
|
transports: ['websocket'],
|
|
autoConnect: true,
|
|
})
|
|
|
|
socketRef.current = socket
|
|
|
|
socket.on('presence:update', (users: PresenceUser[]) => {
|
|
setOthers(users.filter(u => u.userId !== effectiveUserId))
|
|
})
|
|
|
|
socket.on('cursor:update', (data: { userId: string; cursor: { x: number; y: number } }) => {
|
|
setOthers(prev => prev.map(u =>
|
|
u.userId === data.userId ? { ...u, cursor: data.cursor } : u
|
|
))
|
|
})
|
|
|
|
socket.on('activity:new', (event: ActivityEvent) => {
|
|
setActivities(prev => [event, ...prev].slice(0, 50))
|
|
})
|
|
|
|
socket.on('idea:added', () => {})
|
|
socket.on('idea:dismissed', () => {})
|
|
socket.on('idea:moved', (data: { ideaId: string; positionX: number; positionY: number }) => {
|
|
onIdeaMovedRef.current?.(data)
|
|
})
|
|
|
|
socket.on('idea:ai_processing', (data: { ideaId: string }) => {
|
|
setAiProcessingNodeId(data.ideaId)
|
|
})
|
|
|
|
socket.on('idea:ai_completed', (data: { ideaId: string }) => {
|
|
setAiProcessingNodeId(prev => prev === data.ideaId ? null : prev)
|
|
})
|
|
|
|
socket.on('idea:ai_failed', (data: { ideaId: string }) => {
|
|
setAiProcessingNodeId(prev => prev === data.ideaId ? null : prev)
|
|
})
|
|
|
|
return () => {
|
|
socket.disconnect()
|
|
socketRef.current = null
|
|
}
|
|
}, [sessionId, effectiveUserId, userName])
|
|
|
|
const moveCursor = useCallback((cursor: { x: number; y: number } | null) => {
|
|
socketRef.current?.emit('cursor:move', cursor)
|
|
}, [])
|
|
|
|
const broadcastActivity = useCallback((action: string, details?: any) => {
|
|
socketRef.current?.emit('activity:new', {
|
|
action,
|
|
userId: effectiveUserId,
|
|
userName: userName || 'Guest',
|
|
details,
|
|
})
|
|
}, [effectiveUserId, userName])
|
|
|
|
return { others, activities, moveCursor, broadcastActivity, socketRef, aiProcessingNodeId }
|
|
}
|