fix: brainstorm infinite loop, ghost cursor, embedding ::vector cast, semantic search, billing stats, usage meter accordion
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 5s
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
This commit is contained in:
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { paragraphRefactorService } from '@/lib/ai/services/paragraph-refactor.service'
|
||||
import { getAISettings } from '@/app/actions/ai-settings'
|
||||
import { checkEntitlementOrThrow, QuotaExceededError, incrementUsageAsync } from '@/lib/entitlements'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -52,9 +53,27 @@ export async function POST(request: NextRequest) {
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
// Check quota
|
||||
try {
|
||||
await checkEntitlementOrThrow(session.user.id, 'reformulate')
|
||||
} catch (err) {
|
||||
if (err instanceof QuotaExceededError) {
|
||||
const isTierLocked = err.currentQuota === 0
|
||||
return NextResponse.json({
|
||||
error: isTierLocked ? 'feature_locked' : 'quota_exceeded',
|
||||
errorKey: isTierLocked ? 'ai.featureLocked' : 'ai.quotaExceeded',
|
||||
upgradeTier: err.upgradeTier,
|
||||
quotaExceeded: true,
|
||||
}, { status: 402 })
|
||||
}
|
||||
throw err
|
||||
}
|
||||
|
||||
// Use the ParagraphRefactorService
|
||||
const result = await paragraphRefactorService.refactor(text, mode, format === 'html' ? 'html' : 'markdown', language)
|
||||
|
||||
incrementUsageAsync(session.user.id, 'reformulate')
|
||||
|
||||
return NextResponse.json({
|
||||
originalText: result.original,
|
||||
reformulatedText: result.refactored,
|
||||
|
||||
@@ -67,7 +67,7 @@ async function getParentContext(
|
||||
FROM "NoteEmbedding" e
|
||||
JOIN "Note" n ON n.id = e."noteId"
|
||||
WHERE n."userId" = $1 AND n."trashedAt" IS NULL AND n.id NOT IN (${excludeList})
|
||||
ORDER BY e.embedding <=> $2::vector
|
||||
ORDER BY e.embedding::vector <=> $2::vector
|
||||
LIMIT 5`,
|
||||
hostUserId, vectorStr
|
||||
) as any[]
|
||||
|
||||
@@ -131,7 +131,7 @@ export async function POST(
|
||||
FROM "NoteEmbedding" e
|
||||
JOIN "Note" n ON n.id = e."noteId"
|
||||
WHERE n.id IN (${idList}) AND n."trashedAt" IS NULL
|
||||
ORDER BY e.embedding <=> $1::vector
|
||||
ORDER BY e.embedding::vector <=> $1::vector
|
||||
LIMIT 3`,
|
||||
vectorStr
|
||||
) as any[]
|
||||
@@ -142,7 +142,7 @@ export async function POST(
|
||||
FROM "NoteEmbedding" e
|
||||
JOIN "Note" n ON n.id = e."noteId"
|
||||
WHERE n."userId" = $1 AND n."trashedAt" IS NULL
|
||||
ORDER BY e.embedding <=> $2::vector
|
||||
ORDER BY e.embedding::vector <=> $2::vector
|
||||
LIMIT 3`,
|
||||
aiUserId, vectorStr
|
||||
) as any[]
|
||||
|
||||
@@ -44,7 +44,7 @@ async function autoContextSearch(
|
||||
FROM "NoteEmbedding" e
|
||||
JOIN "Note" n ON n.id = e."noteId"
|
||||
WHERE n."userId" = $1 AND n."trashedAt" IS NULL
|
||||
ORDER BY e.embedding <=> $2::vector
|
||||
ORDER BY e.embedding::vector <=> $2::vector
|
||||
LIMIT 8`,
|
||||
userId, vectorStr
|
||||
) as any[]
|
||||
|
||||
@@ -146,7 +146,7 @@ export async function PUT(
|
||||
})
|
||||
|
||||
// Revalidate to refresh UI
|
||||
revalidatePath('/')
|
||||
revalidatePath('/home')
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
@@ -253,7 +253,7 @@ export async function DELETE(
|
||||
})
|
||||
|
||||
// Revalidate to refresh UI
|
||||
revalidatePath('/')
|
||||
revalidatePath('/home')
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
|
||||
@@ -6,7 +6,7 @@ export async function GET() {
|
||||
name: "Memento Notes",
|
||||
short_name: "Memento",
|
||||
description: "A smart, local-first note taking app with AI capabilities.",
|
||||
start_url: "/",
|
||||
start_url: "/home",
|
||||
display: "standalone",
|
||||
background_color: "#F2F0E9",
|
||||
theme_color: "#1C1C1C",
|
||||
|
||||
@@ -98,7 +98,7 @@ export async function PATCH(
|
||||
})
|
||||
}
|
||||
|
||||
try { revalidatePath('/') } catch {}
|
||||
try { revalidatePath('/home') } catch {}
|
||||
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error) {
|
||||
@@ -143,7 +143,7 @@ export async function DELETE(
|
||||
|
||||
await prisma.notebook.delete({ where: { id } })
|
||||
|
||||
try { revalidatePath('/') } catch {}
|
||||
try { revalidatePath('/home') } catch {}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
|
||||
@@ -47,7 +47,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
await prisma.$transaction(updates)
|
||||
|
||||
revalidatePath('/')
|
||||
revalidatePath('/home')
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
|
||||
@@ -105,7 +105,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
})
|
||||
|
||||
try { revalidatePath('/') } catch {}
|
||||
try { revalidatePath('/home') } catch {}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
|
||||
@@ -90,7 +90,7 @@ export async function POST(
|
||||
console.error('[HISTORY] Failed to create snapshot after notebook move:', snapshotError)
|
||||
}
|
||||
|
||||
// No revalidatePath('/') here — the client-side triggerRefresh() in
|
||||
// No revalidatePath('/home') here — the client-side triggerRefresh() in
|
||||
// notebooks-context.tsx handles the refresh. Avoiding server-side
|
||||
// revalidation prevents a double-refresh (server + client).
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export async function POST(req: NextRequest) {
|
||||
})
|
||||
|
||||
// Revalidate paths
|
||||
revalidatePath('/')
|
||||
revalidatePath('/home')
|
||||
revalidatePath('/settings/data')
|
||||
|
||||
// Await cleanup in background (don't block response)
|
||||
|
||||
@@ -222,7 +222,7 @@ export async function POST(req: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
revalidatePath('/')
|
||||
revalidatePath('/home')
|
||||
revalidatePath('/settings/data')
|
||||
|
||||
return NextResponse.json({ success: true, ...stats })
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { auth } from '@/auth';
|
||||
import { getUserQuotas } from '@/lib/entitlements';
|
||||
import { getUserQuotas, getEffectiveTier } from '@/lib/entitlements';
|
||||
|
||||
export async function GET() {
|
||||
const session = await auth();
|
||||
@@ -10,8 +10,11 @@ export async function GET() {
|
||||
}
|
||||
|
||||
try {
|
||||
const quotas = await getUserQuotas(session.user.id);
|
||||
return NextResponse.json({ quotas });
|
||||
const [quotas, tier] = await Promise.all([
|
||||
getUserQuotas(session.user.id),
|
||||
getEffectiveTier(session.user.id),
|
||||
]);
|
||||
return NextResponse.json({ quotas, tier });
|
||||
} catch (error) {
|
||||
console.error('[usage/current] Failed to fetch quotas:', error);
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -8,6 +8,10 @@ export async function GET(request: NextRequest) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
if ((session.user as any)?.role !== 'ADMIN') {
|
||||
return NextResponse.json({ users: [] })
|
||||
}
|
||||
|
||||
const q = request.nextUrl.searchParams.get('q') || ''
|
||||
if (q.length < 2) {
|
||||
return NextResponse.json({ users: [] })
|
||||
|
||||
Reference in New Issue
Block a user