Critical fixes for core functionality: 1. Fixed grapheme-aware text splitting (app/api/nodes/route.ts) - Changed character-based substring to grapheme-ratio calculation - Now properly handles emojis and multi-byte characters - Prevents posts from exceeding 300 grapheme Bluesky limit - Added comprehensive logging for debugging 2. Automatic UMAP coordinate calculation (app/api/nodes/route.ts) - Triggers /api/calculate-graph automatically after node creation - Only when user has 3+ nodes with embeddings (UMAP minimum) - Non-blocking background process - Eliminates need for manual "Calculate Graph" button - Galaxy visualization ready on first visit 3. Simplified galaxy route (app/api/galaxy/route.ts) - Removed auto-trigger logic (now handled on insertion) - Simply returns existing coordinates - More efficient, no redundant calculations 4. Added idempotency (app/api/calculate-graph/route.ts) - Safe to call multiple times - Returns early if all nodes already have coordinates - Better logging for debugging Implementation plans documented in /plans directory. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
101 lines
3.3 KiB
TypeScript
101 lines
3.3 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { cookies } from 'next/headers';
|
|
import { UMAP } from 'umap-js';
|
|
import { connectToDB } from '@/lib/db';
|
|
import { verifySurrealJwt } from '@/lib/auth/jwt';
|
|
|
|
/**
|
|
* POST /api/calculate-graph
|
|
*
|
|
* Calculates 3D coordinates for all nodes using UMAP dimensionality reduction.
|
|
* This route:
|
|
* 1. Fetches all nodes with embeddings but no 3D coordinates
|
|
* 2. Runs UMAP to reduce embeddings from 768-D to 3-D
|
|
* 3. Updates each node with its calculated 3D coordinates
|
|
*/
|
|
export async function POST(request: NextRequest) {
|
|
const cookieStore = await cookies();
|
|
const surrealJwt = cookieStore.get('ponderants-auth')?.value;
|
|
|
|
if (!surrealJwt) {
|
|
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
|
}
|
|
|
|
// Verify JWT to get user's DID
|
|
const userSession = verifySurrealJwt(surrealJwt);
|
|
if (!userSession) {
|
|
return NextResponse.json({ error: 'Invalid auth token' }, { status: 401 });
|
|
}
|
|
|
|
const { did: userDid } = userSession;
|
|
|
|
try {
|
|
const db = await connectToDB();
|
|
|
|
// 1. Fetch all nodes that have an embedding but no coords_3d (filtered by user_did)
|
|
// This query is idempotent - it's safe to run multiple times
|
|
const query = `SELECT id, embedding FROM node WHERE user_did = $userDid AND embedding != NONE AND coords_3d = NONE`;
|
|
const results = await db.query<[Array<{ id: string; embedding: number[] }>]>(query, { userDid });
|
|
|
|
const nodes = results[0] || [];
|
|
|
|
if (nodes.length === 0) {
|
|
// All nodes already have coordinates - nothing to do (idempotency)
|
|
console.log('[Calculate Graph] All nodes already have coordinates');
|
|
return NextResponse.json(
|
|
{ message: 'All nodes already have coordinates', nodes_mapped: 0 },
|
|
{ status: 200 }
|
|
);
|
|
}
|
|
|
|
if (nodes.length < 3) {
|
|
// UMAP needs at least 3 points to work well
|
|
console.log(`[Calculate Graph] Not enough nodes to map (${nodes.length}/3)`);
|
|
return NextResponse.json(
|
|
{ message: 'Not enough nodes to map. Create at least 3 nodes with content.' },
|
|
{ status: 200 }
|
|
);
|
|
}
|
|
|
|
console.log(`[Calculate Graph] Processing ${nodes.length} nodes for UMAP projection`);
|
|
|
|
// 2. Prepare data for UMAP
|
|
const embeddings = nodes.map((n) => n.embedding);
|
|
|
|
// 3. Run UMAP to reduce 768-D (or 1536-D) to 3-D
|
|
const umap = new UMAP({
|
|
nComponents: 3,
|
|
nNeighbors: Math.min(15, nodes.length - 1), // nNeighbors must be < sample size
|
|
minDist: 0.1,
|
|
spread: 1.0,
|
|
});
|
|
|
|
console.log('[Calculate Graph] Running UMAP dimensionality reduction...');
|
|
const coords_3d_array = await umap.fitAsync(embeddings);
|
|
console.log('[Calculate Graph] ✓ UMAP projection complete');
|
|
|
|
// 4. Update nodes in SurrealDB with their new 3D coords
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
const node = nodes[i];
|
|
const coords = coords_3d_array[i];
|
|
|
|
await db.merge(node.id, {
|
|
coords_3d: [coords[0], coords[1], coords[2]],
|
|
});
|
|
}
|
|
|
|
console.log(`[Calculate Graph] ✓ Updated ${nodes.length} nodes with 3D coordinates`);
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
nodes_mapped: nodes.length,
|
|
});
|
|
} catch (error) {
|
|
console.error('[Calculate Graph] Error:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to calculate graph' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|