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:
93
memento-note/components/brainstorm/live-cursors.tsx
Normal file
93
memento-note/components/brainstorm/live-cursors.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import { PresenceUser } from '@/hooks/use-brainstorm-socket'
|
||||
|
||||
function Cursor({ x, y, name, color }: { x: number; y: number; name: string; color: string }) {
|
||||
return (
|
||||
<div
|
||||
className="absolute pointer-events-none z-50 transition-all duration-75"
|
||||
style={{ transform: `translate(${x}px, ${y}px)` }}
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M0 0L16 6L8 8L6 16L0 0Z" fill={color} />
|
||||
</svg>
|
||||
<div
|
||||
className="mt-3 ml-3 px-2 py-0.5 rounded-full text-[10px] font-bold text-white whitespace-nowrap shadow-lg"
|
||||
style={{ backgroundColor: color }}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function LiveCursors({ others }: { others: PresenceUser[] }) {
|
||||
return (
|
||||
<div className="absolute inset-0 pointer-events-none z-50">
|
||||
{others.map((user) => {
|
||||
if (!user.cursor) return null
|
||||
return (
|
||||
<Cursor
|
||||
key={user.userId}
|
||||
x={user.cursor.x}
|
||||
y={user.cursor.y}
|
||||
name={user.name}
|
||||
color={user.color}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function useCursorTracking(
|
||||
containerRef: React.RefObject<HTMLDivElement | null>,
|
||||
moveCursor: (cursor: { x: number; y: number } | null) => void
|
||||
) {
|
||||
React.useEffect(() => {
|
||||
const container = containerRef.current
|
||||
if (!container) return
|
||||
|
||||
const handleMove = (e: MouseEvent) => {
|
||||
const rect = container.getBoundingClientRect()
|
||||
moveCursor({ x: e.clientX - rect.left, y: e.clientY - rect.top })
|
||||
}
|
||||
|
||||
const handleLeave = () => {
|
||||
moveCursor(null)
|
||||
}
|
||||
|
||||
container.addEventListener('mousemove', handleMove)
|
||||
container.addEventListener('mouseleave', handleLeave)
|
||||
return () => {
|
||||
container.removeEventListener('mousemove', handleMove)
|
||||
container.removeEventListener('mouseleave', handleLeave)
|
||||
}
|
||||
}, [containerRef, moveCursor])
|
||||
}
|
||||
|
||||
export function PresenceAvatars({ others }: { others: PresenceUser[] }) {
|
||||
if (others.length === 0) return null
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
{others.slice(0, 4).map((user) => {
|
||||
const initial = (user.name || '?').charAt(0).toUpperCase()
|
||||
return (
|
||||
<div
|
||||
key={user.userId}
|
||||
className="w-6 h-6 rounded-full flex items-center justify-center text-[9px] font-bold text-white border-2 border-white dark:border-zinc-900 shadow-sm"
|
||||
style={{ backgroundColor: user.color, marginLeft: '-6px' }}
|
||||
title={user.name}
|
||||
>
|
||||
{initial}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{others.length > 4 && (
|
||||
<span className="text-[9px] text-muted-foreground ml-1">+{others.length - 4}</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user