File size: 28,126 Bytes
13a153e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0077668
 
 
 
 
 
 
 
13a153e
 
 
 
 
 
 
 
 
0077668
 
 
 
 
 
 
 
13a153e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4319df2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13a153e
4319df2
 
 
 
13a153e
4319df2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13a153e
 
 
 
 
 
 
 
 
 
 
 
 
 
0077668
 
13a153e
0077668
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13a153e
 
 
 
0077668
13a153e
 
 
0077668
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13a153e
 
0077668
13a153e
 
 
 
 
 
 
 
0077668
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13a153e
 
0077668
13a153e
 
 
 
 
 
 
 
 
 
 
0077668
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13a153e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0077668
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13a153e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0077668
13a153e
 
0077668
13a153e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0077668
 
 
 
 
 
 
13a153e
0077668
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13a153e
0077668
 
13a153e
0077668
 
13a153e
 
 
 
 
 
 
 
 
 
0077668
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
import streamlit as st
import os
import json
import anthropic
import requests
from typing import Dict, Any, List, Optional

# Initialize the environment variables for different providers
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
PERPLEXITY_API_KEY = os.getenv("PERPLEXITY_API_KEY")

class AIProviderManager:
    """Manager for multiple AI providers"""
    
    def __init__(self):
        self.providers = {}
        self.initialize_providers()
    
    def initialize_providers(self):
        """Initialize available AI providers"""
        # Initialize Anthropic (Claude)
        if ANTHROPIC_API_KEY:
            try:
                self.providers["claude"] = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
                
                # Test connection by listing models
                try:
                    models = self.providers["claude"].models.list()
                    model_list = [model.id for model in models.data]
                    st.sidebar.success(f"🟢 Claude AI connected - Available models: {', '.join(model_list[:3])}")
                except Exception as e:
                    st.sidebar.warning(f"🟡 Claude AI connected but couldn't list models: {str(e)}")
            except Exception as e:
                st.sidebar.error(f"🔴 Error initializing Claude: {str(e)}")
        
        # Initialize OpenAI if available
        if OPENAI_API_KEY:
            try:
                import openai
                openai.api_key = OPENAI_API_KEY
                self.providers["openai"] = True
                
                # Test connection by listing models
                try:
                    client = openai.OpenAI(api_key=OPENAI_API_KEY)
                    models = client.models.list()
                    st.sidebar.success("🟢 OpenAI connected")
                except Exception as e:
                    st.sidebar.warning(f"🟡 OpenAI connected but couldn't list models: {str(e)}")
            except ImportError:
                st.sidebar.warning("⚠️ OpenAI SDK not installed. Run 'pip install openai'")
            except Exception as e:
                st.sidebar.error(f"🔴 Error initializing OpenAI: {str(e)}")
        
        # Initialize DeepSeek if available
        if DEEPSEEK_API_KEY:
            try:
                import openai as deepseek_client
                self.providers["deepseek"] = {
                    "client": deepseek_client,
                    "base_url": "https://api.deepseek.com"
                }
                st.sidebar.success("🟢 DeepSeek AI connected")
            except ImportError:
                st.sidebar.warning("⚠️ OpenAI SDK needed for DeepSeek. Run 'pip install openai'")
            except Exception as e:
                st.sidebar.error(f"🔴 Error initializing DeepSeek: {str(e)}")
        
        # Initialize Perplexity if available
        if PERPLEXITY_API_KEY:
            self.providers["perplexity"] = True
            st.sidebar.success("🟢 Perplexity API connected")
    
    # In multi_llm_provider.py, update the get_available_models method to provide cleaner model names

def get_available_models(self):
    """Get all available models across providers with improved naming"""
    models = {}
    
    # Claude models - dynamically get if possible
    if "claude" in self.providers:
        try:
            claude_models = self.providers["claude"].models.list()
            for model in claude_models.data:
                # Create more readable model names
                model_id = model.id
                
                # Extract version and type for more readable names
                if "claude-3" in model_id:
                    parts = model_id.split('-')
                    if len(parts) >= 4:
                        version = parts[1]
                        variant = parts[2].capitalize()
                        date = parts[3][:8]  # Get just the date part
                        display_name = f"Claude {version} {variant} ({date})"
                        models[model_id] = display_name
                    else:
                        models[model_id] = f"Claude ({model_id})"
                else:
                    models[model_id] = f"Claude ({model_id})"
        except Exception:
            # Fallback to hardcoded models if API call fails
            models.update({
                "claude-3-sonnet-20250219": "Claude 3 Sonnet (Feb 2025)",
                "claude-3-haiku-20250319": "Claude 3 Haiku (Mar 2025)",
                "claude-3-opus-20250229": "Claude 3 Opus (Feb 2025)",
                "claude-3-7-sonnet-20250219": "Claude 3.7 Sonnet (Feb 2025)"
            })
    
    # OpenAI models - with better naming
    if "openai" in self.providers:
        models.update({
            "gpt-4": "GPT-4",
            "gpt-4-turbo": "GPT-4 Turbo",
            "gpt-3.5-turbo": "GPT-3.5 Turbo"
        })
    
    # DeepSeek models - with better naming
    if "deepseek" in self.providers:
        models.update({
            "deepseek-chat": "DeepSeek Chat",
            "deepseek-coder": "DeepSeek Coder"
        })
    
    return models
    
    def generate_text(self, prompt: str, model: str, system_prompt: str = None, temperature: float = 0.7, max_tokens: int = 1000):
        """Generate text using the specified model"""
        # Check if the specified model is available
        if model.startswith("claude") and "claude" in self.providers:
            return self._generate_with_claude(prompt, model, system_prompt, temperature, max_tokens)
        
        elif model.startswith("gpt") and "openai" in self.providers:
            return self._generate_with_openai(prompt, model, system_prompt, temperature, max_tokens)
        
        elif model.startswith("deepseek") and "deepseek" in self.providers:
            return self._generate_with_deepseek(prompt, model, system_prompt, temperature, max_tokens)
        
        else:
            # Try to find any available provider
            available_providers = []
            if "claude" in self.providers:
                available_providers.append("claude")
            if "openai" in self.providers:
                available_providers.append("openai")
            if "deepseek" in self.providers:
                available_providers.append("deepseek")
                
            if available_providers:
                provider = available_providers[0]
                if provider == "claude":
                    # Get available Claude models
                    try:
                        models = self.providers["claude"].models.list()
                        if models.data:
                            fallback_model = models.data[0].id
                            st.warning(f"Model {model} not available. Falling back to {fallback_model}.")
                            return self._generate_with_claude(prompt, fallback_model, system_prompt, temperature, max_tokens)
                    except:
                        pass
                    # If model list fails, use hardcoded fallback
                    st.warning(f"Model {model} not available. Falling back to Claude 3 Sonnet.")
                    return self._generate_with_claude(prompt, "claude-3-sonnet-20250219", system_prompt, temperature, max_tokens)
                elif provider == "openai":
                    st.warning(f"Model {model} not available. Falling back to GPT-3.5 Turbo.")
                    return self._generate_with_openai(prompt, "gpt-3.5-turbo", system_prompt, temperature, max_tokens)
                elif provider == "deepseek":
                    st.warning(f"Model {model} not available. Falling back to DeepSeek Chat.")
                    return self._generate_with_deepseek(prompt, "deepseek-chat", system_prompt, temperature, max_tokens)
            else:
                raise ValueError(f"No AI provider available for model: {model}")
    
    def _generate_with_claude(self, prompt: str, model: str, system_prompt: str = None, temperature: float = 0.7, max_tokens: int = 1000):
        """Generate text using Claude with enhanced error handling"""
        client = self.providers["claude"]
        messages = [{"role": "user", "content": prompt}]
        
        try:
            response = client.messages.create(
                model=model,
                max_tokens=max_tokens,
                temperature=temperature,
                system=system_prompt if system_prompt else "You are a helpful assistant.",
                messages=messages
            )
            return response.content[0].text
        except Exception as e:
            error_msg = str(e)
            st.error(f"Claude API Error ({model}): {error_msg}")
            
            # Check for model availability errors
            if ("model" in error_msg.lower() and "not" in error_msg.lower()) or "not_found" in error_msg.lower():
                try:
                    # Try to get available models
                    available_models = []
                    try:
                        models_list = client.models.list()
                        available_models = [m.id for m in models_list.data]
                        st.info(f"Available Claude models: {', '.join(available_models)}")
                    except:
                        # If listing fails, use fallback list
                        available_models = ["claude-3-7-sonnet-20250219", "claude-3-sonnet-20250219", "claude-3-haiku-20250319", "claude-3-opus-20250229"]
                    
                    # Try available models
                    for fallback_model in available_models:
                        if fallback_model != model:
                            try:
                                st.warning(f"Trying fallback model: {fallback_model}")
                                response = client.messages.create(
                                    model=fallback_model,
                                    max_tokens=max_tokens,
                                    temperature=temperature,
                                    system=system_prompt if system_prompt else "You are a helpful assistant.",
                                    messages=messages
                                )
                                return response.content[0].text
                            except Exception as fallback_error:
                                st.warning(f"Fallback to {fallback_model} failed: {str(fallback_error)}")
                                continue
                except Exception as list_error:
                    st.error(f"Error while attempting fallbacks: {str(list_error)}")
            
            # If we reach here, all fallbacks failed or it's another type of error
            raise ValueError(f"Claude API failed with error: {error_msg}")
    
    def _generate_with_openai(self, prompt: str, model: str, system_prompt: str = None, temperature: float = 0.7, max_tokens: int = 1000):
        """Generate text using OpenAI with enhanced error handling"""
        import openai
        
        messages = []
        if system_prompt:
            messages.append({"role": "system", "content": system_prompt})
        
        messages.append({"role": "user", "content": prompt})
        
        try:
            response = openai.chat.completions.create(
                model=model,
                messages=messages,
                temperature=temperature,
                max_tokens=max_tokens
            )
            return response.choices[0].message.content
        except Exception as e:
            error_msg = str(e)
            st.error(f"OpenAI API Error ({model}): {error_msg}")
            
            # Check for model availability errors
            if "model" in error_msg.lower() and ("not" in error_msg.lower() or "find" in error_msg.lower()):
                # Try fallback models
                fallback_models = ["gpt-3.5-turbo", "gpt-4"]
                for fallback_model in fallback_models:
                    if fallback_model != model:
                        try:
                            st.warning(f"Trying fallback model: {fallback_model}")
                            response = openai.chat.completions.create(
                                model=fallback_model,
                                messages=messages,
                                temperature=temperature,
                                max_tokens=max_tokens
                            )
                            return response.choices[0].message.content
                        except Exception as fallback_error:
                            st.warning(f"Fallback to {fallback_model} failed: {str(fallback_error)}")
                            continue
            
            # If all fallbacks fail or it's another type of error
            raise ValueError(f"OpenAI API failed with error: {error_msg}")
    
    def _generate_with_deepseek(self, prompt: str, model: str, system_prompt: str = None, temperature: float = 0.7, max_tokens: int = 1000):
        """Generate text using DeepSeek with enhanced error handling"""
        import openai as deepseek
        
        deepseek.api_key = DEEPSEEK_API_KEY
        deepseek.base_url = "https://api.deepseek.com"
        
        messages = []
        if system_prompt:
            messages.append({"role": "system", "content": system_prompt})
        
        messages.append({"role": "user", "content": prompt})
        
        try:
            response = deepseek.chat.completions.create(
                model="deepseek-chat" if model == "deepseek-chat" else "deepseek-coder",
                messages=messages,
                temperature=temperature,
                max_tokens=max_tokens
            )
            return response.choices[0].message.content
        except Exception as e:
            error_msg = str(e)
            st.error(f"DeepSeek API Error ({model}): {error_msg}")
            
            # Check for model availability errors and try fallback
            if model == "deepseek-chat":
                try:
                    st.warning("Trying fallback model: deepseek-coder")
                    response = deepseek.chat.completions.create(
                        model="deepseek-coder",
                        messages=messages,
                        temperature=temperature,
                        max_tokens=max_tokens
                    )
                    return response.choices[0].message.content
                except Exception as fallback_error:
                    st.warning(f"Fallback failed: {str(fallback_error)}")
            elif model == "deepseek-coder":
                try:
                    st.warning("Trying fallback model: deepseek-chat")
                    response = deepseek.chat.completions.create(
                        model="deepseek-chat",
                        messages=messages,
                        temperature=temperature,
                        max_tokens=max_tokens
                    )
                    return response.choices[0].message.content
                except Exception as fallback_error:
                    st.warning(f"Fallback failed: {str(fallback_error)}")
            
            # If fallbacks fail or it's another type of error
            raise ValueError(f"DeepSeek API failed with error: {error_msg}")
    
    def web_search(self, query: str) -> List[Dict[str, Any]]:
        """Perform a web search using Perplexity API"""
        if "perplexity" not in self.providers or not PERPLEXITY_API_KEY:
            raise ValueError("Perplexity API not available")
        
        headers = {
            "Authorization": f"Bearer {PERPLEXITY_API_KEY}",
            "Content-Type": "application/json"
        }
        
        data = {
            "query": query,
            "max_results": 5
        }
        
        try:
            response = requests.post(
                "https://api.perplexity.ai/search",
                headers=headers,
                json=data
            )
            
            if response.status_code == 200:
                results = response.json()
                return results.get("results", [])
            else:
                st.error(f"Error in web search: {response.status_code} - {response.text}")
                return []
        except Exception as e:
            st.error(f"Error performing web search: {str(e)}")
            return []
    
    def enhance_with_web_search(self, slide_content: Dict[str, Any], search_query: str = None) -> Dict[str, Any]:
        """Enhance slide content with web search results"""
        if "perplexity" not in self.providers or not PERPLEXITY_API_KEY:
            st.warning("Web search enhancement not available (Perplexity API key required)")
            return slide_content
        
        # Use slide title if no search query provided
        if not search_query:
            title = slide_content.get("title", "")
            search_query = f"latest information about {title}"
        
        try:
            with st.spinner(f"Searching the web for: {search_query}"):
                results = self.web_search(search_query)
                
                if not results:
                    st.warning("No search results found")
                    return slide_content
                
                # Compile search results
                search_content = "Web search results:\n\n"
                for i, result in enumerate(results, 1):
                    title = result.get("title", "No title")
                    snippet = result.get("snippet", "No snippet")
                    url = result.get("url", "No URL")
                    
                    search_content += f"{i}. {title}\n"
                    search_content += f"   {snippet}\n"
                    search_content += f"   Source: {url}\n\n"
                
                # Create an enrichment prompt
                prompt = f"""
                You are an expert presentation content creator.
                
                Current slide:
                Title: {slide_content.get('title', 'No title')}
                Content: {slide_content.get('content', [])}
                
                I have gathered recent information about this topic. Please use this information to enhance the slide content:
                
                {search_content}
                
                Please provide:
                1. An updated slide title (if needed)
                2. Enhanced content points that incorporate the latest information
                3. A note about the sources of this information
                
                Return your response as JSON with the following structure:
                {{
                  "title": "Enhanced title",
                  "content": ["Point 1", "Point 2", "Point 3"],
                  "notes": "Notes including source information"
                }}
                """
                
                # Get models from available providers
                available_models = []
                if "claude" in self.providers:
                    available_models.append("claude-3-7-sonnet-20250219") # Use newest model first
                    available_models.append("claude-3-sonnet-20250219")  
                if "openai" in self.providers:
                    available_models.append("gpt-4")
                    available_models.append("gpt-3.5-turbo")
                if "deepseek" in self.providers:
                    available_models.append("deepseek-chat")
                
                # Try models until one works
                response = None
                for model in available_models:
                    try:
                        response = self.generate_text(
                            prompt=prompt,
                            model=model,
                            system_prompt="You are an expert at enhancing presentation content with the latest information. Always respond with valid JSON.",
                            temperature=0.5,
                            max_tokens=2000
                        )
                        if response:
                            break
                    except Exception as e:
                        st.warning(f"Error using {model}: {str(e)}. Trying next model...")
                        continue
                
                if not response:
                    st.error("All models failed. Could not enhance content.")
                    return slide_content
                
                # Extract the JSON from the response
                try:
                    # Find JSON in the text
                    json_start = response.find("{")
                    json_end = response.rfind("}") + 1
                    
                    if json_start >= 0 and json_end > 0:
                        json_str = response[json_start:json_end]
                        enhanced_content = json.loads(json_str)
                        
                        # Update the slide content
                        slide_content["title"] = enhanced_content.get("title", slide_content.get("title", ""))
                        slide_content["content"] = enhanced_content.get("content", slide_content.get("content", []))
                        
                        # Append to existing notes
                        existing_notes = slide_content.get("notes", "")
                        new_notes = enhanced_content.get("notes", "")
                        slide_content["notes"] = f"{existing_notes}\n\n{new_notes}".strip()
                        
                        # Add source information
                        slide_content["source_info"] = {
                            "search_query": search_query,
                            "results_count": len(results),
                            "timestamp": "March 2025"
                        }
                        
                        st.success("Slide enhanced with latest web information!")
                    else:
                        st.error("Could not extract JSON from AI response")
                        st.info("Raw response: " + response[:500] + "...")  # Show part of the response for debugging
                except Exception as e:
                    st.error(f"Error processing AI response: {str(e)}")
                    st.info("Raw response: " + response[:500] + "...")  # Show part of the response for debugging
        
        except Exception as e:
            st.error(f"Error enhancing content with web search: {str(e)}")
        
        return slide_content
    
    def generate_image_description(self, slide_content: Dict[str, Any]) -> str:
        """Generate an optimized description for image generation"""
        if not "claude" in self.providers and not "openai" in self.providers:
            return ""
        
        title = slide_content.get('title', '')
        
        if isinstance(slide_content.get('content', []), list):
            content_text = " ".join(slide_content.get('content', []))[:500]  # Limit length
        else:
            content_text = str(slide_content.get('content', ''))[:500]
        
        prompt = f"""
        You are an expert at creating image generation prompts.
        
        I need a detailed visual description for a presentation slide with the following content:
        
        Title: {title}
        Content: {content_text}
        
        Please create a detailed description (under a single paragraph of 50 words or less) that would help an AI image generator create an appropriate, professional, metaphorical image for this slide.
        
        Focus on imagery that would work well in a business presentation context - prefer abstract, conceptual, or metaphorical imagery over literal representations.
        Specify style (photorealistic, illustration, 3D render), color scheme, and composition.
        
        ONLY provide the description itself with no explanations, introductions, or extra text.
        """
        
        try:
            # Choose the model based on availability
            available_models = []
            if "claude" in self.providers:
                available_models.append("claude-3-haiku-20250319")
            if "openai" in self.providers:
                available_models.append("gpt-3.5-turbo")
            if "deepseek" in self.providers:
                available_models.append("deepseek-chat")
            
            # Try models until one works
            description = None
            for model in available_models:
                try:
                    description = self.generate_text(
                        prompt=prompt,
                        model=model,
                        system_prompt="You are an expert at creating image generation prompts for business presentations.",
                        temperature=0.7,
                        max_tokens=200
                    )
                    if description:
                        break
                except Exception as e:
                    st.warning(f"Error using {model} for image description: {str(e)}. Trying next model...")
                    continue
            
            if not description:
                return f"An image representing {title}"
            
            # Clean up the response
            return description.strip()
        except Exception as e:
            st.error(f"Error generating image description: {str(e)}")
            return f"An image representing {title}"

# Initialize the AI provider manager
def get_ai_manager():
    """Get or create the AI provider manager"""
    if 'ai_manager' not in st.session_state:
        st.session_state.ai_manager = AIProviderManager()
    
    return st.session_state.ai_manager

# Add a diagnostic function to test model availability
def test_models():
    """Test model availability across providers"""
    ai_manager = get_ai_manager()
    
    st.write("### LLM Provider Diagnostics")
    
    # Test Claude
    if "claude" in ai_manager.providers:
        try:
            st.write("#### Testing Claude API:")
            claude = ai_manager.providers["claude"]
            models = claude.models.list()
            st.success(f"Available Claude models:")
            for model in models.data:
                st.write(f"- {model.id}")
                
            # Test a quick generation
            test_prompt = "Say hello in one word."
            with st.spinner(f"Testing with {models.data[0].id}..."):
                response = claude.messages.create(
                    model=models.data[0].id,
                    max_tokens=10,
                    messages=[{"role": "user", "content": test_prompt}]
                )
                st.success(f"Test response: {response.content[0].text}")
        except Exception as e:
            st.error(f"Claude API test failed: {str(e)}")
    else:
        st.warning("Claude API not configured")
    
    # Test OpenAI
    if "openai" in ai_manager.providers:
        try:
            st.write("#### Testing OpenAI API:")
            import openai
            client = openai.OpenAI(api_key=OPENAI_API_KEY)
            models = client.models.list()
            st.success(f"Available OpenAI models:")
            for model in models.data[:5]:  # Show first 5 to avoid cluttering
                st.write(f"- {model.id}")
                
            # Test a quick generation
            test_prompt = "Say hello in one word."
            with st.spinner("Testing with gpt-3.5-turbo..."):
                response = client.chat.completions.create(
                    model="gpt-3.5-turbo",
                    messages=[{"role": "user", "content": test_prompt}]
                )
                st.success(f"Test response: {response.choices[0].message.content}")
        except Exception as e:
            st.error(f"OpenAI API test failed: {str(e)}")
    else:
        st.warning("OpenAI API not configured")