File size: 25,339 Bytes
1049a4d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
PULSE-7B Enhanced Handler
Ubdenยฎ Team - Edited by https://github.com/ck-cankurt
Support: Text, Image URLs, and Base64 encoded images
"""

import torch
from typing import Dict, List, Any
import base64
from io import BytesIO
from PIL import Image
import requests
import time

# Import utilities if available
try:
    from utils import (
        performance_monitor, 
        validate_image_input, 
        sanitize_parameters, 
        get_system_info,
        create_health_check,
        deepseek_client
    )
    UTILS_AVAILABLE = True
except ImportError:
    UTILS_AVAILABLE = False
    deepseek_client = None
    print("โš ๏ธ Utils module not found - performance monitoring and DeepSeek integration disabled")

# Try to import LLaVA modules for proper conversation handling
try:
    from llava.constants import IMAGE_TOKEN_INDEX, DEFAULT_IMAGE_TOKEN
    from llava.conversation import conv_templates, SeparatorStyle
    from llava.mm_utils import tokenizer_image_token, process_images, KeywordsStoppingCriteria
    LLAVA_AVAILABLE = True
    print("โœ… LLaVA modules imported successfully")
except ImportError:
    LLAVA_AVAILABLE = False
    print("โš ๏ธ LLaVA modules not available - using basic text processing")


class EndpointHandler:
    def __init__(self, path=""):
        """
        Hey there! Let's get this PULSE-7B model up and running.
        We'll load it from the HuggingFace hub directly, so no worries about local files.
        
        Args:
            path: Model directory path (we actually ignore this and load from HF hub)
        """
        print("๐Ÿš€ Starting up PULSE-7B handler...")
        print("๐Ÿ“ Enhanced by Ubdenยฎ Team - github.com/ck-cankurt")
        import sys
        print(f"๐Ÿ”ง Python version: {sys.version}")
        print(f"๐Ÿ”ง PyTorch version: {torch.__version__}")
        
        # Check transformers version
        try:
            import transformers
            print(f"๐Ÿ”ง Transformers version: {transformers.__version__}")
            
            # PULSE LLaVA works with transformers==4.37.2
            if transformers.__version__ == "4.37.2":
                print("โœ… Using PULSE LLaVA compatible version (4.37.2)")
            elif "dev" in transformers.__version__ or "git" in str(transformers.__version__):
                print("โš ๏ธ Using development version - may conflict with PULSE LLaVA")
            else:
                print("โš ๏ธ Using different version - PULSE LLaVA prefers 4.37.2")
        except Exception as e:
            print(f"โŒ Error checking transformers version: {e}")
        
        print(f"๐Ÿ”ง CUDA available: {torch.cuda.is_available()}")
        if torch.cuda.is_available():
            print(f"๐Ÿ”ง CUDA device: {torch.cuda.get_device_name(0)}")
        
        # Let's see what hardware we're working with
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        print(f"๐Ÿ–ฅ๏ธ Running on: {self.device}")
        
        try:
            # First attempt - PULSE demo's exact approach
            if LLAVA_AVAILABLE:
                print("๐Ÿ“ฆ Using PULSE demo's load_pretrained_model approach...")
                from llava.model.builder import load_pretrained_model
                from llava.mm_utils import get_model_name_from_path
                
                model_path = "PULSE-ECG/PULSE-7B"
                model_name = get_model_name_from_path(model_path)
                
                self.tokenizer, self.model, self.image_processor, self.context_len = load_pretrained_model(
                    model_path=model_path,
                    model_base=None,
                    model_name=model_name,
                    load_8bit=False,
                    load_4bit=False
                )
                
                # Move model to device like demo
                self.model = self.model.to(self.device)
                self.use_pipeline = False
                print("โœ… Model loaded successfully with PULSE demo's approach!")
                print(f"๐Ÿ“ธ Image processor: {type(self.image_processor).__name__}")
                
            else:
                raise ImportError("LLaVA modules not available")
            
        except Exception as e:
            print(f"โš ๏ธ PULSE demo approach failed: {e}")
            print("๐Ÿ”„ Falling back to pipeline...")
            
            try:
                # Fallback - using pipeline
                from transformers import pipeline
                
                print("๐Ÿ“ฆ Fetching model from HuggingFace Hub...")
                self.pipe = pipeline(
                    "text-generation",
                    model="PULSE-ECG/PULSE-7B",
                    torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
                    device=0 if torch.cuda.is_available() else -1,
                    trust_remote_code=True,
                    model_kwargs={
                        "low_cpu_mem_usage": True,
                        "use_safetensors": True
                    }
                )
                self.use_pipeline = True
                self.image_processor = None
                print("โœ… Model loaded successfully via pipeline!")
                
            except Exception as e2:
                print(f"๐Ÿ˜“ Pipeline also failed: {e2}")
                
                try:
                    # Last resort - manual loading
                    from transformers import AutoTokenizer, LlamaForCausalLM
                    
                    print("๐Ÿ“– Manual loading as last resort...")
                    self.tokenizer = AutoTokenizer.from_pretrained(
                        "PULSE-ECG/PULSE-7B",
                        trust_remote_code=True
                    )
                    
                    self.model = LlamaForCausalLM.from_pretrained(
                        "PULSE-ECG/PULSE-7B",
                        torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
                        device_map="auto",
                        low_cpu_mem_usage=True,
                        trust_remote_code=True
                    )
                    
                    if self.tokenizer.pad_token is None:
                        self.tokenizer.pad_token = self.tokenizer.eos_token
                        self.tokenizer.pad_token_id = self.tokenizer.eos_token_id
                    
                    self.model.eval()
                    self.use_pipeline = False
                    self.image_processor = None
                    print("โœ… Model loaded manually!")
                    
                except Exception as e3:
                    print(f"๐Ÿ˜“ All approaches failed: {e3}")
                    self.pipe = None
                    self.model = None
                    self.tokenizer = None
                    self.image_processor = None
                    self.use_pipeline = None
        
        # Final status report
        print("\n๐Ÿ” Model Loading Status Report:")
        print(f"   - use_pipeline: {self.use_pipeline}")
        print(f"   - model: {'โœ… Loaded' if hasattr(self, 'model') and self.model is not None else 'โŒ None'}")
        print(f"   - tokenizer: {'โœ… Loaded' if hasattr(self, 'tokenizer') and self.tokenizer is not None else 'โŒ None'}")
        print(f"   - image_processor: {'โœ… Loaded' if hasattr(self, 'image_processor') and self.image_processor is not None else 'โŒ None'}")
        print(f"   - pipe: {'โœ… Loaded' if hasattr(self, 'pipe') and self.pipe is not None else 'โŒ None'}")
        
        # Check if any model component loaded successfully
        has_model = hasattr(self, 'model') and self.model is not None
        has_tokenizer = hasattr(self, 'tokenizer') and self.tokenizer is not None
        has_pipe = hasattr(self, 'pipe') and self.pipe is not None
        has_image_processor = hasattr(self, 'image_processor') and self.image_processor is not None
        
        if not (has_model or has_tokenizer or has_pipe):
            print("๐Ÿ’ฅ CRITICAL: No model components loaded successfully!")
        else:
            print("โœ… At least one model component loaded successfully")
            if has_image_processor:
                print("๐Ÿ–ผ๏ธ Vision capabilities available!")
            else:
                print("โš ๏ธ No image processor - text-only mode")

    def is_valid_image_format(self, filename_or_url):
        """Validate image format like PULSE demo"""
        # Demo's supported formats
        image_extensions = ["jpg", "jpeg", "png", "bmp", "gif", "tiff", "webp", "heic", "heif", "jfif", "svg", "eps", "raw"]
        
        if filename_or_url.startswith(('http://', 'https://')):
            # For URLs, check the extension or content-type
            ext = filename_or_url.split('.')[-1].split('?')[0].lower()
            return ext in image_extensions
        else:
            # For base64 or local files
            return True  # Base64 will be validated during decode
    
    def process_image_input(self, image_input):
        """
        Handle both URL and base64 image inputs exactly like PULSE demo
        
        Args:
            image_input: Can be a URL string or base64 encoded image
            
        Returns:
            PIL Image object or None if something goes wrong
        """
        try:
            # Check if it's a URL (starts with http/https)
            if isinstance(image_input, str) and (image_input.startswith('http://') or image_input.startswith('https://')):
                print(f"๐ŸŒ Fetching image from URL: {image_input[:50]}...")
                
                # Validate format like demo
                if not self.is_valid_image_format(image_input):
                    print("โŒ Invalid image format in URL")
                    return None
                
                # Demo's exact image loading approach
                response = requests.get(image_input, timeout=15)
                if response.status_code == 200:
                    image = Image.open(BytesIO(response.content)).convert("RGB")
                    print(f"โœ… Image downloaded successfully! Size: {image.size}")
                    return image
                else:
                    print(f"โŒ Failed to load image: status {response.status_code}")
                    return None
            
            # Must be base64 then
            elif isinstance(image_input, str):
                print("๐Ÿ” Decoding base64 image...")
                
                # Remove the data URL prefix if it exists
                base64_data = image_input
                if "base64," in image_input:
                    base64_data = image_input.split("base64,")[1]
                
                # Clean and validate base64 data
                base64_data = base64_data.strip().replace('\n', '').replace('\r', '').replace(' ', '')
                
                try:
                    image_data = base64.b64decode(base64_data)
                    image = Image.open(BytesIO(image_data)).convert('RGB')
                    print(f"โœ… Base64 image decoded successfully! Size: {image.size}")
                    return image
                except Exception as decode_error:
                    print(f"โŒ Base64 decode error: {decode_error}")
                    return None
                
        except Exception as e:
            print(f"โŒ Couldn't process the image: {e}")
            return None
        
        return None

    def add_turkish_commentary(self, response: Dict[str, Any], enable_commentary: bool, timeout: int = 30) -> Dict[str, Any]:
        """Add Turkish commentary to the response using DeepSeek API"""
        if not enable_commentary:
            return response
            
        if not UTILS_AVAILABLE or not deepseek_client:
            print("โš ๏ธ DeepSeek client not available - skipping Turkish commentary")
            response["commentary_status"] = "unavailable"
            return response
            
        if not deepseek_client.is_available():
            print("โš ๏ธ DeepSeek API key not configured - skipping Turkish commentary")
            response["commentary_status"] = "api_key_missing"
            return response
            
        generated_text = response.get("generated_text", "")
        if not generated_text:
            print("โš ๏ธ No generated text to comment on")
            response["commentary_status"] = "no_text"
            return response
            
        print("๐Ÿ”„ DeepSeek ile Tรผrkรงe yorum ekleniyor...")
        commentary_result = deepseek_client.get_turkish_commentary(generated_text, timeout)
        
        if commentary_result["success"]:
            response["comment_text"] = commentary_result["comment_text"]
            response["commentary_model"] = commentary_result.get("model", "deepseek-chat")
            response["commentary_tokens"] = commentary_result.get("tokens_used", 0)
            response["commentary_status"] = "success"
            print("โœ… Tรผrkรงe yorum baลŸarฤฑyla eklendi")
        else:
            response["comment_text"] = ""
            response["commentary_error"] = commentary_result["error"]
            response["commentary_status"] = "failed"
            print(f"โŒ Tรผrkรงe yorum eklenemedi: {commentary_result['error']}")
            
        return response

    def health_check(self) -> Dict[str, Any]:
        """Health check endpoint"""
        if UTILS_AVAILABLE:
            return create_health_check()
        else:
            return {
                'status': 'healthy',
                'model': 'PULSE-7B',
                'timestamp': time.time(),
                'handler_version': '2.0.0'
            }

    def __call__(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
        """
        Main processing function - where the magic happens!
        
        Args:
            data: Input data with 'inputs' and optional 'parameters'
        
        Returns:
            List with the generated response
        """
        # Quick check - is our model ready?
        if self.use_pipeline is None:
            return [{
                "generated_text": "Oops! Model couldn't load properly. Please check the deployment settings.",
                "error": "Model initialization failed",
                "handler": "Ubdenยฎ Team Enhanced Handler"
            }]
        
        try:
            # Parse the inputs - flexible format support
            inputs = data.get("inputs", "")
            text = ""
            image = None
            
            if isinstance(inputs, dict):
                # Dictionary input - check for text and image
                # Support query field (new) plus original text/prompt fields
                text = inputs.get("query", inputs.get("text", inputs.get("prompt", str(inputs))))
                
                # Check for image in various formats
                image_input = inputs.get("image", inputs.get("image_url", inputs.get("image_base64", None)))
                if image_input:
                    image = self.process_image_input(image_input)
                    if image:
                        # Since we're in text-only mode, create smart ECG context
                        print(f"๐Ÿ–ผ๏ธ Image loaded: {image.size[0]}x{image.size[1]} pixels - using text-only ECG analysis mode")
                        
                        # Create ECG-specific prompt that mimics visual analysis
                        ecg_context = f"Analyzing an ECG image ({image.size[0]}x{image.size[1]} pixels). "
                        
                        # Use demo's exact approach - no additional context, just the query
                        # Model is trained to understand ECG images from text queries
                        pass  # Keep text exactly as received
            else:
                # Simple string input
                text = str(inputs)
            
            if not text:
                return [{"generated_text": "Hey, I need some text to work with! Please provide an input."}]
            
            # Get generation parameters - using PULSE-7B demo's exact settings
            parameters = data.get("parameters", {})
            max_new_tokens = min(parameters.get("max_new_tokens", 1024), 8192)  # Demo uses 1024 default
            temperature = parameters.get("temperature", 0.05)  # Demo uses 0.05 for precise medical analysis
            top_p = parameters.get("top_p", 1.0)  # Demo uses 1.0 for full vocabulary access
            do_sample = parameters.get("do_sample", True)  # Demo uses sampling
            repetition_penalty = parameters.get("repetition_penalty", 1.0)  # Demo default
            
            print(f"๐ŸŽ›๏ธ Generation params: max_tokens={max_new_tokens}, temp={temperature}, top_p={top_p}, do_sample={do_sample}, rep_penalty={repetition_penalty}")
            
            # Check if Turkish commentary is requested (NEW FEATURE)
            enable_turkish_commentary = parameters.get("enable_turkish_commentary", False)  # Default false
            
            # Using pipeline? Let's go!
            if self.use_pipeline:
                print(f"๐ŸŽ›๏ธ Pipeline generation: temp={temperature}, tokens={max_new_tokens}")
                print(f"๐Ÿ“ Input text: '{text[:100]}...'")
                
                result = self.pipe(
                    text,
                    max_new_tokens=max_new_tokens,
                                            min_new_tokens=200,  # Force very detailed analysis to match demo
                    temperature=temperature,
                    top_p=top_p,
                    do_sample=do_sample,
                    repetition_penalty=repetition_penalty,
                    return_full_text=False  # Just the new stuff, not the input
                )
                
                # Pipeline returns a list, let's handle it
                if isinstance(result, list) and len(result) > 0:
                    generated_text = result[0].get("generated_text", "").strip()
                    
                    print(f"๐Ÿ” Pipeline debug:")
                    print(f"   - Raw result: '{str(result[0])[:200]}...'")
                    print(f"   - Generated text length: {len(generated_text)}")
                    
                    # Clean up common issues
                    if generated_text.startswith(text):
                        generated_text = generated_text[len(text):].strip()
                        print("๐Ÿ”ง Removed input text from output")
                    
                    # Remove common artifacts
                    generated_text = generated_text.replace("</s>", "").strip()
                    
                    if not generated_text:
                        print("โŒ Pipeline generated empty text!")
                        generated_text = "Empty response from pipeline. Please try different parameters."
                    
                    print(f"โœ… Final pipeline text: '{generated_text[:100]}...' (length: {len(generated_text)})")
                    
                    # Create response
                    response = {"generated_text": generated_text}
                    
                    # Add Turkish commentary if requested (NEW FEATURE)
                    if enable_turkish_commentary:
                        response = self.add_turkish_commentary(response, True)
                    
                    return [response]
                else:
                    generated_text = str(result).strip()
                    
                    # Create response
                    response = {"generated_text": generated_text}
                    
                    # Add Turkish commentary if requested (NEW FEATURE)
                    if enable_turkish_commentary:
                        response = self.add_turkish_commentary(response, True)
                    
                    return [response]
            
            # Manual generation mode - using PULSE demo's exact approach
            else:
                print(f"๐Ÿ”ฅ Manual generation with PULSE demo logic: temp={temperature}, tokens={max_new_tokens}")
                print(f"๐Ÿ“ Input text: '{text[:100]}...'")
                
                # Text-only generation with enhanced ECG context
                print("๐Ÿ”ค Using enhanced text-only generation with ECG context")
                
                # Tokenize the enhanced prompt
                encoded = self.tokenizer(
                    text,
                    return_tensors="pt",
                    truncation=True,
                    max_length=4096  # Increased for longer prompts
                )
                
                input_ids = encoded["input_ids"].to(self.device)
                attention_mask = encoded.get("attention_mask")
                if attention_mask is not None:
                    attention_mask = attention_mask.to(self.device)
                
                print(f"๐Ÿ” Enhanced generation debug:")
                print(f"   - Enhanced prompt length: {len(text)} chars")
                print(f"   - Input tokens: {input_ids.shape[-1]}")
                print(f"   - Prompt preview: '{text[:150]}...'")
                
                # Generate with enhanced settings for medical analysis
                with torch.no_grad():
                    outputs = self.model.generate(
                        input_ids,
                        attention_mask=attention_mask,
                        max_new_tokens=max_new_tokens,
                        min_new_tokens=200,  # Force detailed response like demo
                        temperature=temperature,
                        top_p=top_p,
                        do_sample=do_sample,
                        repetition_penalty=repetition_penalty,
                        pad_token_id=self.tokenizer.pad_token_id,
                        eos_token_id=self.tokenizer.eos_token_id,
                        early_stopping=False
                    )
                
                # Decode and clean response
                generated_ids = outputs[0][input_ids.shape[-1]:]
                generated_text = self.tokenizer.decode(
                    generated_ids,
                    skip_special_tokens=True,
                    clean_up_tokenization_spaces=True
                ).strip()
                
                # Aggressive cleanup of artifacts
                generated_text = generated_text.replace("</s>", "").strip()
                
                # Simple cleanup - just remove Answer prefix and parentheses
                if generated_text.startswith("(Answer:") and ")" in generated_text:
                    # Just remove the parentheses and Answer: prefix
                    end_paren = generated_text.find(")")
                    answer_content = generated_text[8:end_paren].strip()  # Remove "(Answer:"
                    # Keep the rest of the response if there is any
                    rest_of_response = generated_text[end_paren+1:].strip()
                    
                    if rest_of_response:
                        generated_text = f"{answer_content}. {rest_of_response}"
                    else:
                        generated_text = answer_content
                
                elif generated_text.startswith("Answer:"):
                    generated_text = generated_text[7:].strip()
                
                # Remove only clear training artifacts
                cleanup_patterns = [
                    "In this task",
                    "I'm asking the respondent",
                    "The respondent should"
                ]
                
                for pattern in cleanup_patterns:
                    if pattern in generated_text:
                        parts = generated_text.split(pattern)
                        generated_text = parts[0].strip()
                        break
                
                # Only provide fallback if response is truly empty or malformed
                if len(generated_text) < 10 or generated_text.startswith("7)"):
                    print("โš ๏ธ Malformed response detected, providing fallback...")
                    generated_text = "This ECG shows cardiac electrical activity. For accurate interpretation, please consult with a qualified cardiologist who can analyze the specific waveforms, intervals, and morphology patterns."
                
                print(f"โœ… Enhanced text-only generation: '{generated_text[:100]}...' (length: {len(generated_text)})")
                
                # Create response
                response = {"generated_text": generated_text}
                
                # Add Turkish commentary if requested (NEW FEATURE)
                if enable_turkish_commentary:
                    response = self.add_turkish_commentary(response, True)
                
                return [response]
            
            
        except Exception as e:
            error_msg = f"Something went wrong during generation: {str(e)}"
            print(f"โŒ {error_msg}")
            return [{
                "generated_text": "",
                "error": error_msg,
                "handler": "Ubdenยฎ Team Enhanced Handler"
            }]