Module

Downloading the File

Progress 7/8
7/8

Downloading the file

Now that Gemini has returned your assignment list in CSV format, the final step is to let the user download it!

We’ll do this by programmatically creating a downloadable file in the browser using JavaScript.

Here’s the function you’ll use:

1
2
3
4
5
6
7
8
9
10
11
function createFileAndDownload(filename, content) {
    const blob = new Blob([content], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = filename;
    document.body.appendChild(link);
    const p = document.createElement('p');
    p.innerHTML = filename;
    link.append(p);
}

How does this work?

  • new Blob([content]): This creates a binary object (Blob) from your text. Think of it like a fake file we can give to the browser.

  • URL.createObjectURL(blob): Generates a temporary download link from the Blob.

  • document.createElement(‘a’): We create an anchor () tag and set its href to our blob URL.

  • link.download = filename: This tells the browser what to name the downloaded file.

  • Finally, we append the link (with a label) so the user can click and download it.

You can now call this function like so:

1
createFileAndDownload("assignments.csv", geminiResponse);

If your response string includes some extra characters (like Markdown code block markers), make sure to clean it up first:

1
2
const cleaned = geminiResponse.slice(6).slice(0, -3);
createFileAndDownload("assignments.csv", cleaned);

Finishing our Event Listener

By the end of this tutorial, your full addEventListener function should look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
document.getElementById('file-upload').addEventListener('change', async (event) => {
    // Get fileUploaded, returns file object at index 0
    const fileUploaded = event.target.files[0];
    if (fileUploaded == null) {
        return;
    }

    // Create form object for PDF send to OCR API
    const form = new FormData();
    form.append('purpose', 'ocr');
    form.append('file', new File([fileUploaded], `${fileUploaded.name}`));

    // Send to Mistral and get structured markdown
    let ocrJson = await PDFToJson(form);

    // Combine all markdown content into one string
    let markdownExport = "";
    for (const element of ocrJson.pages) {
        markdownExport += element.markdown + " ";
    }

    // Send combined markdown to Gemini for CSV generation
    const geminiJson = await JsonToCSV(markdownExport);
    const geminiResponse = geminiJson.candidates[0].content.parts[0].text;

    // Download the result as a .csv file
    createFileAndDownload("downloadable.csv", geminiResponse.slice(6).slice(0, -3));
});

That’s it! You’ve now built a full Chrome extension that lets users upload a syllabus, extracts all assignments using AI, and downloads the results as a clean CSV file.

Completion & Discussion Checklist

Before joining the group discussion or concluding this tutorial, ensure you have completed the tasks, investigated the bugs, and are ready to discuss the questions below:

Click to expand Completion & Discussion Checklist (7 Items)
#TypeItemPrompt Preview
1Bug HuntArrow Function this.files ContextIf you test this handler, DevTools logs TypeError: Cannot read properties of undefined (reading 'item'). Why is this.files undefined in an arrow function, and how does event.target.files[0] fix it?
2QuestionHTTP Accept Header NegotiationWhat does the header "Accept": "application/json" communicate to the API server? What might happen if a client doesn’t specify which format it expects back?
3QuestionJSON String SerializationWhy do web APIs expect serialized JSON strings (via JSON.stringify()) in HTTP request bodies instead of raw in-memory JavaScript objects?
4TaskFetch Signed Download URLUse fetch() to make a GET request to https://api.mistral.ai/v1/files/FILE_ID/url?expiry=24, pass the required headers, and extract the signed download URL using .json().
5TaskExecute Mistral OCR RequestUse fetch() with method 'POST' to send the JSON-stringified document payload to https://api.mistral.ai/v1/ocr, pass headers, and extract the result using .json().
6TaskAggregate Multi-Page MarkdownLoop through ocrJson.pages and concatenate the markdown property from each page into a single combined Markdown string.
7TaskGemini Structured CSV GenerationImplement async function JsonToCSV(markdownExport) to POST the combined markdown to Gemini’s endpoint and request assignment extraction in CSV format.