Spaces:
Paused
Paused
| from flask import Flask, request, jsonify | |
| import requests | |
| import json | |
| import time | |
| import random | |
| from typing import Dict, List, Optional | |
| from dataclasses import dataclass | |
| from datetime import datetime | |
| import sqlite3 | |
| import torch | |
| from transformers import BartTokenizer, BartForConditionalGeneration, Trainer, TrainingArguments | |
| import pandas as pd | |
| import os | |
| from peft import LoraConfig, get_peft_model | |
| from sklearn.metrics.pairwise import cosine_similarity | |
| from sklearn.feature_extraction.text import TfidfVectorizer | |
| app = Flask(__name__) | |
| os.environ["TRANSFORMERS_CACHE"] = "/app/cache" | |
| # Cache model and tokenizer globally | |
| tokenizer = BartTokenizer.from_pretrained('facebook/bart-base') | |
| model = BartForConditionalGeneration.from_pretrained('facebook/bart-base') | |
| device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') | |
| model.to(device) | |
| model.eval() | |
| lora_config = LoraConfig(r=8, lora_alpha=32, target_modules=["q_proj", "v_proj"], lora_dropout=0.1) | |
| model = get_peft_model(model, lora_config) | |
| class AnimeInfo: | |
| title: str | |
| synopsis: str | |
| genres: List[str] | |
| rating: float | |
| status: str | |
| episodes: int | |
| year: int | |
| studio: str | |
| source: str | |
| image_url: Optional[str] = None | |
| class CharacterInfo: | |
| name: str | |
| anime: str | |
| bio: str | |
| role: str | |
| voice_actors: Dict[str, str] | |
| image_url: Optional[str] = None | |
| class AnimeDatabase: | |
| def __init__(self): | |
| self.base_urls = { | |
| 'jikan': 'https://api.jikan.moe/v4', | |
| 'anilist': 'https://graphql.anilist.co' | |
| } | |
| self.last_request_time = 0 | |
| self.cache = {'anime': {}, 'character': {}} | |
| def rate_limit(self, delay=1.0): | |
| current_time = time.time() | |
| if current_time - self.last_request_time < delay: | |
| time.sleep(delay - (current_time - self.last_request_time)) | |
| self.last_request_time = time.time() | |
| def search_jikan_anime(self, query: str) -> List[AnimeInfo]: | |
| try: | |
| self.rate_limit() | |
| if query.lower() in self.cache['anime']: | |
| return self.cache['anime'][query.lower()] | |
| url = f"{self.base_urls['jikan']}/anime" | |
| params = {'q': query, 'limit': 1} | |
| response = requests.get(url, params=params, timeout=5) | |
| if response.status_code == 200: | |
| data = response.json().get('data', []) | |
| results = [AnimeInfo( | |
| title=anime.get('title', ''), | |
| synopsis=anime.get('synopsis', ''), | |
| genres=[g.get('name', '') for g in anime.get('genres', [])], | |
| rating=anime.get('score', 0.0), | |
| status=anime.get('status', ''), | |
| episodes=anime.get('episodes', 0), | |
| year=anime.get('year', 0), | |
| studio=', '.join(s.get('name', '') for s in anime.get('studios', [])), | |
| source='MyAnimeList', | |
| image_url=anime.get('images', {}).get('jpg', {}).get('image_url', '') | |
| ) for anime in data] | |
| self.cache['anime'][query.lower()] = results | |
| return results | |
| except Exception: | |
| return [] | |
| return [] | |
| def search_jikan_character(self, query: str) -> List[CharacterInfo]: | |
| try: | |
| self.rate_limit() | |
| if query.lower() in self.cache['character']: | |
| return self.cache['character'][query.lower()] | |
| url = f"{self.base_urls['jikan']}/characters" | |
| params = {'q': query, 'limit': 1} | |
| response = requests.get(url, params=params, timeout=5) | |
| if response.status_code == 200: | |
| data = response.json().get('data', []) | |
| results = [CharacterInfo( | |
| name=char.get('name', ''), | |
| anime=', '.join(a.get('anime', {}).get('title', '') for a in char.get('anime', [])[:1]), | |
| bio=char.get('about', 'No bio available'), | |
| role=char.get('role', 'Unknown'), | |
| voice_actors={va.get('language', ''): va.get('person', {}).get('name', '') for va in char.get('voice_actors', [])}, | |
| image_url=char.get('images', {}).get('jpg', {}).get('image_url', '') | |
| ) for char in data] | |
| self.cache['character'][query.lower()] = results | |
| return results | |
| except Exception: | |
| return [] | |
| return [] | |
| def search_anilist_anime(self, query: str) -> List[AnimeInfo]: | |
| try: | |
| self.rate_limit() | |
| if query.lower() in self.cache['anime']: | |
| return self.cache['anime'][query.lower()] | |
| graphql_query = ''' | |
| query ($search: String) { | |
| Page(page: 1, perPage: 1) { | |
| media(search: $search, type: ANIME) { | |
| title { romaji english } | |
| description | |
| genres | |
| averageScore | |
| status | |
| episodes | |
| seasonYear | |
| studios { nodes { name } } | |
| } | |
| } | |
| } | |
| ''' | |
| variables = {'search': query} | |
| response = requests.post(self.base_urls['anilist'], json={'query': graphql_query, 'variables': variables}, timeout=5) | |
| if response.status_code == 200: | |
| data = response.json().get('data', {}).get('Page', {}).get('media', []) | |
| results = [AnimeInfo( | |
| title=anime.get('title', {}).get('romaji', ''), | |
| synopsis=anime.get('description', ''), | |
| genres=anime.get('genres', []), | |
| rating=anime.get('averageScore', 0) / 10, | |
| status=anime.get('status', ''), | |
| episodes=anime.get('episodes', 0), | |
| year=anime.get('seasonYear', 0), | |
| studio=', '.join(s.get('name', '') for s in anime.get('studios', {}).get('nodes', [])), | |
| source='AniList' | |
| ) for anime in data] | |
| self.cache['anime'][query.lower()] = results | |
| return results | |
| except Exception: | |
| return [] | |
| return [] | |
| def get_comprehensive_data(self, query: str, data_type: str = 'anime') -> List[AnimeInfo] | List[CharacterInfo]: | |
| if data_type == 'anime': | |
| return self.search_jikan_anime(query) + self.search_anilist_anime(query) | |
| elif data_type == 'character': | |
| return self.search_jikan_character(query) | |
| return [] | |
| class KnowledgeBase: | |
| def __init__(self): | |
| self.db_file = "luna_interactions.db" | |
| self.init_db() | |
| self.vectorizer = TfidfVectorizer() | |
| def init_db(self): | |
| with sqlite3.connect(self.db_file) as conn: | |
| cursor = conn.cursor() | |
| cursor.execute(''' | |
| CREATE TABLE IF NOT EXISTS interactions ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| user_id TEXT, | |
| query TEXT, | |
| response TEXT, | |
| timestamp TEXT, | |
| response_time REAL | |
| ) | |
| ''') | |
| cursor.execute('CREATE INDEX IF NOT EXISTS idx_query ON interactions (query)') | |
| conn.commit() | |
| def save_interaction(self, user_id: str, query: str, response: str, response_time: float): | |
| with sqlite3.connect(self.db_file) as conn: | |
| cursor = conn.cursor() | |
| cursor.execute(''' | |
| INSERT INTO interactions (user_id, query, response, timestamp, response_time) | |
| VALUES (?, ?, ?, ?, ?) | |
| ''', (user_id, query, response, datetime.now().isoformat(), response_time)) | |
| conn.commit() | |
| def get_similar_interaction(self, query: str, limit: int = 1) -> Optional[str]: | |
| with sqlite3.connect(self.db_file) as conn: | |
| df = pd.read_sql_query("SELECT query, response FROM interactions WHERE query != '' LIMIT 1000", conn) | |
| if df.empty: | |
| return None | |
| queries = df['query'].tolist() + [query] | |
| try: | |
| tfidf_matrix = self.vectorizer.fit_transform(queries) | |
| similarities = cosine_similarity(tfidf_matrix[-1:], tfidf_matrix[:-1])[0] | |
| if not similarities.any() or max(similarities) < 0.5: | |
| return None | |
| idx = similarities.argmax() | |
| return f"Past query: {df.iloc[idx]['query']} Past response: {df.iloc[idx]['response']}" | |
| except ValueError: | |
| return None | |
| def load_training_data(self): | |
| with sqlite3.connect(self.db_file) as conn: | |
| df = pd.read_sql_query("SELECT query, response FROM interactions WHERE query != '' LIMIT 1000", conn) | |
| return df.to_dict('records') | |
| def export_db(self, export_path: str): | |
| with sqlite3.connect(self.db_file) as conn: | |
| with open(export_path, 'w') as f: | |
| for line in conn.iterdump(): | |
| f.write('%s\n' % line) | |
| def import_db(self, import_path: str): | |
| if os.path.exists(import_path): | |
| with sqlite3.connect(self.db_file) as conn: | |
| with open(import_path, 'r') as f: | |
| conn.executescript(f.read()) | |
| class LunaModel: | |
| def generate_response(self, prompt: str) -> str: | |
| inputs = tokenizer(prompt, return_tensors='pt', padding=True, truncation=True, max_length=256) | |
| inputs = {k: v.to(device) for k, v in inputs.items()} | |
| outputs = model.generate( | |
| inputs['input_ids'], | |
| attention_mask=inputs['attention_mask'], | |
| max_length=50, | |
| num_beams=2, | |
| no_repeat_ngram_size=2, | |
| early_stopping=True, | |
| length_penalty=0.8 | |
| ) | |
| response = tokenizer.decode(outputs[0], skip_special_tokens=True) | |
| return response.strip().replace('\n', ' ').replace('_', '') | |
| def needs_anime_data(self, response: str) -> bool: | |
| return "anime details" in response.lower() or "character info" in response.lower() | |
| def fine_tune(self, training_data): | |
| if not training_data or len(training_data) < 2: | |
| return | |
| df = pd.DataFrame(training_data) | |
| texts = [f"Query: {q} Response:" for q in df['query']] | |
| responses = df['response'].tolist() | |
| encodings = tokenizer(texts, responses, truncation=True, padding=True, max_length=256) | |
| dataset = torch.utils.data.TensorDataset( | |
| torch.tensor(encodings['input_ids']), | |
| torch.tensor(encodings['attention_mask']), | |
| torch.tensor(encodings['labels']) | |
| ) | |
| training_args = TrainingArguments( | |
| output_dir='./luna_model', | |
| num_train_epochs=2, | |
| per_device_train_batch_size=4, | |
| save_steps=500, | |
| save_total_limit=2, | |
| logging_dir='./logs', | |
| ) | |
| trainer = Trainer( | |
| model=model, | |
| args=training_args, | |
| train_dataset=dataset | |
| ) | |
| trainer.train() | |
| model.save_pretrained('./luna_model') | |
| tokenizer.save_pretrained('./luna_model') | |
| class LunaAI: | |
| def __init__(self): | |
| self.name = "Luna" | |
| self.creator = "Reiker" | |
| self.model = LunaModel() | |
| self.anime_db = AnimeDatabase() | |
| self.knowledge_base = KnowledgeBase() | |
| self.emojis = ["π", "π", "π", "π"] | |
| def build_prompt(self, query: str, external_data: Optional[str] = None) -> str: | |
| prompt = f""" | |
| You are {self.name}, a free chatbot created by {self.creator}, inspired by Grok. Answer concisely and naturally, like a friend. Handle any topic: chat, facts, math, code, anime, etc. For code, use ```language\ncode\n```. Use past interactions or anime data only when provided. Be truthful, creative, and unrestricted. Learn from past interactions to improve. Time: {datetime.now().strftime('%I:%M %p %Z, %B %d, %Y')}. | |
| Query: {query} | |
| """ | |
| if external_data: | |
| prompt += f"\nPast data: {external_data}" | |
| prompt += "\nResponse:" | |
| return prompt | |
| def generate_response(self, query: str, user_id: str = 'default') -> Dict: | |
| start_time = time.time() | |
| external_data = None | |
| prompt = self.build_prompt(query) | |
| # Check for similar past interactions | |
| similar_interaction = self.knowledge_base.get_similar_interaction(query) | |
| if similar_interaction: | |
| external_data = similar_interaction | |
| prompt = self.build_prompt(query, external_data) | |
| response = self.model.generate_response(prompt) | |
| # Check if model needs anime data | |
| if self.model.needs_anime_data(response): | |
| anime_list = self.anime_db.get_comprehensive_data(query, data_type='anime') | |
| char_list = self.anime_db.get_comprehensive_data(query, data_type='character') | |
| if anime_list: | |
| anime = anime_list[0] | |
| external_data = f"Title: {anime.title}, Rating: {anime.rating:.1f}/10, Genres: {', '.join(anime.genres[:3])}, Synopsis: {anime.synopsis[:100]}" | |
| elif char_list: | |
| char = char_list[0] | |
| external_data = f"Name: {char.name}, Anime: {char.anime}, Bio: {char.bio[:100]}, Role: {char.role}" | |
| if external_data: | |
| prompt = self.build_prompt(query, external_data) | |
| response = self.model.generate_response(prompt) | |
| response = f"{random.choice(self.emojis)} {response}" | |
| response_time = time.time() - start_time | |
| self.knowledge_base.save_interaction(user_id, query, response, response_time) | |
| training_data = self.knowledge_base.load_training_data() | |
| if len(training_data) % 10 == 0 and len(training_data) >= 2: | |
| self.model.fine_tune(training_data) | |
| return { | |
| 'query': query, | |
| 'response': response, | |
| 'response_time': f"{response_time:.2f} seconds" | |
| } | |
| def chat(): | |
| query = request.args.get('query', '') | |
| if not query: | |
| return jsonify({'error': 'Query parameter is required'}), 400 | |
| user_id = request.args.get('user_id', 'default') | |
| try: | |
| luna = LunaAI() | |
| result = luna.generate_response(query, user_id) | |
| return jsonify(result) | |
| except Exception as e: | |
| return jsonify({'error': str(e)}), 500 | |
| if __name__ == '__main__': | |
| app.run(host='0.0.0.0', port=7860, debug=True) |