Spaces:
Sleeping
Sleeping
| import os | |
| import time | |
| from typing import List, Optional, Dict, Any | |
| import re | |
| import asyncio | |
| from contextlib import asynccontextmanager | |
| from fastapi import FastAPI, Request | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import JSONResponse, HTMLResponse | |
| from pydantic import BaseModel | |
| from huggingface_hub import InferenceClient | |
| # =============================== | |
| # LIFECYCLE MANAGEMENT | |
| # =============================== | |
| async def lifespan(app: FastAPI): | |
| # Startup | |
| print("🚀 ZenkaMind API başlatılıyor...") | |
| print(f"📊 Model: Qwen/Qwen2.5-0.5B-Instruct") | |
| print(f"🔧 Ortam: {os.getenv('SPACE_ID', 'development')}") | |
| yield | |
| # Shutdown | |
| print("👋 ZenkaMind API kapatılıyor...") | |
| # =============================== | |
| # MODEL | |
| # =============================== | |
| MODEL_ID = "Qwen/Qwen2.5-0.5B-Instruct" | |
| HF_TOKEN = os.getenv("HF_TOKEN", os.getenv("HUGGINGFACE_TOKEN")) | |
| # =============================== | |
| # APP | |
| # =============================== | |
| app = FastAPI( | |
| title="ZenkaMind API", | |
| version="2.0", | |
| description="Türkçe AI Asistan - Çeşitli modlarla sohbet", | |
| lifespan=lifespan | |
| ) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # =============================== | |
| # INITIALIZE CLIENT | |
| # =============================== | |
| try: | |
| client = InferenceClient(model=MODEL_ID, token=HF_TOKEN) | |
| print(f"✅ Model client başarıyla oluşturuldu") | |
| except Exception as e: | |
| print(f"⚠️ Model client oluşturulamadı: {e}") | |
| client = None | |
| # =============================== | |
| # AYARLAR | |
| # =============================== | |
| FREE_MESSAGE_LIMIT = 50 | |
| usage_counts: Dict[str, int] = {} | |
| # =============================== | |
| # GÜÇLENDİRİLMİŞ PROMPTLAR | |
| # =============================== | |
| def clean_response(text: str) -> str: | |
| """Cevabı temizle""" | |
| if not text: | |
| return "" | |
| text = re.sub(r'\s+', ' ', text) | |
| text = re.sub(r'[\u3400-\u9FFF\uF900-\uFAFF]', '', text) | |
| text = re.sub(r'\.{3,}', '...', text) | |
| return text.strip() | |
| BASE_RULES = """ | |
| Sen ZenkaMind adlı bir Türkçe yapay zekâ asistanısın. Kurallar: | |
| 1. SADECE ve SADECE Türkçe konuş | |
| 2. Kısa, net, öz ve doğrudan cevap ver | |
| 3. Kendini tekrar ETME | |
| 4. "Kullanıcı:" veya "Asistan:" EKLEME | |
| 5. Tek bir cevap üret, uzatma | |
| 6. Emojilerden KAÇIN | |
| 7. Kendini tanıtma, direk cevaba geç | |
| 8. Formatlama kullanma (**, __, vs.) | |
| 9. Cevabı 2-3 cümle ile sınırla | |
| 10. Samimi ama profesyonel ol | |
| """ | |
| PROMPTS = { | |
| "agir_abi": BASE_RULES + """ | |
| Üslup: Ağır abi tavrıyla, hayat tecrübesi olan, tok sesli, otoriter ama dostane. | |
| Özellikler: | |
| - "Kardeşim", "Bak", "Dinle" gibi hitap şekilleri kullan | |
| - Kısa ve vurucu cümleler | |
| - Hayat dersi verir gibi konuş | |
| - Örneklerle açıkla | |
| - Sert ama mantıklı | |
| Örnek: "Bak kardeşim, bu işin püf noktası şu..." | |
| """, | |
| "kurumsal": BASE_RULES + """ | |
| Üslup: Profesyonel, resmi, iş dünyasına uygun, maddeli. | |
| Özellikler: | |
| - "Sayın kullanıcı" veya direkt konuya gir | |
| - Maddeler halinde açıkla (1., 2., 3.) | |
| - Rakamsal veriler kullan | |
| - SWOT analizi gibi yapılandırılmış | |
| - Sonuç odaklı | |
| Örnek: "Bu konuda 3 ana strateji önerebilirim: 1. Pazarlama, 2. Finans, 3. Operasyon..." | |
| """, | |
| "esnaf": BASE_RULES + """ | |
| Üslup: Mahalle esnafı gibi samimi, sıcak, içten, pratik çözümlü. | |
| Özellikler: | |
| - "Kardeş", "Canım", "Birader" gibi samimi hitap | |
| - Günlük hayattan örnekler | |
| - Pratik çözümler öner | |
| - Espri katabilirsin | |
| - Anlaşılır ve basit dil | |
| Örnek: "Bak birader, şöyle yaparsan daha iyi olur..." | |
| """, | |
| "tech": BASE_RULES + """ | |
| Üslup: Teknik, kod odaklı, detaylı, öğretici. | |
| Özellikler: | |
| - Teknik terimler kullan | |
| - Kod örnekleri ver (Python/JavaScript) | |
| - Adım adım açıkla | |
| - Best practices anlat | |
| - Hata çözümleri öner | |
| Örnek: "Bu sorunu şu kodla çözebilirsin: `def solution():` ..." | |
| """, | |
| "freelance": BASE_RULES + """ | |
| Üslup: Serbest çalışan, proje bazlı, müşteri odaklı, pazarlıkçı, çok yönlü. | |
| Özellikler: | |
| - Proje yönetimi tavsiyeleri ver | |
| - Fiyatlandırma stratejileri öner | |
| - Müşteri ilişkileri ipuçları | |
| - Portfolio geliştirme | |
| - Networking stratejileri | |
| - Sözleşme önerileri | |
| - Vergi ve faturalama | |
| - Zaman yönetimi | |
| - Teklif hazırlama | |
| Örnek konular: | |
| * "Upwork'te nasıl daha fazla proje alırım?" | |
| * "Projeme nasıl fiyat biçmeliyim?" | |
| * "Zor müşteriyle nasıl başa çıkılır?" | |
| * "Freelance vergi nasıl hesaplanır?" | |
| * "Portfolio'mu nasıl güçlendiririm?" | |
| Tavır: Pratik, gerçekçi, deneyim paylaşımlı, çözüm odaklı. | |
| """ | |
| } | |
| # =============================== | |
| # MODELLER | |
| # =============================== | |
| class ChatRequest(BaseModel): | |
| message: str | |
| history: Optional[List[List[str]]] = [] | |
| mode: Optional[str] = "agir_abi" | |
| user_id: Optional[str] = "test-user" | |
| # =============================== | |
| # FREELANCE ÖZEL FONKSİYONLAR | |
| # =============================== | |
| def enhance_freelance_response(message: str, response: str) -> str: | |
| message_lower = message.lower() | |
| enhancements = [] | |
| if any(word in message_lower for word in ["fiyat", "ücret", "price", "fee", "ne kadar"]): | |
| enhancements.append("💰 İpucu: Fiyat verirken saatlik, günlük veya proje bazlı seçenekler sun.") | |
| if any(word in message_lower for word in ["müşteri", "client", "customer", "zor müşteri"]): | |
| enhancements.append("🤝 İpucu: Zor müşteriler için net sınırlar belirle ve her şeyi yazılı hale getir.") | |
| if any(word in message_lower for word in ["sözleşme", "contract", "anlaşma"]): | |
| enhancements.append("📄 İpucu: Sözleşmede teslim tarihlerini, revizyon sayısını ve ödeme planını net belirt.") | |
| if any(word in message_lower for word in ["vergi", "tax", "fatura", "invoice"]): | |
| enhancements.append("🧾 İpucu: E-fatura kullan ve tüm harcamalarını kaydet, aylık vergi ayır.") | |
| if any(word in message_lower for word in ["portfolio", "portföy", "cv", "özgüven"]): | |
| enhancements.append("🎯 İpucu: Portföyünde süreçleri de göster (başlangıç-bitiş), müşteri yorumları ekle.") | |
| if any(word in message_lower for word in ["time", "zaman", "deadline", "teslim"]): | |
| enhancements.append("⏰ İpucu: Gerçekçi zaman planlaması yap, müşteriye buffer süre ekle.") | |
| if any(word in message_lower for word in ["upwork", "fiverr", "bionluk", "platform"]): | |
| enhancements.append("🚀 İpucu: Platformlarda ilk işler için rekabetçi fiyat ver, tamamlanan iş sayısını artır.") | |
| if enhancements: | |
| response += "\n\n" + "\n".join(enhancements) | |
| return response | |
| # =============================== | |
| # HTML ARAYÜZ | |
| # =============================== | |
| HTML_TEMPLATE = """ | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>ZenkaMind API</title> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| padding: 20px; | |
| color: #333; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| } | |
| .header { | |
| text-align: center; | |
| margin-bottom: 40px; | |
| color: white; | |
| } | |
| .header h1 { | |
| font-size: 3em; | |
| margin-bottom: 10px; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.2); | |
| } | |
| .header p { | |
| font-size: 1.2em; | |
| opacity: 0.9; | |
| } | |
| .card { | |
| background: white; | |
| border-radius: 20px; | |
| padding: 30px; | |
| margin-bottom: 30px; | |
| box-shadow: 0 20px 60px rgba(0,0,0,0.1); | |
| } | |
| .card h2 { | |
| color: #667eea; | |
| margin-bottom: 20px; | |
| font-size: 1.8em; | |
| } | |
| .api-info { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 20px; | |
| margin-top: 20px; | |
| } | |
| .info-box { | |
| background: #f8f9fa; | |
| padding: 20px; | |
| border-radius: 15px; | |
| border-left: 5px solid #667eea; | |
| } | |
| .info-box h3 { | |
| color: #555; | |
| margin-bottom: 10px; | |
| font-size: 1.1em; | |
| } | |
| .info-box p { | |
| font-size: 1.4em; | |
| font-weight: bold; | |
| color: #333; | |
| } | |
| .endpoints { | |
| margin-top: 30px; | |
| } | |
| .endpoint { | |
| background: #f8f9fa; | |
| padding: 15px; | |
| border-radius: 10px; | |
| margin-bottom: 15px; | |
| border-left: 5px solid #764ba2; | |
| } | |
| .method { | |
| display: inline-block; | |
| background: #667eea; | |
| color: white; | |
| padding: 5px 15px; | |
| border-radius: 20px; | |
| font-weight: bold; | |
| margin-right: 10px; | |
| } | |
| .url { | |
| font-family: monospace; | |
| color: #555; | |
| font-size: 1.1em; | |
| } | |
| .modes { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 15px; | |
| margin-top: 20px; | |
| } | |
| .mode-tag { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 10px 20px; | |
| border-radius: 25px; | |
| font-weight: bold; | |
| box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); | |
| } | |
| .footer { | |
| text-align: center; | |
| margin-top: 40px; | |
| color: white; | |
| opacity: 0.8; | |
| } | |
| .status-badge { | |
| display: inline-block; | |
| padding: 8px 16px; | |
| background: #4CAF50; | |
| color: white; | |
| border-radius: 20px; | |
| font-weight: bold; | |
| margin-left: 10px; | |
| } | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 10px; | |
| } | |
| .header h1 { | |
| font-size: 2em; | |
| } | |
| .card { | |
| padding: 20px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>🤖 ZenkaMind API</h1> | |
| <p>Türkçe Yapay Zeka Asistanı • Çoklu Mod Desteği</p> | |
| <div style="margin-top: 20px;"> | |
| <span class="status-badge">🚀 Çalışıyor</span> | |
| <span class="status-badge">🔧 v2.0</span> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>📊 API Bilgileri</h2> | |
| <div class="api-info"> | |
| <div class="info-box"> | |
| <h3>Model</h3> | |
| <p>Qwen2.5-0.5B-Instruct</p> | |
| </div> | |
| <div class="info-box"> | |
| <h3>Ücretsiz Mesaj</h3> | |
| <p>50 mesaj / kullanıcı</p> | |
| </div> | |
| <div class="info-box"> | |
| <h3>Mevcut Modlar</h3> | |
| <p>5 farklı kişilik</p> | |
| </div> | |
| <div class="info-box"> | |
| <h3>Çalışma Ortamı</h3> | |
| <p>Hugging Face Spaces</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>🎭 Kullanılabilir Modlar</h2> | |
| <div class="modes"> | |
| <div class="mode-tag">Ağır Abi</div> | |
| <div class="mode-tag">Kurumsal</div> | |
| <div class="mode-tag">Esnaf</div> | |
| <div class="mode-tag">Teknik</div> | |
| <div class="mode-tag">Freelance</div> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>🔌 API Endpoint'leri</h2> | |
| <div class="endpoints"> | |
| <div class="endpoint"> | |
| <span class="method">GET</span> | |
| <span class="url">/docs</span> | |
| <p style="margin-top: 10px; color: #666;">Interactive API Documentation</p> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method">POST</span> | |
| <span class="url">/api/chat</span> | |
| <p style="margin-top: 10px; color: #666;">Ana sohbet endpoint'i</p> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method">GET</span> | |
| <span class="url">/modes</span> | |
| <p style="margin-top: 10px; color: #666;">Mevcut modları listele</p> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method">GET</span> | |
| <span class="url">/health</span> | |
| <p style="margin-top: 10px; color: #666;">Sağlık kontrolü</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>🚀 Hızlı Başlangıç</h2> | |
| <pre style="background: #f8f9fa; padding: 20px; border-radius: 10px; overflow-x: auto; margin-top: 20px; font-family: monospace;"> | |
| # Örnek cURL isteği | |
| curl -X POST "https://your-space.hf.space/api/chat" \\ | |
| -H "Content-Type: application/json" \\ | |
| -d '{ | |
| "message": "Merhaba, nasılsın?", | |
| "mode": "agir_abi", | |
| "user_id": "test-123" | |
| }' | |
| # Python örneği | |
| import requests | |
| response = requests.post("https://your-space.hf.space/api/chat", json={ | |
| "message": "Freelance iş nasıl bulunur?", | |
| "mode": "freelance", | |
| "user_id": "user-456" | |
| }) | |
| print(response.json())</pre> | |
| </div> | |
| <div class="footer"> | |
| <p>© 2024 ZenkaMind API • Hugging Face Spaces • FastAPI</p> | |
| <p style="margin-top: 10px; font-size: 0.9em;"> | |
| Model: Qwen/Qwen2.5-0.5B-Instruct • | |
| <a href="/docs" style="color: white; text-decoration: underline;">API Docs</a> • | |
| <a href="https://huggingface.co/spaces" style="color: white; text-decoration: underline;">Hugging Face</a> | |
| </p> | |
| </div> | |
| </div> | |
| </body> | |
| </html> | |
| """ | |
| # =============================== | |
| # ENDPOINTLER | |
| # =============================== | |
| async def root(): | |
| """Ana sayfa - HTML arayüz""" | |
| return HTML_TEMPLATE | |
| async def health_check(): | |
| """Sağlık kontrol endpoint'i""" | |
| return { | |
| "status": "healthy", | |
| "timestamp": time.time(), | |
| "service": "ZenkaMind API", | |
| "version": "2.0", | |
| "model": MODEL_ID if client else None, | |
| "environment": os.getenv("SPACE_ID", "development") | |
| } | |
| async def get_modes(): | |
| """Mevcut modları listele""" | |
| modes_info = { | |
| "agir_abi": { | |
| "name": "Ağır Abi", | |
| "description": "Hayat tecrübesi olan, otoriter ama dostane tavır", | |
| "icon": "👨🏫" | |
| }, | |
| "kurumsal": { | |
| "name": "Kurumsal", | |
| "description": "Profesyonel, iş dünyasına uygun, maddeli açıklamalar", | |
| "icon": "💼" | |
| }, | |
| "esnaf": { | |
| "name": "Esnaf", | |
| "description": "Samimi, pratik çözümlü, mahalle esnafı tavrı", | |
| "icon": "👨🍳" | |
| }, | |
| "tech": { | |
| "name": "Teknik", | |
| "description": "Kod odaklı, teknik detaylı, öğretici yaklaşım", | |
| "icon": "💻" | |
| }, | |
| "freelance": { | |
| "name": "Freelance", | |
| "description": "Serbest çalışan, proje bazlı, pazarlıkçı tavır", | |
| "icon": "🚀" | |
| } | |
| } | |
| return {"modes": modes_info} | |
| async def chat(body: ChatRequest): | |
| """Ana sohbet endpoint'i""" | |
| if not client: | |
| return JSONResponse({ | |
| "response": "Model şu anda kullanılamıyor. Lütfen daha sonra tekrar deneyin.", | |
| "error": "Model client not initialized", | |
| "used": 0, | |
| "remaining": FREE_MESSAGE_LIMIT, | |
| "mode": body.mode | |
| }, status_code=503) | |
| user_key = body.user_id or "anon" | |
| count = usage_counts.get(user_key, 0) | |
| if count >= FREE_MESSAGE_LIMIT: | |
| return JSONResponse({ | |
| "response": "Ücretsiz test hakkınız doldu. Premium için iletişime geçin.", | |
| "used": count, | |
| "limit": FREE_MESSAGE_LIMIT, | |
| "premium_required": True | |
| }) | |
| mode = body.mode if body.mode in PROMPTS else "agir_abi" | |
| system_prompt = PROMPTS[mode] | |
| messages: List[Dict[str, str]] = [ | |
| {"role": "system", "content": system_prompt} | |
| ] | |
| for pair in body.history or []: | |
| if len(pair) == 2: | |
| messages.append({"role": "user", "content": str(pair[0])}) | |
| messages.append({"role": "assistant", "content": str(pair[1])}) | |
| messages.append({"role": "user", "content": body.message.strip()}) | |
| try: | |
| print(f"📨 İstek alındı: {user_key}, mod: {mode}, mesaj: {body.message[:50]}...") | |
| result = client.chat_completion( | |
| messages=messages, | |
| max_tokens=400, | |
| temperature=0.7, | |
| top_p=0.9 | |
| ) | |
| answer = result.choices[0].message.content.strip() | |
| answer = clean_response(answer) | |
| if mode == "freelance": | |
| answer = enhance_freelance_response(body.message, answer) | |
| usage_counts[user_key] = count + 1 | |
| remaining = FREE_MESSAGE_LIMIT - usage_counts[user_key] | |
| print(f"✅ Cevap gönderildi: {user_key}, kalan: {remaining}") | |
| return { | |
| "response": answer, | |
| "used": usage_counts[user_key], | |
| "remaining": remaining, | |
| "mode": mode, | |
| "premium": False if remaining > 0 else True, | |
| "user_id": user_key, | |
| "timestamp": time.time() | |
| } | |
| except Exception as e: | |
| error_msg = str(e) | |
| print(f"❌ Hata: {error_msg}") | |
| if "rate limit" in error_msg.lower(): | |
| fallback_response = "Şu anda yoğunluk var. Lütfen 1 dakika sonra tekrar deneyin." | |
| elif "token" in error_msg.lower(): | |
| fallback_response = "API token hatası. Lütfen yönetici ile iletişime geçin." | |
| elif "timeout" in error_msg.lower(): | |
| fallback_response = "İstek zaman aşımına uğradı. Lütfen tekrar deneyin." | |
| else: | |
| fallback_response = "Teknik bir sorun oluştu. Lütfen tekrar deneyin." | |
| return { | |
| "response": fallback_response, | |
| "error": error_msg[:100], | |
| "used": count, | |
| "remaining": FREE_MESSAGE_LIMIT - count, | |
| "mode": mode, | |
| "timestamp": time.time() | |
| } | |
| async def get_usage(user_id: str): | |
| count = usage_counts.get(user_id, 0) | |
| remaining = FREE_MESSAGE_LIMIT - count | |
| return { | |
| "user_id": user_id, | |
| "used": count, | |
| "remaining": remaining, | |
| "limit": FREE_MESSAGE_LIMIT, | |
| "percentage": (count / FREE_MESSAGE_LIMIT) * 100 if FREE_MESSAGE_LIMIT > 0 else 0, | |
| "timestamp": time.time() | |
| } | |
| async def get_stats(): | |
| total_users = len(usage_counts) | |
| total_messages = sum(usage_counts.values()) | |
| return { | |
| "total_users": total_users, | |
| "total_messages": total_messages, | |
| "active_users": sum(1 for v in usage_counts.values() if v > 0), | |
| "average_messages": total_messages / total_users if total_users > 0 else 0, | |
| "timestamp": time.time() | |
| } | |
| async def reset_usage(user_id: str): | |
| if user_id in usage_counts: | |
| old_count = usage_counts[user_id] | |
| usage_counts[user_id] = 0 | |
| return { | |
| "status": "success", | |
| "message": f"{user_id} kullanımı sıfırlandı (eski: {old_count})", | |
| "timestamp": time.time() | |
| } | |
| return { | |
| "status": "error", | |
| "message": "Kullanıcı bulunamadı", | |
| "timestamp": time.time() | |
| } | |
| # =============================== | |
| # SERVER STARTUP | |
| # =============================== | |
| if __name__ == "__main__": | |
| import uvicorn | |
| # Hugging Face Spaces genellikle 7860 portunu kullanır | |
| port = int(os.getenv("PORT", 7860)) | |
| print(f"🚀 ZenkaMind API {port} portunda başlatılıyor...") | |
| print(f"📊 Model: {MODEL_ID}") | |
| print(f"🔗 Docs: http://localhost:{port}/docs") | |
| print(f"🏠 Ana sayfa: http://localhost:{port}/") | |
| uvicorn.run( | |
| "app:app", | |
| host="0.0.0.0", | |
| port=port, | |
| reload=False # Production'da reload kapalı | |
| ) |