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)