246 lines
7.4 KiB
TypeScript
246 lines
7.4 KiB
TypeScript
import { getPricing } from "@/lib/services/pricing-service";
|
||
import { GoogleGenAI, Modality } from "@google/genai";
|
||
|
||
/**
|
||
* Merkezi yapay zeka istemci örneği
|
||
* Tüm yapay zeka istekleri bu örneği kullanmalıdır
|
||
*/
|
||
export const ai = new GoogleGenAI({
|
||
vertexai: true,
|
||
|
||
apiKey: process.env.GOOGLE_API_KEY,
|
||
});
|
||
|
||
// Kalite artırıcı anahtar kelimeler
|
||
const QUALITY_BOOSTERS = [
|
||
"highly detailed",
|
||
"8k resolution",
|
||
"professional photography",
|
||
"studio lighting",
|
||
"sharp focus",
|
||
"cinematic composition",
|
||
"vibrant colors",
|
||
"masterpiece",
|
||
"masterpiece",
|
||
];
|
||
|
||
/**
|
||
* Yapay Zeka Model yapılandırmaları
|
||
*/
|
||
export const AI_MODELS = {
|
||
FLASH_LITE: "gemini-2.5-flash-lite",
|
||
FLASH: "gemini-2.5-flash",
|
||
FLASH_IMAGE: "gemini-2.5-flash-image",
|
||
} as const;
|
||
|
||
// ——————————————————————————————————————
|
||
// Type definitions for Gemini API responses
|
||
// ——————————————————————————————————————
|
||
|
||
/** Token usage metadata from Gemini API */
|
||
interface UsageMetadata {
|
||
promptTokenCount?: number;
|
||
candidatesTokenCount?: number;
|
||
totalTokenCount?: number;
|
||
}
|
||
|
||
/** Gemini content part (text or inline data) */
|
||
interface ContentPart {
|
||
text?: string;
|
||
inlineData?: { mimeType: string; data: string };
|
||
}
|
||
|
||
/** Generation config passed to Gemini */
|
||
interface GenerationConfig {
|
||
responseModalities?: Modality[];
|
||
temperature?: number;
|
||
maxOutputTokens?: number;
|
||
[key: string]: unknown;
|
||
}
|
||
|
||
/** Prompt content — can be a string, structured content array, or object */
|
||
type PromptContent =
|
||
| string
|
||
| Array<{ role: string; parts: ContentPart[] }>
|
||
| Record<string, unknown>;
|
||
|
||
/**
|
||
* Yapay zeka ile içerik oluştur
|
||
* Tüm yapay zeka içerik oluşturma işlemleri için merkezi fonksiyon
|
||
*
|
||
* @param model - Kullanılacak yapay zeka modeli
|
||
* @param prompt - Gönderilecek istem veya içerikler
|
||
* @param config - İsteğe bağlı yapılandırma
|
||
* @returns Yapay zeka yanıtı
|
||
*/
|
||
export async function generateAIContent(
|
||
model: string,
|
||
prompt: PromptContent,
|
||
config?: GenerationConfig,
|
||
) {
|
||
try {
|
||
const response = await ai.models.generateContent({
|
||
model,
|
||
contents: prompt,
|
||
...(config && { config }),
|
||
});
|
||
|
||
return response;
|
||
} catch (error) {
|
||
console.error("AI generation error:", error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Yapay zeka ile metin içeriği oluştur
|
||
* @param model - Kullanılacak yapay zeka modeli
|
||
* @param prompt - Metin istemi
|
||
* @returns Oluşturulan metin
|
||
*/
|
||
export async function generateText(
|
||
model: string,
|
||
prompt: string,
|
||
): Promise<{ text: string; usage?: UsageMetadata }> {
|
||
const response = await generateAIContent(model, prompt);
|
||
return {
|
||
text: (response.text || "").trim(),
|
||
usage: response.usageMetadata as UsageMetadata | undefined,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Görüntü girdisi ile içerik oluştur
|
||
* @param model - Kullanılacak yapay zeka modeli
|
||
* @param imageBase64 - Base64 kodlanmış görüntü
|
||
* @param textPrompt - Metin istemi
|
||
* @returns Yapay zeka yanıtı
|
||
*/
|
||
export async function generateWithImage(
|
||
model: string,
|
||
imageBase64: string,
|
||
textPrompt: string,
|
||
) {
|
||
const response = await generateAIContent(model, [
|
||
{
|
||
role: "user",
|
||
parts: [
|
||
{ inlineData: { mimeType: "image/jpeg", data: imageBase64 } },
|
||
{ text: textPrompt },
|
||
],
|
||
},
|
||
]);
|
||
|
||
return {
|
||
text: (response.text || "").trim(),
|
||
usage: response.usageMetadata as UsageMetadata | undefined,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* İstemden görüntü oluştur
|
||
* @param prompt - Görüntü oluşturma için metin istemi
|
||
* @param imageBase64 - Düzenleme için isteğe bağlı base64 görüntü
|
||
* @param suggestions - Görsel iyileştirme önerileri (Nano Banana için)
|
||
* @param model - Kullanılacak model (varsayılan: FLASH_IMAGE)
|
||
* @param aspectRatio - Hedef en boy oranı (örn: "16:9", "1:1")
|
||
* @returns Oluşturulan görüntü URL'si (base64)
|
||
*/
|
||
export async function generateImage(
|
||
prompt: string,
|
||
imageBase64?: string,
|
||
suggestions?: string[],
|
||
model: string = AI_MODELS.FLASH_IMAGE,
|
||
aspectRatio?: string,
|
||
): Promise<{ imageUrl: string | null; usage?: UsageMetadata }> {
|
||
let parts: Array<
|
||
{ text: string } | { inlineData: { mimeType: string; data: string } }
|
||
> = [];
|
||
|
||
// En boy oranı talimatı oluştur
|
||
const ratioInstruction = aspectRatio
|
||
? `\n\nTarget Aspect Ratio: ${aspectRatio}\nEnsure the image strictly follows the ${aspectRatio} aspect ratio format.`
|
||
: "";
|
||
|
||
if (imageBase64) {
|
||
// Görüntü düzenleme modu
|
||
const cleanBase64 = imageBase64.replace(
|
||
/^data:image\/(png|jpeg|webp|jpg);base64,/,
|
||
"",
|
||
);
|
||
|
||
// Eğer Nano Banana modeli ve öneriler varsa, prompt'u zenginleştir
|
||
let finalPrompt = prompt;
|
||
if (model === "nano-banana") {
|
||
let improvementInstructions = "";
|
||
if (suggestions && suggestions.length > 0) {
|
||
improvementInstructions = `\n\nApply these specific improvements based on platform analysis:\n${suggestions.map((s) => `- ${s}`).join("\n")}`;
|
||
}
|
||
|
||
finalPrompt = `${prompt}${improvementInstructions}\n\nStyle & Quality Instructions:\nRender in ${QUALITY_BOOSTERS.join(", ")}.\n\nCRITICAL OBJECTIVE: The result MUST achieve a perfect 10/10 score.\n- Clarity: 10/10 (Ultra-sharp, no blur)\n- Professionalism: 10/10 (High-end commercial look)\n- Engagement: 10/10 (Eye-catching contrast and lighting)\n- Platform Fit: 10/10 (Perfect aspect ratio and framing)\nEnsure the image is visually stunning and flawless.`;
|
||
}
|
||
|
||
parts.push({ inlineData: { mimeType: "image/png", data: cleanBase64 } });
|
||
parts.push({ text: finalPrompt + ratioInstruction });
|
||
} else {
|
||
parts.push({ text: prompt + ratioInstruction });
|
||
}
|
||
|
||
console.log("[AI-Helper] generateImage calling Gemini model", {
|
||
model: AI_MODELS.FLASH_IMAGE,
|
||
});
|
||
|
||
try {
|
||
const response = await ai.models.generateContent({
|
||
model,
|
||
contents: [{ role: "user", parts }],
|
||
config: {
|
||
responseModalities: [Modality.IMAGE],
|
||
},
|
||
});
|
||
|
||
const candidate = response.candidates?.[0];
|
||
const imagePart = candidate?.content?.parts?.find((p) =>
|
||
p.inlineData?.mimeType?.startsWith("image/"),
|
||
);
|
||
|
||
if (imagePart?.inlineData?.data) {
|
||
return {
|
||
imageUrl: `data:${imagePart.inlineData.mimeType};base64,${imagePart.inlineData.data}`,
|
||
usage: response.usageMetadata as UsageMetadata | undefined,
|
||
};
|
||
}
|
||
|
||
console.warn("No image part found in response");
|
||
return {
|
||
imageUrl: null,
|
||
usage: response.usageMetadata as UsageMetadata | undefined,
|
||
};
|
||
} catch (error) {
|
||
console.error("generateImage error:", error);
|
||
return { imageUrl: null };
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Token kullanımına dayalı Yapay Zeka Maliyetini hesapla
|
||
* Giriş Maliyeti = (Giriş Tokenları / 1.000.000) * Giriş Fiyatı
|
||
* Çıkış Maliyeti = (Çıkış Tokenları / 1.000.000) * Çıkış Fiyatı
|
||
* @param usage - Yapay zeka yanıtından kullanım meta verileri
|
||
* @returns Para birimi cinsinden toplam maliyet
|
||
*/
|
||
export async function calculateAICost(
|
||
usage: UsageMetadata | null | undefined,
|
||
): Promise<number> {
|
||
if (!usage) return 0;
|
||
|
||
const pricing = await getPricing();
|
||
const inputTokens = usage.promptTokenCount || 0;
|
||
const outputTokens = usage.candidatesTokenCount || 0;
|
||
|
||
const inputCost = (inputTokens / 1_000_000) * pricing.inputCost;
|
||
const outputCost = (outputTokens / 1_000_000) * pricing.outputCost;
|
||
|
||
return inputCost + outputCost;
|
||
}
|