Chandima Prabhath commited on
Commit
b9724da
·
1 Parent(s): 29e070f

flux & audio reply

Browse files
Files changed (5) hide show
  1. .gitignore +6 -0
  2. FLUX.py +146 -0
  3. VoiceReply.py +67 -0
  4. app.py +58 -34
  5. requirements.txt +2 -1
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # directories
2
+ /images
3
+ /audio_replies
4
+
5
+ # env
6
+ .env
FLUX.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import time
3
+ import io
4
+ import os
5
+ import re
6
+ import json
7
+ from PIL import Image, UnidentifiedImageError
8
+ from dotenv import load_dotenv
9
+
10
+ load_dotenv()
11
+
12
+ # Load the ImgBB API key from the environment variables.
13
+ IMGBB_API_KEY = os.getenv("IMGBB_API_KEY")
14
+
15
+ def upload_to_imgbb(image_path, file_name):
16
+ """
17
+ Uploads the image located at image_path to ImgBB.
18
+ Returns:
19
+ str: URL of the uploaded image on ImgBB or None if failed.
20
+ """
21
+ try:
22
+ with open(image_path, 'rb') as f:
23
+ image_data = f.read()
24
+ response = requests.post(
25
+ "https://api.imgbb.com/1/upload",
26
+ params={"key": IMGBB_API_KEY},
27
+ files={"image": (file_name, image_data)}
28
+ )
29
+ response.raise_for_status()
30
+ result = response.json()
31
+ if result.get("data") and "url" in result["data"]:
32
+ return result["data"]["url"]
33
+ else:
34
+ print("Failed to upload image to ImgBB.")
35
+ return None
36
+ except requests.RequestException as e:
37
+ print(f"Error uploading image to ImgBB: {e}")
38
+ return None
39
+ except Exception as e:
40
+ print(f"Unexpected error uploading image to ImgBB: {e}")
41
+ return None
42
+
43
+ def generate_image(prompt, request_id, current_request_id, image_dir, attempt=0):
44
+ """
45
+ Generate an image using the Pollinations API.
46
+
47
+ Parameters:
48
+ prompt (str): The prompt for image generation.
49
+ width (int): Desired image width.
50
+ height (int): Desired image height.
51
+ request_id (int): The request id for the current operation.
52
+ current_request_id (int): The current active request id.
53
+ image_dir (str): Directory where image will be saved.
54
+ attempt (int): Current attempt count (zero-indexed).
55
+
56
+ Returns:
57
+ tuple: (PIL.Image object, image_path (str), returned_prompt (str), image_url (str))
58
+ or None if image fetch fails or request id mismatches.
59
+ """
60
+ model = "flux"
61
+ width = 1920
62
+ height = 1080
63
+ enhance_param = "true"
64
+ url = f"https://image.pollinations.ai/prompt/{prompt}?nologo=true&safe=false&private=true&model={model}&enhance={enhance_param}&width={width}&height={height}"
65
+ print(f"Attempt {attempt + 1}: Fetching image with URL: {url}")
66
+
67
+ try:
68
+ response = requests.get(url, timeout=45)
69
+ except Exception as e:
70
+ print(f"Error fetching image: {e}")
71
+ return None
72
+
73
+ if response.status_code != 200:
74
+ print(f"Failed to fetch image. Status code: {response.status_code}")
75
+ return None
76
+
77
+ if request_id != current_request_id:
78
+ print("Request ID mismatch. Operation cancelled.")
79
+ return None
80
+
81
+ print("Image fetched successfully.")
82
+ image_data = response.content
83
+
84
+ try:
85
+ image = Image.open(io.BytesIO(image_data))
86
+ actual_width, actual_height = image.size
87
+ print(f"Actual image dimensions: {actual_width}x{actual_height}")
88
+
89
+ # Extract metadata from EXIF if available
90
+ exif_data = image.info.get('exif', b'')
91
+ returned_prompt = prompt
92
+ if exif_data:
93
+ json_match = re.search(b'{"prompt":.*}', exif_data)
94
+ if json_match:
95
+ json_str = json_match.group(0).decode('utf-8')
96
+ try:
97
+ metadata_dict = json.loads(json_str)
98
+ returned_prompt = metadata_dict.get('prompt', prompt)
99
+ except json.JSONDecodeError as e:
100
+ print(f"Failed to parse JSON in metadata: {e}")
101
+ else:
102
+ print("No JSON data found in EXIF")
103
+
104
+ if (actual_width, actual_height) != (width, height):
105
+ print(f"Warning: Received image dimensions ({actual_width}x{actual_height}) do not match requested dimensions ({width}x{height})")
106
+ except UnidentifiedImageError:
107
+ print("Error: Received data is not a valid image.")
108
+ raise
109
+
110
+ timestamp = int(time.time())
111
+ image_filename = f"background_{timestamp}.png"
112
+ image_path = os.path.join(image_dir, image_filename)
113
+
114
+ # Ensure the image directory exists
115
+ os.makedirs(image_dir, exist_ok=True)
116
+
117
+ try:
118
+ image.save(image_path, 'PNG')
119
+ print(f"Image saved to {image_path}")
120
+ # Upload image to ImgBB
121
+ image_url = upload_to_imgbb(image_path, image_filename)
122
+ if image_url:
123
+ print(f"Image uploaded to ImgBB: {image_url}")
124
+ else:
125
+ print("Failed to upload image to ImgBB.")
126
+ except Exception as e:
127
+ print(f"Error saving image: {e}")
128
+ return None
129
+
130
+ return image, image_path, returned_prompt, image_url
131
+
132
+ if __name__ == "__main__":
133
+ # Example usage
134
+ prompt = "Beach party, anime style, vibrant colors"
135
+ request_id = 1
136
+ current_request_id = 1
137
+ image_dir = "./images"
138
+
139
+ image, image_path, returned_prompt, image_url = generate_image(prompt, request_id, current_request_id, image_dir)
140
+
141
+ if image:
142
+ print(f"Image generated and saved at {image_path}")
143
+ print(f"Returned prompt: {returned_prompt}")
144
+ print(f"Image URL: {image_url}")
145
+ else:
146
+ print("Failed to generate image.")
VoiceReply.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import os
3
+ import time
4
+ import urllib.parse
5
+
6
+ def generate_voice_reply(prompt, model="openai-audio", voice="coral", audio_dir="."):
7
+ """
8
+ Generate an audio file (MP3) using the Pollinations text-to-speech API.
9
+
10
+ The prompt is appended with a system message explaining that this is Eve,
11
+ a WhatsApp bot that can generate images from Flux and help users generate images.
12
+ To generate an image, the user should send a message like: /imagine {prompt}
13
+
14
+ Parameters:
15
+ prompt (str): The text prompt for audio generation.
16
+ model (str): Model identifier (default: openai-audio).
17
+ voice (str): Voice parameter (default: coral).
18
+ audio_dir (str): Directory where the audio file will be saved.
19
+
20
+ Returns:
21
+ tuple: (audio_file_path (str), audio_data (bytes)) or None if failed.
22
+ """
23
+ # Append system prompt to the user's prompt
24
+ system_prompt = ("Your name is Eve, a WhatsApp bot that can generate images from Flux and help users generate images. "
25
+ "To generate an image, send a message like: /imagine prompt. Rules: Don't say that you can't send whatsapp messages or generate images. Be friendly and helpful to users. help them with prompts. You can engage in general conversation. ")
26
+ full_prompt = f"{prompt}\n\n{system_prompt}"
27
+ encoded_prompt = urllib.parse.quote(full_prompt)
28
+ url = f"http://text.pollinations.ai/{encoded_prompt}?model={model}&voice={voice}"
29
+ print(f"Fetching audio with URL: {url}")
30
+
31
+ try:
32
+ response = requests.get(url, timeout=30)
33
+ except Exception as e:
34
+ print(f"Error fetching audio: {e}")
35
+ return None
36
+
37
+ if response.status_code != 200:
38
+ print(f"Failed to fetch audio. Status code: {response.status_code}")
39
+ return None
40
+
41
+ audio_data = response.content
42
+ timestamp = int(time.time())
43
+ file_name = f"voice_reply_{timestamp}.mp3"
44
+ audio_file_path = os.path.join(audio_dir, file_name)
45
+
46
+ try:
47
+ with open(audio_file_path, "wb") as f:
48
+ f.write(audio_data)
49
+ print(f"Audio saved to {audio_file_path}")
50
+ except Exception as e:
51
+ print(f"Error saving audio file: {e}")
52
+ return None
53
+
54
+ return audio_file_path, audio_data
55
+
56
+
57
+ if __name__ == "__main__":
58
+ # Example usage
59
+ prompt = "Hi. how are you."
60
+ audio_dir = "./audio_replies"
61
+ os.makedirs(audio_dir, exist_ok=True)
62
+
63
+ audio_file_path, audio_data = generate_voice_reply(prompt, audio_dir=audio_dir)
64
+ if audio_file_path:
65
+ print(f"Generated audio file: {audio_file_path}")
66
+ else:
67
+ print("Failed to generate audio file.")
app.py CHANGED
@@ -1,10 +1,12 @@
1
  import os
2
  import threading
3
  import requests
4
- from flask import Flask, request, jsonify
 
 
 
5
 
6
  from llm import generate_llm
7
- from sd import generate_sd
8
 
9
  GREEN_API_URL = os.getenv("GREEN_API_URL")
10
  GREEN_API_MEDIA_URL = os.getenv("GREEN_API_MEDIA_URL", "https://api.green-api.com")
@@ -12,16 +14,15 @@ GREEN_API_TOKEN = os.getenv("GREEN_API_TOKEN")
12
  GREEN_API_ID_INSTANCE = os.getenv("GREEN_API_ID_INSTANCE")
13
  WEBHOOK_AUTH_TOKEN = os.getenv("WEBHOOK_AUTH_TOKEN")
14
  PORT = 7860
 
 
15
 
16
  if not all([GREEN_API_URL, GREEN_API_TOKEN, GREEN_API_ID_INSTANCE, WEBHOOK_AUTH_TOKEN]):
17
  raise ValueError("Environment variables are not set properly")
18
 
19
- app = Flask(__name__)
20
 
21
  def send_message(message_id, to_number, message, retries=3):
22
- """
23
- Send a text message using Green API with retry logic.
24
- """
25
  if to_number.endswith('@g.us'):
26
  chat_id = to_number
27
  else:
@@ -45,9 +46,6 @@ def send_message(message_id, to_number, message, retries=3):
45
  return {"error": str(e)}
46
 
47
  def send_image(message_id, to_number, image_path, retries=3):
48
- """
49
- Send an image using Green API with retry logic.
50
- """
51
  if to_number.endswith('@g.us'):
52
  chat_id = to_number
53
  else:
@@ -67,49 +65,75 @@ def send_image(message_id, to_number, image_path, retries=3):
67
  continue
68
  return {"error": str(e)}
69
 
70
- def response_text(message_id, chat_id, prompt):
71
  """
72
- Generate a response using the LLM and send it to the user.
73
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  try:
75
  msg = generate_llm(prompt)
76
  send_message(message_id, chat_id, msg)
77
  except Exception as e:
78
  send_message(message_id, chat_id, "There was an error processing your request.")
79
 
 
 
 
 
 
 
 
 
 
 
 
80
  def handle_image_generation(message_id, chat_id, prompt):
81
- """
82
- Generate an image from the provided prompt and send it to the user.
83
- """
84
  try:
85
- image_data, image_path = generate_sd(prompt)
86
- if image_data:
87
  send_image(message_id, chat_id, image_path)
 
88
  else:
89
  send_message(message_id, chat_id, "Failed to generate image. Please try again later.")
90
  except Exception as e:
91
  send_message(message_id, chat_id, "There was an error generating the image. Please try again later.")
92
 
93
- @app.route('/', methods=['GET'])
94
  def index():
95
- """
96
- Basic endpoint to check if the script is running.
97
- """
98
  return "Server is running!"
99
 
100
- @app.route('/whatsapp', methods=['POST'])
101
- def whatsapp_webhook():
102
- """
103
- Handle incoming WhatsApp messages.
104
- """
105
- data = request.get_json()
106
  auth_header = request.headers.get('Authorization', '').strip()
107
-
108
  if auth_header != f"Bearer {WEBHOOK_AUTH_TOKEN}":
109
- return jsonify({"error": "Unauthorized"}), 403
 
 
 
 
 
110
 
111
  if data.get('typeWebhook') != 'incomingMessageReceived':
112
- return jsonify(success=True)
113
 
114
  try:
115
  chat_id = data['senderData']['chatId']
@@ -121,10 +145,9 @@ def whatsapp_webhook():
121
  elif 'extendedTextMessageData' in message_data:
122
  body = message_data['extendedTextMessageData']['text'].strip()
123
  else:
124
- return jsonify(success=True)
125
-
126
  except KeyError as e:
127
- return jsonify({"error": f"Missing key in data: {e}"}), 200
128
 
129
  if body.lower().startswith('/imagine'):
130
  prompt = body.replace('/imagine', '').strip()
@@ -136,7 +159,8 @@ def whatsapp_webhook():
136
  else:
137
  threading.Thread(target=response_text, args=(message_id, chat_id, body)).start()
138
 
139
- return jsonify(success=True)
140
 
141
  if __name__ == '__main__':
142
- app.run(debug=True, port=PORT, host="0.0.0.0")
 
 
1
  import os
2
  import threading
3
  import requests
4
+ from fastapi import FastAPI, Request, HTTPException
5
+ from fastapi.responses import PlainTextResponse, JSONResponse
6
+ from FLUX import generate_image
7
+ from VoiceReply import generate_voice_reply
8
 
9
  from llm import generate_llm
 
10
 
11
  GREEN_API_URL = os.getenv("GREEN_API_URL")
12
  GREEN_API_MEDIA_URL = os.getenv("GREEN_API_MEDIA_URL", "https://api.green-api.com")
 
14
  GREEN_API_ID_INSTANCE = os.getenv("GREEN_API_ID_INSTANCE")
15
  WEBHOOK_AUTH_TOKEN = os.getenv("WEBHOOK_AUTH_TOKEN")
16
  PORT = 7860
17
+ image_dir = "/tmp/images"
18
+ audio_dir = "/tmp/audio"
19
 
20
  if not all([GREEN_API_URL, GREEN_API_TOKEN, GREEN_API_ID_INSTANCE, WEBHOOK_AUTH_TOKEN]):
21
  raise ValueError("Environment variables are not set properly")
22
 
23
+ app = FastAPI()
24
 
25
  def send_message(message_id, to_number, message, retries=3):
 
 
 
26
  if to_number.endswith('@g.us'):
27
  chat_id = to_number
28
  else:
 
46
  return {"error": str(e)}
47
 
48
  def send_image(message_id, to_number, image_path, retries=3):
 
 
 
49
  if to_number.endswith('@g.us'):
50
  chat_id = to_number
51
  else:
 
65
  continue
66
  return {"error": str(e)}
67
 
68
+ def send_audio(message_id, to_number, audio_path, retries=3):
69
  """
70
+ Send an audio file using the Green API similar to send_image.
71
  """
72
+ if to_number.endswith('@g.us'):
73
+ chat_id = to_number
74
+ else:
75
+ chat_id = to_number
76
+
77
+ url = f"{GREEN_API_MEDIA_URL}/waInstance{GREEN_API_ID_INSTANCE}/sendFileByUpload/{GREEN_API_TOKEN}"
78
+ payload = {'chatId': chat_id, 'caption': 'Here is your voice reply!', 'quotedMessageId': message_id}
79
+ files = [('file', ('audio.mp3', open(audio_path, 'rb'), 'audio/mpeg'))]
80
+
81
+ for attempt in range(retries):
82
+ try:
83
+ response = requests.post(url, data=payload, files=files)
84
+ response.raise_for_status()
85
+ return response.json()
86
+ except requests.RequestException as e:
87
+ if attempt < retries - 1:
88
+ continue
89
+ return {"error": str(e)}
90
+
91
+ def response_text(message_id, chat_id, prompt):
92
  try:
93
  msg = generate_llm(prompt)
94
  send_message(message_id, chat_id, msg)
95
  except Exception as e:
96
  send_message(message_id, chat_id, "There was an error processing your request.")
97
 
98
+ def response_audio(message_id, chat_id, prompt):
99
+ try:
100
+ audio_file_path, audio_data = generate_voice_reply(prompt, model="openai-audio", voice="coral", audio_dir=audio_dir)
101
+ if audio_file_path:
102
+ send_audio(message_id, chat_id, audio_file_path)
103
+ os.remove(audio_file_path) # Clean up the file after sending
104
+ else:
105
+ response_text(message_id, chat_id, prompt=prompt)
106
+ except Exception as e:
107
+ send_message(message_id, chat_id, "There was an error generating the audio. Please try again later.")
108
+
109
  def handle_image_generation(message_id, chat_id, prompt):
 
 
 
110
  try:
111
+ image, image_path, returned_prompt, image_url = generate_image(prompt, message_id, message_id, image_dir)
112
+ if image:
113
  send_image(message_id, chat_id, image_path)
114
+ send_message(message_id, chat_id, f"Image generated successfully! You can view it here: {image_url}. \nPrompt: > _{returned_prompt}_")
115
  else:
116
  send_message(message_id, chat_id, "Failed to generate image. Please try again later.")
117
  except Exception as e:
118
  send_message(message_id, chat_id, "There was an error generating the image. Please try again later.")
119
 
120
+ @app.get("/", response_class=PlainTextResponse)
121
  def index():
 
 
 
122
  return "Server is running!"
123
 
124
+ @app.post("/whatsapp")
125
+ async def whatsapp_webhook(request: Request):
 
 
 
 
126
  auth_header = request.headers.get('Authorization', '').strip()
 
127
  if auth_header != f"Bearer {WEBHOOK_AUTH_TOKEN}":
128
+ raise HTTPException(status_code=403, detail="Unauthorized")
129
+
130
+ try:
131
+ data = await request.json()
132
+ except Exception:
133
+ return JSONResponse(content={"error": "Invalid JSON"}, status_code=400)
134
 
135
  if data.get('typeWebhook') != 'incomingMessageReceived':
136
+ return {"success": True}
137
 
138
  try:
139
  chat_id = data['senderData']['chatId']
 
145
  elif 'extendedTextMessageData' in message_data:
146
  body = message_data['extendedTextMessageData']['text'].strip()
147
  else:
148
+ return {"success": True}
 
149
  except KeyError as e:
150
+ return JSONResponse(content={"error": f"Missing key in data: {e}"}, status_code=200)
151
 
152
  if body.lower().startswith('/imagine'):
153
  prompt = body.replace('/imagine', '').strip()
 
159
  else:
160
  threading.Thread(target=response_text, args=(message_id, chat_id, body)).start()
161
 
162
+ return {"success": True}
163
 
164
  if __name__ == '__main__':
165
+ import uvicorn
166
+ uvicorn.run(app, host="0.0.0.0", port=PORT, debug=True)
requirements.txt CHANGED
@@ -2,4 +2,5 @@ fastapi
2
  uvicorn[standard]
3
  openai
4
  flask
5
- pillow
 
 
2
  uvicorn[standard]
3
  openai
4
  flask
5
+ pillow
6
+ requests