<%_* // 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); } _%>