Spaces:
Sleeping
Sleeping
| """ | |
| Danışman-Danışan Transkripsiyon Sistemi | |
| Speaker diarization + transcription pipeline. | |
| Zaman damgalı, konuşmacı ayrımlı çıktı + Klinik Analiz Araçları. | |
| """ | |
| import gradio as gr | |
| from faster_whisper import WhisperModel | |
| import tempfile | |
| import time | |
| import os | |
| import torch | |
| import re | |
| from collections import Counter | |
| from diarization import ( | |
| get_diarization_pipeline, | |
| diarize_audio, | |
| format_speaker_label, | |
| format_timestamp | |
| ) | |
| # ==================== CONFIGURATION ==================== | |
| MODEL_SIZE = "medium" | |
| DEVICE = "cuda" if torch.cuda.is_available() else "cpu" | |
| COMPUTE_TYPE = "float16" if DEVICE == "cuda" else "int8" | |
| # ======================================================= | |
| print(f"🔧 Device: {DEVICE}, Compute: {COMPUTE_TYPE}") | |
| # Load models at startup | |
| print("🔄 Whisper model yükleniyor...") | |
| whisper_model = WhisperModel( | |
| MODEL_SIZE, | |
| device=DEVICE, | |
| compute_type=COMPUTE_TYPE | |
| ) | |
| print("✅ Whisper model yüklendi!") | |
| print("🔄 Diarization pipeline yükleniyor...") | |
| diarization_pipeline = get_diarization_pipeline() | |
| # ==================== KLİNİK ANALİZ MATRİSLERİ ==================== | |
| # 1. DUYGU DURUM GÖSTERGELERİ (AFFECTIVE INDICATORS) | |
| CLINICAL_INDICATORS = { | |
| "Disforik/Depresif": [ | |
| "üzgün", "mutsuz", "çaresiz", "umutsuz", "bıktım", "karanlık", "boşluk", | |
| "değersiz", "suçlu", "yorgun", "tükendim", "ölüm", "intihar", "bitse", | |
| "hiçbir şey", "zevk almıyorum", "ağır", "çöküş", "ağlıyorum", "kayıp" | |
| ], | |
| "Anksiyöz/Kaygılı": [ | |
| "korkuyorum", "endişe", "panik", "ne olacak", "ya olursa", "gerginim", | |
| "kalbim", "nefes", "titreme", "kontrolü kaybetme", "tehlike", "felaket", | |
| "huzursuz", "yerimde duramıyorum", "kafayı yiyeceğim", "stres" | |
| ], | |
| "Öfke/Hostilite": [ | |
| "nefret", "kızgınım", "aptal", "haksızlık", "intikam", "dayanamıyorum", | |
| "bağır", "vur", "kır", "sinir", "öfke", "düşman", "zarar", "sinirlendim" | |
| ], | |
| "Ötimik/Pozitif": [ | |
| "iyi", "güzel", "mutlu", "başardım", "umutlu", "sakin", "huzurlu", | |
| "keyifli", "planlıyorum", "seviyorum", "şükür", "rahat" | |
| ] | |
| } | |
| # 2. BİLİŞSEL ÇARPITMALAR (COGNITIVE DISTORTIONS) | |
| COGNITIVE_DISTORTIONS = { | |
| "Aşırı Genelleme": ["her zaman", "asla", "hiçbir zaman", "herkes", "hiç kimse", "hep"], | |
| "Zorunluluk (-meli/-malı)": ["yapmalıyım", "zorundayım", "mecburum", "etmeli", "olmalı", "gerekir"], | |
| "Felaketleştirme": ["mahvoldu", "bitti", "felaket", "korkunç", "dayanılmaz", "berbat"] | |
| } | |
| # 3. ZAMAN ODAĞI (TEMPORAL FOCUS) | |
| TIME_MARKERS = { | |
| "Geçmiş": ["yaptım", "gitti", "oldu", "vardı", "eskiden", "keşke", "geçmişte"], | |
| "Gelecek": ["olacak", "yapacağım", "gidecek", "gelecek", "belki", "acaba", "planlıyorum"] | |
| } | |
| # 4. TEREDDÜT VE DOLGU (HESITATION MARKERS) | |
| FILLERS = ["şey", "yani", "ııı", "eee", "işte", "falan", "filan", "hani"] | |
| # Turkish stop words to exclude from word frequency | |
| TURKISH_STOP_WORDS = { | |
| "bir", "bu", "şu", "o", "ve", "ile", "için", "de", "da", "ki", "ne", "var", "yok", | |
| "ben", "sen", "biz", "siz", "onlar", "ama", "fakat", "çünkü", "eğer", "gibi", | |
| "daha", "en", "çok", "az", "kadar", "sonra", "önce", "şimdi", "zaman", "her", | |
| "hiç", "bile", "sadece", "hem", "ya", "veya", "ise", "mi", "mı", "mu", "mü", | |
| "nasıl", "neden", "nerede", "kim", "hangi", "olan", "olarak", "oldu", "olur", | |
| "oluyor", "olmuş", "olacak", "yapmak", "yapıyor", "yaptı", "etti", "ediyor", | |
| "gidiyor", "geliyor", "diyor", "dedi", "söyledi", "bence", "aslında", "yani", | |
| "işte", "hani", "evet", "hayır", "tamam", "peki" | |
| } | |
| def get_audio_duration(audio_path: str) -> float: | |
| """Get audio duration in seconds using ffprobe.""" | |
| import subprocess | |
| try: | |
| result = subprocess.run([ | |
| 'ffprobe', '-v', 'error', | |
| '-show_entries', 'format=duration', | |
| '-of', 'default=noprint_wrappers=1:nokey=1', | |
| audio_path | |
| ], capture_output=True, text=True, check=True) | |
| return float(result.stdout.strip()) | |
| except: | |
| return 0.0 | |
| def get_word_frequency(text: str, top_n: int = 15) -> list: | |
| """Get most frequent meaningful words.""" | |
| words = re.findall(r'\b[a-zA-ZçğıöşüÇĞİÖŞÜ]{3,}\b', text.lower()) | |
| meaningful_words = [w for w in words if w not in TURKISH_STOP_WORDS] | |
| word_counts = Counter(meaningful_words) | |
| return word_counts.most_common(top_n) | |
| def analyze_clinical_features(text: str, duration_seconds: float) -> dict: | |
| """Metni klinik parametrelere göre analiz eder.""" | |
| text_lower = text.lower() | |
| words = re.findall(r'\b\w+\b', text_lower) | |
| word_count = len(words) | |
| # 1. Konuşma Hızı (Words Per Minute) | |
| speech_rate = (word_count / duration_seconds) * 60 if duration_seconds > 0 else 0 | |
| # 2. Klinik Gösterge Taraması | |
| scores = {category: 0 for category in CLINICAL_INDICATORS} | |
| matched_details = {category: [] for category in CLINICAL_INDICATORS} | |
| for category, keywords in CLINICAL_INDICATORS.items(): | |
| for word in keywords: | |
| count = text_lower.count(word) | |
| if count > 0: | |
| scores[category] += count | |
| if word not in matched_details[category]: | |
| matched_details[category].append(word) | |
| # 3. Bilişsel Çarpıtmalar | |
| distortions = {cat: 0 for cat in COGNITIVE_DISTORTIONS} | |
| distortion_matches = {cat: [] for cat in COGNITIVE_DISTORTIONS} | |
| for category, keywords in COGNITIVE_DISTORTIONS.items(): | |
| for word in keywords: | |
| count = text_lower.count(word) | |
| if count > 0: | |
| distortions[category] += count | |
| if word not in distortion_matches[category]: | |
| distortion_matches[category].append(word) | |
| # 4. Zaman Odağı | |
| time_focus = {"Geçmiş": 0, "Gelecek": 0} | |
| for category, keywords in TIME_MARKERS.items(): | |
| for word in keywords: | |
| time_focus[category] += text_lower.count(word) | |
| # 5. Benlik Odağı (Self-Reference Ratio) | |
| self_refs = text_lower.count("ben") + text_lower.count("benim") + text_lower.count("bana") + text_lower.count("beni") | |
| self_ratio = (self_refs / word_count) * 100 if word_count > 0 else 0 | |
| # 6. Dolgu kelime sayısı | |
| filler_count = sum(text_lower.count(f) for f in FILLERS) | |
| return { | |
| "word_count": word_count, | |
| "speech_rate": speech_rate, | |
| "clinical_scores": scores, | |
| "clinical_matches": matched_details, | |
| "distortions": distortions, | |
| "distortion_matches": distortion_matches, | |
| "time_focus": time_focus, | |
| "self_ratio": self_ratio, | |
| "filler_count": filler_count | |
| } | |
| def generate_psychology_report(transcript: str, client_speaker: str) -> str: | |
| """Danışan için klinik formatta ön değerlendirme raporu oluşturur.""" | |
| # --- Danışan Verisini Ayıkla ve Süre Hesapla --- | |
| lines = transcript.split('\n') | |
| client_text_parts = [] | |
| current_speaker = None | |
| total_client_duration = 0.0 | |
| for line in lines: | |
| # Örnek satır: "[00:05 → 00:12] Kişi 2:" | |
| timestamp_match = re.search(r'\[(\d{2}:\d{2}) → (\d{2}:\d{2})\] (Kişi \d+):', line) | |
| if timestamp_match: | |
| start_str, end_str, spk = timestamp_match.groups() | |
| current_speaker = spk | |
| # Süreyi hesapla | |
| def to_sec(t): | |
| m, s = map(int, t.split(':')) | |
| return m * 60 + s | |
| if spk == client_speaker: | |
| seg_dur = to_sec(end_str) - to_sec(start_str) | |
| total_client_duration += seg_dur | |
| elif current_speaker == client_speaker and line.strip(): | |
| # Metni al (çizgiler ve meta veriler hariç) | |
| clean_line = line.strip() | |
| if not any(clean_line.startswith(c) for c in ['[', '═', '─', '📊', '•']): | |
| client_text_parts.append(clean_line) | |
| full_text = ' '.join(client_text_parts) | |
| if not full_text: | |
| return "⚠️ HATA: Seçilen kişiye ait yeterli veri bulunamadı. Lütfen doğru konuşmacıyı seçtiğinizden emin olun." | |
| # --- Analizi Çalıştır --- | |
| if total_client_duration == 0: | |
| total_client_duration = len(full_text.split()) / 2.5 # Tahmini süre | |
| analysis = analyze_clinical_features(full_text, total_client_duration) | |
| # Kelime frekansı | |
| word_freq = get_word_frequency(full_text) | |
| # --- RAPOR YAZIMI (KLİNİK FORMAT) --- | |
| # 1. Konuşma Hızı Yorumu | |
| wpm = analysis['speech_rate'] | |
| speech_comment = "Olağan sınırlarda (100-150 wpm)" | |
| if wpm < 90: | |
| speech_comment = "⚠️ Bradilali (Yavaşlamış konuşma) - Depresif duygulanım veya yüksek bilişsel yük lehine." | |
| elif wpm > 160: | |
| speech_comment = "⚠️ Taşilali (Basınçlı konuşma) - Anksiyete veya manik dönem lehine." | |
| # 2. Baskın Duygu Yorumu | |
| scores = analysis['clinical_scores'] | |
| dominant_mood = max(scores, key=scores.get) | |
| if scores[dominant_mood] == 0: | |
| dominant_mood = "Nötr/Belirsiz" | |
| report = f""" | |
| ══════════════════════════════════════════════════════════════ | |
| 🏥 KLİNİK GÖRÜŞME ÖN DEĞERLENDİRME RAPORU | |
| ══════════════════════════════════════════════════════════════ | |
| 📅 TARİH: {time.strftime("%d.%m.%Y - %H:%M")} | |
| 👤 DANIŞAN KODU: {client_speaker} | |
| ⏱️ TOPLAM KONUŞMA SÜRESİ: {format_timestamp(total_client_duration)} | |
| ══════════════════════════════════════════════════════════════ | |
| I. GENEL GÖRÜNÜM VE KONUŞMA DAVRANIŞI | |
| ══════════════════════════════════════════════════════════════ | |
| • Toplam Kelime Sayısı: {analysis['word_count']} | |
| • Konuşma Hızı: {wpm:.0f} kelime/dakika | |
| └─ {speech_comment} | |
| • Dolgu Kelime Kullanımı: {analysis['filler_count']} adet (şey, yani, işte vb.) | |
| • Benlik Odağı: %{analysis['self_ratio']:.1f} | |
| └─ {"⚠️ Yüksek - içe dönüklük veya ruminasyon riski" if analysis['self_ratio'] > 4 else "Normal sınırlarda"} | |
| ══════════════════════════════════════════════════════════════ | |
| II. DUYGUDURUM VE DUYGULANIM (MOOD & AFFECT) | |
| ══════════════════════════════════════════════════════════════ | |
| Yapay zeka dil örüntü analizine göre tespit edilen baskın duygulanım: | |
| ╔══════════════════════════════════╗ | |
| ║ 👉 {dominant_mood.upper():^26} ║ | |
| ╚══════════════════════════════════╝ | |
| 📊 Duygu Dağılımı: | |
| """ | |
| # Duygu barları | |
| total_emotion = sum(scores.values()) or 1 | |
| for category, score in sorted(scores.items(), key=lambda x: x[1], reverse=True): | |
| if score > 0: | |
| percentage = (score / total_emotion) * 100 | |
| bar_length = int(percentage / 5) | |
| bar = "█" * bar_length + "░" * (20 - bar_length) | |
| words = ", ".join(analysis['clinical_matches'][category][:3]) | |
| report += f"• {category}: {bar} {percentage:.0f}%\n" | |
| report += f" └─ Anahtar: {words}\n" | |
| report += f""" | |
| ══════════════════════════════════════════════════════════════ | |
| III. DÜŞÜNCE İÇERİĞİ VE BİLİŞSEL SÜREÇLER | |
| ══════════════════════════════════════════════════════════════ | |
| A. BİLİŞSEL ÇARPITMALAR (Cognitive Distortions): | |
| """ | |
| has_distortion = False | |
| for dist, count in analysis['distortions'].items(): | |
| if count > 0: | |
| has_distortion = True | |
| words = ", ".join(analysis['distortion_matches'][dist][:3]) | |
| report += f"• {dist}: {count} kez\n" | |
| report += f" └─ Örnek: \"{words}\"\n" | |
| if not has_distortion: | |
| report += "• ✓ Belirgin bir bilişsel çarpıtma dili tespit edilmedi.\n" | |
| report += "\nB. ZAMAN YÖNELİMİ (Temporal Orientation):\n" | |
| past = analysis['time_focus']['Geçmiş'] | |
| future = analysis['time_focus']['Gelecek'] | |
| if past > future * 1.5: | |
| report += f"• ⚠️ Geçmiş Odaklı ({past} ifade vs {future} gelecek)\n" | |
| report += " └─ Pişmanlık, yas veya depresif ruminasyon eğilimi gösterebilir.\n" | |
| elif future > past * 1.5: | |
| report += f"• ⚠️ Gelecek Odaklı ({future} ifade vs {past} geçmiş)\n" | |
| report += " └─ Beklenti anksiyetesi eğilimi gösterebilir.\n" | |
| else: | |
| report += f"• ✓ Dengeli zaman yönelimi (Geçmiş: {past}, Gelecek: {future})\n" | |
| report += f""" | |
| ══════════════════════════════════════════════════════════════ | |
| IV. SIK KULLANILAN KELİMELER | |
| ══════════════════════════════════════════════════════════════ | |
| """ | |
| for i, (word, count) in enumerate(word_freq[:10], 1): | |
| report += f"{i:2}. {word}: {count} kez\n" | |
| report += f""" | |
| ══════════════════════════════════════════════════════════════ | |
| V. KLİNİK İZLENİM VE ÖNERİLER | |
| ══════════════════════════════════════════════════════════════ | |
| """ | |
| # Dinamik Sonuç Çıkarımı | |
| observations = [] | |
| if dominant_mood == "Disforik/Depresif" and past > future: | |
| observations.append("🔴 DİKKAT: Depresif duygulanım ve geçmişe saplanma (ruminasyon) örüntüsü gözlenmiştir.") | |
| observations.append(" └─ İntihar riski değerlendirmesi önerilir.") | |
| if dominant_mood == "Anksiyöz/Kaygılı" and wpm > 140: | |
| observations.append("🔴 DİKKAT: Yüksek anksiyete belirtileri (hızlı konuşma, kaygı ifadeleri).") | |
| observations.append(" └─ Panik bozukluk veya yaygın anksiyete açısından değerlendirilmeli.") | |
| if analysis['distortions']['Zorunluluk (-meli/-malı)'] >= 2: | |
| observations.append("🟡 Terapötik Hedef: Mükemmeliyetçi şemalar ve '-meli/-malı' kuralları.") | |
| observations.append(" └─ Bilişsel yeniden yapılandırma önerilir.") | |
| if analysis['distortions']['Aşırı Genelleme'] >= 2: | |
| observations.append("🟡 Terapötik Hedef: Aşırı genelleme eğilimi tespit edildi.") | |
| observations.append(" └─ 'Her zaman', 'asla' gibi mutlaklaştırmalar sorgulanmalı.") | |
| if analysis['self_ratio'] > 5: | |
| observations.append("🟡 Yüksek benlik odağı: Ruminatif düşünce örüntüsü olabilir.") | |
| observations.append(" └─ Dikkat odağını genişletme egzersizleri düşünülebilir.") | |
| if not observations: | |
| observations.append("🟢 Acil müdahale gerektiren belirgin bir risk örüntüsü (dilsel düzeyde) saptanmamıştır.") | |
| observations.append(" └─ Rutin takip önerilir.") | |
| for obs in observations: | |
| report += f"{obs}\n" | |
| report += """ | |
| ══════════════════════════════════════════════════════════════ | |
| ⚠️ YASAL UYARI | |
| ══════════════════════════════════════════════════════════════ | |
| Bu rapor Yapay Zeka (AI) algoritmaları tarafından sadece dilsel | |
| verilere dayanarak otomatik olarak oluşturulmuştur. | |
| • Tanı veya teşhis yerine geçmez. | |
| • Klinik karar vermede tek başına kullanılamaz. | |
| • Sadece klinisyene yardımcı veri olarak sunulmuştur. | |
| • Profesyonel değerlendirme mutlaka gereklidir. | |
| ══════════════════════════════════════════════════════════════ | |
| """ | |
| return report | |
| def transcribe_with_diarization(audio_path: str) -> tuple: | |
| """ | |
| Full pipeline: diarization + transcription. | |
| Returns formatted transcript with speaker labels and timestamps. | |
| """ | |
| start_time = time.time() | |
| # Get audio duration for stats | |
| duration = get_audio_duration(audio_path) | |
| # Step 1: Diarization | |
| print("🎭 Diarization başlıyor...") | |
| if diarization_pipeline is None: | |
| # Fallback: no diarization, just transcribe | |
| segments, info = whisper_model.transcribe(audio_path, language="tr", beam_size=5) | |
| full_text = [] | |
| for segment in segments: | |
| timestamp = format_timestamp(segment.start) | |
| full_text.append(f"[{timestamp}] {segment.text}") | |
| result = "\n".join(full_text) | |
| elapsed = time.time() - start_time | |
| stats = f""" | |
| ─────────────────────────────────── | |
| 📊 İstatistikler | |
| • Toplam süre: {format_timestamp(info.duration)} | |
| • İşlem süresi: {elapsed:.1f} saniye | |
| • ⚠️ Diarization kullanılamadı (yalnızca transkripsiyon) | |
| ───────────────────────────────────""" | |
| return result + stats, None | |
| # Run diarization | |
| diarization_segments = diarize_audio(audio_path, diarization_pipeline, num_speakers=None) | |
| if not diarization_segments: | |
| return "❌ Diarization başarısız oldu.", None | |
| # Step 2: Transcribe each segment | |
| print("🎙️ Transkripsiyon başlıyor...") | |
| segments, info = whisper_model.transcribe(audio_path, language="tr", beam_size=5) | |
| whisper_segments = list(segments) | |
| # Track which whisper segments have been used | |
| used_whisper_indices = set() | |
| # Step 3: Merge diarization with transcription | |
| print("🔗 Birleştirme yapılıyor...") | |
| transcript_parts = [] | |
| speaker_times = {} | |
| for start, end, speaker in diarization_segments: | |
| speaker_label = format_speaker_label(speaker) | |
| if speaker_label not in speaker_times: | |
| speaker_times[speaker_label] = 0 | |
| speaker_times[speaker_label] += (end - start) | |
| segment_text = [] | |
| for idx, ws in enumerate(whisper_segments): | |
| if idx in used_whisper_indices: | |
| continue | |
| ws_midpoint = (ws.start + ws.end) / 2 | |
| if start <= ws_midpoint <= end: | |
| segment_text.append(ws.text) | |
| used_whisper_indices.add(idx) | |
| if segment_text: | |
| text = " ".join(segment_text).strip() | |
| timestamp_start = format_timestamp(start) | |
| timestamp_end = format_timestamp(end) | |
| transcript_parts.append(f"[{timestamp_start} → {timestamp_end}] {speaker_label}:\n{text}\n") | |
| header = """═══════════════════════════════════════════════════ | |
| 📋 GÖRÜŞME TRANSKRİPTİ | |
| ═══════════════════════════════════════════════════ | |
| """ | |
| body = "\n".join(transcript_parts) | |
| elapsed = time.time() - start_time | |
| total_time = info.duration | |
| stats_lines = [ | |
| "", | |
| "───────────────────────────────────", | |
| "📊 İstatistikler", | |
| f"• Toplam süre: {format_timestamp(total_time)}", | |
| f"• İşlem süresi: {elapsed:.1f} saniye", | |
| ] | |
| for speaker, stime in sorted(speaker_times.items()): | |
| percentage = (stime / total_time) * 100 if total_time > 0 else 0 | |
| stats_lines.append(f"• {speaker} konuşma: {format_timestamp(stime)} (%{percentage:.0f})") | |
| stats_lines.append("───────────────────────────────────") | |
| stats = "\n".join(stats_lines) | |
| full_result = header + body + stats | |
| txt_file = tempfile.NamedTemporaryFile( | |
| mode='w', | |
| suffix='.txt', | |
| delete=False, | |
| encoding='utf-8' | |
| ) | |
| txt_file.write(full_result) | |
| txt_file.close() | |
| return full_result, txt_file.name | |
| def process_audio(audio_path): | |
| """Gradio handler.""" | |
| if audio_path is None: | |
| return "⚠️ Lütfen bir ses dosyası yükleyin.", None | |
| try: | |
| return transcribe_with_diarization(audio_path) | |
| except Exception as e: | |
| return f"❌ Beklenmeyen hata: {str(e)}", None | |
| def analyze_client(transcript: str, client_selection: str): | |
| """Analyze the selected client's speech.""" | |
| if not transcript or transcript.startswith("⚠️") or transcript.startswith("❌"): | |
| return "⚠️ Önce bir transkript oluşturun.", None | |
| if not client_selection: | |
| return "⚠️ Lütfen danışanı seçin.", None | |
| report = generate_psychology_report(transcript, client_selection) | |
| # Create downloadable report | |
| report_file = tempfile.NamedTemporaryFile( | |
| mode='w', | |
| suffix='_klinik_rapor.txt', | |
| delete=False, | |
| encoding='utf-8' | |
| ) | |
| report_file.write(report) | |
| report_file.close() | |
| return report, report_file.name | |
| # ==================== GRADIO UI ==================== | |
| with gr.Blocks(title="Klinik Görüşme Transkripsiyon & Analiz") as demo: | |
| gr.HTML(""" | |
| <style> | |
| footer { display: none !important; } | |
| .gradio-container { max-width: 1100px !important; margin: auto !important; } | |
| </style> | |
| <div style="text-align: center; padding: 40px 20px 30px; | |
| background: linear-gradient(135deg, #1e3a5f 0%, #2d5a87 100%); | |
| border-radius: 20px; margin-bottom: 24px; color: white;"> | |
| <h1 style="font-size: 2.2rem; font-weight: 700; margin: 0 0 8px 0;"> | |
| 🏥 Klinik Görüşme Transkripsiyon & Analiz | |
| </h1> | |
| <p style="font-size: 1rem; opacity: 0.95; margin: 0;"> | |
| Danışman-Danışan görüşmelerini yazıya dökün ve AI destekli klinik ön değerlendirme alın | |
| </p> | |
| </div> | |
| """) | |
| with gr.Tabs(): | |
| # Tab 1: Transkripsiyon | |
| with gr.TabItem("📝 Transkripsiyon"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.HTML('<div style="font-weight: 600; margin-bottom: 12px;">📤 Ses Dosyası</div>') | |
| audio_input = gr.Audio( | |
| label="Görüşme Kaydı", | |
| type="filepath", | |
| sources=["upload", "microphone"] | |
| ) | |
| submit_btn = gr.Button( | |
| "🚀 Transkripsiyon Başlat", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| gr.HTML(""" | |
| <div style="background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%); | |
| border: 1px solid #7dd3fc; border-radius: 12px; | |
| padding: 16px 20px; margin-top: 16px;"> | |
| <p style="margin: 0; color: #0369a1; font-size: 14px;"> | |
| ℹ️ <strong>Nasıl Çalışır:</strong><br> | |
| 1. Ses dosyasını yükleyin<br> | |
| 2. AI konuşmacıları otomatik ayırır<br> | |
| 3. Transkript oluşturulur<br> | |
| 4. Klinik Analiz sekmesinde danışanı seçip rapor alın | |
| </p> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.HTML('<div style="font-weight: 600; margin-bottom: 12px;">📝 Transkript Sonucu</div>') | |
| output_text = gr.Textbox( | |
| label="", | |
| placeholder="Transkript burada görünecek...", | |
| lines=25, | |
| interactive=False | |
| ) | |
| download_file = gr.File( | |
| label="📥 Transkripti İndir (.txt)" | |
| ) | |
| # Tab 2: Klinik Analiz | |
| with gr.TabItem("🏥 Klinik Analiz"): | |
| gr.HTML(""" | |
| <div style="background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); | |
| border: 1px solid #f59e0b; border-radius: 12px; | |
| padding: 16px 20px; margin-bottom: 16px;"> | |
| <p style="margin: 0; color: #92400e; font-size: 14px;"> | |
| 🏥 <strong>Klinik Ön Değerlendirme:</strong> Bu modül danışanın konuşmasını | |
| duygudurum, bilişsel çarpıtmalar, zaman yönelimi ve konuşma hızı açısından | |
| analiz eder. Profesyonel klinik değerlendirmenin yerini almaz. | |
| </p> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| client_selector = gr.Radio( | |
| label="🎯 Danışanı Seçin", | |
| choices=["Kişi 1", "Kişi 2"], | |
| value="Kişi 2", | |
| info="Hangi kişi danışan?" | |
| ) | |
| analyze_btn = gr.Button( | |
| "🔍 Klinik Analiz Başlat", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| gr.HTML(""" | |
| <div style="background: #f9fafb; border-radius: 12px; | |
| padding: 16px 20px; margin-top: 16px;"> | |
| <p style="margin: 0 0 8px 0; color: #374151; font-size: 13px; font-weight: 600;"> | |
| 📊 Analiz Kapsamı: | |
| </p> | |
| <ul style="margin: 0; padding-left: 20px; color: #6b7280; font-size: 12px;"> | |
| <li>Duygudurum göstergeleri</li> | |
| <li>Bilişsel çarpıtmalar</li> | |
| <li>Zaman yönelimi</li> | |
| <li>Konuşma hızı</li> | |
| <li>Benlik odağı</li> | |
| <li>Kelime frekansı</li> | |
| </ul> | |
| </div> | |
| """) | |
| with gr.Column(scale=2): | |
| analysis_output = gr.Textbox( | |
| label="📋 Klinik Ön Değerlendirme Raporu", | |
| placeholder="Analiz raporu burada görünecek...", | |
| lines=30, | |
| interactive=False | |
| ) | |
| report_download = gr.File( | |
| label="📥 Raporu İndir (.txt)" | |
| ) | |
| # Features | |
| gr.HTML(""" | |
| <div style="display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px; margin-top: 24px;"> | |
| <div style="text-align: center; padding: 14px; background: #f9fafb; border-radius: 12px;"> | |
| <div style="font-size: 22px; margin-bottom: 4px;">🎭</div> | |
| <div style="font-size: 11px; color: #6b7280; font-weight: 500;">Konuşmacı Ayrımı</div> | |
| </div> | |
| <div style="text-align: center; padding: 14px; background: #f9fafb; border-radius: 12px;"> | |
| <div style="font-size: 22px; margin-bottom: 4px;">⏱️</div> | |
| <div style="font-size: 11px; color: #6b7280; font-weight: 500;">Zaman Damgası</div> | |
| </div> | |
| <div style="text-align: center; padding: 14px; background: #f9fafb; border-radius: 12px;"> | |
| <div style="font-size: 22px; margin-bottom: 4px;">🔒</div> | |
| <div style="font-size: 11px; color: #6b7280; font-weight: 500;">%100 Local</div> | |
| </div> | |
| <div style="text-align: center; padding: 14px; background: linear-gradient(135deg, #ede9fe 0%, #ddd6fe 100%); border-radius: 12px;"> | |
| <div style="font-size: 22px; margin-bottom: 4px;">🧠</div> | |
| <div style="font-size: 11px; color: #5b21b6; font-weight: 500;">Duygudurum</div> | |
| </div> | |
| <div style="text-align: center; padding: 14px; background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); border-radius: 12px;"> | |
| <div style="font-size: 22px; margin-bottom: 4px;">💭</div> | |
| <div style="font-size: 11px; color: #92400e; font-weight: 500;">Bilişsel Analiz</div> | |
| </div> | |
| <div style="text-align: center; padding: 14px; background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%); border-radius: 12px;"> | |
| <div style="font-size: 22px; margin-bottom: 4px;">📋</div> | |
| <div style="font-size: 11px; color: #065f46; font-weight: 500;">Klinik Rapor</div> | |
| </div> | |
| </div> | |
| """) | |
| gr.HTML(""" | |
| <div style="background: #ecfdf5; border: 1px solid #6ee7b7; border-radius: 8px; | |
| padding: 12px 16px; margin-top: 16px;"> | |
| <p style="margin: 0; color: #047857; font-size: 13px;"> | |
| 🔒 <strong>Gizlilik:</strong> Tüm işlemler yerel olarak yapılır. | |
| Ses dosyalarınız ve analizler hiçbir sunucuya gönderilmez. | |
| </p> | |
| </div> | |
| """) | |
| gr.HTML(""" | |
| <div style="text-align: center; padding: 24px 0; color: #9ca3af; font-size: 13px;"> | |
| <p>Powered by Faster-Whisper & Pyannote-Audio • Klinisyen Asistanı v2.0</p> | |
| </div> | |
| """) | |
| # Event handling | |
| submit_btn.click( | |
| fn=process_audio, | |
| inputs=[audio_input], | |
| outputs=[output_text, download_file] | |
| ) | |
| analyze_btn.click( | |
| fn=analyze_client, | |
| inputs=[output_text, client_selector], | |
| outputs=[analysis_output, report_download] | |
| ) | |
| # Launch | |
| if __name__ == "__main__": | |
| demo.launch(share=False, show_error=True) | |