File size: 11,264 Bytes
5afb2f8
e68d308
 
 
7eeac78
 
5afb2f8
e68d308
 
 
 
 
7eeac78
5f6b9ea
8e8b790
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7eeac78
26df3c0
 
e68d308
26df3c0
 
 
 
 
 
037abba
26df3c0
 
037abba
26df3c0
 
 
 
8e8b790
26df3c0
 
 
037abba
26df3c0
 
 
 
 
 
 
 
 
 
037abba
5f6b9ea
7eeac78
 
5f6b9ea
 
 
 
 
7eeac78
5f6b9ea
7eeac78
 
 
 
 
 
5afb2f8
e68d308
8e8b790
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5afb2f8
8e8b790
e68d308
7eeac78
5f6b9ea
e68d308
5afb2f8
8e8b790
e68d308
 
 
 
 
 
5afb2f8
8e8b790
e68d308
 
 
 
 
 
 
 
 
 
8e8b790
e68d308
 
 
8e8b790
e68d308
5afb2f8
5f6b9ea
 
7eeac78
5afb2f8
7eeac78
 
 
5afb2f8
e68d308
7eeac78
e68d308
 
7eeac78
e68d308
7eeac78
26df3c0
7eeac78
e68d308
 
 
7eeac78
 
 
 
 
 
 
 
 
 
 
 
 
 
e68d308
7eeac78
e68d308
 
 
 
 
5f6b9ea
8e8b790
e68d308
7eeac78
 
 
 
 
 
 
 
 
 
 
 
 
5afb2f8
e68d308
 
 
 
 
 
 
7eeac78
e68d308
7eeac78
 
e68d308
 
7eeac78
 
5f6b9ea
7eeac78
e68d308
 
7eeac78
26df3c0
 
 
 
e68d308
 
 
5afb2f8
 
e68d308
8e8b790
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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
import gradio as gr
from openai import OpenAI
import requests
import json
from typing import List, Dict, Optional, Tuple
import random

class GifChatBot:
    def __init__(self):
        self.openai_client = None
        self.giphy_key = None
        self.chat_history = []
        self.is_initialized = False
        self.session = requests.Session()
        
        # Define size constraints
        self.MINIMUM_FILE_SIZE = 500 * 1024  # 500KB in bytes
        self.MAXIMUM_FILE_SIZE = 5 * 1024 * 1024  # 5MB in bytes
        self.MAX_RETRIES = 10  # Maximum number of retries for finding a good GIF

    def verify_gif_size(self, gif_url: str) -> bool:
        """Verify if GIF meets size requirements"""
        try:
            # Do HEAD request to get content length
            response = self.session.head(gif_url, timeout=3)
            if response.status_code != 200:
                return False
            
            # Get content length
            content_length = response.headers.get('content-length')
            if not content_length:
                return False
            
            file_size = int(content_length)
            return self.MINIMUM_FILE_SIZE <= file_size <= self.MAXIMUM_FILE_SIZE
            
        except Exception as error:
            print(f"Size verification error: {error}")
            return False

    def get_gif(self, search_query: str) -> Optional[str]:
        """Get a GIF meeting size requirements with retries"""
        for attempt in range(self.MAX_RETRIES):
            try:
                # Calculate offset based on attempt number to get different results
                offset = attempt * 10
                
                params = {
                    'api_key': self.giphy_key,
                    'q': search_query,
                    'limit': 10,
                    'offset': offset,
                    'rating': 'pg-13'
                }
                
                response = self.session.get(
                    "https://api.giphy.com/v1/gifs/search",
                    params=params,
                    timeout=5
                )
                
                if response.status_code == 200:
                    data = response.json()
                    if data["data"]:
                        # Shuffle results for variety
                        gifs = list(data["data"])
                        random.shuffle(gifs)
                        
                        # Try each GIF until we find one meeting size requirements
                        for gif in gifs:
                            gif_url = gif["images"]["original"]["url"]
                            if self.verify_gif_size(gif_url):
                                print(f"Found valid GIF on attempt {attempt + 1}")
                                return gif_url
                            
                        print(f"No valid GIFs found in attempt {attempt + 1}, retrying...")
                    else:
                        print("No GIFs found in search")
                        break
                        
            except Exception as error:
                print(f"Error in attempt {attempt + 1}: {error}")
                continue
        
        # If we get here, try trending as last resort
        return self._get_trending_gif()

    def _get_trending_gif(self) -> Optional[str]:
        """Get a trending GIF meeting size requirements"""
        try:
            params = {
                'api_key': self.giphy_key,
                'limit': 25,
                'rating': 'pg-13'
            }
            
            response = self.session.get(
                "https://api.giphy.com/v1/gifs/trending",
                params=params,
                timeout=5
            )
            
            if response.status_code == 200:
                data = response.json()
                if data["data"]:
                    gifs = list(data["data"])
                    random.shuffle(gifs)
                    
                    for gif in gifs:
                        gif_url = gif["images"]["original"]["url"]
                        if self.verify_gif_size(gif_url):
                            print("Found valid trending GIF")
                            return gif_url
                            
        except Exception as error:
            print(f"Trending GIF error: {error}")
        return None

    def setup_keys(self, openai_key: str, giphy_key: str) -> str:
        """Initialize API clients with user's keys"""
        try:
            self.openai_client = OpenAI(api_key=openai_key)
            self.giphy_key = giphy_key
            self._test_giphy_key()
            self._test_openai_key()
            self.is_initialized = True
            return "βœ… Setup successful! Let's chat!"
        except Exception as error:
            self.is_initialized = False
            return f"❌ Error setting up: {str(error)}"

    def _test_giphy_key(self):
        """Test if GIPHY key is valid"""
        response = self.session.get(
            "https://api.giphy.com/v1/gifs/trending",
            params={"api_key": self.giphy_key, "limit": 1}
        )
        if response.status_code != 200:
            raise Exception("Invalid GIPHY API key")

    def _test_openai_key(self):
        """Test if OpenAI key is valid"""
        try:
            self.openai_client.chat.completions.create(
                model="gpt-4o-mini",
                messages=[{"role": "user", "content": "test"}],
                max_tokens=5
            )
        except Exception:
            raise Exception("Invalid OpenAI API key")

    def reset_chat(self) -> Tuple[List[Dict[str, str]], str]:
        """Reset the chat history"""
        self.chat_history = []
        return [], ""

    def format_message(self, role: str, content: str) -> Dict[str, str]:
        """Format message in the new Gradio chat format"""
        return {"role": role, "content": content}

    def chat(self, message: str, history: List[Dict[str, str]]) -> Tuple[str, List[Dict[str, str]], str]:
        """Main chat function with natural GIF integration"""
        if not self.is_initialized:
            return message, history, "Please set up your API keys first!"

        if not message.strip():
            return message, history, ""

        try:
            # System message emphasizing natural GIF usage
            system_message = """You are a supportive, empathetic friend who uses GIFs naturally in conversation. 
            When using GIFs, keep search terms simple and contextual:

            Examples:
            - User feeling hungry -> [GIF: hungry]
            - User feeling sad -> [GIF: comforting hug]
            - User celebrating -> [GIF: celebration]
            - User confused -> [GIF: confused]
            
            Keep your responses:
            1. Empathetic and natural
            2. Context-aware (reference previous messages)
            3. Use GIFs that match the emotion
            
            Use 0-1 GIFs per message unless the moment really calls for more."""

            # Prepare conversation history
            messages = [{"role": "system", "content": system_message}]
            for chat in history:
                messages.append({"role": chat["role"], "content": chat["content"]})
            messages.append({"role": "user", "content": message})

            # Get AI response
            response = self.openai_client.chat.completions.create(
                model="gpt-4o-mini",
                messages=messages,
                temperature=0.9,
                max_tokens=150
            )

            # Process response and insert GIFs
            ai_message = response.choices[0].message.content
            final_response = ""
            
            parts = ai_message.split("[GIF:")
            final_response += parts[0]
            
            for part in parts[1:]:
                gif_desc_end = part.find("]")
                if gif_desc_end != -1:
                    gif_desc = part[:gif_desc_end].strip()
                    print(f"Searching for GIF: {gif_desc}")
                    gif_url = self.get_gif(gif_desc)
                    if gif_url:
                        final_response += f"\n![GIF]({gif_url})\n"
                        print(f"Added GIF: {gif_url}")
                    final_response += part[gif_desc_end + 1:]

            history.append(self.format_message("user", message))
            history.append(self.format_message("assistant", final_response))
            return "", history, ""

        except Exception as error:
            error_message = f"Oops! Something went wrong: {str(error)}"
            return message, history, error_message

def create_interface():
    """Create the Gradio interface"""
    bot = GifChatBot()
    
    with gr.Blocks(theme=gr.themes.Soft()) as interface:
        gr.Markdown("""
        # 🎭 Friendly Chat Bot with GIFs
        Chat with an empathetic AI friend who expresses themselves through GIFs! 
        Enter your API keys below to start.
        """)
        
        with gr.Row():
            with gr.Column(scale=1):
                openai_key = gr.Textbox(
                    label="OpenAI API Key",
                    placeholder="sk-...",
                    type="password",
                    scale=2
                )
            with gr.Column(scale=1):
                giphy_key = gr.Textbox(
                    label="GIPHY API Key",
                    placeholder="Enter your GIPHY API key",
                    type="password",
                    scale=2
                )
        
        setup_button = gr.Button("Set up Keys", variant="primary")
        setup_status = gr.Textbox(label="Setup Status")
        
        chatbot = gr.Chatbot(
            label="Chat",
            bubble_full_width=False,
            height=450,
            type="messages"
        )
        
        with gr.Row():
            with gr.Column(scale=4):
                message_box = gr.Textbox(
                    label="Type your message",
                    placeholder="Say something...",
                    show_label=False,
                    container=False
                )
            with gr.Column(scale=1):
                clear_button = gr.Button("Clear Chat", variant="secondary")
        
        error_box = gr.Textbox(label="Error Messages", visible=True)

        # Set up event handlers
        setup_button.click(
            bot.setup_keys,
            inputs=[openai_key, giphy_key],
            outputs=setup_status
        )
        
        message_box.submit(
            bot.chat,
            inputs=[message_box, chatbot],
            outputs=[message_box, chatbot, error_box]
        )
        
        clear_button.click(
            bot.reset_chat,
            outputs=[chatbot, error_box]
        )
        
        gr.Markdown("""
        ### Tips:
        - 🀝 Share how you're feeling - the AI responds empathetically
        - πŸ’­ The conversation is context-aware
        - 🎯 GIFs are chosen to match the emotion
        - πŸ”„ Use 'Clear Chat' to start fresh
        """)
    
    return interface

if __name__ == "__main__":
    demo = create_interface()
    demo.launch()