140 lines
3.6 KiB
JavaScript
140 lines
3.6 KiB
JavaScript
|
|
/**
|
||
|
|
* Gemini 번역 서비스
|
||
|
|
* @google/genai SDK를 사용한 이메일 번역
|
||
|
|
*/
|
||
|
|
const SystemConfig = require("../models/SystemConfig");
|
||
|
|
const GeminiModel = require("../models/GeminiModel");
|
||
|
|
|
||
|
|
/**
|
||
|
|
* DB에서 활성화된 Gemini 모델 목록 조회
|
||
|
|
*/
|
||
|
|
const listModels = async () => {
|
||
|
|
try {
|
||
|
|
const models = await GeminiModel.findAll({
|
||
|
|
where: { isActive: true },
|
||
|
|
order: [["sortOrder", "ASC"]],
|
||
|
|
attributes: ["modelId", "name", "description"],
|
||
|
|
});
|
||
|
|
|
||
|
|
return models.map((m) => ({
|
||
|
|
id: m.modelId,
|
||
|
|
name: m.name,
|
||
|
|
description: m.description || "",
|
||
|
|
}));
|
||
|
|
} catch (error) {
|
||
|
|
console.error("Gemini 모델 목록 조회 오류:", error);
|
||
|
|
// 폴백: 기본 모델 반환
|
||
|
|
return [
|
||
|
|
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash", description: "" },
|
||
|
|
];
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Gemini API로 텍스트 번역
|
||
|
|
* @param {string} text - 번역할 텍스트
|
||
|
|
* @param {string} targetLang - 대상 언어 (ko, en, ja, zh 등)
|
||
|
|
* @param {string} apiKey - Gemini API 키
|
||
|
|
* @param {string} model - 사용할 모델 ID
|
||
|
|
*/
|
||
|
|
const translateText = async (text, targetLang, apiKey, model) => {
|
||
|
|
if (!apiKey) {
|
||
|
|
throw new Error("Gemini API 키가 설정되지 않았습니다.");
|
||
|
|
}
|
||
|
|
|
||
|
|
const langNames = {
|
||
|
|
ko: "한국어",
|
||
|
|
en: "영어",
|
||
|
|
ja: "일본어",
|
||
|
|
zh: "중국어",
|
||
|
|
es: "스페인어",
|
||
|
|
fr: "프랑스어",
|
||
|
|
de: "독일어",
|
||
|
|
ru: "러시아어",
|
||
|
|
pt: "포르투갈어",
|
||
|
|
it: "이탈리아어",
|
||
|
|
};
|
||
|
|
|
||
|
|
const targetLangName = langNames[targetLang] || targetLang;
|
||
|
|
const selectedModel = model || "gemini-2.5-flash";
|
||
|
|
|
||
|
|
// ES Module 동적 import
|
||
|
|
const { GoogleGenAI } = await import("@google/genai");
|
||
|
|
|
||
|
|
// Google GenAI SDK 초기화
|
||
|
|
const ai = new GoogleGenAI({ apiKey });
|
||
|
|
|
||
|
|
// HTML 태그 보존하면서 번역하도록 프롬프트 작성
|
||
|
|
const prompt = `다음 이메일 내용을 ${targetLangName}로 번역해주세요. HTML 태그가 있다면 그대로 유지하고 텍스트만 번역해주세요. 번역만 출력하고 다른 설명은 하지 마세요.
|
||
|
|
|
||
|
|
${text}`;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await ai.models.generateContent({
|
||
|
|
model: selectedModel,
|
||
|
|
contents: prompt,
|
||
|
|
});
|
||
|
|
|
||
|
|
const translatedText = response.text;
|
||
|
|
|
||
|
|
if (!translatedText) {
|
||
|
|
throw new Error("번역 결과를 가져올 수 없습니다.");
|
||
|
|
}
|
||
|
|
|
||
|
|
return translatedText;
|
||
|
|
} catch (error) {
|
||
|
|
console.error("Gemini 번역 오류:", error);
|
||
|
|
throw new Error(error.message || "번역 중 오류가 발생했습니다.");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Gemini 설정 조회 (DB에서 모델 목록 가져오기)
|
||
|
|
*/
|
||
|
|
const getGeminiConfig = async () => {
|
||
|
|
const apiKeyRecord = await SystemConfig.findOne({
|
||
|
|
where: { key: "gemini_api_key" },
|
||
|
|
});
|
||
|
|
const modelRecord = await SystemConfig.findOne({
|
||
|
|
where: { key: "gemini_model" },
|
||
|
|
});
|
||
|
|
|
||
|
|
// DB에서 모델 목록 조회
|
||
|
|
const models = await listModels();
|
||
|
|
|
||
|
|
return {
|
||
|
|
gemini_api_key: apiKeyRecord?.value || "",
|
||
|
|
gemini_model: modelRecord?.value || "gemini-2.5-flash",
|
||
|
|
models,
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Gemini 설정 저장
|
||
|
|
*/
|
||
|
|
const saveGeminiConfig = async (config) => {
|
||
|
|
// API 키가 마스킹된 값이 아닐 때만 저장 (********로 시작하지 않을 때)
|
||
|
|
if (
|
||
|
|
config.gemini_api_key !== undefined &&
|
||
|
|
!config.gemini_api_key.includes("*")
|
||
|
|
) {
|
||
|
|
await SystemConfig.upsert({
|
||
|
|
key: "gemini_api_key",
|
||
|
|
value: config.gemini_api_key,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (config.gemini_model !== undefined) {
|
||
|
|
await SystemConfig.upsert({
|
||
|
|
key: "gemini_model",
|
||
|
|
value: config.gemini_model,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
module.exports = {
|
||
|
|
translateText,
|
||
|
|
getGeminiConfig,
|
||
|
|
saveGeminiConfig,
|
||
|
|
listModels,
|
||
|
|
};
|