Files
Momento/architectural-grid/server.ts
Antigravity 31e882856c
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 5m30s
CI / Deploy production (on server) (push) Successful in 54s
fix(audit): prototypes Gemini server-side + retire metrics-token du dépôt
Proxy Gemini côté serveur (plus de clé dans le bundle Vite), port prototype 4000,
et suppression du token métriques placeholder versionné.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-20 17:11:32 +00:00

226 lines
6.9 KiB
TypeScript

import express from "express";
import path from "path";
import { createServer as createViteServer } from "vite";
import { v4 as uuidv4 } from "uuid";
import { WebSocketServer, WebSocket } from "ws";
import { createServer } from "http";
interface BrainstormIdea {
id: string;
sessionId: string;
waveNumber: number;
title: string;
description: string;
connectionToSeed: string;
noveltyScore: number;
parentIdeaId?: string;
convertedToNoteId?: string;
status: 'active' | 'dismissed' | 'converted';
position?: { x: number; y: number };
}
interface BrainstormSession {
id: string;
seedIdea: string;
sourceNoteId?: string;
contextNoteIds?: string[];
createdAt: string;
updatedAt: string;
}
// In-memory store
const sessions: BrainstormSession[] = [];
const ideas: BrainstormIdea[] = [];
async function startServer() {
const app = express();
const server = createServer(app);
const wss = new WebSocketServer({ server });
const PORT = 4000;
app.use(express.json());
// WebSocket logic
const rooms = new Map<string, Set<WebSocket>>();
const roomUsers = new Map<string, Map<WebSocket, any>>();
wss.on('connection', (ws) => {
let currentRoom: string | null = null;
ws.on('message', (message) => {
const data = JSON.parse(message.toString());
if (data.type === 'join') {
const { sessionId, user } = data;
currentRoom = sessionId;
if (!rooms.has(sessionId)) rooms.set(sessionId, new Set());
if (!roomUsers.has(sessionId)) roomUsers.set(sessionId, new Map());
rooms.get(sessionId)!.add(ws);
roomUsers.get(sessionId)!.set(ws, user || { id: uuidv4(), name: 'Guest' });
// Broadcast presence to the room
const usersInRoom = Array.from(roomUsers.get(sessionId)!.values());
rooms.get(sessionId)!.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ type: 'presence', users: usersInRoom }));
}
});
console.log(`User ${user?.name || 'Guest'} joined session: ${sessionId}`);
}
if (data.type === 'idea_added' || data.type === 'idea_updated' || data.type === 'activity' || data.type === 'living_block_update') {
if (currentRoom && rooms.has(currentRoom)) {
rooms.get(currentRoom)!.forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(data));
}
});
}
}
});
ws.on('close', () => {
if (currentRoom && rooms.has(currentRoom)) {
rooms.get(currentRoom)!.delete(ws);
roomUsers.get(currentRoom)!.delete(ws);
// Update presence
if (rooms.get(currentRoom)!.size > 0) {
const usersInRoom = Array.from(roomUsers.get(currentRoom)!.values());
rooms.get(currentRoom)!.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ type: 'presence', users: usersInRoom }));
}
});
} else {
rooms.delete(currentRoom);
roomUsers.delete(currentRoom);
}
}
});
});
// API Routes
app.get("/api/health", (req, res) => {
res.json({ status: "ok" });
});
// 1. Create session
app.post("/api/brainstorm/sessions", (req, res) => {
const { seedIdea, sourceNoteId, contextNoteIds } = req.body;
const session: BrainstormSession = {
id: uuidv4(),
seedIdea,
sourceNoteId,
contextNoteIds,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
sessions.unshift(session);
res.json(session);
});
// 2. Add ideas to session
app.post("/api/brainstorm/:sessionId/ideas", (req, res) => {
const { sessionId } = req.params;
const { ideas: newIdeasData } = req.body;
const session = sessions.find(s => s.id === sessionId);
if (!session) return res.status(404).json({ error: "Session not found" });
const newIdeas = newIdeasData.map((item: any) => ({
id: item.id || uuidv4(),
sessionId,
waveNumber: item.waveNumber,
title: item.title,
description: item.description,
connectionToSeed: item.connectionToSeed,
noveltyScore: item.noveltyScore,
parentIdeaId: item.parentIdeaId,
status: 'active'
}));
newIdeas.forEach((i: any) => ideas.push(i));
res.json(newIdeas);
});
// 3. Get all sessions
app.get("/api/brainstorm/sessions", (req, res) => {
res.json(sessions);
});
// 4. Get session with ideas
app.get("/api/brainstorm/:sessionId", (req, res) => {
const session = sessions.find(s => s.id === req.params.sessionId);
if (!session) return res.status(404).json({ error: "Session not found" });
const sessionIdeas = ideas.filter(i => i.sessionId === session.id);
res.json({ session, ideas: sessionIdeas });
});
// 5. Update idea (position, status)
app.patch("/api/brainstorm/ideas/:ideaId", (req, res) => {
const index = ideas.findIndex(i => i.id === req.params.ideaId);
if (index === -1) return res.status(404).json({ error: "Idea not found" });
ideas[index] = { ...ideas[index], ...req.body };
res.json(ideas[index]);
});
app.post('/api/gemini/embed', async (req, res) => {
const key = process.env.GEMINI_API_KEY;
if (!key) {
return res.status(503).json({ error: 'GEMINI_API_KEY not configured on server' });
}
try {
const { GoogleGenAI } = await import('@google/genai');
const ai = new GoogleGenAI({ apiKey: key });
const response = await ai.models.embedContent(req.body);
res.json(response);
} catch (error) {
console.error('Gemini embed error:', error);
res.status(500).json({ error: 'Gemini embed failed' });
}
});
app.post('/api/gemini/generate', async (req, res) => {
const key = process.env.GEMINI_API_KEY;
if (!key) {
return res.status(503).json({ error: 'GEMINI_API_KEY not configured on server' });
}
try {
const { GoogleGenAI } = await import('@google/genai');
const ai = new GoogleGenAI({ apiKey: key });
const response = await ai.models.generateContent(req.body);
res.json({ text: response.text ?? '' });
} catch (error) {
console.error('Gemini proxy error:', error);
res.status(500).json({ error: 'Gemini request failed' });
}
});
// Vite middleware for development
if (process.env.NODE_ENV !== "production") {
const vite = await createViteServer({
server: { middlewareMode: true },
appType: "spa",
});
app.use(vite.middlewares);
} else {
const distPath = path.join(process.cwd(), 'dist');
app.use(express.static(distPath));
app.get('*', (req, res) => {
res.sendFile(path.join(distPath, 'index.html'));
});
}
server.listen(PORT, "0.0.0.0", () => {
console.log(`Server running on http://localhost:${PORT}`);
});
}
startServer();