 }})
Files
25 |Attach files to make them available to Gemini
42 |├── .env.example
├── .gitignore
├── LICENSE
├── README.md
├── app.py
├── requirements.txt
├── static
├── icon.png
└── main.css
└── templates
├── index.html
└── javascript
├── chat.js
└── event-listener.js
/.env.example:
--------------------------------------------------------------------------------
1 | GOOGLE_API_KEY=Enter_Key_Here
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | venv/
2 |
3 | *.pyc
4 | __pycache__/
5 |
6 | instance/
7 |
8 | .pytest_cache/
9 | .coverage
10 | htmlcov/
11 |
12 | dist/
13 | build/
14 | *.egg-info/
15 |
16 | .DS_STORE
17 |
18 | .env
19 |
20 | /.venv
21 |
22 | /uploads
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Logan Kilpatrick
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/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 [Gemini API Python SDK](https://github.com/google-gemini/generative-ai-python), make sure you have the right dependencies installed (see installation steps below) and then run the following code:
10 |
11 | ```python
12 | import os
13 | import google.generativeai as genai
14 |
15 | GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
16 |
17 | genai.configure(api_key=GOOGLE_API_KEY)
18 | model = genai.GenerativeModel('gemini-pro')
19 |
20 | chat = model.start_chat(history=[])
21 | response = chat.send_message("In one sentence, explain how AI works to a child.")
22 | # Note that the chat object is temporarily stateful, as you send messages and get responses, you can
23 | # see the history changing by doing `chat.history`.
24 |
25 | print(response.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.
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 |
76 | #### Attribution
77 |
78 | This repo includes code that was forked from [another repo I made](https://github.com/openai/openai-quickstart-python), under an MIT license.
79 |
--------------------------------------------------------------------------------
/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 | import os
13 |
14 | import google.generativeai as genai
15 |
16 | ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg"}
17 |
18 | # WARNING: Do not share code with you API key hard coded in it.
19 | # Get your Gemini API key from: https://aistudio.google.com/app/apikey
20 | GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
21 |
22 | # Initialize configuration
23 | genai.configure(api_key=GOOGLE_API_KEY)
24 | # The rate limits are low on this model, so you might
25 | # need to switch to `gemini-pro`
26 | model = genai.GenerativeModel('gemini-1.5-pro-latest')
27 |
28 | app = Flask(__name__)
29 |
30 | chat_session = model.start_chat(history=[])
31 | next_message = ""
32 | next_image = ""
33 |
34 |
35 | def allowed_file(filename):
36 | """Returns if a filename is supported via its extension"""
37 | _, ext = os.path.splitext(filename)
38 | return ext.lstrip('.').lower() in ALLOWED_EXTENSIONS
39 |
40 |
41 | @app.route("/upload", methods=["POST"])
42 | def upload_file():
43 | """Takes in a file, checks if it is valid,
44 | and saves it for the next request to the API
45 | """
46 | global next_image
47 |
48 | if "file" not in request.files:
49 | return jsonify(success=False, message="No file part")
50 |
51 | file = request.files["file"]
52 |
53 | if file.filename == "":
54 | return jsonify(success=False, message="No selected file")
55 | if file and allowed_file(file.filename):
56 | filename = secure_filename(file.filename)
57 |
58 | # Read the file stream into a BytesIO object
59 | file_stream = io.BytesIO(file.read())
60 | file_stream.seek(0)
61 | next_image = Image.open(file_stream)
62 |
63 | return jsonify(
64 | success=True,
65 | message="File uploaded successfully and added to the conversation",
66 | filename=filename,
67 | )
68 | return jsonify(success=False, message="File type not allowed")
69 |
70 |
71 | @app.route("/", methods=["GET"])
72 | def index():
73 | """Renders the main homepage for the app"""
74 | return render_template("index.html", chat_history=chat_session.history)
75 |
76 |
77 | @app.route("/chat", methods=["POST"])
78 | def chat():
79 | """
80 | Takes in the message the user wants to send
81 | to the Gemini API, saves it
82 | """
83 | global next_message
84 | next_message = request.json["message"]
85 | print(chat_session.history)
86 |
87 | return jsonify(success=True)
88 |
89 |
90 | @app.route("/stream", methods=["GET"])
91 | def stream():
92 | """
93 | Streams the response from the serve for
94 | both multi-modal and plain text requests
95 | """
96 | def generate():
97 | global next_message
98 | global next_image
99 | assistant_response_content = ""
100 |
101 | if next_image != "":
102 | # This only works with `gemini-1.5-pro-latest`
103 | response = chat_session.send_message([next_message, next_image],
104 | stream=True)
105 | next_image = ""
106 | else:
107 | response = chat_session.send_message(next_message, stream=True)
108 | next_message = ""
109 |
110 | for chunk in response:
111 | assistant_response_content += chunk.text
112 | yield f"data: {chunk.text}\n\n"
113 |
114 | return Response(stream_with_context(generate()),
115 | mimetype="text/event-stream")
116 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | annotated-types==0.6.0
2 | blinker==1.8.2
3 | cachetools==5.3.3
4 | certifi==2024.2.2
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-generativeai==0.5.2
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.4
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 | requests==2.31.0
31 | rsa==4.9
32 | tqdm==4.66.4
33 | typing_extensions==4.11.0
34 | uritemplate==4.1.1
35 | urllib3==2.2.1
36 | Werkzeug==3.0.3
--------------------------------------------------------------------------------
/static/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/logankilpatrick/gemini-api-quickstart/bf6953df9e717884c089ceafefa2e17b4a8e6b63/static/icon.png
--------------------------------------------------------------------------------
/static/main.css:
--------------------------------------------------------------------------------
1 | body,
2 | input {
3 | line-height: 24px;
4 | color: #353740;
5 | font-family: "ColfaxAI", Helvetica, sans-serif;
6 | }
7 |
8 | body {
9 | display: flex;
10 | flex-direction: column; /* Change to row to align children horizontally */
11 | align-items: stretch; /* Align items at the start of the cross axis */
12 | justify-content: center; /* Center items on the main axis */
13 | width: 100%; /* Ensure full width */
14 | margin: 0; /* Remove default margin */
15 | padding: 0; /* Remove default padding */
16 | overflow: hidden;
17 | }
18 |
19 | .icon {
20 | width: 34px;
21 | }
22 |
23 | h3 {
24 | font-size: 32px;
25 | line-height: 40px;
26 | font-weight: bold;
27 | color: #202123;
28 | margin: 16px 0 40px;
29 | }
30 |
31 | .chat-container {
32 | max-width: 600px;
33 | width: 70%;
34 | padding: 20px;
35 | margin: 0 auto;
36 | padding-top: 40px;
37 | display: flex;
38 | flex-direction: column;
39 | overflow: hidden;
40 | float: right;
41 | overflow-y: auto;
42 | box-sizing: border-box;
43 | justify-content: space-between;
44 | }
45 |
46 | .user-message,
47 | .assistant-message {
48 | padding: 12px 16px;
49 | border-radius: 4px;
50 | margin-bottom: 8px;
51 | max-width: 80%;
52 | word-wrap: break-word;
53 | font-size: 16px;
54 | }
55 |
56 | .user-message {
57 | align-self: flex-end !important;
58 | background-color: #f7f7f8;
59 | }
60 |
61 | .assistant-message {
62 | align-self: flex-start !important;
63 | background-color: #ebfaeb;
64 | }
65 |
66 | .message-input-container {
67 | position: relative;
68 | width: 100%; /* Match the chat container's width */
69 | background: #fff;
70 | box-sizing: border-box;
71 | display: flex;
72 | justify-content: center;
73 | align-items: start;
74 | z-index: 100;
75 | }
76 |
77 | .message-input-container form {
78 | display: flex; /* Use flexbox to layout children */
79 | align-items: center; /* Align items vertically */
80 | width: 100%; /* Take full width to accommodate children */
81 | }
82 |
83 | form {
84 | max-width: 600px;
85 | width: 100%;
86 | margin: 0 auto;
87 | display: flex;
88 | justify-content: space-between;
89 | align-items: center;
90 | }
91 |
92 | textarea {
93 | border: 1px solid #c5c5d2;
94 | border-radius: 8px;
95 | margin-bottom: 24px;
96 | width: calc(100% - 20px);
97 | resize: vertical;
98 | overflow: auto;
99 | margin: 0;
100 | margin-right: 10px;
101 | flex-grow: 1;
102 | padding: 8px 12px;
103 | max-height: 32px;
104 | box-sizing: border-box; /* Include padding in the element's total dimensions */
105 | }
106 |
107 | input[type="submit"],
108 | input[type="button"] {
109 | padding: 12px 16px;
110 | color: #fff;
111 | background-color: #1a73e8;
112 | border: none;
113 | border-radius: 4px;
114 | text-align: center;
115 | cursor: pointer;
116 | flex: 1;
117 | margin: 0 2px;
118 | }
119 |
120 | input[type="submit"] {
121 | flex-grow: 4;
122 | }
123 |
124 | input[type="button"] {
125 | flex-grow: 1;
126 | background-color: #f44336;
127 | }
128 |
129 | input[type="text"] {
130 | padding: 12px 16px;
131 | border: 1px solid #1a73e8;
132 | border-radius: 4px;
133 | margin-bottom: 24px;
134 | }
135 |
136 | ::placeholder {
137 | color: #8e8ea0;
138 | opacity: 1;
139 | }
140 |
141 | .result {
142 | font-weight: bold;
143 | margin-top: 40px;
144 | }
145 |
146 | .typing-indicator-container {
147 | display: flex;
148 | justify-content: flex-start;
149 | }
150 |
151 | .typing-indicator {
152 | margin-left: 4px;
153 | font-size: 16px; /* Adjust size as needed */
154 | }
155 |
156 | .typing-indicator::after {
157 | content: "•";
158 | animation: typing 1.5s infinite step-start;
159 | }
160 |
161 | @keyframes typing {
162 | 0%,
163 | 100% {
164 | content: "•";
165 | }
166 | 33% {
167 | content: "••";
168 | }
169 | 66% {
170 | content: "•••";
171 | }
172 | }
173 |
174 | .button-group {
175 | display: flex;
176 | align-items: center; /* Add this to vertically center the elements */
177 | justify-content: space-between; /* Adjust as needed */
178 | }
179 |
180 | .file-upload-input {
181 | display: none; /* Hide the actual input */
182 | }
183 |
184 | #upload-banner {
185 | display: none;
186 | position: fixed;
187 | top: 0;
188 | width: 100%;
189 | background-color: #1a73e8;
190 | color: white;
191 | text-align: center;
192 | padding: 10px;
193 | z-index: 1000;
194 | }
195 |
196 | #ids-container {
197 | font-size: 9px;
198 | margin-bottom: 20px;
199 | text-align: center;
200 | }
201 |
202 | /* The Modal (background) */
203 | .modal {
204 | display: none; /* Hidden by default */
205 | position: fixed; /* Stay in place */
206 | z-index: 1; /* Sit on top */
207 | left: 0;
208 | top: 0;
209 | width: 100%; /* Full width */
210 | height: 100%; /* Full height */
211 | overflow: auto; /* Enable scroll if needed */
212 | background-color: rgb(0, 0, 0); /* Fallback color */
213 | background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
214 | }
215 |
216 | /* Modal Content */
217 | .modal-content {
218 | background-color: #fefefe;
219 | margin: 15% auto; /* 15% from the top and centered */
220 | padding: 20px;
221 | border: 1px solid #888;
222 | width: 80%; /* Could be more or less, depending on screen size */
223 | }
224 |
225 | /* The Close Button */
226 | .close {
227 | color: #aaa;
228 | float: right;
229 | font-size: 28px;
230 | font-weight: bold;
231 | }
232 |
233 | .close:hover,
234 | .close:focus {
235 | color: black;
236 | text-decoration: none;
237 | cursor: pointer;
238 | }
239 |
240 | .file-icon {
241 | cursor: pointer;
242 | color: #000;
243 | display: inline-block;
244 | font-size: 24px;
245 | padding-left: 5px;
246 | }
247 |
248 | .header {
249 | background-color: white;
250 | width: 100%;
251 | padding: 10px 0;
252 | display: flex; /* Add this line */
253 | align-items: center; /* Center items vertically */
254 | justify-content: start;
255 | }
256 |
257 | .header img {
258 | height: 20px;
259 | margin-right: 10px;
260 | }
261 |
262 | .header .demo-text {
263 | font-size: 15px;
264 | font-weight: bold;
265 | }
266 |
267 | .message-role {
268 | align-self: flex-start;
269 | font-size: 12pt;
270 | color: #000000;
271 | margin-bottom: 4px;
272 | }
273 |
274 | .message-role.user {
275 | align-self: flex-end;
276 | }
277 |
278 | #send-btn {
279 | width: 42px;
280 | height: 32px;
281 | border-radius: 4px;
282 | border: 1px solid #c5c5d2;
283 | background-color: #1a73e8;
284 | color: #fff;
285 | cursor: pointer;
286 | display: flex;
287 | justify-content: center;
288 | align-items: center;
289 | }
290 |
291 | .file-upload-section {
292 | display: flex;
293 | flex-direction: column;
294 | justify-content: space-around; /* Adjust content distribution */
295 | align-items: center; /* Center content horizontally */
296 | padding: 20px;
297 | box-sizing: border-box;
298 | border-right: 1px solid #c5c5d2; /* Optional: border to separate from the chat section */
299 | }
300 | .centered-text {
301 | margin-top: 85%;
302 | padding: 20px;
303 | text-align: center; /* Center the text inside the div */
304 | width: 100%; /* Ensure the div takes up the full width */
305 | }
306 |
307 | .file-upload-btn,
308 | .file-upload-input + label {
309 | margin-top: auto; /* Push the button to the bottom */
310 | width: calc(
311 | 100% - 40px
312 | ); /* Adjust width to ensure it fits within the parent's padding */
313 | padding: 4px 8px;
314 | cursor: pointer;
315 | background-color: #1a73e8;
316 | color: white;
317 | border: none;
318 | max-height: 32px;
319 | border-radius: 8px;
320 | box-sizing: border-box;
321 | text-align: center;
322 | }
323 |
324 | /* Clear floats */
325 | body::after {
326 | content: none;
327 | }
328 |
329 | .file-upload-section,
330 | .chat-container {
331 | float: none;
332 | }
333 |
334 | .main-content {
335 | display: flex;
336 | flex-direction: row; /* Align children (file upload section and chat container) horizontally */
337 | height: 100vh; /* Adjust height based on header height */
338 | overflow: auto; /* Allow scrolling within this container */
339 | align-items: stretch;
340 | }
341 |
342 | .messages {
343 | flex-grow: 1; /* Allow this container to take up available space */
344 | overflow-y: auto; /* Scroll if content exceeds height */
345 | display: flex;
346 | flex-direction: column;
347 | }
348 |
349 | .hidden {
350 | display: none;
351 | }
352 |
353 | #filesList {
354 | width: 100%;
355 | }
356 |
357 | .file-entry {
358 | display: flex;
359 | justify-content: space-between;
360 | align-items: center; /* Vertically center the items */
361 | gap: 10px; /* Add some space between the icon and the text */
362 | padding-bottom: 15px;
363 | }
364 |
365 | .file-entry div {
366 | display: flex;
367 | align-items: center;
368 | }
369 |
370 | .file-entry span {
371 | flex-grow: 1; /* Allows the file ID to take up any available space */
372 | margin: 0 10px; /* Adds some spacing around the file ID */
373 | }
374 |
375 | #filesDivider {
376 | border-top: 1px solid #ececf1; /* Sets the color and height of the divider */
377 | width: 100%; /* Ensures the divider stretches across the full width of its container */
378 | }
379 |
--------------------------------------------------------------------------------
/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Files
25 |Attach files to make them available to Gemini
42 |