Spaces:
Running
Running
| from flask import Flask, request, jsonify, render_template_string, Response | |
| import torch | |
| from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer | |
| from threading import Thread | |
| import json | |
| app = Flask(__name__) | |
| # ==================== CSS STİLLERİ ==================== | |
| STYLES = """ | |
| :root { | |
| --primary: #2c3e50; | |
| --secondary: #3498db; | |
| --accent: #e74c3c; | |
| --success: #27ae60; | |
| --warning: #f39c12; | |
| --light: #ecf0f1; | |
| --dark: #1a252f; | |
| --gray: #95a5a6; | |
| --white: #ffffff; | |
| --shadow: 0 4px 20px rgba(0,0,0,0.1); | |
| --radius: 12px; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| color: var(--dark); | |
| line-height: 1.6; | |
| } | |
| .navbar { | |
| background: var(--dark); | |
| padding: 1rem 2rem; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| box-shadow: var(--shadow); | |
| } | |
| .navbar h1 { | |
| color: var(--white); | |
| font-size: 1.5rem; | |
| font-weight: 600; | |
| } | |
| .nav-links { | |
| display: flex; | |
| gap: 1.5rem; | |
| } | |
| .nav-links a { | |
| color: var(--light); | |
| text-decoration: none; | |
| font-weight: 500; | |
| padding: 0.5rem 1rem; | |
| border-radius: 6px; | |
| transition: all 0.3s ease; | |
| } | |
| .nav-links a:hover, | |
| .nav-links a.active { | |
| background: var(--secondary); | |
| color: var(--white); | |
| } | |
| .container { | |
| max-width: 1000px; | |
| margin: 2rem auto; | |
| padding: 0 1rem; | |
| } | |
| .card { | |
| background: var(--white); | |
| border-radius: var(--radius); | |
| box-shadow: var(--shadow); | |
| padding: 2rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .card-header { | |
| border-bottom: 2px solid var(--light); | |
| padding-bottom: 1rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .card-header h2 { | |
| color: var(--primary); | |
| font-size: 1.5rem; | |
| } | |
| .card-header p { | |
| color: var(--gray); | |
| margin-top: 0.5rem; | |
| } | |
| label { | |
| display: block; | |
| font-weight: 600; | |
| color: var(--primary); | |
| margin-bottom: 0.5rem; | |
| } | |
| textarea { | |
| width: 100%; | |
| height: 140px; | |
| padding: 1rem; | |
| border: 2px solid var(--light); | |
| border-radius: 8px; | |
| font-size: 1rem; | |
| font-family: inherit; | |
| resize: vertical; | |
| transition: border-color 0.3s ease; | |
| } | |
| textarea:focus { | |
| outline: none; | |
| border-color: var(--secondary); | |
| } | |
| select { | |
| width: 100%; | |
| padding: 0.8rem 1rem; | |
| border: 2px solid var(--light); | |
| border-radius: 8px; | |
| font-size: 1rem; | |
| font-family: inherit; | |
| background: var(--white); | |
| cursor: pointer; | |
| margin-bottom: 1rem; | |
| } | |
| select:focus { | |
| outline: none; | |
| border-color: var(--secondary); | |
| } | |
| .btn { | |
| display: inline-block; | |
| padding: 1rem 2rem; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| border: none; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| width: 100%; | |
| text-align: center; | |
| } | |
| .btn-primary { | |
| background: linear-gradient(135deg, var(--secondary), #2980b9); | |
| color: var(--white); | |
| } | |
| .btn-primary:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4); | |
| } | |
| .btn-primary:disabled { | |
| background: var(--gray); | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .btn-stop { | |
| background: linear-gradient(135deg, var(--accent), #c0392b); | |
| color: var(--white); | |
| margin-top: 0.5rem; | |
| display: none; | |
| } | |
| .btn-stop.active { | |
| display: block; | |
| } | |
| .form-group { | |
| margin-bottom: 1.5rem; | |
| } | |
| /* ==================== SETTINGS PANEL ==================== */ | |
| .settings-panel { | |
| background: linear-gradient(135deg, #f8f9fa, #e9ecef); | |
| border-radius: var(--radius); | |
| padding: 1.5rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .settings-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| cursor: pointer; | |
| margin-bottom: 1rem; | |
| } | |
| .settings-header h3 { | |
| color: var(--primary); | |
| font-size: 1.1rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .settings-toggle { | |
| background: var(--secondary); | |
| color: var(--white); | |
| border: none; | |
| padding: 0.3rem 0.8rem; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-size: 0.85rem; | |
| transition: all 0.3s ease; | |
| } | |
| .settings-toggle:hover { | |
| background: #2980b9; | |
| } | |
| .settings-content { | |
| display: none; | |
| } | |
| .settings-content.active { | |
| display: block; | |
| } | |
| .settings-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); | |
| gap: 1.5rem; | |
| } | |
| .setting-item { | |
| background: var(--white); | |
| padding: 1rem; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.05); | |
| } | |
| .setting-item label { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 0.8rem; | |
| } | |
| .setting-item label span.value { | |
| background: var(--secondary); | |
| color: var(--white); | |
| padding: 0.2rem 0.6rem; | |
| border-radius: 4px; | |
| font-size: 0.85rem; | |
| min-width: 50px; | |
| text-align: center; | |
| } | |
| .setting-item input[type="range"] { | |
| width: 100%; | |
| height: 8px; | |
| border-radius: 4px; | |
| background: var(--light); | |
| outline: none; | |
| -webkit-appearance: none; | |
| } | |
| .setting-item input[type="range"]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: var(--secondary); | |
| cursor: pointer; | |
| box-shadow: 0 2px 6px rgba(0,0,0,0.2); | |
| transition: transform 0.2s ease; | |
| } | |
| .setting-item input[type="range"]::-webkit-slider-thumb:hover { | |
| transform: scale(1.1); | |
| } | |
| .setting-item input[type="number"] { | |
| width: 100%; | |
| padding: 0.6rem; | |
| border: 2px solid var(--light); | |
| border-radius: 6px; | |
| font-size: 1rem; | |
| text-align: center; | |
| } | |
| .setting-item input[type="number"]:focus { | |
| outline: none; | |
| border-color: var(--secondary); | |
| } | |
| .setting-description { | |
| font-size: 0.8rem; | |
| color: var(--gray); | |
| margin-top: 0.5rem; | |
| } | |
| /* Stream Toggle */ | |
| .stream-toggle { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| padding: 0.5rem 0; | |
| } | |
| .toggle-switch { | |
| position: relative; | |
| width: 60px; | |
| height: 30px; | |
| } | |
| .toggle-switch input { | |
| opacity: 0; | |
| width: 0; | |
| height: 0; | |
| } | |
| .toggle-slider { | |
| position: absolute; | |
| cursor: pointer; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background-color: var(--gray); | |
| transition: 0.4s; | |
| border-radius: 30px; | |
| } | |
| .toggle-slider:before { | |
| position: absolute; | |
| content: ""; | |
| height: 22px; | |
| width: 22px; | |
| left: 4px; | |
| bottom: 4px; | |
| background-color: white; | |
| transition: 0.4s; | |
| border-radius: 50%; | |
| } | |
| .toggle-switch input:checked + .toggle-slider { | |
| background-color: var(--success); | |
| } | |
| .toggle-switch input:checked + .toggle-slider:before { | |
| transform: translateX(30px); | |
| } | |
| .toggle-label { | |
| font-weight: 600; | |
| color: var(--primary); | |
| } | |
| /* ==================== END SETTINGS PANEL ==================== */ | |
| .response-box { | |
| background: linear-gradient(135deg, #f8f9fa, #e9ecef); | |
| border-radius: var(--radius); | |
| padding: 1.5rem; | |
| margin-top: 1.5rem; | |
| display: none; | |
| } | |
| .response-box.active { | |
| display: block; | |
| } | |
| .response-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| margin-bottom: 1rem; | |
| padding-bottom: 0.5rem; | |
| border-bottom: 1px solid var(--gray); | |
| } | |
| .model-badge { | |
| background: var(--secondary); | |
| color: var(--white); | |
| padding: 0.3rem 0.8rem; | |
| border-radius: 20px; | |
| font-size: 0.85rem; | |
| font-weight: 600; | |
| } | |
| .stream-badge { | |
| background: var(--success); | |
| color: var(--white); | |
| padding: 0.2rem 0.6rem; | |
| border-radius: 12px; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| margin-left: 0.5rem; | |
| } | |
| .response-content { | |
| white-space: pre-wrap; | |
| font-size: 1rem; | |
| } | |
| .response-content strong { | |
| color: var(--primary); | |
| } | |
| .cursor-blink { | |
| display: inline-block; | |
| width: 8px; | |
| height: 1.2em; | |
| background: var(--secondary); | |
| margin-left: 2px; | |
| animation: blink 1s infinite; | |
| vertical-align: text-bottom; | |
| } | |
| @keyframes blink { | |
| 0%, 50% { opacity: 1; } | |
| 51%, 100% { opacity: 0; } | |
| } | |
| .loading { | |
| text-align: center; | |
| padding: 2rem; | |
| color: var(--gray); | |
| display: none; | |
| } | |
| .loading.active { | |
| display: block; | |
| } | |
| .spinner { | |
| width: 40px; | |
| height: 40px; | |
| border: 4px solid var(--light); | |
| border-top: 4px solid var(--secondary); | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| margin: 0 auto 1rem; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .error-box { | |
| background: #fdeaea; | |
| border: 1px solid var(--accent); | |
| color: var(--accent); | |
| padding: 1rem; | |
| border-radius: 8px; | |
| margin-top: 1rem; | |
| } | |
| .info-section { | |
| margin-bottom: 2rem; | |
| } | |
| .info-section h3 { | |
| color: var(--primary); | |
| margin-bottom: 1rem; | |
| padding-bottom: 0.5rem; | |
| border-bottom: 2px solid var(--secondary); | |
| } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 1rem; | |
| margin: 1rem 0; | |
| } | |
| .stat-card { | |
| background: linear-gradient(135deg, var(--light), #dfe6e9); | |
| padding: 1.5rem; | |
| border-radius: 8px; | |
| text-align: center; | |
| } | |
| .stat-value { | |
| font-size: 2rem; | |
| font-weight: 700; | |
| color: var(--secondary); | |
| } | |
| .stat-label { | |
| color: var(--gray); | |
| font-size: 0.9rem; | |
| margin-top: 0.3rem; | |
| } | |
| table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin: 1rem 0; | |
| } | |
| th, td { | |
| padding: 0.8rem; | |
| text-align: left; | |
| border-bottom: 1px solid var(--light); | |
| } | |
| th { | |
| background: var(--primary); | |
| color: var(--white); | |
| } | |
| tr:hover { | |
| background: var(--light); | |
| } | |
| .badge { | |
| display: inline-block; | |
| padding: 0.2rem 0.6rem; | |
| border-radius: 4px; | |
| font-size: 0.8rem; | |
| font-weight: 600; | |
| } | |
| .badge-success { | |
| background: #d4edda; | |
| color: var(--success); | |
| } | |
| .badge-danger { | |
| background: #f8d7da; | |
| color: var(--accent); | |
| } | |
| .badge-warning { | |
| background: #fff3cd; | |
| color: #856404; | |
| } | |
| .badge-info { | |
| background: #d1ecf1; | |
| color: #0c5460; | |
| } | |
| .code-block { | |
| background: var(--dark); | |
| color: #a6e22e; | |
| padding: 1rem; | |
| border-radius: 8px; | |
| overflow-x: auto; | |
| font-family: 'Consolas', 'Monaco', monospace; | |
| font-size: 0.9rem; | |
| margin: 1rem 0; | |
| } | |
| .highlight { | |
| background: linear-gradient(135deg, #fff3cd, #ffeeba); | |
| padding: 1rem; | |
| border-radius: 8px; | |
| border-left: 4px solid var(--warning); | |
| margin: 1rem 0; | |
| } | |
| .comparison-table tr:nth-child(even) { | |
| background: #f8f9fa; | |
| } | |
| .footer { | |
| text-align: center; | |
| padding: 2rem; | |
| color: var(--white); | |
| opacity: 0.8; | |
| } | |
| .footer a { | |
| color: var(--white); | |
| } | |
| @media (max-width: 768px) { | |
| .navbar { | |
| flex-direction: column; | |
| gap: 1rem; | |
| } | |
| .stats-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .two-column { | |
| grid-template-columns: 1fr; | |
| } | |
| .settings-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| .two-column { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 1rem; | |
| } | |
| /* Preset buttons */ | |
| .preset-buttons { | |
| display: flex; | |
| gap: 0.5rem; | |
| margin-bottom: 1rem; | |
| flex-wrap: wrap; | |
| } | |
| .preset-btn { | |
| padding: 0.4rem 0.8rem; | |
| border: 2px solid var(--secondary); | |
| background: var(--white); | |
| color: var(--secondary); | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-size: 0.85rem; | |
| font-weight: 500; | |
| transition: all 0.3s ease; | |
| } | |
| .preset-btn:hover { | |
| background: var(--secondary); | |
| color: var(--white); | |
| } | |
| .preset-btn.active { | |
| background: var(--secondary); | |
| color: var(--white); | |
| } | |
| /* Token counter */ | |
| .token-info { | |
| display: flex; | |
| justify-content: space-between; | |
| font-size: 0.85rem; | |
| color: var(--gray); | |
| margin-top: 1rem; | |
| padding-top: 0.5rem; | |
| border-top: 1px dashed var(--light); | |
| } | |
| .token-count { | |
| color: var(--secondary); | |
| font-weight: 600; | |
| } | |
| """ | |
| # ==================== ANA SAYFA HTML ==================== | |
| INDEX_HTML = """ | |
| <!DOCTYPE html> | |
| <html lang="tr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Kayra - Türkçe Dil Modeli Test</title> | |
| <style>{{ styles }}</style> | |
| </head> | |
| <body> | |
| <nav class="navbar"> | |
| <h1>🤖 Kayra Türkçe Dil Modeli</h1> | |
| <div class="nav-links"> | |
| <a href="/" class="active">Test</a> | |
| <a href="/info">Bilgi</a> | |
| </div> | |
| </nav> | |
| <div class="container"> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <h2>Model Test Arayüzü</h2> | |
| <p>Küçük ölçekli Türkçe dil modellerini karşılaştırmalı olarak test edin.</p> | |
| </div> | |
| <div class="form-group"> | |
| <label for="prompt">Soru veya mesajınız:</label> | |
| <textarea id="prompt" placeholder="Örnek: Türkiye'nin başkenti neresidir?"></textarea> | |
| </div> | |
| <div class="form-group"> | |
| <label for="model">Model seçin:</label> | |
| <select id="model"> | |
| <option value="stable">kayra-1 (Stable - Instruction Tuned)</option> | |
| <option value="exp">kayra-1-exp (Deneysel - Sadece Pretrained)</option> | |
| </select> | |
| </div> | |
| <!-- ==================== SETTINGS PANEL ==================== --> | |
| <div class="settings-panel"> | |
| <div class="settings-header" onclick="toggleSettings()"> | |
| <h3>⚙️ Gelişmiş Ayarlar</h3> | |
| <button class="settings-toggle" id="settingsToggleBtn">Göster</button> | |
| </div> | |
| <div class="settings-content" id="settingsContent"> | |
| <!-- Preset Buttons --> | |
| <div class="preset-buttons"> | |
| <button class="preset-btn" onclick="applyPreset('creative')">🎨 Yaratıcı</button> | |
| <button class="preset-btn active" onclick="applyPreset('balanced')">⚖️ Dengeli</button> | |
| <button class="preset-btn" onclick="applyPreset('precise')">🎯 Kesin</button> | |
| <button class="preset-btn" onclick="applyPreset('random')">🎲 Rastgele</button> | |
| </div> | |
| <div class="settings-grid"> | |
| <!-- Temperature --> | |
| <div class="setting-item"> | |
| <label> | |
| 🌡️ Temperature | |
| <span class="value" id="tempValue">0.7</span> | |
| </label> | |
| <input type="range" id="temperature" min="0.1" max="2.0" step="0.1" value="0.7" | |
| oninput="updateSlider('temperature', 'tempValue')"> | |
| <p class="setting-description"> | |
| Düşük = daha tutarlı, Yüksek = daha yaratıcı | |
| </p> | |
| </div> | |
| <!-- Max Tokens --> | |
| <div class="setting-item"> | |
| <label> | |
| 📝 Max Tokens | |
| <span class="value" id="maxTokensValue">150</span> | |
| </label> | |
| <input type="range" id="maxTokens" min="10" max="512" step="10" value="150" | |
| oninput="updateSlider('maxTokens', 'maxTokensValue')"> | |
| <p class="setting-description"> | |
| Üretilecek maksimum token sayısı | |
| </p> | |
| </div> | |
| <!-- Top K --> | |
| <div class="setting-item"> | |
| <label> | |
| 🔝 Top K | |
| <span class="value" id="topKValue">50</span> | |
| </label> | |
| <input type="range" id="topK" min="1" max="100" step="1" value="50" | |
| oninput="updateSlider('topK', 'topKValue')"> | |
| <p class="setting-description"> | |
| Her adımda değerlendirilecek en olası K token | |
| </p> | |
| </div> | |
| <!-- Top P --> | |
| <div class="setting-item"> | |
| <label> | |
| 📊 Top P (Nucleus) | |
| <span class="value" id="topPValue">0.9</span> | |
| </label> | |
| <input type="range" id="topP" min="0.1" max="1.0" step="0.05" value="0.9" | |
| oninput="updateSlider('topP', 'topPValue')"> | |
| <p class="setting-description"> | |
| Kümülatif olasılık eşiği | |
| </p> | |
| </div> | |
| <!-- Repetition Penalty --> | |
| <div class="setting-item"> | |
| <label> | |
| 🔄 Repetition Penalty | |
| <span class="value" id="repPenaltyValue">1.3</span> | |
| </label> | |
| <input type="range" id="repPenalty" min="1.0" max="2.0" step="0.1" value="1.3" | |
| oninput="updateSlider('repPenalty', 'repPenaltyValue')"> | |
| <p class="setting-description"> | |
| Tekrar eden kelimeleri engelleme gücü | |
| </p> | |
| </div> | |
| <!-- Stream Toggle --> | |
| <div class="setting-item"> | |
| <label>🌊 Stream Modu</label> | |
| <div class="stream-toggle"> | |
| <label class="toggle-switch"> | |
| <input type="checkbox" id="streamMode" checked> | |
| <span class="toggle-slider"></span> | |
| </label> | |
| <span class="toggle-label" id="streamLabel">Açık</span> | |
| </div> | |
| <p class="setting-description"> | |
| Cevabı kelime kelime göster | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ==================== END SETTINGS PANEL ==================== --> | |
| <button class="btn btn-primary" id="submitBtn" onclick="generate()"> | |
| 🚀 Gönder ve Cevap Al | |
| </button> | |
| <button class="btn btn-stop" id="stopBtn" onclick="stopGeneration()"> | |
| ⏹️ Durdur | |
| </button> | |
| <div class="loading" id="loading"> | |
| <div class="spinner"></div> | |
| <p>Yanıt oluşturuluyor, lütfen bekleyin...</p> | |
| </div> | |
| <div class="response-box" id="responseBox"> | |
| <div class="response-header"> | |
| <span>Model:</span> | |
| <span class="model-badge" id="modelBadge"></span> | |
| <span class="stream-badge" id="streamBadge" style="display:none;">STREAM</span> | |
| </div> | |
| <div class="response-content" id="responseContent"></div> | |
| <div class="token-info"> | |
| <span>Üretilen token: <span class="token-count" id="tokenCount">0</span></span> | |
| <span>Süre: <span class="token-count" id="genTime">0</span>s</span> | |
| </div> | |
| </div> | |
| <div class="error-box" id="errorBox" style="display:none;"></div> | |
| </div> | |
| </div> | |
| <div class="footer"> | |
| <p>Kayra - Sıfırdan Türkçe ile eğitilmiş GPT modeli</p> | |
| <p><a href="https://huggingface.co/sixfingerdev" target="_blank">Hugging Face</a></p> | |
| </div> | |
| <script> | |
| let eventSource = null; | |
| let isGenerating = false; | |
| let startTime = null; | |
| // Settings toggle | |
| function toggleSettings() { | |
| const content = document.getElementById('settingsContent'); | |
| const btn = document.getElementById('settingsToggleBtn'); | |
| content.classList.toggle('active'); | |
| btn.textContent = content.classList.contains('active') ? 'Gizle' : 'Göster'; | |
| } | |
| // Update slider value display | |
| function updateSlider(sliderId, valueId) { | |
| const slider = document.getElementById(sliderId); | |
| const valueSpan = document.getElementById(valueId); | |
| valueSpan.textContent = slider.value; | |
| } | |
| // Stream toggle | |
| document.getElementById('streamMode').addEventListener('change', function() { | |
| document.getElementById('streamLabel').textContent = this.checked ? 'Açık' : 'Kapalı'; | |
| }); | |
| // Presets | |
| const presets = { | |
| creative: { temperature: 1.2, maxTokens: 200, topK: 80, topP: 0.95, repPenalty: 1.1 }, | |
| balanced: { temperature: 0.7, maxTokens: 150, topK: 50, topP: 0.9, repPenalty: 1.3 }, | |
| precise: { temperature: 0.3, maxTokens: 100, topK: 20, topP: 0.8, repPenalty: 1.5 }, | |
| random: { temperature: 1.8, maxTokens: 250, topK: 100, topP: 1.0, repPenalty: 1.0 } | |
| }; | |
| function applyPreset(presetName) { | |
| const preset = presets[presetName]; | |
| document.getElementById('temperature').value = preset.temperature; | |
| document.getElementById('tempValue').textContent = preset.temperature; | |
| document.getElementById('maxTokens').value = preset.maxTokens; | |
| document.getElementById('maxTokensValue').textContent = preset.maxTokens; | |
| document.getElementById('topK').value = preset.topK; | |
| document.getElementById('topKValue').textContent = preset.topK; | |
| document.getElementById('topP').value = preset.topP; | |
| document.getElementById('topPValue').textContent = preset.topP; | |
| document.getElementById('repPenalty').value = preset.repPenalty; | |
| document.getElementById('repPenaltyValue').textContent = preset.repPenalty; | |
| // Update active button | |
| document.querySelectorAll('.preset-btn').forEach(btn => btn.classList.remove('active')); | |
| event.target.classList.add('active'); | |
| } | |
| // Stop generation | |
| function stopGeneration() { | |
| if (eventSource) { | |
| eventSource.close(); | |
| eventSource = null; | |
| } | |
| isGenerating = false; | |
| document.getElementById('submitBtn').disabled = false; | |
| document.getElementById('stopBtn').classList.remove('active'); | |
| document.getElementById('loading').classList.remove('active'); | |
| // Remove cursor | |
| const cursor = document.querySelector('.cursor-blink'); | |
| if (cursor) cursor.remove(); | |
| } | |
| // Main generate function | |
| async function generate() { | |
| const prompt = document.getElementById("prompt").value.trim(); | |
| const model = document.getElementById("model").value; | |
| const streamMode = document.getElementById("streamMode").checked; | |
| const settings = { | |
| temperature: parseFloat(document.getElementById("temperature").value), | |
| max_tokens: parseInt(document.getElementById("maxTokens").value), | |
| top_k: parseInt(document.getElementById("topK").value), | |
| top_p: parseFloat(document.getElementById("topP").value), | |
| repetition_penalty: parseFloat(document.getElementById("repPenalty").value) | |
| }; | |
| const responseBox = document.getElementById("responseBox"); | |
| const loading = document.getElementById("loading"); | |
| const errorBox = document.getElementById("errorBox"); | |
| const submitBtn = document.getElementById("submitBtn"); | |
| const stopBtn = document.getElementById("stopBtn"); | |
| const streamBadge = document.getElementById("streamBadge"); | |
| if (!prompt) { | |
| errorBox.textContent = "Lütfen bir mesaj girin!"; | |
| errorBox.style.display = "block"; | |
| responseBox.classList.remove("active"); | |
| return; | |
| } | |
| errorBox.style.display = "none"; | |
| responseBox.classList.remove("active"); | |
| submitBtn.disabled = true; | |
| isGenerating = true; | |
| startTime = Date.now(); | |
| document.getElementById("tokenCount").textContent = "0"; | |
| document.getElementById("genTime").textContent = "0"; | |
| if (streamMode) { | |
| // Stream mode | |
| loading.classList.add("active"); | |
| stopBtn.classList.add("active"); | |
| streamBadge.style.display = "inline-block"; | |
| const params = new URLSearchParams({ | |
| prompt: prompt, | |
| model: model, | |
| ...settings | |
| }); | |
| eventSource = new EventSource("/generate_stream?" + params.toString()); | |
| let fullResponse = ""; | |
| let tokenCount = 0; | |
| document.getElementById("modelBadge").textContent = model === "stable" ? "kayra-1 (Stable)" : "kayra-1-exp (Deneysel)"; | |
| document.getElementById("responseContent").innerHTML = | |
| "<strong>Soru:</strong> " + escapeHtml(prompt) + | |
| "\\n\\n<strong>Cevap:</strong>\\n<span id='streamOutput'></span><span class='cursor-blink'></span>"; | |
| responseBox.classList.add("active"); | |
| loading.classList.remove("active"); | |
| eventSource.onmessage = function(event) { | |
| const data = JSON.parse(event.data); | |
| if (data.token) { | |
| fullResponse += data.token; | |
| tokenCount++; | |
| document.getElementById("streamOutput").textContent = fullResponse; | |
| document.getElementById("tokenCount").textContent = tokenCount; | |
| document.getElementById("genTime").textContent = ((Date.now() - startTime) / 1000).toFixed(1); | |
| } | |
| if (data.done) { | |
| stopGeneration(); | |
| } | |
| if (data.error) { | |
| errorBox.textContent = "Hata: " + data.error; | |
| errorBox.style.display = "block"; | |
| stopGeneration(); | |
| } | |
| }; | |
| eventSource.onerror = function() { | |
| stopGeneration(); | |
| }; | |
| } else { | |
| // Non-stream mode | |
| loading.classList.add("active"); | |
| streamBadge.style.display = "none"; | |
| try { | |
| const response = await fetch("/generate", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ prompt, model, ...settings }) | |
| }); | |
| const data = await response.json(); | |
| if (data.error) { | |
| errorBox.textContent = "Hata: " + data.error; | |
| errorBox.style.display = "block"; | |
| } else { | |
| document.getElementById("modelBadge").textContent = data.model; | |
| document.getElementById("responseContent").innerHTML = | |
| "<strong>Soru:</strong> " + escapeHtml(data.prompt) + | |
| "\\n\\n<strong>Cevap:</strong>\\n" + escapeHtml(data.response); | |
| document.getElementById("tokenCount").textContent = data.token_count || "N/A"; | |
| document.getElementById("genTime").textContent = ((Date.now() - startTime) / 1000).toFixed(1); | |
| responseBox.classList.add("active"); | |
| } | |
| } catch (err) { | |
| errorBox.textContent = "Bağlantı hatası: " + err.message; | |
| errorBox.style.display = "block"; | |
| } finally { | |
| loading.classList.remove("active"); | |
| submitBtn.disabled = false; | |
| isGenerating = false; | |
| } | |
| } | |
| } | |
| function escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| document.getElementById("prompt").addEventListener("keydown", function(e) { | |
| if (e.ctrlKey && e.key === "Enter") { | |
| generate(); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| # ==================== BİLGİ SAYFASI HTML ==================== | |
| INFO_HTML = """ | |
| <!DOCTYPE html> | |
| <html lang="tr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Kayra - Model Bilgileri</title> | |
| <style>{{ styles }}</style> | |
| </head> | |
| <body> | |
| <nav class="navbar"> | |
| <h1>🤖 Kayra Türkçe Dil Modeli</h1> | |
| <div class="nav-links"> | |
| <a href="/">Test</a> | |
| <a href="/info" class="active">Bilgi</a> | |
| </div> | |
| </nav> | |
| <div class="container"> | |
| <!-- Genel Bakış --> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <h2>Kayra Nedir?</h2> | |
| <p>Sıfırdan Türkçe ile eğitilmiş deneysel GPT modelleri</p> | |
| </div> | |
| <p>Kayra, Türkçe dil işleme araştırmaları için geliştirilmiş, sıfırdan eğitilmiş | |
| küçük ölçekli GPT tabanlı dil modelleridir. Bu projede iki farklı model bulunmaktadır:</p> | |
| <div class="stats-grid"> | |
| <div class="stat-card"> | |
| <div class="stat-value">85M</div> | |
| <div class="stat-label">Parametre</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value">500K</div> | |
| <div class="stat-label">Eğitim Dokümanı</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value">42.7</div> | |
| <div class="stat-label">Validation PPL</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value">MIT</div> | |
| <div class="stat-label">Lisans</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Parametre Açıklamaları --> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <h2>⚙️ Parametre Açıklamaları</h2> | |
| </div> | |
| <table> | |
| <thead> | |
| <tr> | |
| <th>Parametre</th> | |
| <th>Aralık</th> | |
| <th>Varsayılan</th> | |
| <th>Açıklama</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td><strong>Temperature</strong></td> | |
| <td>0.1 - 2.0</td> | |
| <td>0.7</td> | |
| <td>Çıktının rastgeleliğini kontrol eder. Düşük değerler daha tutarlı, yüksek değerler daha yaratıcı sonuçlar üretir.</td> | |
| </tr> | |
| <tr> | |
| <td><strong>Max Tokens</strong></td> | |
| <td>10 - 512</td> | |
| <td>150</td> | |
| <td>Üretilecek maksimum token (kelime parçası) sayısı.</td> | |
| </tr> | |
| <tr> | |
| <td><strong>Top K</strong></td> | |
| <td>1 - 100</td> | |
| <td>50</td> | |
| <td>Her adımda sadece en olası K tokenden seçim yapılır. Düşük K daha odaklı, yüksek K daha çeşitli çıktı üretir.</td> | |
| </tr> | |
| <tr> | |
| <td><strong>Top P (Nucleus)</strong></td> | |
| <td>0.1 - 1.0</td> | |
| <td>0.9</td> | |
| <td>Kümülatif olasılığı P'yi geçen tokenlerden seçim yapılır. Temperature ile birlikte kullanılır.</td> | |
| </tr> | |
| <tr> | |
| <td><strong>Repetition Penalty</strong></td> | |
| <td>1.0 - 2.0</td> | |
| <td>1.3</td> | |
| <td>Tekrarlayan kelimeleri cezalandırır. Yüksek değerler tekrarı azaltır ama anlam bozulabilir.</td> | |
| </tr> | |
| <tr> | |
| <td><strong>Stream Mode</strong></td> | |
| <td>Açık/Kapalı</td> | |
| <td>Açık</td> | |
| <td>Açıkken cevap kelime kelime gösterilir, kapalıyken tamamı bir anda gösterilir.</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <!-- Preset Açıklamaları --> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <h2>🎛️ Hazır Ayarlar (Presets)</h2> | |
| </div> | |
| <div class="two-column"> | |
| <div class="setting-item"> | |
| <h4>🎨 Yaratıcı</h4> | |
| <p>Yüksek temperature ve top-k ile daha özgün ve beklenmedik çıktılar üretir. Hikaye yazımı, beyin fırtınası için ideal.</p> | |
| <code>temp=1.2, top_k=80, top_p=0.95, rep=1.1</code> | |
| </div> | |
| <div class="setting-item"> | |
| <h4>⚖️ Dengeli</h4> | |
| <p>Varsayılan ayarlar. Çoğu kullanım senaryosu için uygundur.</p> | |
| <code>temp=0.7, top_k=50, top_p=0.9, rep=1.3</code> | |
| </div> | |
| <div class="setting-item"> | |
| <h4>🎯 Kesin</h4> | |
| <p>Düşük temperature ile daha tutarlı ve tahmin edilebilir çıktılar. Bilgi gerektiren sorular için.</p> | |
| <code>temp=0.3, top_k=20, top_p=0.8, rep=1.5</code> | |
| </div> | |
| <div class="setting-item"> | |
| <h4>🎲 Rastgele</h4> | |
| <p>Maksimum rastgelelik. Çılgın ve beklenmedik sonuçlar için. Dikkatli kullanın!</p> | |
| <code>temp=1.8, top_k=100, top_p=1.0, rep=1.0</code> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Model Karşılaştırması --> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <h2>Model Karşılaştırması</h2> | |
| </div> | |
| <table> | |
| <thead> | |
| <tr> | |
| <th>Özellik</th> | |
| <th>kayra-1 (Stable)</th> | |
| <th>kayra-1-exp (Deneysel)</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td>Tür</td> | |
| <td>Instruction-tuned</td> | |
| <td>Sadece Pretrained</td> | |
| </tr> | |
| <tr> | |
| <td>Kullanım</td> | |
| <td>Soru-Cevap formatında</td> | |
| <td>Metin tamamlama</td> | |
| </tr> | |
| <tr> | |
| <td>Stabilite</td> | |
| <td><span class="badge badge-success">Stabil</span></td> | |
| <td><span class="badge badge-warning">Deneysel</span></td> | |
| </tr> | |
| <tr> | |
| <td>Prompt Formatı</td> | |
| <td>### Soru: ... ### Cevap:</td> | |
| <td>Düz metin</td> | |
| </tr> | |
| <tr> | |
| <td>Önerilen Kullanım</td> | |
| <td>Genel test ve demo</td> | |
| <td>Araştırma ve analiz</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <!-- Mimari Detaylar --> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <h2>Teknik Mimari</h2> | |
| </div> | |
| <table> | |
| <tbody> | |
| <tr><td><strong>Model Türü</strong></td><td>Decoder-only Transformer (GPT-style)</td></tr> | |
| <tr><td><strong>Katman Sayısı</strong></td><td>10</td></tr> | |
| <tr><td><strong>Hidden Size</strong></td><td>640</td></tr> | |
| <tr><td><strong>Attention Heads</strong></td><td>10</td></tr> | |
| <tr><td><strong>FFN Size</strong></td><td>2560</td></tr> | |
| <tr><td><strong>Vocabulary</strong></td><td>32,000 BPE tokens</td></tr> | |
| <tr><td><strong>Context Length</strong></td><td>512 tokens</td></tr> | |
| <tr><td><strong>Toplam Parametre</strong></td><td>~85 milyon</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <!-- Eğitim Verisi --> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <h2>Eğitim Verisi</h2> | |
| </div> | |
| <table> | |
| <thead> | |
| <tr> | |
| <th>Kaynak</th> | |
| <th>Doküman Sayısı</th> | |
| <th>Açıklama</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td>Wikipedia TR</td> | |
| <td>~170,000</td> | |
| <td>Türkçe Vikipedi makaleleri</td> | |
| </tr> | |
| <tr> | |
| <td>mC4 Turkish</td> | |
| <td>~330,000</td> | |
| <td>Common Crawl web dokümanları</td> | |
| </tr> | |
| <tr> | |
| <td><strong>Toplam</strong></td> | |
| <td><strong>~500,000</strong></td> | |
| <td>MinHash LSH ile dedupe edilmiş</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <!-- Kullanım Örneği --> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <h2>Kod Örneği</h2> | |
| </div> | |
| <div class="code-block"> | |
| <pre>from transformers import AutoModelForCausalLM, AutoTokenizer | |
| model = AutoModelForCausalLM.from_pretrained( | |
| "sixfingerdev/kayra-1-exp", | |
| trust_remote_code=True # ONEMLI! | |
| ) | |
| tokenizer = AutoTokenizer.from_pretrained("sixfingerdev/kayra-1-exp") | |
| prompt = "Türkiye'nin başkenti" | |
| inputs = tokenizer(prompt, return_tensors="pt") | |
| outputs = model.generate( | |
| inputs.input_ids, | |
| max_new_tokens=100, | |
| temperature=0.8, | |
| top_k=50, | |
| top_p=0.9, | |
| repetition_penalty=1.2, | |
| do_sample=True | |
| ) | |
| print(tokenizer.decode(outputs[0], skip_special_tokens=True))</pre> | |
| </div> | |
| </div> | |
| <!-- Lisans ve Atıf --> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <h2>Lisans ve Atıf</h2> | |
| </div> | |
| <p><strong>Lisans:</strong> MIT License - Ticari ve akademik kullanım serbesttir.</p> | |
| <div class="code-block"> | |
| <pre>@misc{kayra2024hallucination, | |
| title={Why Small Turkish GPTs Hallucinate Facts: An Experimental 85M Model}, | |
| author={sixfingerdev}, | |
| year={2024}, | |
| publisher={HuggingFace}, | |
| howpublished={\\url{https://huggingface.co/sixfingerdev/kayra-1-exp}}, | |
| note={Research on loss-factuality divergence in low-resource language models} | |
| }</pre> | |
| </div> | |
| <div class="highlight" style="margin-top: 1rem;"> | |
| <strong>Uyarı:</strong> Bu model bilerek kusurlarıyla birlikte paylaşılmıştır. | |
| Küçük LM'lerin neden hallucination yaptığını gösteren bir öğrenme kaynağı olarak | |
| hizmet eder, production aracı olarak değil. | |
| </div> | |
| </div> | |
| </div> | |
| <div class="footer"> | |
| <p>Kayra - Türkçe'yi Yaratan Zeka</p> | |
| <p><a href="https://huggingface.co/sixfingerdev" target="_blank">Hugging Face</a></p> | |
| </div> | |
| </body> | |
| </html> | |
| """ | |
| # ==================== MODEL YÜKLEME ==================== | |
| print("Modeller yükleniyor... Bu biraz sürebilir (özellikle ilk seferde).") | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| model_stable = AutoModelForCausalLM.from_pretrained( | |
| "sixfingerdev/kayra-1", | |
| trust_remote_code=True, | |
| torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32 | |
| ).to(device) | |
| tokenizer_stable = AutoTokenizer.from_pretrained("sixfingerdev/kayra-1") | |
| model_exp = AutoModelForCausalLM.from_pretrained( | |
| "sixfingerdev/kayra-1-exp", | |
| trust_remote_code=True, | |
| torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32 | |
| ).to(device) | |
| tokenizer_exp = AutoTokenizer.from_pretrained("sixfingerdev/kayra-1-exp") | |
| print("Modeller başarıyla yüklendi!") | |
| # ==================== YARDIMCI FONKSİYONLAR ==================== | |
| def generate_response(model, tokenizer, prompt, max_new_tokens=150, temperature=0.7, | |
| top_k=50, top_p=0.9, repetition_penalty=1.3): | |
| if model == model_stable: | |
| formatted_prompt = f"### Soru: {prompt}\n\n### Cevap:" | |
| else: | |
| formatted_prompt = prompt | |
| inputs = tokenizer(formatted_prompt, return_tensors="pt") | |
| inputs = inputs.to(model.device) | |
| if "token_type_ids" in inputs: | |
| inputs.pop("token_type_ids") | |
| with torch.no_grad(): | |
| outputs = model.generate( | |
| **inputs, | |
| max_new_tokens=max_new_tokens, | |
| temperature=temperature, | |
| do_sample=True, | |
| top_k=top_k, | |
| top_p=top_p, | |
| repetition_penalty=repetition_penalty, | |
| pad_token_id=tokenizer.eos_token_id | |
| ) | |
| response = tokenizer.decode(outputs[0], skip_special_tokens=True) | |
| token_count = outputs.shape[1] - inputs['input_ids'].shape[1] | |
| if model == model_stable: | |
| if "### Cevap:" in response: | |
| response = response.split("### Cevap:")[-1].strip() | |
| return response, token_count | |
| def generate_stream(model, tokenizer, prompt, max_new_tokens=150, temperature=0.7, | |
| top_k=50, top_p=0.9, repetition_penalty=1.3): | |
| """Generator function for streaming responses""" | |
| if model == model_stable: | |
| formatted_prompt = f"### Soru: {prompt}\n\n### Cevap:" | |
| else: | |
| formatted_prompt = prompt | |
| inputs = tokenizer(formatted_prompt, return_tensors="pt") | |
| inputs = inputs.to(model.device) | |
| if "token_type_ids" in inputs: | |
| inputs.pop("token_type_ids") | |
| streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True) | |
| generation_kwargs = dict( | |
| **inputs, | |
| max_new_tokens=max_new_tokens, | |
| temperature=temperature, | |
| do_sample=True, | |
| top_k=top_k, | |
| top_p=top_p, | |
| repetition_penalty=repetition_penalty, | |
| pad_token_id=tokenizer.eos_token_id, | |
| streamer=streamer | |
| ) | |
| thread = Thread(target=model.generate, kwargs=generation_kwargs) | |
| thread.start() | |
| for text in streamer: | |
| yield text | |
| thread.join() | |
| # ==================== ROUTE'LAR ==================== | |
| def index(): | |
| return render_template_string(INDEX_HTML, styles=STYLES) | |
| def info(): | |
| return render_template_string(INFO_HTML, styles=STYLES) | |
| def generate(): | |
| data = request.json | |
| prompt = data.get("prompt", "").strip() | |
| model_choice = data.get("model", "stable") | |
| # Get settings with defaults | |
| max_tokens = data.get("max_tokens", 150) | |
| temperature = data.get("temperature", 0.7) | |
| top_k = data.get("top_k", 50) | |
| top_p = data.get("top_p", 0.9) | |
| repetition_penalty = data.get("repetition_penalty", 1.3) | |
| if not prompt: | |
| return jsonify({"error": "Lütfen bir soru veya mesaj girin."}) | |
| if model_choice == "stable": | |
| response, token_count = generate_response( | |
| model_stable, tokenizer_stable, prompt, | |
| max_new_tokens=max_tokens, | |
| temperature=temperature, | |
| top_k=top_k, | |
| top_p=top_p, | |
| repetition_penalty=repetition_penalty | |
| ) | |
| model_name = "kayra-1 (Stable)" | |
| else: | |
| response, token_count = generate_response( | |
| model_exp, tokenizer_exp, prompt, | |
| max_new_tokens=max_tokens, | |
| temperature=temperature, | |
| top_k=top_k, | |
| top_p=top_p, | |
| repetition_penalty=repetition_penalty | |
| ) | |
| model_name = "kayra-1-exp (Deneysel)" | |
| return jsonify({ | |
| "model": model_name, | |
| "prompt": prompt, | |
| "response": response, | |
| "token_count": token_count | |
| }) | |
| def generate_stream_route(): | |
| prompt = request.args.get("prompt", "").strip() | |
| model_choice = request.args.get("model", "stable") | |
| # Get settings with defaults | |
| max_tokens = int(request.args.get("max_tokens", 150)) | |
| temperature = float(request.args.get("temperature", 0.7)) | |
| top_k = int(request.args.get("top_k", 50)) | |
| top_p = float(request.args.get("top_p", 0.9)) | |
| repetition_penalty = float(request.args.get("repetition_penalty", 1.3)) | |
| if not prompt: | |
| def error_gen(): | |
| yield f"data: {json.dumps({'error': 'Prompt gerekli'})}\n\n" | |
| return Response(error_gen(), mimetype="text/event-stream") | |
| if model_choice == "stable": | |
| model = model_stable | |
| tokenizer = tokenizer_stable | |
| else: | |
| model = model_exp | |
| tokenizer = tokenizer_exp | |
| def event_stream(): | |
| try: | |
| for token in generate_stream( | |
| model, tokenizer, prompt, | |
| max_new_tokens=max_tokens, | |
| temperature=temperature, | |
| top_k=top_k, | |
| top_p=top_p, | |
| repetition_penalty=repetition_penalty | |
| ): | |
| yield f"data: {json.dumps({'token': token})}\n\n" | |
| yield f"data: {json.dumps({'done': True})}\n\n" | |
| except Exception as e: | |
| yield f"data: {json.dumps({'error': str(e)})}\n\n" | |
| return Response( | |
| event_stream(), | |
| mimetype="text/event-stream", | |
| headers={ | |
| "Cache-Control": "no-cache", | |
| "Connection": "keep-alive", | |
| "X-Accel-Buffering": "no" | |
| } | |
| ) | |
| # ==================== UYGULAMA BAŞLATMA ==================== | |
| if __name__ == "__main__": | |
| app.run(host="0.0.0.0", port=7860, debug=False, threaded=True) |