🚀 Apps Script + Gemini Mastery — Issue #10
https://github.com/lsvekis/Apps-Script-Code-Snippets
AI Knowledge Base Builder for Google Drive
Turn a Drive folder of Docs into a searchable “AI knowledge base” using Apps Script + Gemini.
⭐ What You Will Build
You’ll build a Google Workspace tool that:
📁 Scans a Drive folder of Google Docs
🧠 Uses Gemini to extract + summarize key knowledge
🧾 Stores structured knowledge in a Google Sheet (your “index”)
🔎 Lets users ask questions from a sidebar
✨ Returns answers with citations (Doc name + link)
This is the foundation for internal helpdesks, course knowledge bases, SOP assistants, or client documentation bots.
🧠 Learning Objectives
Readers will learn how to:
✔ Process Drive folders with DriveApp
✔ Extract text from Google Docs programmatically
✔ Build an “AI index” stored in Sheets
✔ Design a question-answer pipeline (search → context → answer)
✔ Implement citation-style output (links to original sources)
✔ Add a sidebar UI in Google Sheets
🧩 Architecture (Simple RAG Pattern)
Step 1 — Indexing (Build once / refresh):
Docs → extract text → Gemini summary → store in Sheet
Step 2 — Q&A (Run anytime):
User question → pick best summaries → Gemini answer → show citations
No external database needed.
✅ Issue #10 Lesson Build
1️⃣ Create the Sheet “Index” Structure
In a Google Sheet, create a tab called: KB_Index
Headers (Row 1):
| A | B | C | D | E |
|---|---|---|---|---|
| docId | title | url | summary | keywords |
This sheet is your lightweight knowledge base.
2️⃣ Menu + Sidebar
Code.gs
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("AI Tools")
.addItem("KB Builder (Index Folder)", "showKbBuilder")
.addItem("KB Q&A (Ask)", "showKbAsk")
.addToUi();
}
function showKbBuilder() {
SpreadsheetApp.getUi().showSidebar(
HtmlService.createHtmlOutputFromFile("Sidebar_Index")
.setTitle("KB Builder")
);
}
function showKbAsk() {
SpreadsheetApp.getUi().showSidebar(
HtmlService.createHtmlOutputFromFile("Sidebar_Ask")
.setTitle("KB Q&A")
);
}
3️⃣ Sidebar UI — Index Builder
Sidebar_Index.html
<div style="font-family:Arial;padding:14px;">
<h2>KB Builder</h2>
<p>Index a Google Drive folder of Docs into a Sheet.</p>
<label><b>Drive Folder ID</b></label><br>
<input id="folderId" style="width:100%;margin-bottom:8px;"
placeholder="Paste folder ID here" />
<button onclick="runIndex()">Index Folder</button>
<pre id="out" style="white-space:pre-wrap;margin-top:10px;"></pre>
<script>
function runIndex() {
document.getElementById("out").textContent = "Indexing...";
google.script.run
.withSuccessHandler(msg => document.getElementById("out").textContent = msg)
.indexFolder(document.getElementById("folderId").value);
}
</script>
</div>
4️⃣ Sidebar UI — Ask Questions
Sidebar_Ask.html
<div style="font-family:Arial;padding:14px;">
<h2>KB Q&A</h2>
<p>Ask questions using your indexed Docs.</p>
<label><b>Question</b></label><br>
<textarea id="q" style="width:100%;height:80px;"></textarea>
<button onclick="ask()">Ask</button>
<pre id="out" style="white-space:pre-wrap;margin-top:10px;"></pre>
<script>
function ask() {
document.getElementById("out").textContent = "Searching...";
google.script.run
.withSuccessHandler(msg => document.getElementById("out").textContent = msg)
.askKb(document.getElementById("q").value);
}
</script>
</div>
5️⃣ Index Folder → Store Summaries
KbIndexer.gs
function indexFolder(folderId) {
if (!folderId) return "Please provide a folder ID.";
const folder = DriveApp.getFolderById(folderId);
const files = folder.getFilesByType(MimeType.GOOGLE_DOCS);
const sheet = getKbSheet_();
let count = 0;
while (files.hasNext()) {
const file = files.next();
const docId = file.getId();
const title = file.getName();
const url = file.getUrl();
const text = extractDocText_(docId);
const summary = summarizeForKb_(title, text);
const keywords = extractKeywords_(title, text);
sheet.appendRow([docId, title, url, summary, keywords.join(", ")]);
count++;
}
return "✅ Indexed " + count + " docs into KB_Index.";
}
6️⃣ Doc Text Extraction
DocUtils.gs
function extractDocText_(docId) {
const doc = DocumentApp.openById(docId);
return doc.getBody().getText();
}
function getKbSheet_() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("KB_Index");
if (!sheet) throw new Error("Create a sheet tab named KB_Index.");
return sheet;
}
7️⃣ Gemini Summarizer + Keyword Extractor
KbGemini.gs
function summarizeForKb_(title, text) {
const prompt = `
Summarize this doc for a searchable knowledge base.
Return 4-7 bullet-style sentences in plain text.
Doc title: ${title}
Doc text:
${text.substring(0, 12000)}
`;
return callGemini(prompt, "");
}
function extractKeywords_(title, text) {
const prompt = `
Return ONLY JSON array of 8-15 keywords for search.
Doc title: ${title}
Doc text:
${text.substring(0, 12000)}
`;
const out = callGemini(prompt, "");
try { return JSON.parse(out); } catch(e) { return []; }
}
8️⃣ Ask → Find Best Matches → Answer with Citations
KbAsk.gs
function askKb(question) {
if (!question) return "Enter a question.";
const sheet = getKbSheet_();
const values = sheet.getDataRange().getValues();
if (values.length < 2) return "KB_Index is empty. Run indexing first.";
const rows = values.slice(1);
// Simple scoring: keyword/title contains
const ranked = rows.map(r => {
const title = String(r[1] || "");
const summary = String(r[3] || "");
const keywords = String(r[4] || "");
const hay = (title + " " + summary + " " + keywords).toLowerCase();
const score = (hay.includes(question.toLowerCase())) ? 10 : 0;
return { row:r, score };
}).sort((a,b)=>b.score-a.score);
const top = ranked.slice(0, 5).map(x => ({
title: x.row[1],
url: x.row[2],
summary: x.row[3]
}));
const prompt = `
Answer the question using ONLY the provided sources.
If the sources don't contain the answer, say what is missing.
Question: ${question}
Sources:
${JSON.stringify(top)}
Return:
- Answer (short)
- Citations list (title + url)
`;
return callGemini(prompt, "");
}
🔥 Exercise Upgrades (For Advanced Users)
✅ Add “re-index” mode (update rows instead of append)
✅ Add embeddings (optional) using text-embedding models
✅ Add chunking for large Docs
✅ Better ranking (keyword overlap scoring)
✅ Add a “Source preview” in sidebar
✅ Testing Checklist
- Create KB_Index tab with headers
- Run
testGeminiConnection() - Use a folder with 3–5 docs to start
- Run Indexing
- Ask questions + verify citations
🔜 Coming Next (Issue #11)
AI Email Draft Assistant for Gmail (Context-Aware)
Use Gmail threads + Gemini to draft replies with tone + action items.