Files
Momento/architectural-grid1/src/services/geminiService.ts
Antigravity f46654f574 feat: editor improvements and architectural grid prototype
Multiple feature additions and improvements across the application:

- NextGen Editor: drag handles, smart paste, block actions
- Structured views: Kanban and table layouts for notes
- Architectural Grid: new brainstorming/agent interface prototype
- Flashcards: SM-2 revision algorithm with AI generation
- MCP server: robustness improvements
- Graph/PDF chat: fix click propagation and copy behavior
- Various UI/UX enhancements and bug fixes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 19:45:15 +00:00

314 lines
10 KiB
TypeScript

import { GoogleGenAI, Type } from "@google/genai";
import { BrainstormIdea } from "../types";
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const BRAINSTORM_SCHEMA = {
type: Type.OBJECT,
properties: {
ideas: {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: {
title: { type: Type.STRING },
description: { type: Type.STRING },
connection_to_seed: { type: Type.STRING },
novelty_score: { type: Type.NUMBER }
},
required: ["title", "description", "connection_to_seed", "novelty_score"]
}
}
},
required: ["ideas"]
};
const SUGGESTIONS_SCHEMA = {
type: Type.OBJECT,
properties: {
suggestions: {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: {
title: { type: Type.STRING },
description: { type: Type.STRING },
reasoning: { type: Type.STRING }
},
required: ["title", "description", "reasoning"]
}
}
},
required: ["suggestions"]
};
export async function generateBrainstormWave(
seedIdea: string,
waveNumber: number,
contextSummaries: string = ""
): Promise<Partial<BrainstormIdea>[]> {
const waveDescriptions = [
"", // index 0 unused
"VAGUE 1 (proximité directe) : Sous-aspects, reformulations, variations de l'idée. Reste dans le même domaine.",
"VAGUE 2 (analogies) : Trouve des parallèles dans d'autres domaines. Comment cette idée se manifeste-t-elle ailleurs ? Quelles techniques d'autres industries pourraient s'appliquer ?",
"VAGUE 3 (disruption) : Inverse l'idée. Pousse-la à l'extrême. Combine-la avec un domaine totalement non lié. Que se passe-t-il si l'opposé est vrai ?"
];
const prompt = `
Idée seed : "${seedIdea}"
Contexte : ${contextSummaries}
Génère 5 idées pour la VAGUE ${waveNumber} : ${waveDescriptions[waveNumber]}
Format JSON selon le schéma.
`;
try {
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: [{ role: "user", parts: [{ text: prompt }] }],
config: {
systemInstruction: "Tu es un expert en brainstorming. Réponds uniquement en JSON valide.",
responseMimeType: "application/json",
responseSchema: BRAINSTORM_SCHEMA,
temperature: 1.0
}
});
const resText = response.text;
if (!resText) return [];
const parsed = JSON.parse(resText.replace(/^```json\n?/, '').replace(/\n?```$/, '').trim());
const ideas = Array.isArray(parsed.ideas) ? parsed.ideas : (Array.isArray(parsed) ? parsed : []);
return ideas.map((item: any) => ({
title: item.title,
description: item.description,
connectionToSeed: item.connection_to_seed,
noveltyScore: item.novelty_score,
waveNumber
}));
} catch (error) {
console.error(`Error generating brainstorm wave ${waveNumber}:`, error);
throw error;
}
}
export async function generateExpansion(parentIdeaTitle: string, parentIdeaDescription: string): Promise<Partial<BrainstormIdea>[]> {
const prompt = `
Idée source : "${parentIdeaTitle} - ${parentIdeaDescription}"
Génère 3 idées d'extension ou de sous-aspects.
Format JSON.
`;
try {
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: [{ role: "user", parts: [{ text: prompt }] }],
config: {
systemInstruction: "Tu es un expert en brainstorming. Réponds uniquement en JSON valide.",
responseMimeType: "application/json",
responseSchema: BRAINSTORM_SCHEMA,
temperature: 1.0
}
});
const resText = response.text;
if (!resText) return [];
const parsed = JSON.parse(resText.replace(/^```json\n?/, '').replace(/\n?```$/, '').trim());
const ideas = Array.isArray(parsed.ideas) ? parsed.ideas : (Array.isArray(parsed) ? parsed : []);
return ideas.map((item: any) => ({
title: item.title,
description: item.description,
connectionToSeed: item.connection_to_seed,
noveltyScore: item.novelty_score
}));
} catch (error) {
console.error("Error generating expansion:", error);
throw error;
}
}
export async function getEmbedding(text: string): Promise<number[]> {
try {
const result = await ai.models.embedContent({
model: 'gemini-embedding-2-preview',
contents: [text],
});
return result.embeddings[0].values;
} catch (error) {
console.error("Error generating embedding:", error);
throw error;
}
}
export function cosineSimilarity(a: number[], b: number[]): number {
if (!a || !b || a.length !== b.length) return 0;
const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0);
const magnitudeA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));
const magnitudeB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));
if (magnitudeA === 0 || magnitudeB === 0) return 0;
return dotProduct / (magnitudeA * magnitudeB);
}
export async function nameCluster(noteSummaries: string[]): Promise<string> {
const prompt = `Quel thème commun relie ces notes ? Donne un nom court (2-4 mots).\nNotes :\n${noteSummaries.join('\n- ')}`;
try {
const result = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: prompt
});
return result.text.trim();
} catch (error) {
console.error("Error naming cluster:", error);
return "Thematic Cluster";
}
}
export async function suggestBridgeIdeas(
clusterAName: string,
clusterBName: string,
clusterASummaries: string,
clusterBSummaries: string
): Promise<any[]> {
const prompt = `
Cluster A (${clusterAName}) contient des notes sur : ${clusterASummaries}
Cluster B (${clusterBName}) contient des notes sur : ${clusterBSummaries}
Ces deux clusters ne sont pas connectés. Propose 3 idées
de "notes pont" qui pourraient créer un lien créatif entre eux.
Pour chaque idée : titre, description, pourquoi ça connecte les deux.
Format JSON.
`;
try {
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: prompt,
config: {
responseMimeType: "application/json",
responseSchema: SUGGESTIONS_SCHEMA
}
});
const parsed = JSON.parse(response.text);
return Array.isArray(parsed.suggestions) ? parsed.suggestions : [];
} catch (error) {
console.error("Error suggesting bridge ideas:", error);
return [];
}
}
export async function parseDocument(fileUrl: string, fileName: string): Promise<string> {
const prompt = `Extraits et résume le texte de ce document nommé "${fileName}".
Si c'est un PDF, ignore les éléments purement graphiques et concentre-toi sur le contenu sémantique.
Fais une extraction structurée.`;
try {
// In a real scenario, we would use media upload.
// Here we simulate the extraction.
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: [{ role: "user", parts: [{ text: prompt }] }],
config: {
systemInstruction: "Tu es un expert en extraction de texte et analyse de documents.",
temperature: 0.2
}
});
return response.text || "Échec de l'extraction du texte.";
} catch (error) {
console.error("Error parsing document:", error);
return "Erreur lors de l'analyse du document.";
}
}
export async function extractActionItems(notes: { title: string; content: string }[]): Promise<string> {
const notesContext = notes.map(n => `TITLE: ${n.title}\nCONTENT: ${n.content}`).join('\n\n---\n\n');
const prompt = `
Analyse les notes suivantes et extrais la liste des actions à accomplir (TODOs).
Pour chaque tâche, identifie si possible l'assigné et la date limite.
Présente le résultat sous forme d'un tableau Markdown structuré ou d'une liste claire.
Si aucune tâche n'est trouvée, indique-le.
Notes:
${notesContext}
`;
try {
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: prompt,
config: {
systemInstruction: "Tu es un agent spécialisé dans l'organisation et la gestion de tâches. Ton but est d'être précis et exhaustif.",
temperature: 0.1
}
});
return response.text;
} catch (error) {
console.error("Error extracting action items:", error);
return "Erreur lors de l'extraction des tâches.";
}
}
const FLASHCARDS_SCHEMA = {
type: Type.OBJECT,
properties: {
flashcards: {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: {
question: { type: Type.STRING },
answer: { type: Type.STRING }
},
required: ["question", "answer"]
}
}
},
required: ["flashcards"]
};
export async function generateFlashcardsForNote(
noteTitle: string,
noteContent: string
): Promise<{ question: string; answer: string }[]> {
const prompt = `
Titre de la note : "${noteTitle}"
Contenu de la note :
${noteContent}
Génère entre 4 et 8 flashcards (paires question/réponse) d'apprentissage basées sur le contenu ci-dessus.
Règles de style :
- Les questions doivent être claires et guider vers une révision active (ex: "Quelle est la particularité de... ?", "Pourquoi utilise-t-on... ?").
- Les réponses doivent être courtes et percutantes.
- Langue : Français.
- Format de retour : JSON correspondant au schéma.
`;
try {
const response = await ai.models.generateContent({
model: "gemini-3.5-flash",
contents: [{ role: "user", parts: [{ text: prompt }] }],
config: {
systemInstruction: "Tu es un assistant de révision agile. Tu convertis le contenu d'un cours ou d'une note en de superbes flashcards mémo-techniques.",
responseMimeType: "application/json",
responseSchema: FLASHCARDS_SCHEMA,
temperature: 0.7
}
});
const resText = response.text;
if (!resText) return [];
const parsed = JSON.parse(resText.replace(/^```json\n?/, '').replace(/\n?```$/, '').trim());
return Array.isArray(parsed.flashcards) ? parsed.flashcards : (Array.isArray(parsed) ? parsed : []);
} catch (error) {
console.error("Error generating flashcards with Gemini:", error);
return [];
}
}