<%_* // Gemini API 키를 외부 파일에서 로드 (content 폴더 밖의 secrets.json) const secretsContent = await app.vault.adapter.read(“secrets.json”); const secrets = JSON.parse(secretsContent); const GEMINI_API_KEY = secrets.GEMINI_API_KEY;
// Gemini 모델 설정 const MODEL_NAME = “gemini-flash-latest”;
// RAG 모드 선택 추가 const use_rag_mode = await tp.system.suggester( [“① 현재 문서만 사용”, “② RAG: 볼트 전체 문서 검색”], [false, true], true, “문서 처리 모드를 선택하세요.”);
const user_output_option = await tp.system.suggester( [“① Callout”, “② Markdown”], [“① Callout”, “② Markdown”], true, “출력 옵션을 선택하세요.”);
const USE_CALLOUT = user_output_option === “① Callout”; _%>
<%_* // 사용자 프롬프트 입력 const custom_prompt = await tp.system.prompt(“프롬프트를 입력하세요”); _%>
<%_* // 볼트 내 모든 마크다운 파일 수집하는 함수 async function collectAllVaultFiles() { const files = app.vault.getMarkdownFiles(); const fileContents = [];
for (const file of files) {
try {
const content = await app.vault.read(file);
fileContents.push({
path: file.path,
name: file.name,
content: content
});
} catch (error) {
console.error(`파일 읽기 오류 (${file.path}):`, error);
}
}
return fileContents;
}
// 파일을 Gemini File API에 업로드하는 함수
async function uploadFileToGemini(fileName, content) {
const uploadUrl = https://generativelanguage.googleapis.com/upload/v1beta/files?key=${GEMINI_API_KEY};
// 메타데이터 설정
const metadata = {
file: {
display_name: fileName
}
};
// 멀티파트 폼 데이터 생성
const boundary = '----WebKitFormBoundary' + Math.random().toString(36).substring(2);
const body = [
`--${boundary}`,
'Content-Type: application/json; charset=UTF-8',
'',
JSON.stringify(metadata),
`--${boundary}`,
'Content-Type: text/plain',
'',
content,
`--${boundary}--`
].join('\r\n');
try {
const response = await tp.obsidian.requestUrl({
method: "POST",
url: uploadUrl,
contentType: `multipart/related; boundary=${boundary}`,
body: body
});
return response.json;
} catch (error) {
console.error('파일 업로드 오류:', error);
return null;
}
}
// 여러 파일을 하나의 컨텍스트로 결합 function combineVaultFilesForContext(fileContents, maxLength = 30000) { let combined = ”= OBSIDIAN VAULT CONTENTS =\n\n”;
for (const file of fileContents) {
const fileSection = `\n--- FILE: ${file.path} ---\n${file.content}\n`;
if (combined.length + fileSection.length > maxLength) {
combined += "\n... (내용이 너무 길어 일부 파일이 생략되었습니다) ...\n";
break;
}
combined += fileSection;
}
return combined;
}
// Gemini API를 호출하여 응답을 생성하는 함수 (RAG 지원)
async function generateResponse(content, useRAG = false) {
const url = https://generativelanguage.googleapis.com/v1beta/models/${MODEL_NAME}:generateContent?key=${GEMINI_API_KEY};
let contextContent = content;
// RAG 모드일 경우 볼트 전체 파일 수집
if (useRAG) {
new Notice('볼트 파일 수집 중...', 3000);
const vaultFiles = await collectAllVaultFiles();
const vaultContext = combineVaultFilesForContext(vaultFiles);
contextContent = vaultContext + "\n\n=== CURRENT DOCUMENT ===\n" + content;
new Notice(`${vaultFiles.length}개 파일 로드 완료`, 2000);
}
const full_prompt = `${custom_prompt}\n\nHere is the content:\n${contextContent}`;
try {
const response = await tp.obsidian.requestUrl({
method: "POST",
url: url,
contentType: "application/json",
body: JSON.stringify({
contents: [{ parts: [{ text: full_prompt }] }]
})
});
const resultText = response.json.candidates[0].content.parts[0].text;
return resultText.trim();
} catch (error) {
console.error('Gemini API 호출 중 오류 발생:', error);
if (error.response) {
console.error('Gemini API Error Response:', await error.response.text());
}
return null;
}
}
// frontmatter와 내용을 분리하는 함수 function separateFrontmatterAndContent(content) { const match = content.match(/^---\n(.?)\n---\n([\s\S])/s); if (!match) { return { frontmatter: ”, content: content }; } return { frontmatter: match[1], content: match[2] }; }
// 파일 내용을 업데이트하는 함수 async function updateFileContent(file, response) { try { const currentContent = await tp.file.content; const { frontmatter, content } = separateFrontmatterAndContent(currentContent);
let newContent;
if (USE_CALLOUT) {
const summaryCallout = `> [!${MODEL_NAME}]\n${response.split('\n').map(line => `> ${line}`).join('\n')}\n\n`;
if (frontmatter) {
newContent = `---\n${frontmatter}\n---\n\n${summaryCallout}${content.trimStart()}`;
} else {
newContent = `${summaryCallout}${content.trimStart()}`;
}
} else {
const markdownResponse = `\n----\n## ✅ ${MODEL_NAME} Response\n\n${response}\n\n----\n`;
if (frontmatter) {
newContent = `---\n${frontmatter}\n---\n\n${markdownResponse}${content.trimStart()}`;
} else {
newContent = `${markdownResponse}${content.trimStart()}`;
}
}
await app.vault.modify(file, newContent);
return true;
} catch (error) { console.error(‘파일 업데이트 중 오류 발생:’, error); throw error; } }
// 메인 실행 로직 const file = tp.config.target_file; const fileContent = tp.file.content;
try { // RAG 모드 여부에 따라 응답 생성 const response = await generateResponse(fileContent, use_rag_mode); if (!response) { throw new Error(‘API 호출 실패’); }
await updateFileContent(file, response);
// 완료 알림 if (use_rag_mode) { new Notice(’✅ RAG 모드로 응답 생성 완료!’, 3000); } else { new Notice(’✅ 응답 생성 완료!’, 2000); } } catch (error) { console.error(‘메인 실행 중 오류 발생:’, error); new Notice(’❌ 오류 발생: ’ + error.message, 5000); } _%>