Omartificial-Intelligence-Space commited on
Commit
5f30414
·
verified ·
1 Parent(s): 3228f67

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +795 -0
app.py ADDED
@@ -0,0 +1,795 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, render_template_string
2
+ from flask_cors import CORS
3
+ from google import genai
4
+ from google.genai import types
5
+ import os
6
+ import io
7
+ import httpx
8
+ import uuid
9
+ from datetime import datetime, timezone, timedelta
10
+ from dotenv import load_dotenv
11
+ import json
12
+
13
+ # Load environment variables
14
+ load_dotenv()
15
+
16
+ app = Flask(__name__)
17
+ CORS(app)
18
+
19
+ # Initialize Gemini client
20
+ client = genai.Client(api_key=os.getenv('GOOGLE_API_KEY'))
21
+
22
+ # In-memory storage for demo (in production, use a database)
23
+ document_caches = {}
24
+ user_sessions = {}
25
+
26
+ # HTML template for the web interface
27
+ HTML_TEMPLATE = """
28
+ <!DOCTYPE html>
29
+ <html lang="en">
30
+ <head>
31
+ <meta charset="UTF-8">
32
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
33
+ <title>Smart Document Analysis Platform</title>
34
+ <style>
35
+ * {
36
+ margin: 0;
37
+ padding: 0;
38
+ box-sizing: border-box;
39
+ }
40
+
41
+ body {
42
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
43
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
44
+ min-height: 100vh;
45
+ color: #333;
46
+ }
47
+
48
+ .container {
49
+ max-width: 1400px;
50
+ margin: 0 auto;
51
+ padding: 20px;
52
+ min-height: 100vh;
53
+ }
54
+
55
+ .header {
56
+ text-align: center;
57
+ margin-bottom: 30px;
58
+ color: white;
59
+ }
60
+
61
+ .header h1 {
62
+ font-size: 2.8em;
63
+ font-weight: 700;
64
+ margin-bottom: 10px;
65
+ text-shadow: 0 2px 4px rgba(0,0,0,0.3);
66
+ }
67
+
68
+ .header p {
69
+ font-size: 1.2em;
70
+ opacity: 0.9;
71
+ font-weight: 300;
72
+ }
73
+
74
+ .main-content {
75
+ display: grid;
76
+ grid-template-columns: 1fr 1fr;
77
+ gap: 30px;
78
+ height: calc(100vh - 200px);
79
+ }
80
+
81
+ .left-panel {
82
+ background: white;
83
+ border-radius: 20px;
84
+ padding: 30px;
85
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
86
+ overflow-y: auto;
87
+ }
88
+
89
+ .right-panel {
90
+ background: white;
91
+ border-radius: 20px;
92
+ padding: 30px;
93
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
94
+ display: flex;
95
+ flex-direction: column;
96
+ }
97
+
98
+ .panel-title {
99
+ font-size: 1.5em;
100
+ font-weight: 600;
101
+ margin-bottom: 20px;
102
+ color: #2d3748;
103
+ display: flex;
104
+ align-items: center;
105
+ gap: 10px;
106
+ }
107
+
108
+ .upload-section {
109
+ margin-bottom: 30px;
110
+ }
111
+
112
+ .upload-area {
113
+ border: 2px dashed #667eea;
114
+ border-radius: 15px;
115
+ padding: 40px;
116
+ text-align: center;
117
+ background: #f8fafc;
118
+ transition: all 0.3s ease;
119
+ margin-bottom: 20px;
120
+ }
121
+
122
+ .upload-area:hover {
123
+ border-color: #764ba2;
124
+ background: #f0f2ff;
125
+ transform: translateY(-2px);
126
+ }
127
+
128
+ .upload-area.dragover {
129
+ border-color: #764ba2;
130
+ background: #e8f0ff;
131
+ transform: scale(1.02);
132
+ }
133
+
134
+ .upload-icon {
135
+ font-size: 3em;
136
+ color: #667eea;
137
+ margin-bottom: 15px;
138
+ }
139
+
140
+ .file-input {
141
+ display: none;
142
+ }
143
+
144
+ .upload-btn {
145
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
146
+ color: white;
147
+ border: none;
148
+ padding: 15px 30px;
149
+ border-radius: 25px;
150
+ cursor: pointer;
151
+ font-size: 1.1em;
152
+ font-weight: 500;
153
+ transition: all 0.3s ease;
154
+ margin: 10px;
155
+ }
156
+
157
+ .upload-btn:hover {
158
+ transform: translateY(-2px);
159
+ box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
160
+ }
161
+
162
+ .url-input {
163
+ width: 100%;
164
+ padding: 15px;
165
+ border: 2px solid #e2e8f0;
166
+ border-radius: 10px;
167
+ font-size: 1em;
168
+ margin-bottom: 15px;
169
+ transition: border-color 0.3s ease;
170
+ }
171
+
172
+ .url-input:focus {
173
+ outline: none;
174
+ border-color: #667eea;
175
+ }
176
+
177
+ .btn {
178
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
179
+ color: white;
180
+ border: none;
181
+ padding: 12px 25px;
182
+ border-radius: 20px;
183
+ cursor: pointer;
184
+ font-size: 1em;
185
+ font-weight: 500;
186
+ transition: all 0.3s ease;
187
+ }
188
+
189
+ .btn:hover {
190
+ transform: translateY(-1px);
191
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
192
+ }
193
+
194
+ .btn:disabled {
195
+ opacity: 0.6;
196
+ cursor: not-allowed;
197
+ transform: none;
198
+ }
199
+
200
+ .chat-container {
201
+ flex: 1;
202
+ border: 1px solid #e2e8f0;
203
+ border-radius: 15px;
204
+ overflow-y: auto;
205
+ padding: 20px;
206
+ background: #f8fafc;
207
+ margin-bottom: 20px;
208
+ }
209
+
210
+ .message {
211
+ margin-bottom: 15px;
212
+ padding: 15px;
213
+ border-radius: 12px;
214
+ max-width: 85%;
215
+ animation: fadeIn 0.3s ease;
216
+ }
217
+
218
+ @keyframes fadeIn {
219
+ from { opacity: 0; transform: translateY(10px); }
220
+ to { opacity: 1; transform: translateY(0); }
221
+ }
222
+
223
+ .user-message {
224
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
225
+ color: white;
226
+ margin-left: auto;
227
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
228
+ }
229
+
230
+ .ai-message {
231
+ background: white;
232
+ color: #333;
233
+ border: 1px solid #e2e8f0;
234
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
235
+ }
236
+
237
+ .input-group {
238
+ display: flex;
239
+ gap: 10px;
240
+ }
241
+
242
+ .question-input {
243
+ flex: 1;
244
+ padding: 15px;
245
+ border: 2px solid #e2e8f0;
246
+ border-radius: 12px;
247
+ font-size: 1em;
248
+ transition: border-color 0.3s ease;
249
+ }
250
+
251
+ .question-input:focus {
252
+ outline: none;
253
+ border-color: #667eea;
254
+ }
255
+
256
+ .cache-info {
257
+ background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
258
+ border-radius: 12px;
259
+ padding: 20px;
260
+ margin-bottom: 20px;
261
+ color: white;
262
+ box-shadow: 0 4px 12px rgba(72, 187, 120, 0.3);
263
+ }
264
+
265
+ .cache-info h3 {
266
+ margin-bottom: 10px;
267
+ font-weight: 600;
268
+ }
269
+
270
+ .loading {
271
+ text-align: center;
272
+ padding: 40px;
273
+ color: #666;
274
+ }
275
+
276
+ .loading-spinner {
277
+ border: 3px solid #f3f3f3;
278
+ border-top: 3px solid #667eea;
279
+ border-radius: 50%;
280
+ width: 40px;
281
+ height: 40px;
282
+ animation: spin 1s linear infinite;
283
+ margin: 0 auto 20px;
284
+ }
285
+
286
+ @keyframes spin {
287
+ 0% { transform: rotate(0deg); }
288
+ 100% { transform: rotate(360deg); }
289
+ }
290
+
291
+ .error {
292
+ background: linear-gradient(135deg, #f56565 0%, #e53e3e 100%);
293
+ border-radius: 12px;
294
+ padding: 15px;
295
+ color: white;
296
+ margin-bottom: 20px;
297
+ box-shadow: 0 4px 12px rgba(245, 101, 101, 0.3);
298
+ }
299
+
300
+ .success {
301
+ background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
302
+ border-radius: 12px;
303
+ padding: 15px;
304
+ color: white;
305
+ margin-bottom: 20px;
306
+ box-shadow: 0 4px 12px rgba(72, 187, 120, 0.3);
307
+ }
308
+
309
+ @media (max-width: 768px) {
310
+ .main-content {
311
+ grid-template-columns: 1fr;
312
+ gap: 20px;
313
+ }
314
+
315
+ .header h1 {
316
+ font-size: 2em;
317
+ }
318
+ }
319
+ </style>
320
+ </head>
321
+ <body>
322
+ <div class="container">
323
+ <div class="header">
324
+ <h1>📚 Smart Document Analysis Platform</h1>
325
+ <p>Upload PDF documents once, ask questions forever with Gemini API caching</p>
326
+ </div>
327
+
328
+ <div class="main-content">
329
+ <!-- Left Panel - Upload Section -->
330
+ <div class="left-panel">
331
+ <div class="panel-title">
332
+ 📤 Upload PDF Document
333
+ </div>
334
+
335
+ <div class="upload-section">
336
+ <div class="upload-area" id="uploadArea">
337
+ <div class="upload-icon">📄</div>
338
+ <p>Drag and drop your PDF file here, or click to select</p>
339
+ <input type="file" id="fileInput" class="file-input" accept=".pdf">
340
+ <button class="upload-btn" onclick="document.getElementById('fileInput').click()">
341
+ Choose PDF File
342
+ </button>
343
+ </div>
344
+
345
+ <div style="margin-top: 20px;">
346
+ <h3>Or provide a URL:</h3>
347
+ <input type="url" id="urlInput" class="url-input" placeholder="https://example.com/document.pdf">
348
+ <button class="btn" onclick="uploadFromUrl()">Upload from URL</button>
349
+ </div>
350
+ </div>
351
+
352
+ <div id="loading" class="loading" style="display: none;">
353
+ <div class="loading-spinner"></div>
354
+ <p id="loadingText">Processing your PDF... This may take a moment.</p>
355
+ </div>
356
+
357
+ <div id="error" class="error" style="display: none;"></div>
358
+ <div id="success" class="success" style="display: none;"></div>
359
+ </div>
360
+
361
+ <!-- Right Panel - Chat Section -->
362
+ <div class="right-panel">
363
+ <div class="panel-title">
364
+ 💬 Ask Questions
365
+ </div>
366
+
367
+ <div id="cacheInfo" class="cache-info" style="display: none;">
368
+ <h3>✅ Document Cached Successfully!</h3>
369
+ <p>Your PDF has been cached using Gemini API. You can now ask multiple questions without re-uploading.</p>
370
+ <p><strong>Cache ID:</strong> <span id="cacheId"></span></p>
371
+ <p><strong>Tokens Cached:</strong> <span id="tokenCount"></span></p>
372
+ </div>
373
+
374
+ <div class="chat-container" id="chatContainer">
375
+ <div class="message ai-message">
376
+ 👋 Hello! I'm ready to analyze your PDF documents. Upload a document to get started!
377
+ </div>
378
+ </div>
379
+
380
+ <div class="input-group">
381
+ <input type="text" id="questionInput" class="question-input" placeholder="Ask a question about your document...">
382
+ <button class="btn" onclick="askQuestion()" id="askBtn">Ask</button>
383
+ </div>
384
+ </div>
385
+ </div>
386
+ </div>
387
+
388
+ <script>
389
+ let currentCacheId = null;
390
+
391
+ // File upload handling
392
+ const uploadArea = document.getElementById('uploadArea');
393
+ const fileInput = document.getElementById('fileInput');
394
+
395
+ uploadArea.addEventListener('dragover', (e) => {
396
+ e.preventDefault();
397
+ uploadArea.classList.add('dragover');
398
+ });
399
+
400
+ uploadArea.addEventListener('dragleave', () => {
401
+ uploadArea.classList.remove('dragover');
402
+ });
403
+
404
+ uploadArea.addEventListener('drop', (e) => {
405
+ e.preventDefault();
406
+ uploadArea.classList.remove('dragover');
407
+ const files = e.dataTransfer.files;
408
+ if (files.length > 0) {
409
+ uploadFile(files[0]);
410
+ }
411
+ });
412
+
413
+ fileInput.addEventListener('change', (e) => {
414
+ if (e.target.files.length > 0) {
415
+ uploadFile(e.target.files[0]);
416
+ }
417
+ });
418
+
419
+ async function uploadFile(file) {
420
+ if (!file.type.includes('pdf')) {
421
+ showError('Please select a PDF file.');
422
+ return;
423
+ }
424
+
425
+ showLoading('Uploading PDF...');
426
+
427
+ const formData = new FormData();
428
+ formData.append('file', file);
429
+
430
+ try {
431
+ const response = await fetch('/upload', {
432
+ method: 'POST',
433
+ body: formData
434
+ });
435
+
436
+ const result = await response.json();
437
+
438
+ if (result.success) {
439
+ currentCacheId = result.cache_id;
440
+ document.getElementById('cacheId').textContent = result.cache_id;
441
+ document.getElementById('tokenCount').textContent = result.token_count;
442
+ document.getElementById('cacheInfo').style.display = 'block';
443
+ showSuccess('PDF uploaded and cached successfully!');
444
+
445
+ // Add initial message
446
+ addMessage('I\'ve analyzed your PDF document. What would you like to know about it?', 'ai');
447
+ } else {
448
+ showError(result.error);
449
+ }
450
+ } catch (error) {
451
+ showError('Error uploading file: ' + error.message);
452
+ } finally {
453
+ hideLoading();
454
+ }
455
+ }
456
+
457
+ async function uploadFromUrl() {
458
+ const url = document.getElementById('urlInput').value;
459
+ if (!url) {
460
+ showError('Please enter a valid URL.');
461
+ return;
462
+ }
463
+
464
+ showLoading('Uploading PDF from URL...');
465
+
466
+ try {
467
+ const response = await fetch('/upload-url', {
468
+ method: 'POST',
469
+ headers: {
470
+ 'Content-Type': 'application/json'
471
+ },
472
+ body: JSON.stringify({ url: url })
473
+ });
474
+
475
+ const result = await response.json();
476
+
477
+ if (result.success) {
478
+ currentCacheId = result.cache_id;
479
+ document.getElementById('cacheId').textContent = result.cache_id;
480
+ document.getElementById('tokenCount').textContent = result.token_count;
481
+ document.getElementById('cacheInfo').style.display = 'block';
482
+ showSuccess('PDF uploaded and cached successfully!');
483
+
484
+ // Add initial message
485
+ addMessage('I\'ve analyzed your PDF document. What would you like to know about it?', 'ai');
486
+ } else {
487
+ showError(result.error);
488
+ }
489
+ } catch (error) {
490
+ showError('Error uploading from URL: ' + error.message);
491
+ } finally {
492
+ hideLoading();
493
+ }
494
+ }
495
+
496
+ async function askQuestion() {
497
+ const question = document.getElementById('questionInput').value;
498
+ if (!question.trim()) return;
499
+
500
+ if (!currentCacheId) {
501
+ showError('Please upload a PDF document first.');
502
+ return;
503
+ }
504
+
505
+ // Add user message to chat
506
+ addMessage(question, 'user');
507
+ document.getElementById('questionInput').value = '';
508
+
509
+ // Show loading state
510
+ const askBtn = document.getElementById('askBtn');
511
+ const originalText = askBtn.textContent;
512
+ askBtn.textContent = 'Generating...';
513
+ askBtn.disabled = true;
514
+
515
+ try {
516
+ const response = await fetch('/ask', {
517
+ method: 'POST',
518
+ headers: {
519
+ 'Content-Type': 'application/json'
520
+ },
521
+ body: JSON.stringify({
522
+ question: question,
523
+ cache_id: currentCacheId
524
+ })
525
+ });
526
+
527
+ const result = await response.json();
528
+
529
+ if (result.success) {
530
+ addMessage(result.answer, 'ai');
531
+ } else {
532
+ addMessage('Error: ' + result.error, 'ai');
533
+ }
534
+ } catch (error) {
535
+ addMessage('Error: ' + error.message, 'ai');
536
+ } finally {
537
+ askBtn.textContent = originalText;
538
+ askBtn.disabled = false;
539
+ }
540
+ }
541
+
542
+ function addMessage(text, sender) {
543
+ const chatContainer = document.getElementById('chatContainer');
544
+ const messageDiv = document.createElement('div');
545
+ messageDiv.className = `message ${sender}-message`;
546
+ messageDiv.textContent = text;
547
+ chatContainer.appendChild(messageDiv);
548
+ chatContainer.scrollTop = chatContainer.scrollHeight;
549
+ }
550
+
551
+ function showLoading(text = 'Processing...') {
552
+ document.getElementById('loadingText').textContent = text;
553
+ document.getElementById('loading').style.display = 'block';
554
+ }
555
+
556
+ function hideLoading() {
557
+ document.getElementById('loading').style.display = 'none';
558
+ }
559
+
560
+ function showError(message) {
561
+ const errorDiv = document.getElementById('error');
562
+ errorDiv.textContent = message;
563
+ errorDiv.style.display = 'block';
564
+ setTimeout(() => {
565
+ errorDiv.style.display = 'none';
566
+ }, 5000);
567
+ }
568
+
569
+ function showSuccess(message) {
570
+ const successDiv = document.getElementById('success');
571
+ successDiv.textContent = message;
572
+ successDiv.style.display = 'block';
573
+ setTimeout(() => {
574
+ successDiv.style.display = 'none';
575
+ }, 5000);
576
+ }
577
+
578
+ // Enter key to ask question
579
+ document.getElementById('questionInput').addEventListener('keypress', (e) => {
580
+ if (e.key === 'Enter') {
581
+ askQuestion();
582
+ }
583
+ });
584
+ </script>
585
+ </body>
586
+ </html>
587
+ """
588
+
589
+ @app.route('/')
590
+ def index():
591
+ return render_template_string(HTML_TEMPLATE)
592
+
593
+ @app.route('/upload', methods=['POST'])
594
+ def upload_file():
595
+ try:
596
+ if 'file' not in request.files:
597
+ return jsonify({'success': False, 'error': 'No file provided'})
598
+
599
+ file = request.files['file']
600
+
601
+ if file.filename == '':
602
+ return jsonify({'success': False, 'error': 'No file selected'})
603
+
604
+ # Read file content
605
+ file_content = file.read()
606
+ file_io = io.BytesIO(file_content)
607
+
608
+ # Upload to Gemini File API
609
+ document = client.files.upload(
610
+ file=file_io,
611
+ config=dict(mime_type='application/pdf')
612
+ )
613
+
614
+ # Create cache with system instruction
615
+ try:
616
+ system_instruction = "You are an expert document analyzer. Provide detailed, accurate answers based on the uploaded document content. Always be helpful and thorough in your responses."
617
+
618
+ # Use the correct model format as per documentation
619
+ model = 'models/gemini-2.0-flash-001'
620
+
621
+ cache = client.caches.create(
622
+ model=model,
623
+ config=types.CreateCachedContentConfig(
624
+ display_name='pdf document cache',
625
+ system_instruction=system_instruction,
626
+ contents=[document],
627
+ ttl="3600s", # 1 hour TTL
628
+ )
629
+ )
630
+
631
+ # Store cache info
632
+ cache_id = str(uuid.uuid4())
633
+ document_caches[cache_id] = {
634
+ 'cache_name': cache.name,
635
+ 'document_name': file.filename,
636
+ 'created_at': datetime.now().isoformat()
637
+ }
638
+
639
+ return jsonify({
640
+ 'success': True,
641
+ 'cache_id': cache_id,
642
+ 'token_count': getattr(cache.usage_metadata, 'cached_token_count', 'Unknown')
643
+ })
644
+
645
+ except Exception as cache_error:
646
+ # If caching fails due to small content, provide alternative approach
647
+ if "Cached content is too small" in str(cache_error):
648
+ return jsonify({
649
+ 'success': False,
650
+ 'error': 'PDF is too small for caching. Please upload a larger document (minimum 4,096 tokens required).',
651
+ 'suggestion': 'Try uploading a longer document or combine multiple documents.'
652
+ })
653
+ else:
654
+ raise cache_error
655
+
656
+ except Exception as e:
657
+ return jsonify({'success': False, 'error': str(e)})
658
+
659
+ @app.route('/upload-url', methods=['POST'])
660
+ def upload_from_url():
661
+ try:
662
+ data = request.get_json()
663
+ url = data.get('url')
664
+
665
+ if not url:
666
+ return jsonify({'success': False, 'error': 'No URL provided'})
667
+
668
+ # Download file from URL
669
+ response = httpx.get(url)
670
+ response.raise_for_status()
671
+
672
+ file_io = io.BytesIO(response.content)
673
+
674
+ # Upload to Gemini File API
675
+ document = client.files.upload(
676
+ file=file_io,
677
+ config=dict(mime_type='application/pdf')
678
+ )
679
+
680
+ # Create cache with system instruction
681
+ try:
682
+ system_instruction = "You are an expert document analyzer. Provide detailed, accurate answers based on the uploaded document content. Always be helpful and thorough in your responses."
683
+
684
+ # Use the correct model format as per documentation
685
+ model = 'models/gemini-2.0-flash-001'
686
+
687
+ cache = client.caches.create(
688
+ model=model,
689
+ config=types.CreateCachedContentConfig(
690
+ display_name='pdf document cache',
691
+ system_instruction=system_instruction,
692
+ contents=[document],
693
+ ttl="3600s", # 1 hour TTL
694
+ )
695
+ )
696
+
697
+ # Store cache info
698
+ cache_id = str(uuid.uuid4())
699
+ document_caches[cache_id] = {
700
+ 'cache_name': cache.name,
701
+ 'document_name': url,
702
+ 'created_at': datetime.now().isoformat()
703
+ }
704
+
705
+ return jsonify({
706
+ 'success': True,
707
+ 'cache_id': cache_id,
708
+ 'token_count': getattr(cache.usage_metadata, 'cached_token_count', 'Unknown')
709
+ })
710
+
711
+ except Exception as cache_error:
712
+ # If caching fails due to small content, provide alternative approach
713
+ if "Cached content is too small" in str(cache_error):
714
+ return jsonify({
715
+ 'success': False,
716
+ 'error': 'PDF is too small for caching. Please upload a larger document (minimum 4,096 tokens required).',
717
+ 'suggestion': 'Try uploading a longer document or combine multiple documents.'
718
+ })
719
+ else:
720
+ raise cache_error
721
+
722
+ except Exception as e:
723
+ return jsonify({'success': False, 'error': str(e)})
724
+
725
+ @app.route('/ask', methods=['POST'])
726
+ def ask_question():
727
+ try:
728
+ data = request.get_json()
729
+ question = data.get('question')
730
+ cache_id = data.get('cache_id')
731
+
732
+ if not question or not cache_id:
733
+ return jsonify({'success': False, 'error': 'Missing question or cache_id'})
734
+
735
+ if cache_id not in document_caches:
736
+ return jsonify({'success': False, 'error': 'Cache not found'})
737
+
738
+ cache_info = document_caches[cache_id]
739
+
740
+ # Generate response using cached content with correct model format
741
+ response = client.models.generate_content(
742
+ model='models/gemini-2.0-flash-001',
743
+ contents=question,
744
+ config=types.GenerateContentConfig(
745
+ cached_content=cache_info['cache_name']
746
+ )
747
+ )
748
+
749
+ return jsonify({
750
+ 'success': True,
751
+ 'answer': response.text
752
+ })
753
+
754
+ except Exception as e:
755
+ return jsonify({'success': False, 'error': str(e)})
756
+
757
+ @app.route('/caches', methods=['GET'])
758
+ def list_caches():
759
+ try:
760
+ caches = []
761
+ for cache_id, cache_info in document_caches.items():
762
+ caches.append({
763
+ 'cache_id': cache_id,
764
+ 'document_name': cache_info['document_name'],
765
+ 'created_at': cache_info['created_at']
766
+ })
767
+
768
+ return jsonify({'success': True, 'caches': caches})
769
+
770
+ except Exception as e:
771
+ return jsonify({'success': False, 'error': str(e)})
772
+
773
+ @app.route('/cache/<cache_id>', methods=['DELETE'])
774
+ def delete_cache(cache_id):
775
+ try:
776
+ if cache_id not in document_caches:
777
+ return jsonify({'success': False, 'error': 'Cache not found'})
778
+
779
+ cache_info = document_caches[cache_id]
780
+
781
+ # Delete from Gemini API
782
+ client.caches.delete(cache_info['cache_name'])
783
+
784
+ # Remove from local storage
785
+ del document_caches[cache_id]
786
+
787
+ return jsonify({'success': True, 'message': 'Cache deleted successfully'})
788
+
789
+ except Exception as e:
790
+ return jsonify({'success': False, 'error': str(e)})
791
+
792
+ if __name__ == '__main__':
793
+ import os
794
+ port = int(os.environ.get("PORT", 7860))
795
+ app.run(debug=True, host='0.0.0.0', port=port)