bibibi12345 commited on
Commit
b6eebc3
·
1 Parent(s): 5ef2ea1

added animation frontend

Browse files
Files changed (2) hide show
  1. index.html +317 -0
  2. main.py +7 -1
index.html ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Gemini Image Generator</title>
7
+ <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ max-width: 800px;
11
+ margin: 0 auto;
12
+ padding: 20px;
13
+ background-color: #f5f5f5;
14
+ }
15
+ .container {
16
+ background: white;
17
+ padding: 30px;
18
+ border-radius: 10px;
19
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
20
+ }
21
+ h1 {
22
+ color: #333;
23
+ text-align: center;
24
+ margin-bottom: 30px;
25
+ }
26
+ .input-group {
27
+ margin-bottom: 20px;
28
+ }
29
+ label {
30
+ display: block;
31
+ margin-bottom: 5px;
32
+ font-weight: bold;
33
+ color: #555;
34
+ }
35
+ input[type="text"], textarea {
36
+ width: 100%;
37
+ padding: 12px;
38
+ border: 2px solid #ddd;
39
+ border-radius: 5px;
40
+ font-size: 16px;
41
+ box-sizing: border-box;
42
+ }
43
+ textarea {
44
+ height: 100px;
45
+ resize: vertical;
46
+ }
47
+ button {
48
+ background-color: #4CAF50;
49
+ color: white;
50
+ padding: 12px 24px;
51
+ border: none;
52
+ border-radius: 5px;
53
+ cursor: pointer;
54
+ font-size: 16px;
55
+ width: 100%;
56
+ margin-top: 10px;
57
+ }
58
+ button:hover {
59
+ background-color: #45a049;
60
+ }
61
+ button:disabled {
62
+ background-color: #cccccc;
63
+ cursor: not-allowed;
64
+ }
65
+ .loading {
66
+ display: none;
67
+ text-align: center;
68
+ margin: 20px 0;
69
+ }
70
+ .spinner {
71
+ border: 4px solid #f3f3f3;
72
+ border-top: 4px solid #3498db;
73
+ border-radius: 50%;
74
+ width: 40px;
75
+ height: 40px;
76
+ animation: spin 2s linear infinite;
77
+ margin: 0 auto;
78
+ }
79
+ @keyframes spin {
80
+ 0% { transform: rotate(0deg); }
81
+ 100% { transform: rotate(360deg); }
82
+ }
83
+ .result {
84
+ margin-top: 30px;
85
+ padding: 20px;
86
+ background-color: #f9f9f9;
87
+ border-radius: 5px;
88
+ display: none;
89
+ }
90
+ .result h3 {
91
+ margin-top: 0;
92
+ color: #333;
93
+ }
94
+ .text-content {
95
+ margin-bottom: 20px;
96
+ line-height: 1.6;
97
+ }
98
+ .image-container {
99
+ text-align: center;
100
+ margin-top: 20px;
101
+ }
102
+ .generated-image {
103
+ max-width: 100%;
104
+ height: auto;
105
+ border-radius: 5px;
106
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
107
+ }
108
+ .error {
109
+ color: #d32f2f;
110
+ background-color: #ffebee;
111
+ padding: 15px;
112
+ border-radius: 5px;
113
+ margin-top: 20px;
114
+ display: none;
115
+ }
116
+ </style>
117
+ </head>
118
+ <body>
119
+ <div class="container">
120
+ <h1>🎨 Gemini Image Generator</h1>
121
+
122
+ <div class="input-group">
123
+ <label for="apiKey">API Key:</label>
124
+ <input type="text" id="apiKey" placeholder="Enter your API key">
125
+ </div>
126
+
127
+ <div class="input-group">
128
+ <label for="prompt">Image Prompt:</label>
129
+ <textarea id="prompt" placeholder="Describe the image you want to generate..."></textarea>
130
+ </div>
131
+
132
+ <button onclick="generateImage()">Generate Image</button>
133
+
134
+ <div class="loading" id="loading">
135
+ <div class="spinner"></div>
136
+ <p>Generating image...</p>
137
+ </div>
138
+
139
+ <div class="error" id="error"></div>
140
+
141
+ <div class="result" id="result">
142
+ <h3>Generated Content:</h3>
143
+ <div class="text-content" id="textContent"></div>
144
+ <div class="image-container" id="imageContainer"></div>
145
+ </div>
146
+ </div>
147
+
148
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.min.js"></script>
149
+ <script>
150
+ async function generateImage() {
151
+ const apiKey = document.getElementById('apiKey').value.trim();
152
+ const prompt = document.getElementById('prompt').value.trim();
153
+
154
+ if (!apiKey) {
155
+ showError('Please enter your API key');
156
+ return;
157
+ }
158
+
159
+ if (!prompt) {
160
+ showError('Please enter a prompt');
161
+ return;
162
+ }
163
+
164
+ // Show loading state
165
+ document.getElementById('loading').style.display = 'block';
166
+ document.getElementById('result').style.display = 'none';
167
+ document.getElementById('error').style.display = 'none';
168
+ document.querySelector('button').disabled = true;
169
+
170
+ try {
171
+ const response = await fetch('/v1beta/models/gemini-2.5-flash-image-preview:generateContent', {
172
+ method: 'POST',
173
+ headers: {
174
+ 'Content-Type': 'application/json',
175
+ 'x-goog-api-key': apiKey
176
+ },
177
+ body: JSON.stringify({
178
+ contents: [{
179
+ parts: [{
180
+ text: prompt
181
+ }]
182
+ }]
183
+ })
184
+ });
185
+
186
+ if (!response.ok) {
187
+ const errorData = await response.text();
188
+ throw new Error(`HTTP ${response.status}: ${errorData}`);
189
+ }
190
+
191
+ const data = await response.json();
192
+ displayResult(data);
193
+
194
+ } catch (error) {
195
+ console.error('Error:', error);
196
+ showError('Error generating image: ' + error.message);
197
+ } finally {
198
+ // Hide loading state
199
+ document.getElementById('loading').style.display = 'none';
200
+ document.querySelector('button').disabled = false;
201
+ }
202
+ }
203
+
204
+ function displayResult(data) {
205
+ const resultDiv = document.getElementById('result');
206
+ const textContentDiv = document.getElementById('textContent');
207
+ const imageContainerDiv = document.getElementById('imageContainer');
208
+
209
+ // Clear previous results
210
+ textContentDiv.innerHTML = '';
211
+ imageContainerDiv.innerHTML = '';
212
+
213
+ if (data.candidates && data.candidates.length > 0) {
214
+ const candidate = data.candidates[0];
215
+ const parts = candidate.content.parts;
216
+
217
+ let textParts = [];
218
+ let imageParts = [];
219
+
220
+ // Separate text and image parts
221
+ parts.forEach(part => {
222
+ if (part.text) {
223
+ textParts.push(part.text);
224
+ } else if (part.inlineData && part.inlineData.data) {
225
+ imageParts.push(part.inlineData.data);
226
+ }
227
+ });
228
+
229
+ // Display text content
230
+ if (textParts.length > 0) {
231
+ textContentDiv.innerHTML = textParts.join('');
232
+ }
233
+
234
+ // Handle images
235
+ if (imageParts.length === 1) {
236
+ // Single image - display as is
237
+ const img = document.createElement('img');
238
+ img.src = `data:image/png;base64,${imageParts[0]}`;
239
+ img.className = 'generated-image';
240
+ img.alt = 'Generated image';
241
+ imageContainerDiv.appendChild(img);
242
+ } else if (imageParts.length > 1) {
243
+ // Multiple images - create animated GIF
244
+ createAnimatedGif(imageParts, imageContainerDiv);
245
+ }
246
+
247
+ resultDiv.style.display = 'block';
248
+ } else {
249
+ showError('No content generated');
250
+ }
251
+ }
252
+
253
+ function createAnimatedGif(imageDataArray, container) {
254
+ const gif = new GIF({
255
+ workers: 2,
256
+ quality: 10,
257
+ width: 512,
258
+ height: 512
259
+ });
260
+
261
+ let loadedImages = 0;
262
+ const totalImages = imageDataArray.length;
263
+
264
+ // Show loading message for GIF creation
265
+ container.innerHTML = '<p>Creating animated GIF...</p>';
266
+
267
+ imageDataArray.forEach((imageData, index) => {
268
+ const img = new Image();
269
+ img.onload = function() {
270
+ // Create a canvas to resize the image
271
+ const canvas = document.createElement('canvas');
272
+ const ctx = canvas.getContext('2d');
273
+ canvas.width = 512;
274
+ canvas.height = 512;
275
+
276
+ // Draw and resize image
277
+ ctx.drawImage(img, 0, 0, 512, 512);
278
+
279
+ // Add frame to GIF with 500ms delay
280
+ gif.addFrame(canvas, {delay: 500});
281
+
282
+ loadedImages++;
283
+ if (loadedImages === totalImages) {
284
+ // All images loaded, render GIF
285
+ gif.on('finished', function(blob) {
286
+ const url = URL.createObjectURL(blob);
287
+ const gifImg = document.createElement('img');
288
+ gifImg.src = url;
289
+ gifImg.className = 'generated-image';
290
+ gifImg.alt = 'Generated animated GIF';
291
+
292
+ container.innerHTML = '';
293
+ container.appendChild(gifImg);
294
+ });
295
+
296
+ gif.render();
297
+ }
298
+ };
299
+ img.src = `data:image/png;base64,${imageData}`;
300
+ });
301
+ }
302
+
303
+ function showError(message) {
304
+ const errorDiv = document.getElementById('error');
305
+ errorDiv.textContent = message;
306
+ errorDiv.style.display = 'block';
307
+ }
308
+
309
+ // Allow Enter key to submit
310
+ document.getElementById('prompt').addEventListener('keypress', function(e) {
311
+ if (e.key === 'Enter' && e.ctrlKey) {
312
+ generateImage();
313
+ }
314
+ });
315
+ </script>
316
+ </body>
317
+ </html>
main.py CHANGED
@@ -2,8 +2,9 @@ import os
2
  import re
3
  import httpx
4
  from fastapi import FastAPI, Request, HTTPException, Security
5
- from fastapi.responses import StreamingResponse, Response
6
  from fastapi.security import APIKeyHeader, APIKeyQuery
 
7
  from itertools import cycle
8
  import asyncio
9
  import json
@@ -65,6 +66,11 @@ async def get_project_id(key: str):
65
 
66
  raise HTTPException(status_code=500, detail="Could not extract project ID from any key.")
67
 
 
 
 
 
 
68
  # --- Proxy Endpoint ---
69
  @app.post("/v1beta/models/{model_path:path}")
70
  async def proxy(request: Request, model_path: str, api_key: str = Security(get_api_key)):
 
2
  import re
3
  import httpx
4
  from fastapi import FastAPI, Request, HTTPException, Security
5
+ from fastapi.responses import StreamingResponse, Response, FileResponse
6
  from fastapi.security import APIKeyHeader, APIKeyQuery
7
+ from fastapi.staticfiles import StaticFiles
8
  from itertools import cycle
9
  import asyncio
10
  import json
 
66
 
67
  raise HTTPException(status_code=500, detail="Could not extract project ID from any key.")
68
 
69
+ # --- Frontend Route ---
70
+ @app.get("/")
71
+ async def frontend():
72
+ return FileResponse("index.html")
73
+
74
  # --- Proxy Endpoint ---
75
  @app.post("/v1beta/models/{model_path:path}")
76
  async def proxy(request: Request, model_path: str, api_key: str = Security(get_api_key)):