Files
21 |Attach files to make them available to Gemini
29 |├── static
├── icon.png
├── event-listener.js
├── chat.js
└── main.css
├── .env.example
├── requirements.txt
├── README.md
├── app.py
├── .gitignore
├── templates
└── index.html
└── LICENSE
/static/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-gemini/gemini-api-quickstart/HEAD/static/icon.png
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # Get your Gemini API key from: https://aistudio.google.com/app/apikey
2 | GOOGLE_API_KEY=
3 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | annotated-types==0.6.0
2 | blinker==1.8.2
3 | cachetools==5.3.3
4 | certifi==2024.7.4
5 | charset-normalizer==3.3.2
6 | click==8.1.7
7 | Flask==3.0.3
8 | google-ai-generativelanguage==0.6.2
9 | google-api-core==2.19.0
10 | google-api-python-client==2.127.0
11 | google-auth==2.29.0
12 | google-auth-httplib2==0.2.0
13 | google-genai==1.10.0
14 | googleapis-common-protos==1.63.0
15 | grpcio==1.63.0
16 | grpcio-status==1.62.2
17 | httplib2==0.22.0
18 | idna==3.7
19 | itsdangerous==2.2.0
20 | Jinja2==3.1.5
21 | MarkupSafe==2.1.5
22 | pillow==10.3.0
23 | proto-plus==1.23.0
24 | protobuf==4.25.3
25 | pyasn1==0.6.0
26 | pyasn1_modules==0.4.0
27 | pydantic==2.7.1
28 | pydantic_core==2.18.2
29 | pyparsing==3.1.2
30 | python-dotenv==1.0.1
31 | requests==2.32.2
32 | rsa==4.9
33 | tqdm==4.66.4
34 | typing_extensions==4.11.0
35 | uritemplate==4.1.1
36 | urllib3==2.2.2
37 | Werkzeug==3.0.6
38 |
--------------------------------------------------------------------------------
/static/event-listener.js:
--------------------------------------------------------------------------------
1 | document.getElementById("file-upload").addEventListener("change", function (event) {
2 | const file = event.target.files[0];
3 | if (!file) {
4 | return;
5 | }
6 | const formData = new FormData();
7 | formData.append("file", file);
8 |
9 | fetch("/upload", {
10 | method: "POST",
11 | body: formData,
12 | })
13 | .then((response) => response.json())
14 | .then((data) => {
15 | if (data.success) {
16 | // Update and show the banner
17 | const banner = document.getElementById("upload-banner");
18 | banner.textContent = data.message;
19 | banner.style.display = "block";
20 |
21 | // Hide the banner after 3 seconds
22 | setTimeout(() => {
23 | banner.style.display = "none";
24 | }, 3000);
25 |
26 | fetch("/get_files")
27 | .then((response) => response.json())
28 | .then((data) => {
29 | populateFiles(data.assistant_files);
30 | });
31 | } else {
32 | console.error("Upload failed:", data.message);
33 | // Update and show the banner
34 | const banner = document.getElementById("upload-banner");
35 | banner.textContent = data.message;
36 | banner.style.display = "block";
37 | banner.style.color = "red";
38 |
39 | // Hide the banner after 3 seconds
40 | setTimeout(() => {
41 | banner.style.display = "none";
42 | }, 3500);
43 | }
44 | })
45 | .catch((error) => {
46 | console.error("Error uploading file:", error);
47 | });
48 | });
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gemini API Quickstart - Python
2 |
3 | This repository contains a simple Python Flask App running with the Google AI Gemini API, designed to get you started building with Gemini's multi-modal capabilities. The app comes with a basic UI and a Flask backend.
4 |
5 |
6 |
7 | ## Basic request
8 |
9 | To send your first API request with the [Google Gen AI SDK](https://ai.google.dev/gemini-api/docs/libraries#python), make sure you have the right dependencies installed (see installation steps below) and then run the following code:
10 |
11 | ```python
12 | from google import genai
13 |
14 | client = genai.Client(api_key="GEMINI_API_KEY")
15 | chat = client.chats.create(model="gemini-2.0-flash")
16 |
17 | response = chat.send_message("Hello world!")
18 | print(response.text)
19 |
20 | response = chat.send_message("Explain to me how AI works")
21 | print(response.text)
22 |
23 | for message in chat.get_history():
24 | print(f'role - {message.role}',end=": ")
25 | print(message.parts[0].text)
26 | ```
27 |
28 | ## Setup
29 |
30 | 1. If you don’t have Python installed, install it [from Python.org](https://www.python.org/downloads/).
31 |
32 | 2. [Clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) this repository.
33 |
34 | 3. Create a new virtual environment:
35 |
36 | - macOS:
37 | ```bash
38 | $ python -m venv venv
39 | $ . venv/bin/activate
40 | ```
41 |
42 | - Windows:
43 | ```cmd
44 | > python -m venv venv
45 | > .\venv\Scripts\activate
46 | ```
47 |
48 | - Linux:
49 | ```bash
50 | $ python -m venv venv
51 | $ source venv/bin/activate
52 | ```
53 |
54 | 4. Install the requirements:
55 |
56 | ```bash
57 | $ pip install -r requirements.txt
58 | ```
59 |
60 | 5. Make a copy of the example environment variables file:
61 |
62 | ```bash
63 | $ cp .env.example .env
64 | ```
65 |
66 | 6. Add your [API key](https://ai.google.dev/gemini-api/docs/api-key) to the newly created `.env` file or as an environment variable.
67 |
68 | 7. Run the app:
69 |
70 | ```bash
71 | $ flask run
72 | ```
73 |
74 | You should now be able to access the app from your browser at the following URL: [http://localhost:5000](http://localhost:5000)!
75 |
--------------------------------------------------------------------------------
/static/chat.js:
--------------------------------------------------------------------------------
1 | document.querySelector("form").addEventListener("submit", function (event) {
2 | event.preventDefault();
3 | const messageInput = document.querySelector(
4 | 'textarea[name="message"]'
5 | );
6 | const message = messageInput.value.trim();
7 | const chatContainer = document.querySelector(".messages");
8 |
9 | // Append the user's message to the chat container
10 | if (message) {
11 | const roleDiv = document.createElement("div");
12 | roleDiv.classList.add("message-role");
13 | roleDiv.classList.add("user");
14 |
15 | roleDiv.textContent = "User";
16 | chatContainer.appendChild(roleDiv);
17 |
18 | const userMessageDiv = document.createElement("div");
19 | userMessageDiv.classList.add("user-message");
20 | userMessageDiv.textContent = message;
21 | chatContainer.appendChild(userMessageDiv);
22 | }
23 |
24 | // Clear the message input
25 | messageInput.value = "";
26 |
27 | // Send the user's message to the server using AJAX
28 | fetch("/chat", {
29 | method: "POST",
30 | headers: {
31 | "Content-Type": "application/json",
32 | },
33 | body: JSON.stringify({ message: message }),
34 | })
35 | .then((response) => response.json())
36 | .then((data) => {
37 | if (data.success) {
38 | const roleDiv = document.createElement("div");
39 | roleDiv.classList.add("message-role");
40 | roleDiv.classList.add("assistant");
41 |
42 | roleDiv.textContent = "Model";
43 | chatContainer.appendChild(roleDiv);
44 |
45 | // Prepare the model message container
46 | const assistantMessageDiv = document.createElement("div");
47 | assistantMessageDiv.classList.add("assistant-message");
48 | chatContainer.appendChild(assistantMessageDiv);
49 |
50 | // Open a connection to receive streamed responses
51 | const eventSource = new EventSource("/stream");
52 | eventSource.onmessage = function (event) {
53 | const currentText = assistantMessageDiv.textContent;
54 | const newText = event.data;
55 | const lastChar = currentText.slice(-1);
56 |
57 | // Check if we need to add a space (streamed chunks might be missing it)
58 | if (/[.,!?]/.test(lastChar) && newText.charAt(0) !== " ") {
59 | assistantMessageDiv.textContent += " " + newText;
60 | } else {
61 | assistantMessageDiv.textContent += newText;
62 | }
63 |
64 | // Scroll to the bottom of the chat container
65 | chatContainer.scrollTop = chatContainer.scrollHeight;
66 | };
67 | eventSource.onerror = function () {
68 | eventSource.close();
69 | };
70 | }
71 | });
72 | });
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | from flask import (
2 | Flask,
3 | render_template,
4 | request,
5 | Response,
6 | stream_with_context,
7 | jsonify,
8 | )
9 | from werkzeug.utils import secure_filename
10 | from PIL import Image
11 | import io
12 | from dotenv import load_dotenv
13 | import os
14 |
15 | from google import genai
16 |
17 | # Load environment variables from .env file
18 | load_dotenv()
19 |
20 | ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg"}
21 |
22 | client = genai.Client(api_key=os.getenv("GOOGLE_API_KEY"))
23 | chat_session = client.chats.create(model="gemini-2.0-flash")
24 |
25 | app = Flask(__name__, static_folder='static', template_folder='templates')
26 |
27 | next_message = ""
28 | next_image = ""
29 |
30 |
31 | def allowed_file(filename):
32 | """Returns if a filename is supported via its extension"""
33 | _, ext = os.path.splitext(filename)
34 | return ext.lstrip('.').lower() in ALLOWED_EXTENSIONS
35 |
36 |
37 | @app.route("/upload", methods=["POST"])
38 | def upload_file():
39 | """Takes in a file, checks if it is valid,
40 | and saves it for the next request to the API
41 | """
42 | global next_image
43 |
44 | if "file" not in request.files:
45 | return jsonify(success=False, message="No file part")
46 |
47 | file = request.files["file"]
48 |
49 | if file.filename == "":
50 | return jsonify(success=False, message="No selected file")
51 | if file and allowed_file(file.filename):
52 | filename = secure_filename(file.filename)
53 |
54 | # Read the file stream into a BytesIO object
55 | file_stream = io.BytesIO(file.read())
56 | file_stream.seek(0)
57 | next_image = Image.open(file_stream)
58 |
59 | return jsonify(
60 | success=True,
61 | message="File uploaded successfully and added to the conversation",
62 | filename=filename,
63 | )
64 | return jsonify(success=False, message="File type not allowed")
65 |
66 |
67 | @app.route("/", methods=["GET"])
68 | def index():
69 | """Renders the main homepage for the app"""
70 | return render_template("index.html", chat_history=chat_session.get_history())
71 |
72 |
73 | @app.route("/chat", methods=["POST"])
74 | def chat():
75 | """
76 | Takes in the message the user wants to send
77 | to the Gemini API, saves it
78 | """
79 | global next_message
80 | next_message = request.json["message"]
81 | print(chat_session.get_history())
82 |
83 | return jsonify(success=True)
84 |
85 |
86 | @app.route("/stream", methods=["GET"])
87 | def stream():
88 | """
89 | Streams the response from the server for
90 | both multi-modal and plain text requests
91 | """
92 | def generate():
93 | global next_message
94 | global next_image
95 | assistant_response_content = ""
96 |
97 | if next_image != "":
98 | response = chat_session.send_message_stream([next_message, next_image])
99 | next_image = ""
100 | else:
101 | response = chat_session.send_message_stream(next_message)
102 | next_message = ""
103 |
104 | for chunk in response:
105 | assistant_response_content += chunk.text
106 | yield f"data: {chunk.text}\n\n"
107 |
108 | return Response(stream_with_context(generate()),
109 | mimetype="text/event-stream")
110 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
110 | .pdm.toml
111 | .pdm-python
112 | .pdm-build/
113 |
114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
115 | __pypackages__/
116 |
117 | # Celery stuff
118 | celerybeat-schedule
119 | celerybeat.pid
120 |
121 | # SageMath parsed files
122 | *.sage.py
123 |
124 | # Environments
125 | .env
126 | .venv
127 | env/
128 | venv/
129 | ENV/
130 | env.bak/
131 | venv.bak/
132 |
133 | # Spyder project settings
134 | .spyderproject
135 | .spyproject
136 |
137 | # Rope project settings
138 | .ropeproject
139 |
140 | # mkdocs documentation
141 | /site
142 |
143 | # mypy
144 | .mypy_cache/
145 | .dmypy.json
146 | dmypy.json
147 |
148 | # Pyre type checker
149 | .pyre/
150 |
151 | # pytype static type analyzer
152 | .pytype/
153 |
154 | # Cython debug symbols
155 | cython_debug/
156 |
157 | # PyCharm
158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
160 | # and can be added to the global gitignore or merged into this file. For a more nuclear
161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
162 | #.idea/
163 |
--------------------------------------------------------------------------------
/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Files
21 |Attach files to make them available to Gemini
29 |