Files
pagodera/lib/ai/generate-item-structure.ts
T

165 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { secureLogger } from "@/lib/secure-logger";
import { buildGeminiParts, buildItemPrompt, InlineImageData } from "@/lib/ai/item-prompt";
interface GenerateItemStructureInput {
name: string;
description: string;
scope: "global" | "server";
server?: string | null;
categories: string[];
servers: string[];
images?: InlineImageData[];
}
interface GeminiResponse {
structure: {
table: string;
columns: Record<string, unknown>;
};
raw: unknown;
}
const FALLBACK_STRUCTURE = {
table: "items_all",
columns: {
name: "Final Fate",
description:
"Item lendário utilizado no slot de tocha. Oferece bônus massivos em skills mágicas, velocidade e efeitos de leech.",
attributes: {
shielding: 20,
death_magic: 120,
mana_leech: { value: 30, chance: 100 },
life_leech: { value: 25, chance: 100 },
critical_hit: { damage: 25, chance: 75 },
speed: 1000,
protection_physical: 40,
damage_reflection: 80,
},
},
};
export async function generateItemStructure(
input: GenerateItemStructureInput
): Promise<GeminiResponse> {
const apiKey = process.env.GEMINI_API_KEY;
if (!apiKey) {
secureLogger.warn("⚠️ GEMINI_API_KEY ausente – utilizando fallback estático.");
return {
structure: FALLBACK_STRUCTURE,
raw: FALLBACK_STRUCTURE,
};
}
const url =
"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=" +
apiKey;
const prompt = await buildItemPrompt(
{
name: input.name,
description: input.description,
scope: input.scope,
server: input.server,
categories: input.categories,
servers: input.servers,
},
input.images && input.images.length > 0
? "Duas imagens de contexto foram fornecidas como partes inline (após o prompt). Utilize-as para compreender atributos visuais."
: undefined,
);
const parts = await buildGeminiParts(prompt, input.images);
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
contents: [
{
role: "user",
parts,
},
],
generationConfig: {
temperature: 0,
topP: 0.1,
},
}),
});
if (!response.ok) {
throw new Error(`Gemini retornou status ${response.status}`);
}
const data = await response.json();
const text =
data?.candidates?.[0]?.content?.parts?.[0]?.text ??
data?.candidates?.[0]?.output_text;
if (!text) {
throw new Error("Resposta do Gemini vazia ou inválida.");
}
const cleaned = text
.replace(/```json/gi, "")
.replace(/```/g, "")
.trim();
const parsed = JSON.parse(cleaned);
return {
structure: parsed,
raw: data,
};
} catch (error) {
secureLogger.error("❌ Falha ao gerar estrutura via Gemini", {
error: error instanceof Error ? error.message : "Erro desconhecido",
});
return {
structure: FALLBACK_STRUCTURE,
raw: FALLBACK_STRUCTURE,
};
}
}
function buildPrompt({ name, description, scope, server }: GenerateItemStructureInput) {
return `
Você é um assistente que prepara carga útil em JSON para inserção de itens em banco de dados.
Requisitos obrigatórios:
- Responda SOMENTE com JSON válido, sem comentários, sem markdown, sem texto adicional.
- Formato esperado:
{
"table": "nome_da_tabela",
"columns": {
"...": "..."
}
}
- Nunca inclua explicações fora do JSON.
Contexto do item:
- Nome: ${name}
- Escopo: ${scope === "server" ? `específico do servidor ${server ?? "não informado"}` : "global"}
- Descrição original (mantenha estrutura e extrair atributos quando possível):
"""
${description}
"""
Instruções:
- Escolha uma tabela coerente com o escopo (ex.: "items_all" para global ou "items_${(server ?? "custom")
.toLowerCase()
.replace(/[^a-z0-9]+/g, "_")}" para servidor).
- Dentro de "columns", inicie com "name" e "description".
- Modele atributos numéricos em objetos ou subtabelas conforme necessário (ex.: {"attributes": {...}}).
- Quando não tiver certeza de um campo, deixe nulo.
- Nunca invente dados além do que estiver na descrição.
- Garante que o JSON seja minuciosamente válido.
`;
}