CORVO-AI commited on
Commit
5c37ebc
·
1 Parent(s): 50b2a10

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +351 -429
app.py CHANGED
@@ -1,471 +1,393 @@
1
- from flask import Flask, request, jsonify
2
  import requests
3
- import random
4
- import string
5
- import time
6
- import json
 
 
 
7
 
8
  app = Flask(__name__)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- # Global variables to store workspace and bot IDs
11
- GLOBAL_WORKSPACE_ID = None
12
- GLOBAL_BOT_ID = None
13
-
14
-
15
- # Authorization value used in requests (should be updated with a valid Authorization),
16
- TOKEN = "Bearer bp_pat_vTuxol25N0ymBpYaWqtWpFfGPKt260IfT784"
17
- # -------------------------------------------------------------------
18
- # Helper functions for random bot/workspace names
19
- # -------------------------------------------------------------------
20
- def generate_random_name(length=5):
21
- """Generate a random name for workspace or bot"""
22
- return ''.join(random.choices(string.ascii_letters, k=length))
23
-
24
- # -------------------------------------------------------------------
25
- # Functions to create/delete workspaces and bots
26
- # -------------------------------------------------------------------
27
- def create_workspace():
28
- """Create a new workspace and return its ID"""
29
- ws_url = "https://api.botpress.cloud/v1/admin/workspaces"
30
- headers = {
31
- "User-Agent": "Mozilla/5.0",
32
- "Authorization": TOKEN
33
- }
34
- payload = {"name": generate_random_name()}
35
 
36
- try:
37
- response = requests.post(ws_url, headers=headers, json=payload)
38
- if response.status_code == 200:
39
- response_json = response.json()
40
- workspace_id = response_json.get('id')
41
- print(f"Successfully created workspace: {workspace_id}")
42
- return workspace_id
43
- else:
44
- print(f"Workspace creation failed with: {response.status_code}, {response.text}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  return None
46
- except Exception as e:
47
- print(f"Error creating workspace: {str(e)}")
48
- return None
49
 
 
 
 
50
 
51
- def create_bot(workspace_id):
52
- """Create a new bot in the specified workspace and return its ID"""
53
- if not workspace_id:
54
- print("Cannot create bot: No workspace ID provided")
55
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
- bot_url = "https://api.botpress.cloud/v1/admin/bots"
58
- headers = {
59
- "User-Agent": "Mozilla/5.0",
60
- "x-workspace-id": workspace_id,
61
- "Authorization": TOKEN,
62
- "Content-Type": "application/json"
63
- }
64
- payload = {"name": generate_random_name()}
65
 
66
- try:
67
- response = requests.post(bot_url, headers=headers, json=payload)
68
- if response.status_code == 200:
69
- response_json = response.json()
70
- bot_id = response_json.get("bot", {}).get("id")
71
- if not bot_id:
72
- print("Bot ID not found in the response.")
73
  return None
74
 
75
- print(f"Successfully created bot: {bot_id} in workspace: {workspace_id}")
 
 
76
 
77
- # Install integration for the new bot
78
- integration_success = install_bot_integration(bot_id, workspace_id)
79
- if integration_success:
80
- print(f"Successfully installed integration for bot {bot_id}")
81
- return bot_id
82
- else:
83
- print(f"Failed to install integration for bot {bot_id}")
84
- return bot_id # Still return the bot ID even if integration fails
85
- else:
86
- print(f"Bot creation failed with: {response.status_code}, {response.text}")
 
 
 
 
87
  return None
88
- except Exception as e:
89
- print(f"Error creating bot: {str(e)}")
90
- return None
91
-
92
-
93
- def install_bot_integration(bot_id, workspace_id):
94
- """Install required integration for the bot to function properly"""
95
- if not bot_id or not workspace_id:
96
- print("Cannot install integration: Missing bot ID or workspace ID")
97
- return False
98
-
99
- url = f"https://api.botpress.cloud/v1/admin/bots/{bot_id}"
100
- headers = {
101
- "User-Agent": "Mozilla/5.0",
102
- "Authorization": TOKEN,
103
- "Content-Type": "application/json",
104
- "x-bot-id": bot_id,
105
- "x-workspace-id": workspace_id
106
- }
107
- # Integration payload
108
- payload = {
109
- "integrations": {
110
- "intver_01KCHBZMZEM2017N9C0ND1STA5": {
111
- "enabled": True
112
- }
113
- }
114
- }
115
 
116
- try:
117
- response = requests.put(url, headers=headers, json=payload)
118
- if response.status_code == 200:
119
- print(f"Successfully installed integration for bot {bot_id}")
120
- return True
121
- else:
122
- print(f"Failed to install integration: {response.status_code}, {response.text}")
123
- return False
124
- except Exception as e:
125
- print(f"Error installing integration: {str(e)}")
126
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
 
 
 
 
 
 
128
 
129
- def try_delete_bot(bot_id, workspace_id):
130
- """Attempt to delete a bot from the specified workspace but continue if it fails"""
131
- if not bot_id or not workspace_id:
132
- print("Cannot delete bot: Missing bot ID or workspace ID")
133
- return False
134
 
135
- url = f"https://api.botpress.cloud/v1/admin/bots/{bot_id}"
136
- headers = {
137
- "User-Agent": "Mozilla/5.0",
138
- "x-workspace-id": workspace_id,
139
- "Authorization": TOKEN
140
- }
141
 
142
- try:
143
- response = requests.delete(url, headers=headers)
144
- if response.status_code in [200, 204]:
145
- print(f"Successfully deleted bot: {bot_id}")
146
- return True
 
 
 
 
 
 
 
 
 
147
  else:
148
- print(f"Failed to delete bot: {response.status_code}, {response.text}")
149
- return False
150
- except Exception as e:
151
- print(f"Error deleting bot: {str(e)}")
152
- return False
153
 
 
154
 
155
- def try_delete_workspace(workspace_id):
156
- """Attempt to delete a workspace but continue if it fails"""
157
- if not workspace_id:
158
- print("Cannot delete workspace: No workspace ID provided")
159
- return False
160
 
161
- url = f"https://api.botpress.cloud/v1/admin/workspaces/{workspace_id}"
162
- headers = {
163
- "User-Agent": "Mozilla/5.0",
164
- "Authorization": TOKEN
165
- }
166
 
167
- try:
168
- response = requests.delete(url, headers=headers)
169
- if response.status_code in [200, 204]:
170
- print(f"Successfully deleted workspace: {workspace_id}")
171
- return True
172
- else:
173
- print(f"Failed to delete workspace: {response.status_code}, {response.text}")
174
- return False
175
- except Exception as e:
176
- print(f"Error deleting workspace: {str(e)}")
177
- return False
178
-
179
-
180
- # -------------------------------------------------------------------
181
- # Main function that calls the Botpress API endpoint
182
- # -------------------------------------------------------------------
183
- def chat_with_assistant(user_input, chat_history, bot_id, workspace_id, temperature=0.9, top_p=0.95, max_tokens=None):
184
- """
185
- Sends the user input and chat history to the Botpress API endpoint,
186
- returns the assistant's response and (possibly updated) bot/workspace IDs.
187
- """
188
- # Prepare the headers
189
- headers = {
190
- "User-Agent": "Mozilla/5.0",
191
- "x-bot-id": bot_id,
192
- "Content-Type": "application/json",
193
- "Authorization": TOKEN
194
- }
195
-
196
- # Process chat history into the format expected by the API
197
- messages = []
198
- system_prompt = ""
199
-
200
- for msg in chat_history:
201
- if msg["role"] == "system":
202
- system_prompt = msg["content"]
203
- elif msg["role"] in ["user", "assistant"]:
204
- # Pass multipart messages directly without modifying their structure
205
- if "type" in msg and msg["type"] == "multipart" and "content" in msg:
206
- messages.append(msg) # Keep the original multipart structure
207
- # Handle regular text messages
208
- else:
209
- messages.append({
210
- "role": msg["role"],
211
- "content": msg["content"]
212
- })
213
-
214
- # Add the latest user input if not already in chat history
215
- if user_input and isinstance(user_input, str) and (not messages or messages[-1]["role"] != "user" or messages[-1]["content"] != user_input):
216
- messages.append({
217
- "role": "user",
218
- "content": user_input
219
- })
220
 
221
- # Prepare the payload for the API
222
- payload = {
223
- "type": "openai:generateContent",
224
- "input": {
225
- "model": {
226
- "id": "gpt-5.2-2025-12-11"
227
- },
228
- "systemPrompt": system_prompt,
229
- "messages": messages,
230
- "temperature": temperature,
231
- "debug": False,
232
- }
233
- }
234
 
235
- # Add maxTokens to the payload if provided
236
- if max_tokens is not None:
237
- payload["input"]["maxTokens"] = max_tokens
 
 
 
 
 
 
 
 
 
238
 
239
- botpress_url = "https://api.botpress.cloud/v1/chat/actions"
240
- max_retries = 3
241
- timeout = 120 # Increased timeout for long messages
242
 
243
- # For debugging
244
- print("Payload being sent to Botpress:")
245
- print(json.dumps(payload, indent=2))
246
 
247
- # Attempt to send the request
248
- for attempt in range(max_retries):
249
- try:
250
- print(f"Attempt {attempt+1}: Sending request to Botpress API with bot_id={bot_id}, workspace_id={workspace_id}")
251
- response = requests.post(botpress_url, json=payload, headers=headers, timeout=timeout)
 
 
252
 
253
- # If successful (200)
254
- if response.status_code == 200:
255
- data = response.json()
256
- assistant_content = data.get('output', {}).get('choices', [{}])[0].get('content', '')
257
- print(f"Successfully received response from Botpress API")
258
- return assistant_content, bot_id, workspace_id
259
-
260
- # Check for authentication or permission errors (401, 403)
261
- elif response.status_code in [401, 403]:
262
- error_message = "Authentication error"
263
- try:
264
- error_data = response.json()
265
- error_message = error_data.get('message', 'Authentication error')
266
- except:
267
- pass
268
-
269
- print(f"Authentication error detected: {error_message}")
270
-
271
- # We need to create new resources immediately
272
- print("Creating new workspace and bot...")
273
- new_workspace_id = create_workspace()
274
- if not new_workspace_id:
275
- print("Failed to create a new workspace")
276
- if attempt < max_retries - 1:
277
- time.sleep(3)
278
- continue
279
- else:
280
- return "Unable to create new resources. Please try again later.", bot_id, workspace_id
281
-
282
- new_bot_id = create_bot(new_workspace_id)
283
- if not new_bot_id:
284
- print("Failed to create a new bot")
285
- if attempt < max_retries - 1:
286
- time.sleep(3)
287
- continue
288
- else:
289
- return "Unable to create new bot. Please try again later.", new_workspace_id, workspace_id
290
 
291
- print(f"Created new workspace: {new_workspace_id} and bot: {new_bot_id}")
 
292
 
293
- # Try again with new IDs
294
- headers["x-bot-id"] = new_bot_id
295
- try:
296
- print(f"Retrying with new bot_id={new_bot_id}")
297
- retry_response = requests.post(botpress_url, json=payload, headers=headers, timeout=timeout)
298
 
299
- if retry_response.status_code == 200:
300
- data = retry_response.json()
301
- assistant_content = data.get('output', {}).get('choices', [{}])[0].get('content', '')
302
- print(f"Successfully received response with new IDs")
303
 
304
- # Try to clean up old resources in the background, but don't wait for result
305
- if bot_id and workspace_id:
306
- print(f"Attempting to clean up old resources in the background")
307
- try_delete_bot(bot_id, workspace_id)
308
- try_delete_workspace(workspace_id)
309
 
310
- return assistant_content, new_bot_id, new_workspace_id
311
- else:
312
- print(f"Failed with new IDs: {retry_response.status_code}")
313
- if attempt < max_retries - 1:
314
- time.sleep(2)
315
- continue
316
- else:
317
- return f"Unable to get a response even with new credentials.", new_bot_id, new_workspace_id
318
-
319
- except Exception as e:
320
- print(f"Error with new IDs: {str(e)}")
321
- if attempt < max_retries - 1:
322
- time.sleep(2)
323
- continue
324
- else:
325
- return f"Error with new credentials: {str(e)}", new_bot_id, new_workspace_id
326
 
327
- # Handle network errors or timeouts (just retry)
328
- elif response.status_code in [404, 408, 502, 503, 504]:
329
- print(f"Received error {response.status_code}. Retrying...")
330
- time.sleep(3) # Wait before retrying
331
- continue
332
 
333
- # Any other error status code
334
- else:
335
- print(f"Received unexpected error: {response.status_code}, {response.text}")
336
- if attempt < max_retries - 1:
337
- time.sleep(2)
338
- continue
339
- else:
340
- return f"Unable to get a response from the assistant (Error {response.status_code}).", bot_id, workspace_id
341
 
342
- except requests.exceptions.Timeout:
343
- print(f"Request timed out. Retrying...")
344
- if attempt < max_retries - 1:
345
- time.sleep(2)
346
- continue
347
- else:
348
- return "The assistant is taking too long to respond. Please try again with a shorter message.", bot_id, workspace_id
349
 
350
- except Exception as e:
351
- print(f"Error during request: {str(e)}")
352
- if attempt < max_retries - 1:
353
- time.sleep(2)
354
- continue
355
- else:
356
- return f"Unable to get a response from the assistant: {str(e)}", bot_id, workspace_id
357
-
358
- # Should not reach here due to the handling in the loop
359
- return "Unable to get a response from the assistant.", bot_id, workspace_id
360
-
361
-
362
- # -------------------------------------------------------------------
363
- # Flask Endpoint
364
- # -------------------------------------------------------------------
365
- @app.route("/chat", methods=["POST"])
366
- def chat_endpoint():
367
- """
368
- Expects JSON with:
369
- {
370
- "user_input": "string", // Can be null if multipart message is in chat_history
371
- "chat_history": [
372
- {"role": "system", "content": "..."},
373
- {"role": "user", "content": "..."},
374
- // Or for images:
375
- {"role": "user", "type": "multipart", "content": [
376
- {"type": "image", "url": "https://example.com/image.jpg"},
377
- {"type": "text", "text": "What's in this image?"}
378
- ]},
379
- ...
380
- ],
381
- "temperature": 0.9, // Optional, defaults to 0.9
382
- "top_p": 0.95, // Optional, defaults to 0.95
383
- "max_tokens": 1000 // Optional, defaults to null (no limit)
384
- }
385
- Returns JSON with:
386
- {
387
- "assistant_response": "string"
388
- }
389
- """
390
- global GLOBAL_WORKSPACE_ID, GLOBAL_BOT_ID
391
-
392
- # Parse JSON from request
393
- data = request.get_json(force=True)
394
- user_input = data.get("user_input", "")
395
- chat_history = data.get("chat_history", [])
396
-
397
- # Get temperature, top_p, and max_tokens from request, or use defaults
398
- temperature = data.get("temperature", 0.9)
399
- top_p = data.get("top_p", 0.95)
400
- max_tokens = data.get("max_tokens", None)
401
-
402
- # Validate temperature and top_p values
403
  try:
404
- temperature = float(temperature)
405
- if not 0 <= temperature <= 2:
406
- temperature = 0.9
407
- print(f"Invalid temperature value. Using default: {temperature}")
408
- except (ValueError, TypeError):
409
- temperature = 0.9
410
- print(f"Invalid temperature format. Using default: {temperature}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  try:
413
- top_p = float(top_p)
414
- if not 0 <= top_p <= 1:
415
- top_p = 0.95
416
- print(f"Invalid top_p value. Using default: {top_p}")
417
- except (ValueError, TypeError):
418
- top_p = 0.95
419
- print(f"Invalid top_p format. Using default: {top_p}")
420
-
421
- # Validate max_tokens if provided
422
- if max_tokens is not None:
423
- try:
424
- max_tokens = int(max_tokens)
425
- if max_tokens <= 0:
426
- print("Invalid max_tokens value (must be positive). Not using max_tokens.")
427
- max_tokens = None
428
- except (ValueError, TypeError):
429
- print("Invalid max_tokens format. Not using max_tokens.")
430
- max_tokens = None
431
-
432
- # If we don't yet have a workspace or bot, create them
433
- if not GLOBAL_WORKSPACE_ID or not GLOBAL_BOT_ID:
434
- print("No existing IDs found. Creating new workspace and bot...")
435
- GLOBAL_WORKSPACE_ID = create_workspace()
436
- if GLOBAL_WORKSPACE_ID:
437
- GLOBAL_BOT_ID = create_bot(GLOBAL_WORKSPACE_ID)
438
-
439
- # If creation failed
440
- if not GLOBAL_WORKSPACE_ID or not GLOBAL_BOT_ID:
441
- return jsonify({"assistant_response": "I'm currently unavailable. Please try again later."}), 500
442
-
443
- # Call our function that interacts with Botpress API
444
- print(f"Sending chat request with existing bot_id={GLOBAL_BOT_ID}, workspace_id={GLOBAL_WORKSPACE_ID}")
445
- print(f"Using temperature={temperature}, top_p={top_p}, max_tokens={max_tokens}")
446
-
447
- assistant_response, updated_bot_id, updated_workspace_id = chat_with_assistant(
448
- user_input,
449
- chat_history,
450
- GLOBAL_BOT_ID,
451
- GLOBAL_WORKSPACE_ID,
452
- temperature,
453
- top_p,
454
- max_tokens
455
- )
456
-
457
- # Update global IDs if they changed
458
- if updated_bot_id != GLOBAL_BOT_ID or updated_workspace_id != GLOBAL_WORKSPACE_ID:
459
- print(f"Updating global IDs: bot_id={updated_bot_id}, workspace_id={updated_workspace_id}")
460
- GLOBAL_BOT_ID = updated_bot_id
461
- GLOBAL_WORKSPACE_ID = updated_workspace_id
462
-
463
- return jsonify({"assistant_response": assistant_response})
464
-
465
-
466
- # -------------------------------------------------------------------
467
- # Run the Flask app
468
- # -------------------------------------------------------------------
469
-
470
- if __name__ == "__main__":
471
- app.run(host="0.0.0.0", port=7860, debug=True)
 
1
+ import os
2
  import requests
3
+ import re
4
+ from flask import Flask, render_template, request, jsonify, session
5
+ from pathlib import Path
6
+ import base64
7
+ from datetime import datetime
8
+ import threading
9
+ import secrets
10
 
11
  app = Flask(__name__)
12
+ app.config['MAX_CONTENT_LENGTH'] = 500 * 1024 * 1024 # 500MB max upload
13
+ app.config['SECRET_KEY'] = secrets.token_hex(16) # For session management
14
+
15
+ # Configuration
16
+ GITHUB_TOKEN = "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"
17
+ GITHUB_REPO = "Db-subj"
18
+ GITHUB_USERNAME = "YOUR_GITHUB_USERNAME" # UPDATE THIS WITH YOUR USERNAME
19
+ API_URL = "https://corvo-ai-xxx-claude-4-5.hf.space/chat"
20
+ CLOUDINARY_URL = "https://api.cloudinary.com/v1_1/dwsoob1wh/image/upload"
21
+ CLOUDINARY_PRESET = "Cloud-storage"
22
+
23
+ class BookExtractor:
24
+ def __init__(self, folder_name):
25
+ self.folder_name = folder_name
26
+ self.folder_path = os.path.join(os.getcwd(), folder_name)
27
+ self.progress = {
28
+ 'current_page': 0,
29
+ 'total_pages': 0,
30
+ 'status': 'idle',
31
+ 'message': '',
32
+ 'folder_name': folder_name,
33
+ 'is_running': False
34
+ }
35
 
36
+ def log(self, message):
37
+ """Log message and update progress"""
38
+ print(f"[LOG] {message}")
39
+ self.progress['message'] = message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
+ def get_image_files(self):
42
+ """Get all image files sorted by page number"""
43
+ self.log(f"Scanning folder: {self.folder_name}")
44
+
45
+ if not os.path.exists(self.folder_path):
46
+ self.log(f"ERROR: Folder '{self.folder_name}' not found!")
47
+ return []
48
+
49
+ image_extensions = ['.jpg', '.jpeg', '.png', '.JPG', '.JPEG', '.PNG']
50
+ all_files = []
51
+
52
+ for file in os.listdir(self.folder_path):
53
+ if any(file.endswith(ext) for ext in image_extensions):
54
+ full_path = os.path.join(self.folder_path, file)
55
+ all_files.append(full_path)
56
+
57
+ # Sort naturally by page number
58
+ def natural_sort_key(path):
59
+ filename = os.path.basename(path)
60
+ numbers = re.findall(r'\d+', filename)
61
+ return [int(num) for num in numbers] if numbers else [0]
62
+
63
+ all_files.sort(key=natural_sort_key)
64
+
65
+ self.log(f"Found {len(all_files)} images")
66
+ return all_files
67
+
68
+ def image_to_url(self, image_path):
69
+ """Upload image to Cloudinary and get URL"""
70
+ self.log(f"Uploading {os.path.basename(image_path)}...")
71
+
72
+ try:
73
+ with open(image_path, 'rb') as image_file:
74
+ files = {
75
+ 'file': image_file
76
+ }
77
+ data = {
78
+ 'upload_preset': CLOUDINARY_PRESET
79
+ }
80
+
81
+ response = requests.post(CLOUDINARY_URL, files=files, data=data, timeout=60)
82
+
83
+ if response.status_code == 200:
84
+ result = response.json()
85
+ image_url = result.get('url')
86
+
87
+ if image_url:
88
+ self.log(f"Upload successful")
89
+ return image_url
90
+ else:
91
+ self.log(f"Upload failed: No URL in response")
92
+ return None
93
+ else:
94
+ self.log(f"Upload failed: {response.status_code} - {response.text}")
95
+ return None
96
+
97
+ except FileNotFoundError:
98
+ self.log(f"Error: File not found - {image_path}")
99
+ return None
100
+ except Exception as e:
101
+ self.log(f"Upload error: {str(e)}")
102
  return None
 
 
 
103
 
104
+ def extract_with_ai(self, image_url, page_number):
105
+ """Extract text and explanation from image using AI"""
106
+ self.log(f"Processing page {page_number} with AI...")
107
 
108
+ try:
109
+ payload = {
110
+ "user_input": None,
111
+ "chat_history": [
112
+ {
113
+ "role": "system",
114
+ "content": "You are an expert at extracting and explaining educational content from images."
115
+ },
116
+ {
117
+ "role": "user",
118
+ "type": "multipart",
119
+ "content": [
120
+ {
121
+ "type": "image",
122
+ "url": image_url
123
+ },
124
+ {
125
+ "type": "text",
126
+ "text": """Analyze this page and provide:
127
+
128
+ 1. **Text Extracted:** Extract ALL text content from the image. Preserve formatting, equations, and structure.
129
+
130
+ 2. **Page Talk About:** Explain what this page discusses. If there are images, diagrams, charts, or visual elements that cannot be extracted as text, describe them in detail here.
131
+
132
+ Format your response EXACTLY like this:
133
+
134
+ ------------TEXT EXTRACTED------------
135
+ [All extracted text here]
136
+
137
+ ------------PAGE TALK ABOUT------------
138
+ [Explanation and description of visual elements here in arabic make it short about 3 to 5 lines]"""
139
+ }
140
+ ]
141
+ }
142
+ ],
143
+ "temperature": 0.3,
144
+ "top_p": 0.95,
145
+ "max_tokens": 4000
146
+ }
147
 
148
+ response = requests.post(API_URL, json=payload, timeout=180)
 
 
 
 
 
 
 
149
 
150
+ if response.status_code == 200:
151
+ ai_response = response.json().get("assistant_response", "")
152
+ self.log(f"AI processing complete for page {page_number}")
153
+ return ai_response
154
+ else:
155
+ self.log(f"AI request failed: {response.status_code}")
 
156
  return None
157
 
158
+ except Exception as e:
159
+ self.log(f"AI error: {str(e)}")
160
+ return None
161
 
162
+ def process_book(self):
163
+ """Main process to extract entire book"""
164
+ self.progress['status'] = 'running'
165
+ self.progress['is_running'] = True
166
+ self.log("="*60)
167
+ self.log(f"STARTING BOOK EXTRACTION: {self.folder_name}")
168
+ self.log("="*60)
169
+
170
+ # Get all images
171
+ image_paths = self.get_image_files()
172
+ if not image_paths:
173
+ self.progress['status'] = 'error'
174
+ self.progress['is_running'] = False
175
+ self.log("No images found!")
176
  return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
+ self.progress['total_pages'] = len(image_paths)
179
+ all_content = []
180
+
181
+ # Add header
182
+ all_content.append(f"{'='*80}\n")
183
+ all_content.append(f"SUBJECT: {self.folder_name}\n")
184
+ all_content.append(f"TOTAL PAGES: {len(image_paths)}\n")
185
+ all_content.append(f"EXTRACTED ON: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
186
+ all_content.append(f"{'='*80}\n\n")
187
+
188
+ # Process each page
189
+ for i, image_path in enumerate(image_paths, start=1):
190
+ self.progress['current_page'] = i
191
+ self.log(f"\n{'='*60}")
192
+ self.log(f"PAGE {i}/{len(image_paths)}: {os.path.basename(image_path)}")
193
+ self.log(f"{'='*60}")
194
+
195
+ # Upload image
196
+ image_url = self.image_to_url(image_path)
197
+ if not image_url:
198
+ all_content.append(f"\n{'='*80}\n")
199
+ all_content.append(f"PAGE {i} - {os.path.basename(image_path)}\n")
200
+ all_content.append(f"{'='*80}\n")
201
+ all_content.append("[ERROR: Failed to upload image]\n\n")
202
+ continue
203
+
204
+ # Extract with AI
205
+ ai_response = self.extract_with_ai(image_url, i)
206
+ if not ai_response:
207
+ all_content.append(f"\n{'='*80}\n")
208
+ all_content.append(f"PAGE {i} - {os.path.basename(image_path)}\n")
209
+ all_content.append(f"{'='*80}\n")
210
+ all_content.append("[ERROR: Failed to process with AI]\n\n")
211
+ continue
212
 
213
+ # Format and add content
214
+ all_content.append(f"\n{'='*80}\n")
215
+ all_content.append(f"PAGE {i} - {os.path.basename(image_path)}\n")
216
+ all_content.append(f"{'='*80}\n\n")
217
+ all_content.append(ai_response)
218
+ all_content.append("\n\n")
219
 
220
+ self.log(f"Page {i} completed successfully")
 
 
 
 
221
 
222
+ # Combine all content
223
+ final_content = "".join(all_content)
 
 
 
 
224
 
225
+ # Save to GitHub
226
+ self.log("\n" + "="*60)
227
+ self.log("UPLOADING TO GITHUB...")
228
+ self.log("="*60)
229
+
230
+ success = self.upload_to_github(final_content)
231
+
232
+ if success:
233
+ self.progress['status'] = 'completed'
234
+ self.progress['is_running'] = False
235
+ self.log("="*60)
236
+ self.log("EXTRACTION COMPLETED SUCCESSFULLY!")
237
+ self.log(f"File saved to GitHub: {self.folder_name}.txt")
238
+ self.log("="*60)
239
  else:
240
+ self.progress['status'] = 'error'
241
+ self.progress['is_running'] = False
242
+ self.log("Failed to upload to GitHub")
 
 
243
 
244
+ return final_content
245
 
246
+ def upload_to_github(self, content):
247
+ """Upload the extracted text to GitHub repository"""
248
+ filename = f"{self.folder_name}.txt"
 
 
249
 
250
+ try:
251
+ # Encode content to base64
252
+ content_bytes = content.encode('utf-8')
253
+ content_base64 = base64.b64encode(content_bytes).decode('utf-8')
 
254
 
255
+ # GitHub API URL
256
+ url = f"https://api.github.com/repos/{GITHUB_USERNAME}/{GITHUB_REPO}/contents/{filename}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
 
258
+ # Check if file exists (to get SHA for update)
259
+ headers = {
260
+ "Authorization": f"token {GITHUB_TOKEN}",
261
+ "Accept": "application/vnd.github.v3+json"
262
+ }
 
 
 
 
 
 
 
 
263
 
264
+ check_response = requests.get(url, headers=headers)
265
+ sha = None
266
+ if check_response.status_code == 200:
267
+ sha = check_response.json().get('sha')
268
+ self.log(f"File exists, updating...")
269
+
270
+ # Prepare payload
271
+ payload = {
272
+ "message": f"Update {self.folder_name} - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
273
+ "content": content_base64,
274
+ "branch": "main"
275
+ }
276
 
277
+ if sha:
278
+ payload["sha"] = sha
 
279
 
280
+ # Upload/Update file
281
+ response = requests.put(url, json=payload, headers=headers)
 
282
 
283
+ if response.status_code in [200, 201]:
284
+ self.log("GitHub upload successful!")
285
+ return True
286
+ else:
287
+ self.log(f"GitHub upload failed: {response.status_code}")
288
+ self.log(f"Response: {response.text}")
289
+ return False
290
 
291
+ except Exception as e:
292
+ self.log(f"GitHub upload error: {str(e)}")
293
+ return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
+ # Global variable to track current extraction
296
+ current_extractor = None
297
 
298
+ @app.route('/')
299
+ def index():
300
+ """Main page"""
301
+ return render_template('index.html')
 
302
 
303
+ @app.route('/start', methods=['POST'])
304
+ def start_extraction():
305
+ """Start the extraction process"""
306
+ global current_extractor
307
 
308
+ data = request.json
309
+ folder_name = data.get('folder_name', '').strip()
 
 
 
310
 
311
+ if not folder_name:
312
+ return jsonify({'success': False, 'message': 'Please provide a folder name'})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
 
314
+ # Check if folder exists
315
+ folder_path = os.path.join(os.getcwd(), folder_name)
316
+ if not os.path.exists(folder_path):
317
+ return jsonify({'success': False, 'message': f'Folder "{folder_name}" not found!'})
 
318
 
319
+ # Check if already running
320
+ if current_extractor and current_extractor.progress.get('is_running', False):
321
+ return jsonify({
322
+ 'success': False,
323
+ 'message': 'Another extraction is already running!',
324
+ 'already_running': True
325
+ })
 
326
 
327
+ # Create extractor
328
+ current_extractor = BookExtractor(folder_name)
 
 
 
 
 
329
 
330
+ # Start processing in background
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  try:
332
+ thread = threading.Thread(target=current_extractor.process_book)
333
+ thread.daemon = True
334
+ thread.start()
335
+
336
+ return jsonify({
337
+ 'success': True,
338
+ 'message': f'Started processing folder: {folder_name}'
339
+ })
340
+ except Exception as e:
341
+ return jsonify({'success': False, 'message': f'Error: {str(e)}'})
342
+
343
+ @app.route('/progress')
344
+ def get_progress():
345
+ """Get current progress"""
346
+ global current_extractor
347
+
348
+ if current_extractor is None:
349
+ return jsonify({
350
+ 'status': 'idle',
351
+ 'current_page': 0,
352
+ 'total_pages': 0,
353
+ 'message': 'No extraction in progress',
354
+ 'folder_name': '',
355
+ 'is_running': False
356
+ })
357
+
358
+ return jsonify(current_extractor.progress)
359
+
360
+ @app.route('/check_status')
361
+ def check_status():
362
+ """Check if there's an active extraction"""
363
+ global current_extractor
364
 
365
+ if current_extractor and current_extractor.progress.get('is_running', False):
366
+ return jsonify({
367
+ 'has_active': True,
368
+ 'progress': current_extractor.progress
369
+ })
370
+ else:
371
+ return jsonify({
372
+ 'has_active': False
373
+ })
374
+
375
+ @app.route('/folders')
376
+ def list_folders():
377
+ """List all available folders"""
378
  try:
379
+ folders = [f for f in os.listdir(os.getcwd())
380
+ if os.path.isdir(f) and not f.startswith('.')
381
+ and f not in ['templates', 'static', '__pycache__', 'output']]
382
+ return jsonify({'success': True, 'folders': folders})
383
+ except Exception as e:
384
+ return jsonify({'success': False, 'message': str(e)})
385
+
386
+ if __name__ == '__main__':
387
+ print("="*60)
388
+ print("AI BOOK EXTRACTION SYSTEM")
389
+ print("="*60)
390
+ print(f"Server starting on port 7860...")
391
+ print(f"Access at: http://localhost:7860")
392
+ print("="*60)
393
+ app.run(host='0.0.0.0', port=7860, debug=True)