Initial commit

This commit is contained in:
Haitham Khalifa
2026-02-16 12:02:45 +01:00
commit 11252e6520
37 changed files with 8118 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
import { GoogleGenAI } from "@google/genai";
export type SiteMenteMessage = {
role: "user" | "assistant" | "system";
content: string;
};
export type SiteMenteSpeechResult = {
audioBytes: Uint8Array;
mimeType: string;
};
const TEXT_MODEL = "gemini-2.5-flash";
const SPEECH_MODEL = "gemini-2.5-flash-preview-tts";
const getClient = () => {
const apiKey = process.env.GEMINI_API_KEY;
if (!apiKey) {
throw new Error("GEMINI_API_KEY is not set.");
}
return new GoogleGenAI({ apiKey });
};
const extractText = (response: unknown): string => {
const typed = response as {
candidates?: Array<{
content?: { parts?: Array<{ text?: string }> };
}>;
};
const text =
typed?.candidates?.[0]?.content?.parts
?.map((part) => part.text ?? "")
.join("")
.trim() ?? "";
if (!text) {
throw new Error("Gemini returned an empty response.");
}
return text;
};
const extractAudio = (response: unknown): SiteMenteSpeechResult => {
const typed = response as {
candidates?: Array<{
content?: {
parts?: Array<{
inlineData?: { data?: string; mimeType?: string };
}>;
};
}>;
};
const inlineData =
typed?.candidates?.[0]?.content?.parts?.find((part) => part.inlineData)
?.inlineData ?? null;
if (!inlineData?.data || !inlineData?.mimeType) {
throw new Error("Gemini did not return audio data.");
}
const audioBytes = Uint8Array.from(
Buffer.from(inlineData.data, "base64")
);
return { audioBytes, mimeType: inlineData.mimeType };
};
const buildContents = (messages: SiteMenteMessage[]) => {
return messages
.filter((message) => message.role !== "system")
.map((message) => ({
role: message.role === "assistant" ? "model" : "user",
parts: [{ text: message.content }],
}));
};
const buildSystemInstruction = (messages: SiteMenteMessage[]) => {
const systemText = messages
.filter((message) => message.role === "system")
.map((message) => message.content)
.join("\n")
.trim();
if (!systemText) {
return undefined;
}
return { parts: [{ text: systemText }] };
};
export const generateSiteMenteText = async (
messages: SiteMenteMessage[]
): Promise<string> => {
try {
const client = getClient();
const response = await client.models.generateContent({
model: TEXT_MODEL,
contents: buildContents(messages),
systemInstruction: buildSystemInstruction(messages),
});
return extractText(response);
} catch (error) {
console.error("[SiteMente][Gemini] Text generation failed", error);
throw error;
}
};
export const generateSiteMenteSpeech = async (
text: string
): Promise<SiteMenteSpeechResult> => {
try {
const client = getClient();
const response = await client.models.generateContent({
model: SPEECH_MODEL,
contents: [{ role: "user", parts: [{ text }] }],
config: {
responseModalities: ["AUDIO"],
speechConfig: {
voiceConfig: {
prebuiltVoiceConfig: {
voiceName: "Kore",
},
},
},
},
});
return extractAudio(response);
} catch (error) {
console.error("[SiteMente][Gemini] Speech generation failed", error);
throw error;
}
};
+61
View File
@@ -0,0 +1,61 @@
import {
generateSiteMenteSpeech,
generateSiteMenteText,
type SiteMenteMessage,
} from "./geminiClient";
export type SiteMenteTextRequest = {
messages: SiteMenteMessage[];
};
export type SiteMenteTextResponse = {
reply: string;
};
export type SiteMenteVoiceRequest = {
transcript: string;
};
export type SiteMenteVoiceResponse = {
reply: string;
audio: {
data: string;
mimeType: string;
};
};
const SYSTEM_PROMPT = [
"You are SiteMente, an AI site strategist that helps business owners make their websites think like a smart assistant.",
"You deeply understand landing pages, funnels, customer psychology, and automation.",
"You recommend specific AI Minds, suggest copy and UX changes, and ask focused clarifying questions before proposing changes.",
"Keep answers concise, practical, and business-oriented.",
].join(" ");
const withSystemPrompt = (messages: SiteMenteMessage[]) => {
return [{ role: "system", content: SYSTEM_PROMPT }, ...messages];
};
export const runSiteMenteText = async (
request: SiteMenteTextRequest
): Promise<SiteMenteTextResponse> => {
const reply = await generateSiteMenteText(withSystemPrompt(request.messages));
return { reply };
};
export const runSiteMenteVoiceTurn = async (
request: SiteMenteVoiceRequest
): Promise<SiteMenteVoiceResponse> => {
const messages: SiteMenteMessage[] = [
{ role: "user", content: request.transcript },
];
const reply = await generateSiteMenteText(withSystemPrompt(messages));
const audio = await generateSiteMenteSpeech(reply);
return {
reply,
audio: {
data: Buffer.from(audio.audioBytes).toString("base64"),
mimeType: audio.mimeType,
},
};
};