fix(audit): prototypes Gemini server-side + retire metrics-token du dépôt
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 5m30s
CI / Deploy production (on server) (push) Successful in 54s

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>
This commit is contained in:
Antigravity
2026-06-20 17:11:32 +00:00
parent eab4b3e27b
commit 31e882856c
5 changed files with 152 additions and 19 deletions

View File

@@ -170,6 +170,38 @@ async function startServer() {
res.json(ideas[index]); 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 // Vite middleware for development
if (process.env.NODE_ENV !== "production") { if (process.env.NODE_ENV !== "production") {
const vite = await createViteServer({ const vite = await createViteServer({

View File

@@ -2,7 +2,24 @@
import { GoogleGenAI, Type } from "@google/genai"; import { GoogleGenAI, Type } from "@google/genai";
import { BrainstormIdea } from "../types"; import { BrainstormIdea } from "../types";
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); async function generateContent(
request: Parameters<InstanceType<typeof GoogleGenAI>['models']['generateContent']>[0],
) {
if (typeof window !== 'undefined') {
const res = await fetch('/api/gemini/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request),
});
if (!res.ok) throw new Error('Gemini request failed');
const data = await res.json();
return { text: data.text ?? '' };
}
const key = process.env.GEMINI_API_KEY;
if (!key) throw new Error('GEMINI_API_KEY required on server');
const ai = new GoogleGenAI({ apiKey: key });
return ai.models.generateContent(request);
}
const BRAINSTORM_SCHEMA = { const BRAINSTORM_SCHEMA = {
type: Type.OBJECT, type: Type.OBJECT,
@@ -63,7 +80,7 @@ export async function generateBrainstormWave(
`; `;
try { try {
const response = await ai.models.generateContent({ const response = await generateContent({
model: "gemini-3-flash-preview", model: "gemini-3-flash-preview",
contents: [{ role: "user", parts: [{ text: prompt }] }], contents: [{ role: "user", parts: [{ text: prompt }] }],
config: { config: {
@@ -101,7 +118,7 @@ export async function generateExpansion(parentIdeaTitle: string, parentIdeaDescr
`; `;
try { try {
const response = await ai.models.generateContent({ const response = await generateContent({
model: "gemini-3-flash-preview", model: "gemini-3-flash-preview",
contents: [{ role: "user", parts: [{ text: prompt }] }], contents: [{ role: "user", parts: [{ text: prompt }] }],
config: { config: {
@@ -130,9 +147,27 @@ export async function generateExpansion(parentIdeaTitle: string, parentIdeaDescr
} }
} }
async function embedContent(
request: Parameters<InstanceType<typeof GoogleGenAI>['models']['embedContent']>[0],
) {
if (typeof window !== 'undefined') {
const res = await fetch('/api/gemini/embed', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request),
});
if (!res.ok) throw new Error('Gemini embed failed');
return res.json();
}
const key = process.env.GEMINI_API_KEY;
if (!key) throw new Error('GEMINI_API_KEY required on server');
const ai = new GoogleGenAI({ apiKey: key });
return ai.models.embedContent(request);
}
export async function getEmbedding(text: string): Promise<number[]> { export async function getEmbedding(text: string): Promise<number[]> {
try { try {
const result = await ai.models.embedContent({ const result = await embedContent({
model: 'gemini-embedding-2-preview', model: 'gemini-embedding-2-preview',
contents: [text], contents: [text],
}); });
@@ -155,7 +190,7 @@ export function cosineSimilarity(a: number[], b: number[]): number {
export async function nameCluster(noteSummaries: string[]): Promise<string> { 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- ')}`; const prompt = `Quel thème commun relie ces notes ? Donne un nom court (2-4 mots).\nNotes :\n${noteSummaries.join('\n- ')}`;
try { try {
const result = await ai.models.generateContent({ const result = await generateContent({
model: "gemini-3-flash-preview", model: "gemini-3-flash-preview",
contents: prompt contents: prompt
}); });
@@ -184,7 +219,7 @@ export async function suggestBridgeIdeas(
`; `;
try { try {
const response = await ai.models.generateContent({ const response = await generateContent({
model: "gemini-3-flash-preview", model: "gemini-3-flash-preview",
contents: prompt, contents: prompt,
config: { config: {
@@ -207,7 +242,7 @@ export async function parseDocument(fileUrl: string, fileName: string): Promise<
try { try {
// In a real scenario, we would use media upload. // In a real scenario, we would use media upload.
// Here we simulate the extraction. // Here we simulate the extraction.
const response = await ai.models.generateContent({ const response = await generateContent({
model: "gemini-3-flash-preview", model: "gemini-3-flash-preview",
contents: [{ role: "user", parts: [{ text: prompt }] }], contents: [{ role: "user", parts: [{ text: prompt }] }],
config: { config: {
@@ -236,7 +271,7 @@ export async function extractActionItems(notes: { title: string; content: string
`; `;
try { try {
const response = await ai.models.generateContent({ const response = await generateContent({
model: "gemini-3-flash-preview", model: "gemini-3-flash-preview",
contents: prompt, contents: prompt,
config: { config: {
@@ -289,7 +324,7 @@ export async function generateFlashcardsForNote(
`; `;
try { try {
const response = await ai.models.generateContent({ const response = await generateContent({
model: "gemini-3.5-flash", model: "gemini-3.5-flash",
contents: [{ role: "user", parts: [{ text: prompt }] }], contents: [{ role: "user", parts: [{ text: prompt }] }],
config: { config: {

View File

@@ -170,6 +170,38 @@ async function startServer() {
res.json(ideas[index]); 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 // Vite middleware for development
if (process.env.NODE_ENV !== "production") { if (process.env.NODE_ENV !== "production") {
const vite = await createViteServer({ const vite = await createViteServer({

View File

@@ -2,7 +2,24 @@
import { GoogleGenAI, Type } from "@google/genai"; import { GoogleGenAI, Type } from "@google/genai";
import { BrainstormIdea } from "../types"; import { BrainstormIdea } from "../types";
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); async function generateContent(
request: Parameters<InstanceType<typeof GoogleGenAI>['models']['generateContent']>[0],
) {
if (typeof window !== 'undefined') {
const res = await fetch('/api/gemini/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request),
});
if (!res.ok) throw new Error('Gemini request failed');
const data = await res.json();
return { text: data.text ?? '' };
}
const key = process.env.GEMINI_API_KEY;
if (!key) throw new Error('GEMINI_API_KEY required on server');
const ai = new GoogleGenAI({ apiKey: key });
return ai.models.generateContent(request);
}
const BRAINSTORM_SCHEMA = { const BRAINSTORM_SCHEMA = {
type: Type.OBJECT, type: Type.OBJECT,
@@ -63,7 +80,7 @@ export async function generateBrainstormWave(
`; `;
try { try {
const response = await ai.models.generateContent({ const response = await generateContent({
model: "gemini-3-flash-preview", model: "gemini-3-flash-preview",
contents: [{ role: "user", parts: [{ text: prompt }] }], contents: [{ role: "user", parts: [{ text: prompt }] }],
config: { config: {
@@ -101,7 +118,7 @@ export async function generateExpansion(parentIdeaTitle: string, parentIdeaDescr
`; `;
try { try {
const response = await ai.models.generateContent({ const response = await generateContent({
model: "gemini-3-flash-preview", model: "gemini-3-flash-preview",
contents: [{ role: "user", parts: [{ text: prompt }] }], contents: [{ role: "user", parts: [{ text: prompt }] }],
config: { config: {
@@ -130,9 +147,27 @@ export async function generateExpansion(parentIdeaTitle: string, parentIdeaDescr
} }
} }
async function embedContent(
request: Parameters<InstanceType<typeof GoogleGenAI>['models']['embedContent']>[0],
) {
if (typeof window !== 'undefined') {
const res = await fetch('/api/gemini/embed', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request),
});
if (!res.ok) throw new Error('Gemini embed failed');
return res.json();
}
const key = process.env.GEMINI_API_KEY;
if (!key) throw new Error('GEMINI_API_KEY required on server');
const ai = new GoogleGenAI({ apiKey: key });
return ai.models.embedContent(request);
}
export async function getEmbedding(text: string): Promise<number[]> { export async function getEmbedding(text: string): Promise<number[]> {
try { try {
const result = await ai.models.embedContent({ const result = await embedContent({
model: 'gemini-embedding-2-preview', model: 'gemini-embedding-2-preview',
contents: [text], contents: [text],
}); });
@@ -155,7 +190,7 @@ export function cosineSimilarity(a: number[], b: number[]): number {
export async function nameCluster(noteSummaries: string[]): Promise<string> { 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- ')}`; const prompt = `Quel thème commun relie ces notes ? Donne un nom court (2-4 mots).\nNotes :\n${noteSummaries.join('\n- ')}`;
try { try {
const result = await ai.models.generateContent({ const result = await generateContent({
model: "gemini-3-flash-preview", model: "gemini-3-flash-preview",
contents: prompt contents: prompt
}); });
@@ -184,7 +219,7 @@ export async function suggestBridgeIdeas(
`; `;
try { try {
const response = await ai.models.generateContent({ const response = await generateContent({
model: "gemini-3-flash-preview", model: "gemini-3-flash-preview",
contents: prompt, contents: prompt,
config: { config: {
@@ -207,7 +242,7 @@ export async function parseDocument(fileUrl: string, fileName: string): Promise<
try { try {
// In a real scenario, we would use media upload. // In a real scenario, we would use media upload.
// Here we simulate the extraction. // Here we simulate the extraction.
const response = await ai.models.generateContent({ const response = await generateContent({
model: "gemini-3-flash-preview", model: "gemini-3-flash-preview",
contents: [{ role: "user", parts: [{ text: prompt }] }], contents: [{ role: "user", parts: [{ text: prompt }] }],
config: { config: {
@@ -236,7 +271,7 @@ export async function extractActionItems(notes: { title: string; content: string
`; `;
try { try {
const response = await ai.models.generateContent({ const response = await generateContent({
model: "gemini-3-flash-preview", model: "gemini-3-flash-preview",
contents: prompt, contents: prompt,
config: { config: {
@@ -289,7 +324,7 @@ export async function generateFlashcardsForNote(
`; `;
try { try {
const response = await ai.models.generateContent({ const response = await generateContent({
model: "gemini-3.5-flash", model: "gemini-3.5-flash",
contents: [{ role: "user", parts: [{ text: prompt }] }], contents: [{ role: "user", parts: [{ text: prompt }] }],
config: { config: {

View File

@@ -1 +0,0 @@
changeme-replace-with-real-token