Reset Chat Show Sources Lesson 5

📘 Lesson 5 — Reset Chat + Show Sources (Docs & Chunks) in the UI

By now you have:

– A RAG index in Sheets (Lesson 4B)

– Chat history + RAG working together (Lesson 4C)

– A web app UI (from Lesson 2) calling google.script.run

But two things are missing:

– Users can’t reset the conversation easily

– You can’t see which documents Gemini actually used

Lesson 5 fixes both:

– Add a “Reset chat” function on the server + button in the UI

– Extend the API to return debug info (sources) for each answer

– Show that info in the UI under the assistant messages


🧠 What We’ll Add

1. On the server (Apps Script)

We’ll:

Upgrade answerWithChatRag() so it returns:

{

  answer: “…”,

  sources: [

    { fileName, chunkIndex, score, preview }

  ]

}

– Add chatWithChatRagDebug(question) that returns this object to the UI

– Add resetChatHistoryPublic() to clear chat history from the UI

2. On the client (Index.html)

We’ll:

– Add a “Reset conversation” button at the top

– Update onSend() to use chatWithChatRagDebug

– When the assistant responds, show a small “Sources” block under the message with file names + scores


🧩 Part 1 — Update the Server: Chat + Sources + Reset

In your Lesson4C_ChatRag.gs file (or similar), we’ll:

– Replace answerWithChatRag() with a version that returns {answer, sources}

– Adjust the chatWithChatRag() wrapper (or add a new one)

– Add a public reset function

1.1 Updated answerWithChatRag (return answer + sources)

/**

 * Main function for Lesson 5:

 * – Same idea as Lesson 4C, but now returns:

 *    {

 *      answer: string,

 *      sources: [

 *        { fileName, chunkIndex, score, preview }

 *      ]

 *    }

 */

function answerWithChatRag(question) {

  const q = (question || ”).trim();

  if (!q) throw new Error(‘Question cannot be empty.’);

  // 1) Load RAG index (from Sheet)

  const index = loadRagIndex_();

  if (index.length === 0) {

    throw new Error(‘RAG index is empty. Run buildRagIndex() first.’);

  }

  // 2) Load chat history

  const history = getChatHistory_(); // [{role, text}, …]

  // 3) Embed the question and compute similarity

  const queryEmbedding = embedText_(q);

  const ranked = index

    .map(entry => ({

      entry,

      score: cosineSimilarity_(queryEmbedding, entry.embedding)

    }))

    .sort((a, b) => b.score – a.score);

  const top = ranked.slice(0, TOP_K);

  // 4) Build document context + source metadata

  const sources = top.map(r => {

    const preview = (r.entry.text || ”).slice(0, 180).replace(/\s+/g, ‘ ‘) +

      (r.entry.text.length > 180 ? ‘…’ : ”);

    return {

      fileName: r.entry.fileName,

      chunkIndex: r.entry.chunkIndex,

      score: r.score,

      preview

    };

  });

  const docContext = top.map((r, i) =>

    `[#${i + 1} — ${r.entry.fileName} — chunk=${r.entry.chunkIndex} — score=${r.score.toFixed(3)}]\n` +

    r.entry.text

  ).join(‘\n\n’);

  // 5) Format chat history

  const conversationText = history.map(turn =>

    `${turn.role.toUpperCase()}: ${turn.text}`

  ).join(‘\n’);

  // 6) Build prompt

  const prompt =

    ‘You are a helpful assistant that uses BOTH:\n’ +

    ‘- The previous conversation with the user (chat history)\n’ +

    ‘- Retrieved document chunks from a RAG index in Sheets\n\n’ +

    ‘Rules:\n’ +

    ‘- Use the chat history for follow-up understanding.\n’ +

    ‘- Use the retrieved chunks for factual or policy information.\n’ +

    ‘- If the documents do not contain the answer, say you do not know.\n’ +

    ‘- Do NOT invent details that are not present in the documents.\n\n’ +

    ‘— CHAT HISTORY —\n’ +

    (conversationText || ‘[No previous messages; this is the first turn.]’) +

    ‘\n\n’ +

    ‘— RETRIEVED DOCUMENT CHUNKS —\n’ +

    docContext +

    ‘\n— END DOCUMENT CHUNKS —\n\n’ +

    ‘Now the user says:\n’ +

    ‘USER: ‘ + q + ‘\n\n’ +

    ‘Please answer as the ASSISTANT in a clear, concise way.’;

  // 7) Call Gemini

  const answer = callGeminiText_(prompt);

  // 8) Update history (user + assistant)

  const newHistory = addTurnToHistory_(history, { role: ‘user’, text: q });

  const newHistoryWithAssistant = addTurnToHistory_(newHistory, {

    role: ‘assistant’,

    text: answer

  });

  saveChatHistory_(newHistoryWithAssistant);

  // 9) Return both answer + sources

  return {

    answer: answer || ”,

    sources

  };

}

1.2 New wrapper for UI: chatWithChatRagDebug

If you already have chatWithChatRag, you can either replace it or add a new one. For clarity, let’s use a debug-friendly name:

/**

 * Web-app wrapper for Lesson 5.

 * The UI will call this via google.script.run.

 */

function chatWithChatRagDebug(question) {

  return answerWithChatRag(question); // already returns {answer, sources}

}

1.3 Public function to reset chat history

We already have getChatHistory_, saveChatHistory_, etc.
Now we expose a public reset:

/**

 * Clears chat history for the current user.

 * Can be called from the web UI (Reset Conversation button).

 */

function resetChatHistoryPublic() {

  const cache = CacheService.getUserCache();

  cache.remove(CHAT_HISTORY_KEY);

  return { ok: true };

}

That’s all you need server-side for Lesson 5.


🧩 Part 2 — Update the Web App UI

Now we’ll adjust your Index.html to:

– Add a Reset button

– Use chatWithChatRagDebug instead of the old function

– Render Sources under each assistant answer

I’ll show a minimal version assuming you’re using the chat UI style we built earlier (Lesson 2 / API + Drive assistant).

2.1 Add a “Reset” button in the header

Inside <body>, your main container probably looks like this:

<div class=”app”>

  <header>

    <h1>API + Drive + Gemini Assistant</h1>

    <div class=”subtitle”>

      Ask a question and the assistant will use your documents.

    </div>

  </header>

  …

</div>

Update the header to include a small button on the right:

<header style=”display:flex; justify-content:space-between; align-items:center; gap:8px;”>

  <div>

    <h1>Docs + RAG Chat Assistant</h1>

    <div class=”subtitle”>

      Ask questions grounded in your Drive documents with short-term chat memory.

    </div>

  </div>

  <button id=”resetBtn” onclick=”onResetChat()” style=”font-size:0.8rem;”>

    Reset conversation

  </button>

</header>

2.2 Update the message rendering to support “Sources”

In your <script> block, you probably had something like:

function addMessage(text, sender) {

  const wrapper = document.createElement(‘div’);

  wrapper.className = ‘message ‘ + sender;

  …

}

Let’s extend it to optionally attach a “Sources” panel.

Replace your addMessage function with:

<script>

  const messagesEl = document.getElementById(‘messages’);

  const userInputEl = document.getElementById(‘userInput’);

  const sendBtn = document.getElementById(‘sendBtn’);

  const statusEl = document.getElementById(‘status’);

  let typingMessageEl = null;

  function addMessage(text, sender, sources) {

    const wrapper = document.createElement(‘div’);

    wrapper.className = ‘message ‘ + sender;

    // Label (You / Assistant)

    if (sender === ‘user’ || sender === ‘bot’) {

      const label = document.createElement(‘span’);

      label.className = ‘bubble-label’;

      label.textContent = sender === ‘user’ ? ‘You’ : ‘Assistant’;

      wrapper.appendChild(label);

    }

    // Main text

    const content = document.createElement(‘div’);

    content.textContent = text;

    wrapper.appendChild(content);

    // Optional sources block (only for bot messages with debug data)

    if (sender === ‘bot’ && Array.isArray(sources) && sources.length > 0) {

      const src = document.createElement(‘div’);

      src.style.marginTop = ‘6px’;

      src.style.paddingTop = ‘4px’;

      src.style.borderTop = ‘1px solid #1f2937’;

      src.style.fontSize = ‘0.75rem’;

      src.style.color = ‘#9ca3af’;

      const title = document.createElement(‘div’);

      title.textContent = ‘Sources used:’;

      title.style.marginBottom = ‘2px’;

      src.appendChild(title);

      const list = document.createElement(‘ul’);

      list.style.margin = 0;

      list.style.paddingLeft = ’16px’;

      sources.forEach((s) => {

        const li = document.createElement(‘li’);

        li.textContent =

          `${s.fileName} (chunk ${s.chunkIndex}, score ${s.score.toFixed(3)})` +

          (s.preview ? ` — “${s.preview}”` : ”);

        list.appendChild(li);

      });

      src.appendChild(list);

      wrapper.appendChild(src);

    }

    messagesEl.appendChild(wrapper);

    messagesEl.scrollTop = messagesEl.scrollHeight;

  }

  // … keep your typing, status, setLoading, etc.

</script>

2.3 Update onSend() to use the new server function

Change your previous onSend() (which probably called chatWithFolderAndApi, chatWithRagFromSheet, or chatWithChatRag) to now call chatWithChatRagDebug.

For example:

function onSend() {

  const text = userInputEl.value.trim();

  if (!text) {

    showStatus(‘Please enter a question.’, true);

    return;

  }

  showStatus(”, false);

  addMessage(text, ‘user’);

  userInputEl.value = ”;

  autoGrowTextarea();

  setLoading(true);

  showTyping();

  google.script.run

    .withSuccessHandler(function (res) {

      setLoading(false);

      hideTyping();

      if (res && res.answer) {

        // res.sources may be undefined or empty

        addMessage(res.answer, ‘bot’, res.sources || []);

      } else {

        addMessage(

          ‘I could not generate an answer.’,

          ‘bot’

        );

      }

    })

    .withFailureHandler(function (err) {

      setLoading(false);

      hideTyping();

      showStatus(err.message || String(err), true);

      addMessage(‘Sorry, something went wrong on the server.’, ‘bot’);

    })

    .chatWithChatRagDebug(text);

}

2.4 Implement the Reset button behavior

Add a onResetChat() function in the same script:

function onResetChat() {

  setLoading(true);

  showStatus(‘Resetting conversation…’, false);

  google.script.run

    .withSuccessHandler(function (res) {

      setLoading(false);

      showStatus(‘Conversation reset.’, false);

      // Clear messages in the UI

      messagesEl.innerHTML = ”;

      // Optionally add a system message

      addSystemMessage(

        ‘Conversation reset. Ask a new question to start a fresh chat.’

      );

    })

    .withFailureHandler(function (err) {

      setLoading(false);

      showStatus(err.message || String(err), true);

    })

    .resetChatHistoryPublic();

}

Make sure you still have this helper (we used it earlier):

function addSystemMessage(text) {

  const wrapper = document.createElement(‘div’);

  wrapper.className = ‘message system’;

  wrapper.textContent = text;

  messagesEl.appendChild(wrapper);

  messagesEl.scrollTop = messagesEl.scrollHeight;

}


🧪 Things to Try in Lesson 5

You can give learners a few practice tasks:

– Check the sources

– Ask a question about one specific document.

– Confirm that the “Sources used” list only shows that file.

– See how scores change

– Ask a broad question → multiple files, similar scores

– Ask a very specific question → one chunk with a much higher score

– Test reset

– Have a multi-step conversation

– Click Reset conversation

– Ask: “What did I just ask you before this?”

– The assistant should say it doesn’t know (history cleared).

– Optional: add a “Show/Hide Sources” toggle per message

– Wrap the sources block in a <details> element

– Let users expand/collapse to keep the UI clean.