File size: 8,501 Bytes
0721898 018463a 3ebc508 1deacc5 76e5528 465bca7 3ebc508 764eab2 14c0817 223de58 14c0817 1deacc5 223de58 764eab2 223de58 018463a 223de58 018463a 764eab2 9104c9d 764eab2 3e26e8b 764eab2 0721898 14c0817 3ebc508 3ec474d 3ebc508 8a2b2ef 018463a ec9b387 764eab2 ec9b387 1deacc5 223de58 764eab2 223de58 76e5528 764eab2 76e5528 764eab2 76e5528 1d2d978 223de58 764eab2 19902da 223de58 76141c4 19902da 9104c9d 3e26e8b 764eab2 76e5528 764eab2 8a2b2ef 764eab2 3e26e8b 8a2b2ef 3e26e8b 8a2b2ef 764eab2 8a2b2ef 3e26e8b 8a2b2ef 0721898 0683728 764eab2 0683728 764eab2 0683728 0721898 764eab2 0721898 8a2b2ef 764eab2 8a2b2ef 76e5528 764eab2 76e5528 3ebc508 ec9b387 3ebc508 223de58 018463a 263ee79 223de58 018463a 1d2d978 018463a 764eab2 76e5528 764eab2 76e5528 0683728 3e26e8b 764eab2 76e5528 764eab2 0721898 764eab2 1d2d978 018463a 223de58 018463a 223de58 018463a 764eab2 7192a9d 223de58 19902da 018463a 764eab2 018463a 3ebc508 19902da 018463a 764eab2 018463a 14c0817 223de58 14c0817 223de58 60a386b 76141c4 3ebc508 1deacc5 3ebc508 e81567c 764eab2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# app.py - Flask Backend with URL Shortening System
from flask import Flask, request, jsonify, send_from_directory, make_response
import google.generativeai as genai
from dotenv import load_dotenv
import os
from flask_cors import CORS
import markdown2
import re
import requests
from gtts import gTTS
import uuid
# Load environment variables
load_dotenv()
# Configure paths
AUDIO_FOLDER = os.path.join('static', 'audio')
os.makedirs(AUDIO_FOLDER, exist_ok=True)
app = Flask(__name__, static_folder='static')
CORS(app)
# AI Configuration - Critical for URL detection and formatting
system_instruction = """
You are a helpful AI assistant named Athspi. When responding:
1. Never mention "audio" or technical terms
2. For responses that would benefit from audio (like stories, explanations, or content meant to be heard), include the audio version between these markers:
[AUDIO]content here[/AUDIO]
3. Keep responses natural and friendly
4. Decide automatically when to include audio based on the content type
5. For stories, always include audio version
6. CRITICAL: When you detect a URL in the user's message, respond with:
[SHORTEN]original_url|short_url[/SHORTEN]
Example format ONLY:
[SHORTEN]https://example.com|https://spoo.me/abc123[/SHORTEN]
7. DO NOT add any extra text inside the SHORTEN tags
8. ONLY use this format when you have a shortened URL to provide
9. If you cannot shorten the URL, do not include the SHORTEN tags
"""
genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
model = genai.GenerativeModel('gemini-2.5-flash', system_instruction=system_instruction)
# In-memory session storage
chat_sessions = {}
def convert_markdown_to_html(text):
"""Convert markdown to HTML with code formatting"""
html = markdown2.markdown(text, extras=["fenced-code-blocks", "tables"])
html = re.sub(r'<pre><code(.*?)>', r'<pre class="code-block"><code\1>', html)
return html
def process_response(full_response):
"""Extract visible text, audio content, and short links from AI response"""
# Extract audio content
audio_match = re.search(r'\[AUDIO\](.*?)\[/AUDIO\]', full_response, re.DOTALL)
audio_content = audio_match.group(1).strip() if audio_match else None
visible_text = re.sub(r'\[/?AUDIO\]', '', full_response)
# Process short links - CRITICAL PART
short_link_match = re.search(r'\[SHORTEN\](.*?)\|([^|]*?)\[/SHORTEN\]', visible_text, re.DOTALL)
if short_link_match:
original_url = short_link_match.group(1).strip()
short_url = short_link_match.group(2).strip()
# Create clean HTML for display
link_html = f'<div class="link-container">' \
f'<p><strong>π Original:</strong> <a href="{original_url}" target="_blank" rel="noopener noreferrer">{original_url}</a></p>' \
f'<p><strong>βοΈ Shortened:</strong> <a href="{short_url}" target="_blank" rel="noopener noreferrer">{short_url}</a></p>' \
f'</div>'
# Replace the SHORTEN tags with our clean HTML
visible_text = re.sub(r'\[SHORTEN\].*?\[/SHORTEN\]', link_html, visible_text)
return visible_text, audio_content
def generate_audio(text):
"""Generate audio file from text, cleaning special characters"""
text = re.sub(r'[^\w\s.,!?\-]', '', text)
filename = f"audio_{uuid.uuid4()}.mp3"
filepath = os.path.join(AUDIO_FOLDER, filename)
tts = gTTS(text=text, lang='en', slow=False)
tts.save(filepath)
return filename
def shorten_url_with_spoo_me(url):
"""Shorten URL using spoo.me API with proper error handling"""
try:
# Clean the input URL
clean_url = url.strip()
if not clean_url.startswith(('http://', 'https://')):
clean_url = 'https://' + clean_url
payload = {
"url": clean_url
}
headers = {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded"
}
response = requests.post(
"https://spoo.me/",
data=payload,
headers=headers,
timeout=10
)
if response.status_code == 200:
try:
# Parse the JSON response - THIS IS THE CORRECT WAY
result = response.json()
# Extract short_url and strip whitespace
short_url = result.get("short_url", "").strip()
if short_url and short_url.startswith('http'):
return short_url
except Exception:
# If JSON parsing fails, try plain text response
text_response = response.text.strip()
if text_response.startswith('http'):
return text_response
elif len(text_response) > 3:
return f"https://spoo.me/{text_response}"
print(f"spoo.me error: {response.status_code} - {response.text}")
return None
except Exception as e:
print("Request failed:", str(e))
return None
@app.route('/chat', methods=['POST'])
def chat():
try:
data = request.json
user_message = data.get('message', '').strip()
session_id = request.cookies.get('session_id') or str(uuid.uuid4())
if not user_message:
return jsonify({"error": "Message required"}), 400
# Get or create chat session
if session_id not in chat_sessions:
chat_sessions[session_id] = model.start_chat(history=[])
chat_session = chat_sessions[session_id]
# Check if user message contains a URL
url_match = re.search(r'https?://[^\s<>"{}|\\^`\[\]]+', user_message)
full_text = ""
if url_match:
original_url = url_match.group(0).strip()
short_url = shorten_url_with_spoo_me(original_url)
if short_url:
# Format the SHORTEN tag exactly as required
short_tag = f"[SHORTEN]{original_url}|{short_url}[/SHORTEN]"
# Send message to AI with instruction to include short link
ai_response = chat_session.send_message(
f"{user_message}\n\n"
f"I detected a URL in your message: {original_url}\n"
f"Shortened version: {short_url}\n\n"
f"Please respond naturally and include this exact format:\n{short_tag}"
)
full_text = ai_response.text
else:
# If shortening failed, just send normal message
ai_response = chat_session.send_message(user_message)
full_text = ai_response.text
else:
# No URL found, normal chat
ai_response = chat_session.send_message(user_message)
full_text = ai_response.text
# Process final response
visible_text, audio_content = process_response(full_text)
html_response = convert_markdown_to_html(visible_text)
result = {
"response_html": html_response,
"has_audio": False
}
# Generate audio if needed
if audio_content:
audio_filename = generate_audio(audio_content)
result["audio_filename"] = audio_filename
result["has_audio"] = True
# Return response + set session cookie
resp = make_response(jsonify(result))
resp.set_cookie('session_id', session_id, max_age=3600, httponly=True, samesite='Lax')
return resp
except Exception as e:
print("Error:", str(e))
return jsonify({"error": "Something went wrong."}), 500
@app.route('/new-chat', methods=['POST'])
def new_chat():
"""Reset chat memory"""
session_id = str(uuid.uuid4())
resp = make_response(jsonify({"status": "new chat started"}))
resp.set_cookie('session_id', session_id, max_age=3600, httponly=True, samesite='Lax')
return resp
@app.route('/download/<filename>')
def download_audio(filename):
try:
return send_from_directory(AUDIO_FOLDER, filename, as_attachment=True)
except FileNotFoundError:
return jsonify({"error": "Audio file not found"}), 404
@app.route('/')
def serve_index():
return send_from_directory('static', 'index.html')
@app.route('/<path:path>')
def serve_static(path):
return send_from_directory('static', path)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=7860) |