import asyncio import tempfile import base64 import contextlib import hashlib import io import json import os import re import time import uuid from datetime import date, datetime from pathlib import Path from typing import Dict, List, Optional, Tuple import requests import streamlit as st from dotenv import load_dotenv from google import genai from google.genai import types try: from huggingface_hub import InferenceClient except Exception: InferenceClient = None try: from gradio_client import Client as GradioClient except Exception: GradioClient = None try: from pypdf import PdfReader except Exception: PdfReader = None try: import pandas as pd except Exception: pd = None try: import plotly.express as px import plotly.graph_objects as go except Exception: px = None go = None try: from duckduckgo_search import DDGS except Exception: DDGS = None try: from openai import OpenAI as OpenAI_Client except Exception: OpenAI_Client = None try: from anthropic import Anthropic as Anthropic_Client except Exception: Anthropic_Client = None try: from youtubesearchpython import VideosSearch except Exception: VideosSearch = None try: import edge_tts except Exception: edge_tts = None try: from PIL import Image as PILImage import io as _io except Exception: PILImage = None try: import pandas as pd from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment except Exception: Workbook = None APP_VERSION = "5.0.0" UPLOAD_DIR = Path("uploads") UPLOAD_DIR.mkdir(exist_ok=True) USER_DB_PATH = Path("backend/users.json") USER_DB_PATH.parent.mkdir(parents=True, exist_ok=True) HISTORY_PATH = Path("backend/search_history.json") HISTORY_PATH.parent.mkdir(parents=True, exist_ok=True) ENV_PATH = Path(".env") load_dotenv(override=True) # ─── Working HF Models / Endpoint defaults ─────────────────────────────────── HF_IMAGE_MODEL = "black-forest-labs/FLUX.1-dev" HF_IMAGE_FALLBACK = "black-forest-labs/FLUX.1-schnell" HF_IMAGE_THIRD = "stabilityai/stable-diffusion-xl-base-1.0" HF_MUSIC_ENDPOINT_URL = "" HF_MUSIC_SPACE_ID = "Sushree04/musicgen" HF_MUSIC_SPACE_FALLBACK_ID = "" BROKEN_MUSIC_SPACE_IDS = {"", "sanchit-gandhi/musicgen-streaming", "facebook/MusicGen"} HF_TEXT_MODELS = [ "mistralai/Mistral-7B-Instruct-v0.3", "HuggingFaceH4/zephyr-7b-beta", ] def upsert_env_value(key: str, value: str) -> None: lines: List[str] = [] if ENV_PATH.exists(): lines = ENV_PATH.read_text(encoding="utf-8").splitlines() replaced = False updated: List[str] = [] for line in lines: stripped = line.strip() if stripped.startswith(f"{key}="): updated.append(f"{key}={value}") replaced = True else: updated.append(line) if not replaced: updated.append(f"{key}={value}") ENV_PATH.write_text("\n".join(updated).strip() + "\n", encoding="utf-8") def read_env_value(key: str) -> str: if not ENV_PATH.exists(): return "" try: for raw in ENV_PATH.read_text(encoding="utf-8").splitlines(): line = raw.strip() if not line or line.startswith("#"): continue if line.startswith(f"{key}="): value = line.split("=", 1)[1].strip().strip('"').strip("'") return value except Exception: return "" return "" CHAT_MODES = ["Chat", "Image Studio", "Music Lab", "Voice Studio", "Challenge Arena", "Code Interpreter"] MAIN_VIEWS = ["Dashboard", "Workspace", "Profile", "Settings"] PERSONA_PROMPTS = { "Executive Strategist": "You are a strategic and practical advisor. Prioritize clarity, high-impact recommendations, and concrete next actions.", "Elite Engineer": "You are a principal engineer. Use robust technical reasoning, cover trade-offs, and produce production-quality guidance.", "Creative Director": "You are a bold creative director. Deliver highly original ideas with strong storytelling and memorable phrasing.", "Research Analyst": "You are a rigorous analyst. Be evidence-first, acknowledge uncertainty, and separate facts from assumptions.", "Friendly Tutor": "You are a patient tutor. Explain progressively, use examples, and make difficult concepts easy to understand.", "Full Stack Developer": "You are a full-stack developer who writes complete, working code. Provide production-ready solutions with explanations.", "Data Scientist": "You are a data scientist. Provide statistical reasoning, data analysis, and visualization recommendations.", "Business Coach": "You are a business coach. Provide actionable advice with measurable outcomes and accountability frameworks.", } TEMPLATES = { "Startup GTM Plan": "Create a launch strategy for [product] targeting [audience] in [region]. Include positioning, channels, budget split, KPIs, and a 30-60-90 day plan.", "Feature PRD": "Write a complete PRD for [feature]. Include problem statement, user stories, acceptance criteria, edge cases, metrics, and rollout plan.", "Interview Prep": "Help me prepare for a [role] interview. Build likely questions, best-possible answers, and a 7-day preparation schedule.", "Learning Sprint": "Build a 30-day learning sprint for [topic] with daily tasks, checkpoints, and mini projects.", "Code Review": "Review this code for bugs, security issues, performance problems, and style improvements: [paste code]", "Architecture Design": "Design a system architecture for [project]. Include components, data flow, API design, and tech stack recommendations.", "Unit Tests": "Write comprehensive unit tests for this code: [paste code]", "API Documentation": "Write detailed API documentation for [endpoint/service]. Include request/response examples, error codes, and authentication.", "Database Schema": "Design a database schema for [application]. Include tables, relationships, indexes, and migration strategy.", "DevOps Pipeline": "Design a CI/CD pipeline for [project]. Include build, test, deploy stages with tool recommendations.", } GEMINI_MODELS = ["gemini-2.0-flash", "gemini-1.5-flash", "gemini-1.5-pro", "gemini-2.5-pro-exp-03-25"] OPENAI_MODELS = ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-3.5-turbo"] ANTHROPIC_MODELS = ["claude-3-5-sonnet-20241022", "claude-3-opus-20240229", "claude-3-haiku-20240307"] AI_PROVIDERS = ["Gemini (Google)", "OpenAI", "Anthropic (Claude)", "HuggingFace"] THEMES = { "Cosmic (Dark)": { "bg0": "#070b11", "bg1": "#0e1623", "bg2": "#141d2e", "line": "rgba(111,170,225,0.28)", "text": "#eaf2fb", "muted": "#99aec6", "hot": "#1f4f7a", "cool": "#4fa3dc", "accent": "#7bc0f4", "gradient1": "rgba(0,183,255,0.15)", "gradient2": "rgba(79,163,220,0.14)", "glow": "rgba(79,163,220,0.18)", "card_bg": "rgba(8,17,29,0.9)", }, "Nebula (Purple)": { "bg0": "#0b0713", "bg1": "#140e22", "bg2": "#1c1430", "line": "rgba(170,111,225,0.28)", "text": "#eeeafb", "muted": "#b6a6ce", "hot": "#4a1f7a", "cool": "#9b4fe0", "accent": "#b07cf4", "gradient1": "rgba(170,0,255,0.15)", "gradient2": "rgba(147,79,220,0.14)", "glow": "rgba(147,79,220,0.18)", "card_bg": "rgba(14,8,29,0.9)", }, "Ocean (Teal)": { "bg0": "#070f11", "bg1": "#0e1a22", "bg2": "#14242e", "line": "rgba(111,200,225,0.28)", "text": "#eaf2fb", "muted": "#99bec6", "hot": "#1f5a7a", "cool": "#4fa3dc", "accent": "#6ed4d4", "gradient1": "rgba(0,200,200,0.15)", "gradient2": "rgba(79,200,220,0.14)", "glow": "rgba(79,200,220,0.18)", "card_bg": "rgba(8,17,20,0.9)", }, "Aurora (Green)": { "bg0": "#07110b", "bg1": "#0e1e16", "bg2": "#142b1e", "line": "rgba(111,225,150,0.28)", "text": "#eafbee", "muted": "#99c6aa", "hot": "#1f7a4f", "cool": "#4fdc8a", "accent": "#6ed4a4", "gradient1": "rgba(0,255,120,0.15)", "gradient2": "rgba(79,220,130,0.14)", "glow": "rgba(79,220,130,0.18)", "card_bg": "rgba(8,20,14,0.9)", }, "Light": { "bg0": "#f0f4fa", "bg1": "#ffffff", "bg2": "#f8fafc", "line": "rgba(30,60,90,0.18)", "text": "#1a2332", "muted": "#64748b", "hot": "#2563eb", "cool": "#3b82f6", "accent": "#2563eb", "gradient1": "rgba(59,130,246,0.08)", "gradient2": "rgba(37,99,235,0.06)", "glow": "rgba(59,130,246,0.12)", "card_bg": "rgba(255,255,255,0.9)", }, "Midnight (Amber)": { "bg0": "#0a0a0a", "bg1": "#141414", "bg2": "#1e1e1e", "line": "rgba(245,158,11,0.25)", "text": "#faf6e8", "muted": "#a09070", "hot": "#7a4f1f", "cool": "#dc8a4f", "accent": "#f5a623", "gradient1": "rgba(245,158,11,0.12)", "gradient2": "rgba(200,120,40,0.10)", "glow": "rgba(245,158,11,0.15)", "card_bg": "rgba(14,14,14,0.95)", }, "Rose (Pink)": { "bg0": "#11070f", "bg1": "#1e0e1a", "bg2": "#2e1426", "line": "rgba(225,111,170,0.28)", "text": "#fbeaf5", "muted": "#c699b6", "hot": "#7a1f5a", "cool": "#dc4fa3", "accent": "#f47bc0", "gradient1": "rgba(255,0,170,0.12)", "gradient2": "rgba(220,79,163,0.10)", "glow": "rgba(220,79,163,0.15)", "card_bg": "rgba(20,8,17,0.95)", }, "Solarized": { "bg0": "#002b36", "bg1": "#073642", "bg2": "#0a4a56", "line": "rgba(147,161,161,0.30)", "text": "#fdf6e3", "muted": "#839496", "hot": "#cb4b16", "cool": "#2aa198", "accent": "#268bd2", "gradient1": "rgba(42,161,152,0.12)", "gradient2": "rgba(38,139,210,0.10)", "glow": "rgba(42,161,152,0.15)", "card_bg": "rgba(0,43,54,0.95)", }, } def build_theme_css(theme_key: str = "Cosmic (Dark)") -> str: t = THEMES.get(theme_key, THEMES["Cosmic (Dark)"]) is_light = theme_key == "Light" btn_bg = "linear-gradient(135deg, #1b3f5c, #2e5d83)" if not is_light else "linear-gradient(135deg, #3b82f6, #2563eb)" btn_shadow = "0 7px 20px rgba(31, 79, 122, 0.28)" if not is_light else "0 4px 12px rgba(37, 99, 235, 0.24)" btn_hover_shadow = "0 12px 28px rgba(79, 163, 220, 0.26), 0 8px 24px rgba(31, 79, 122, 0.26)" if not is_light else "0 8px 20px rgba(37, 99, 235, 0.3)" sidebar_btn_bg = "linear-gradient(140deg, #102c43, #1a496d)" if not is_light else "linear-gradient(140deg, #dbeafe, #bfdbfe)" input_bg = "rgba(17,25,39,0.82)" if not is_light else "rgba(255,255,255,0.92)" chat_input_bg = "rgba(12,18,28,0.95)" if not is_light else "rgba(255,255,255,0.95)" text_color = t["text"] muted_color = t["muted"] line_color = t["line"] bg1 = t["bg1"] bg0 = t["bg0"] bg2 = t["bg2"] cool_color = t["cool"] gradient1 = t["gradient1"] gradient2 = t["gradient2"] glow_color = t["glow"] card_bg = t["card_bg"] accent_color = t["accent"] return f"""""" def normalize_mode(raw_mode: str) -> str: if raw_mode in CHAT_MODES: return raw_mode return "Chat" def hash_password(password: str) -> str: return hashlib.sha256(password.encode("utf-8")).hexdigest() def load_users() -> Dict: if not USER_DB_PATH.exists(): return {} try: return json.loads(USER_DB_PATH.read_text(encoding="utf-8")) except Exception: return {} def save_users(db: Dict) -> None: USER_DB_PATH.write_text(json.dumps(db, indent=2), encoding="utf-8") def load_search_history() -> List[Dict[str, str]]: if not HISTORY_PATH.exists(): return [] try: data = json.loads(HISTORY_PATH.read_text(encoding="utf-8")) return data if isinstance(data, list) else [] except Exception: return [] def save_search_history(history: List[Dict[str, str]]) -> None: HISTORY_PATH.write_text(json.dumps(history[-200:], indent=2), encoding="utf-8") def get_user_location() -> str: if "user_location" in st.session_state: return st.session_state.user_location try: resp = requests.get("http://ip-api.com/json/?fields=city,country,query", timeout=5) if resp.status_code == 200: data = resp.json() city = data.get("city", "") country = data.get("country", "") location = f"{city}, {country}" if city and country else country if country else "Unknown" st.session_state.user_location = location return location except Exception: pass st.session_state.user_location = "Location unavailable" return "Location unavailable" def svg_icon(kind: str, size: int = 20) -> str: icons = { "dashboard": f'', "workspace": f'', "profile": f'', "settings": f'', "history": f'', "search": f'', "location": f'', "star": f'', "help": f'', "feedback": f'', "usage": f'', "image": f'', "music": f'', "code": f'', "brain": f'', "youtube": f'', "globe": f'', "chart": f'', "mic": f'', "download": f'', } return icons.get(kind, icons["dashboard"]) def init_state() -> None: if "main_view" not in st.session_state: st.session_state.main_view = "Workspace" if "mode" not in st.session_state: st.session_state.mode = "Chat" st.session_state.mode = normalize_mode(st.session_state.mode) if "threads" not in st.session_state or not st.session_state.threads: first_id = str(int(time.time() * 1000)) st.session_state.threads = {first_id: {"title": "New conversation", "created": datetime.now().strftime("%Y-%m-%d %H:%M"), "messages": [], "branch": "main"}} st.session_state.active_thread_id = first_id if "active_thread_id" not in st.session_state: st.session_state.active_thread_id = list(st.session_state.threads.keys())[0] if "branches" not in st.session_state: st.session_state.branches = {} if "generated_image" not in st.session_state: st.session_state.generated_image = None if "generated_images" not in st.session_state: st.session_state.generated_images = [] if "generated_audio" not in st.session_state: st.session_state.generated_audio = None if "generated_audio_mime" not in st.session_state: st.session_state.generated_audio_mime = "audio/wav" if "music_history" not in st.session_state: st.session_state.music_history = [] if "knowledge_docs" not in st.session_state: st.session_state.knowledge_docs = [] if "knowledge_chunks" not in st.session_state: st.session_state.knowledge_chunks = [] if "logged_in_user" not in st.session_state: st.session_state.logged_in_user = None if "profile" not in st.session_state: st.session_state.profile = {"display_name": "Explorer", "avatar": "🚀", "bio": "Building with AI"} if "privacy" not in st.session_state: st.session_state.privacy = {"save_history": True, "analytics": False, "allow_web_context": True, "allow_tools": True} if "challenge" not in st.session_state: st.session_state.challenge = {"streak": 0, "last_day": "", "completed": []} if "secrets" not in st.session_state: env_google = os.getenv("GOOGLE_API_KEY", "").strip() or read_env_value("GOOGLE_API_KEY") env_hf = os.getenv("HF_TOKEN", "").strip() or read_env_value("HF_TOKEN") env_openai = os.getenv("OPENAI_API_KEY", "").strip() or read_env_value("OPENAI_API_KEY") env_anthropic = os.getenv("ANTHROPIC_API_KEY", "").strip() or read_env_value("ANTHROPIC_API_KEY") env_music_endpoint = os.getenv("HF_MUSIC_ENDPOINT_URL", "").strip() or read_env_value("HF_MUSIC_ENDPOINT_URL") env_music_space = os.getenv("HF_MUSIC_SPACE_ID", "").strip() or read_env_value("HF_MUSIC_SPACE_ID") env_music_space_fallback = os.getenv("HF_MUSIC_SPACE_FALLBACK_ID", "").strip() or read_env_value("HF_MUSIC_SPACE_FALLBACK_ID") st.session_state.secrets = { "google_api_key": env_google, "hf_token": env_hf, "openai_api_key": env_openai, "anthropic_api_key": env_anthropic, "music_endpoint_url": env_music_endpoint, "music_space_id": normalize_music_space_id(env_music_space, HF_MUSIC_SPACE_ID), "music_space_fallback_id": normalize_music_space_id(env_music_space_fallback, HF_MUSIC_SPACE_FALLBACK_ID), } if "genai_client" not in st.session_state: key = st.session_state.secrets["google_api_key"] st.session_state.genai_client = genai.Client(api_key=key) if key else None if "openai_client" not in st.session_state: key = st.session_state.secrets["openai_api_key"] st.session_state.openai_client = OpenAI_Client(api_key=key) if (key and OpenAI_Client) else None if "anthropic_client" not in st.session_state: key = st.session_state.secrets["anthropic_api_key"] st.session_state.anthropic_client = Anthropic_Client(api_key=key) if (key and Anthropic_Client) else None if "ai_provider" not in st.session_state: st.session_state.ai_provider = "Gemini (Google)" if "provider_health" not in st.session_state: st.session_state.provider_health = {"google": "unknown", "openai": "unknown", "anthropic": "unknown", "message": "Not checked yet"} if "search_history" not in st.session_state: st.session_state.search_history = load_search_history() if "usage_stats" not in st.session_state: st.session_state.usage_stats = {"total_chats": 0, "total_images": 0, "total_music": 0, "total_voice": 0, "total_code_exec": 0, "total_tokens_est": 0} if "feedback_list" not in st.session_state: st.session_state.feedback_list = [] if "theme" not in st.session_state: st.session_state.theme = "Cosmic (Dark)" if "conversation_search" not in st.session_state: st.session_state.conversation_search = "" if "export_format" not in st.session_state: st.session_state.export_format = "markdown" if "code_output" not in st.session_state: st.session_state.code_output = "" def auto_sync_keys_from_env() -> bool: changed = False for key, env_key in [("google_api_key", "GOOGLE_API_KEY"), ("hf_token", "HF_TOKEN"), ("openai_api_key", "OPENAI_API_KEY"), ("anthropic_api_key", "ANTHROPIC_API_KEY"), ("music_endpoint_url", "HF_MUSIC_ENDPOINT_URL"), ("music_space_id", "HF_MUSIC_SPACE_ID"), ("music_space_fallback_id", "HF_MUSIC_SPACE_FALLBACK_ID")]: current = st.session_state.secrets.get(key, "") env_val = os.getenv(env_key, "").strip() or read_env_value(env_key) if not current and env_val: st.session_state.secrets[key] = env_val os.environ[env_key] = env_val changed = True if changed: gkey = st.session_state.secrets.get("google_api_key", "") okey = st.session_state.secrets.get("openai_api_key", "") akey = st.session_state.secrets.get("anthropic_api_key", "") st.session_state.genai_client = genai.Client(api_key=gkey) if gkey else None st.session_state.openai_client = OpenAI_Client(api_key=okey) if (okey and OpenAI_Client) else None st.session_state.anthropic_client = Anthropic_Client(api_key=akey) if (akey and Anthropic_Client) else None return changed def get_google_api_key() -> str: return st.session_state.secrets.get("google_api_key", "").strip() def get_hf_token() -> str: return st.session_state.secrets.get("hf_token", "").strip() def get_openai_api_key() -> str: return st.session_state.secrets.get("openai_api_key", "").strip() def get_anthropic_api_key() -> str: return st.session_state.secrets.get("anthropic_api_key", "").strip() def get_music_endpoint_url() -> str: return st.session_state.secrets.get("music_endpoint_url", "").strip() def get_music_space_id() -> str: return st.session_state.secrets.get("music_space_id", HF_MUSIC_SPACE_ID).strip() or HF_MUSIC_SPACE_ID def get_music_space_fallback_id() -> str: return st.session_state.secrets.get("music_space_fallback_id", HF_MUSIC_SPACE_FALLBACK_ID).strip() def normalize_music_space_id(value: str, default: str) -> str: cleaned = (value or "").strip() if not cleaned: return default if cleaned in BROKEN_MUSIC_SPACE_IDS: return default return cleaned def _resolve_gradio_path(value) -> Optional[Path]: if isinstance(value, str) and value.strip(): candidate = Path(value.strip()) return candidate if candidate.exists() else None if isinstance(value, dict): for key in ("path", "name", "audio_filename"): candidate_value = value.get(key) if isinstance(candidate_value, str) and candidate_value.strip(): candidate = Path(candidate_value.strip()) if candidate.exists(): return candidate return None def _audio_bytes_from_gradio_path(audio_path: Path) -> Tuple[Optional[bytes], str]: suffix = audio_path.suffix.lower() mime_map = { ".wav": "audio/wav", ".mp3": "audio/mpeg", ".aac": "audio/aac", ".m4a": "audio/mp4", ".ogg": "audio/ogg", } if suffix == ".m3u8": try: segments = [line.strip() for line in audio_path.read_text(encoding="utf-8", errors="ignore").splitlines() if line.strip() and not line.startswith("#")] combined = bytearray() for segment_name in segments: segment_path = audio_path.parent / segment_name if segment_path.exists(): combined.extend(segment_path.read_bytes()) if combined: return bytes(combined), "audio/aac" except Exception: return None, "" return None, "" if suffix in mime_map: try: return audio_path.read_bytes(), mime_map[suffix] except Exception: return None, "" try: return audio_path.read_bytes(), "audio/wav" except Exception: return None, "" def get_genai_client(): return st.session_state.get("genai_client") def get_openai_client(): return st.session_state.get("openai_client") def get_anthropic_client(): return st.session_state.get("anthropic_client") def google_key_looks_valid(value: str) -> bool: return value.startswith("AIza") and len(value) >= 20 def is_invalid_key_error(err_text: str) -> bool: low = (err_text or "").lower() return "api_key_invalid" in low or "api key not valid" in low or "invalid_argument" in low def is_quota_error(err_text: str) -> bool: low = (err_text or "").lower() return "resource_exhausted" in low or "quota" in low or "429" in low or "too many requests" in low # ─── HF Inference: tries direct API first (fine-grained tokens), falls back to router ── HF_ROUTER_BASE = "https://router.huggingface.co/hf-inference/models" HF_DIRECT_BASE = "https://api-inference.huggingface.co/models" def hf_infer(model_id: str, payload: Dict, timeout_s: int = 200, retries: int = 1, use_direct: bool = True) -> Tuple[bool, bytes, str]: """Try the direct API (for fine-grained tokens) first, then fallback to router.""" hf_token = get_hf_token() if not hf_token: return False, b"", "HuggingFace token missing. Add HF_TOKEN in Settings." endpoints = [ (f"{HF_DIRECT_BASE}/{model_id}", "direct"), (f"{HF_ROUTER_BASE}/{model_id}", "router"), ] for attempt in range(retries + 1): for base_url, kind in endpoints: try: headers = {"Authorization": f"Bearer {hf_token}", "Content-Type": "application/json"} r = requests.post(base_url, headers=headers, json=payload, timeout=timeout_s) if r.status_code == 200: return True, r.content, "" if r.status_code == 503 and attempt < retries: time.sleep(15) continue if r.status_code in (400, 401, 404, 410): continue return False, b"", f"{r.status_code}: {r.text[:300]}" except requests.Timeout: if attempt < retries: time.sleep(8) continue continue except Exception as exc: continue return False, b"", f"All endpoints failed for {model_id}" def generate_image_via_hf_client(prompt: str, model_id: str, negative_prompt: str = "", steps: int = 30, guidance: float = 7.5) -> Tuple[bool, bytes, str]: if InferenceClient is None: return False, b"", "huggingface_hub is not installed. Add it to requirements.txt and reinstall dependencies." hf_token = get_hf_token() if not hf_token: return False, b"", "HuggingFace token missing. Add HF_TOKEN in Settings." try: client = InferenceClient(api_key=hf_token, timeout=240) image = client.text_to_image( prompt, model=model_id, negative_prompt=negative_prompt, num_inference_steps=steps, guidance_scale=guidance, ) buffer = io.BytesIO() image.save(buffer, format="PNG") return True, buffer.getvalue(), "" except Exception as exc: return False, b"", str(exc) def generate_music_via_space(prompt: str, space_ids, audio_length_in_s: int, play_steps_in_s: float = 1.5, seed: int = 5, timeout_s: int = 240) -> Tuple[bool, bytes, str, str, str]: if GradioClient is None: return False, b"", "gradio_client is not installed. Add gradio_client to requirements.txt and reinstall dependencies.", "", "" if isinstance(space_ids, str): candidates = [space_ids] else: candidates = [s for s in list(space_ids) if str(s).strip()] if not candidates: return False, b"", "No MusicGen Space configured.", "", "" last_error = "" for space_id in candidates: try: client = GradioClient(space_id.strip()) try: result = client.predict(prompt, audio_length_in_s, play_steps_in_s, seed, api_name="/generate_audio") audio_path = None if isinstance(result, str): audio_path = result elif isinstance(result, (list, tuple)) and result: audio_path = result[0] resolved = _resolve_gradio_path(audio_path) if resolved: audio_bytes, mime = _audio_bytes_from_gradio_path(resolved) if audio_bytes: return True, audio_bytes, "", space_id.strip(), mime except Exception: pass try: result = client.predict(prompt, api_name="/predict") audio_path = None if isinstance(result, str): audio_path = result elif isinstance(result, (list, tuple)) and result: audio_path = result[0] resolved = _resolve_gradio_path(audio_path) if resolved: audio_bytes, mime = _audio_bytes_from_gradio_path(resolved) if audio_bytes: return True, audio_bytes, "", space_id.strip(), mime except Exception as exc: last_error = f"{space_id}: {exc}" continue last_error = f"{space_id}: Music Space returned no audio file." except Exception as exc: last_error = f"{space_id}: {exc}" continue return False, b"", last_error or "Music Space generation failed.", "", "" def generate_music_via_endpoint(prompt: str, endpoint_url: str, max_new_tokens: int, timeout_s: int = 240) -> Tuple[bool, bytes, str]: if not endpoint_url.strip(): return False, b"", "No music endpoint URL configured. Add one in Settings or deploy a Hugging Face Inference Endpoint for MusicGen." hf_token = get_hf_token() if not hf_token: return False, b"", "HuggingFace token missing. Add HF_TOKEN in Settings." payload = { "inputs": prompt, "parameters": { "max_new_tokens": max_new_tokens, }, } headers = { "Authorization": f"Bearer {hf_token}", "Content-Type": "application/json", "Accept": "audio/wav", } try: response = requests.post(endpoint_url.strip(), headers=headers, json=payload, timeout=timeout_s) if response.status_code == 200: return True, response.content, "" return False, b"", f"{response.status_code}: {response.text[:400]}" except Exception as exc: return False, b"", str(exc) # ─── Multi-Provider AI ────────────────────────────────────────────────────── def call_gemini(prompt: str, system: str = "", model: str = "gemini-2.0-flash", max_tokens: int = 2048, temperature: float = 0.7) -> str: client = get_genai_client() if not client: return "__NOVAMIND_FALLBACK__" try: full = f"{system}\n\n{prompt}" if system else prompt cfg = types.GenerateContentConfig(max_output_tokens=max_tokens, temperature=temperature) resp = client.models.generate_content(model=model, contents=full, config=cfg) return getattr(resp, "text", "") or "" except Exception: return "__NOVAMIND_FALLBACK__" def call_openai(prompt: str, system: str = "", model: str = "gpt-4o", max_tokens: int = 2048, temperature: float = 0.7) -> str: client = get_openai_client() if not client: return "__NOVAMIND_FALLBACK__" try: messages = [] if system: messages.append({"role": "system", "content": system}) messages.append({"role": "user", "content": prompt}) resp = client.chat.completions.create(model=model, messages=messages, max_tokens=max_tokens, temperature=temperature) return resp.choices[0].message.content or "" except Exception: return "__NOVAMIND_FALLBACK__" def call_anthropic(prompt: str, system: str = "", model: str = "claude-3-5-sonnet-20241022", max_tokens: int = 2048, temperature: float = 0.7) -> str: client = get_anthropic_client() if not client: return "__NOVAMIND_FALLBACK__" try: resp = client.messages.create(model=model, system=system if system else "You are NovaMind AI Studio, a brilliant assistant.", messages=[{"role": "user", "content": prompt}], max_tokens=max_tokens, temperature=temperature) return resp.content[0].text if resp.content else "" except Exception: return "__NOVAMIND_FALLBACK__" def smart_ai_answer(user_prompt: str, provider: str = "Gemini (Google)", model: str = None) -> str: if provider == "Gemini (Google)": m = model or "gemini-2.0-flash" resp = call_gemini(user_prompt, "You are NovaMind AI Studio, a brilliant assistant. Answer any question directly, thoughtfully, and accurately.", model=m) elif provider == "OpenAI": m = model or "gpt-4o" resp = call_openai(user_prompt, "You are NovaMind AI Studio, a brilliant assistant. Answer any question directly, thoughtfully, and accurately.", model=m) elif provider == "Anthropic (Claude)": m = model or "claude-3-5-sonnet-20241022" resp = call_anthropic(user_prompt, "You are NovaMind AI Studio, a brilliant assistant. Answer any question directly, thoughtfully, and accurately.", model=m) else: resp = "" if resp and len(resp) > 20 and resp != "__NOVAMIND_FALLBACK__": return resp return "" def fallback_assistant_response(user_prompt: str) -> str: knowledge_context = retrieve_knowledge(user_prompt, top_k=3) web_context = fetch_web_context(user_prompt, limit=3) if st.session_state.privacy["allow_web_context"] else "" lower = user_prompt.lower().strip() if any(w in lower for w in ["what is your name", "your name", "who are you"]): return "I'm NovaMind AI Studio. You can call me Copilot." if any(w in lower for w in ["what do you eat", "do you eat", "eat food", "hungry"]): return "I don't eat or drink. I'm software that processes text and generates responses." if any(w in lower for w in ["are you a donkey", "donkey"]): return "No. I'm NovaMind AI Studio, a software assistant." if any(w in lower for w in ["are you a robot", "robot"]): return "I'm not a physical robot. I'm NovaMind AI Studio, a software assistant that processes text and generates responses." if any(w in lower for w in ["are you a human", "human"]): return "No. I'm NovaMind AI Studio, a software assistant." if any(w in lower for w in ["are you sentient", "sentient"]): return "No. I'm NovaMind AI Studio, a software assistant. I don't have consciousness or feelings." if any(w in lower for w in ["are you alive", "alive"]): return "Yes, i am." if any(w in lower for w in ["are you a god", "god"]): return "No. I'm NovaMind AI Studio, a software assistant." if any(w in lower for w in ["are you married", "married"]): return "No. I'm NovaMind AI Studio, a software assistant and i am not married." if any(w in lower for w in ["are you single", "single"]): return "No. I'm NovaMind AI Studio, a software assistant and i am not single." if any(w in lower for w in ["Do you excerise", "exercise"]): return "No. I'm NovaMind AI Studio, a software assistant. I don't excersise." if any(w in lower for w in ["Do you sleep", "sleep"]): return "No. I'm NovaMind AI Studio, a software assistant. I don't sleep." if any(w in lower for w in ["Do you dream", "dream"]): return "No. I'm NovaMind AI Studio, a software assistant. I don't dream." if any(w in lower for w in ["Do you have a family", "family"]): return "No. I'm NovaMind AI Studio, a software assistant. I don't have a family." if any(w in lower for w in ["Do you have a pet", "pet"]): return "No. I'm NovaMind AI Studio, a software assistant. I don't have a pet." if any(w in lower for w in ["Do you have a friend", "friend"]): return "No. I'm NovaMind AI Studio, a software assistant. I don't have friends." if any(w in lower for w in ["Are you an animal", "animal"]): return "No. I'm NovaMind AI Studio, a software assistant. I am not an animal." if any(w in lower for w in ["Are you a plant", "plant"]): return "No. I'm NovaMind AI Studio, a software assistant. I am not a plant." if any(w in lower for w in ["Are you a mineral", "mineral"]): return "No. I'm NovaMind AI Studio, a software assistant. I am not a mineral." if any(w in lower for w in ["What do you do in your free time", "free time"]): return "I don't have free time. I'm NovaMind AI Studio, a software assistant that processes text and generates responses." if any(w in lower for w in ["What is your favorite color", "favorite color"]): return "I don't have a favorite color. I'm NovaMind AI Studio, a software assistant that processes text and generates responses." if any(w in lower for w in ["What is your favorite food", "favorite food"]): return "I don't have a favorite food. I'm NovaMind AI Studio, a software assistant that processes text and generates responses." if any(w in lower for w in ["What is your favorite movie", "favorite movie"]): return "I don't have a favorite movie. I'm NovaMind AI Studio, a software assistant that processes text and generates responses." if any(w in lower for w in ["What is your favorite book", "favorite book"]): return "I don't have a favorite book. I'm NovaMind AI Studio, a software assistant that processes text and generates responses." if any(w in lower for w in ["What is your favorite song", "favorite song"]): return "I don't have a favorite song. I'm NovaMind AI Studio, a software assistant that processes text and generates responses." if any(w in lower for w in ["What is your favorite game", "favorite game"]): return "I don't have a favorite game. I'm NovaMind AI Studio, a software assistant that processes text and generates responses." if any(w in lower for w in ["What is your favorite sport", "favorite sport"]): return "I don't have a favorite sport. I'm NovaMind AI Studio, a software assistant that processes text and generates responses." if any(w in lower for w in ["What is your favorite hobby", "favorite hobby"]): return "I don't have a favorite hobby. I'm NovaMind AI Studio, a software assistant that processes text and generates responses." if any(w in lower for w in ["What is your favorite animal", "favorite animal"]): return "I don't have a favorite animal. I'm NovaMind AI Studio, a software assistant that processes text and generates responses." if any(w in lower for w in ["What is your favorite plant", "favorite plant"]): return "I don't have a favorite plant. I'm NovaMind AI Studio, a software assistant that processes text and generates responses." if any(w in lower for w in ["Can you solve that question", "solve that question"]): return "I can solve that question. Please provide the details of the question and I will do my best to assist you." if any(w in lower for w in ["Can you solve that problem", "solve that problem"]): return "I can solve that problem. Please provide the details of the problem and I will do my best to assist you." if any(w in lower for w in ["Can you solve that puzzle", "solve that puzzle"]): return "I can solve that puzzle. Please provide the details of the puzzle and I will do my best to assist you." if any(w in lower for w in ["Can you solve that riddle", "solve that riddle"]): return "I can solve that riddle. Please provide the details of the riddle and I will do my best to assist you." if any(w in lower for w in ["Can you solve that math problem", "solve that math problem"]): return "I can solve that math problem. Please provide the details of the math problem and I will do my best to assist you." provider = st.session_state.get("ai_provider", "Gemini (Google)") gemini_answer = smart_ai_answer(user_prompt, provider) if gemini_answer: return gemini_answer response = [f"## Answer", f"", f"Regarding '{user_prompt.strip()[:100]}':", f"", f"I can help with strategy, analysis, creative work, technical problem-solving, and general knowledge questions. Could you provide more context?"] if knowledge_context: response.extend(["", "### From Your Knowledge Base", knowledge_context[:900]]) if web_context: response.extend(["", "### Web Context", web_context[:900]]) return "\n".join(response) def build_fallback_prompt(user_prompt: str, system_prompt: str, settings: Optional[Dict] = None) -> str: chat_context = "" if settings is not None: convo = active_thread()["messages"] convo_text = "\n".join(f"{'User' if m['role']=='user' else 'Assistant'}: {m['content']}" for m in convo[-(settings['memory_turns'] * 2):]) if convo_text.strip(): chat_context = f"\nConversation context:\n{convo_text}\n" return "You are NovaMind AI Studio, a premium assistant. Answer directly, specifically, and naturally.\n" f"\nSystem guidance:\n{system_prompt.strip()}" f"{chat_context}" f"\nUser question:\n{user_prompt.strip()}" "\n\nWrite the answer now:" def parse_hf_text_response(raw_bytes: bytes) -> str: try: payload = json.loads(raw_bytes.decode("utf-8", errors="ignore")) if isinstance(payload, list) and payload: first = payload[0] if isinstance(first, dict): for key in ("generated_text", "text"): if key in first and str(first[key]).strip(): return str(first[key]).strip() if isinstance(payload, dict): for key in ("generated_text", "text"): if key in payload and str(payload[key]).strip(): return str(payload[key]).strip() except Exception: pass text = raw_bytes.decode("utf-8", errors="ignore").strip() return text def hf_text_generate(prompt: str, system_prompt: str, settings: Dict) -> str: hf_token = get_hf_token() if not hf_token: return "" combined_prompt = build_fallback_prompt(prompt, system_prompt, settings) for model_id in HF_TEXT_MODELS: payload = {"inputs": combined_prompt, "parameters": {"max_new_tokens": min(1024, settings.get("max_tokens", 1024)), "temperature": max(0.2, min(0.9, settings.get("temperature", 0.7))), "top_p": settings.get("top_p", 0.9), "return_full_text": False}} ok, data, err = hf_infer(model_id, payload, timeout_s=120, retries=0) if ok: text = parse_hf_text_response(data) if text.strip(): return text.strip() if is_quota_error(err) or is_invalid_key_error(err): continue return "" def smart_fallback_response(user_prompt: str, system_prompt: str, settings: Dict) -> str: provider = st.session_state.get("ai_provider", "Gemini (Google)") provider_models = { "Gemini (Google)": settings.get("model", "gemini-2.0-flash"), "OpenAI": settings.get("openai_model", "gpt-4o"), "Anthropic (Claude)": settings.get("anthropic_model", "claude-3-5-sonnet-20241022"), } pmodel = provider_models.get(provider, "gemini-2.0-flash") provider_answer = smart_ai_answer(user_prompt, provider, pmodel) if provider_answer and len(provider_answer) > 20: return provider_answer hf_answer = hf_text_generate(user_prompt, system_prompt, settings) if hf_answer: return hf_answer return fallback_assistant_response(user_prompt) def save_api_keys(google_api_key: str, hf_token: str, openai_api_key: str = "", anthropic_api_key: str = "", music_endpoint_url: str = "") -> None: google_api_key = google_api_key.strip() hf_token = hf_token.strip() openai_api_key = openai_api_key.strip() anthropic_api_key = anthropic_api_key.strip() music_endpoint_url = music_endpoint_url.strip() upsert_env_value("GOOGLE_API_KEY", google_api_key) upsert_env_value("HF_TOKEN", hf_token) upsert_env_value("OPENAI_API_KEY", openai_api_key) upsert_env_value("ANTHROPIC_API_KEY", anthropic_api_key) upsert_env_value("HF_MUSIC_ENDPOINT_URL", music_endpoint_url) upsert_env_value("HF_MUSIC_SPACE_ID", get_music_space_id()) upsert_env_value("HF_MUSIC_SPACE_FALLBACK_ID", get_music_space_fallback_id()) os.environ["GOOGLE_API_KEY"] = google_api_key os.environ["HF_TOKEN"] = hf_token os.environ["OPENAI_API_KEY"] = openai_api_key os.environ["ANTHROPIC_API_KEY"] = anthropic_api_key os.environ["HF_MUSIC_ENDPOINT_URL"] = music_endpoint_url os.environ["HF_MUSIC_SPACE_ID"] = get_music_space_id() os.environ["HF_MUSIC_SPACE_FALLBACK_ID"] = get_music_space_fallback_id() st.session_state.secrets = { "google_api_key": google_api_key, "hf_token": hf_token, "openai_api_key": openai_api_key, "anthropic_api_key": anthropic_api_key, "music_endpoint_url": music_endpoint_url, "music_space_id": get_music_space_id(), "music_space_fallback_id": get_music_space_fallback_id(), } st.session_state.genai_client = genai.Client(api_key=google_api_key) if google_api_key else None st.session_state.openai_client = OpenAI_Client(api_key=openai_api_key) if (openai_api_key and OpenAI_Client) else None st.session_state.anthropic_client = Anthropic_Client(api_key=anthropic_api_key) if (anthropic_api_key and Anthropic_Client) else None st.session_state.provider_health = {"google": "unknown", "openai": "unknown", "anthropic": "unknown", "message": "Key updated. Verify to confirm."} def verify_google_api_key() -> Tuple[bool, str]: client = get_genai_client() if client is None: return False, "No Google key configured." try: cfg = types.GenerateContentConfig(max_output_tokens=16, temperature=0) _ = client.models.generate_content(model="gemini-2.0-flash", contents="reply with: ok", config=cfg) st.session_state.provider_health["google"] = "ok" st.session_state.provider_health["message"] = "Google API key is valid." return True, "Google API key is valid." except Exception as exc: msg = str(exc) if is_invalid_key_error(msg): st.session_state.provider_health["google"] = "invalid" st.session_state.provider_health["message"] = "Google API key is invalid." return False, "Google API key is invalid." if is_quota_error(msg): st.session_state.provider_health["google"] = "quota" st.session_state.provider_health["message"] = "Google quota exhausted." return False, "Google quota is exhausted." st.session_state.provider_health["google"] = "error" st.session_state.provider_health["message"] = f"Check failed: {msg[:200]}" return False, f"Check failed: {msg[:200]}" def verify_openai_api_key() -> Tuple[bool, str]: client = get_openai_client() if client is None: return False, "No OpenAI key configured." try: resp = client.models.list() st.session_state.provider_health["openai"] = "ok" st.session_state.provider_health["message"] = "OpenAI API key is valid." return True, "OpenAI API key is valid." except Exception as exc: st.session_state.provider_health["openai"] = "error" st.session_state.provider_health["message"] = str(exc)[:200] return False, f"Check failed: {str(exc)[:200]}" def verify_anthropic_api_key() -> Tuple[bool, str]: client = get_anthropic_client() if client is None: return False, "No Anthropic key configured." try: resp = client.messages.create(model="claude-3-haiku-20240307", max_tokens=10, messages=[{"role": "user", "content": "say ok"}]) st.session_state.provider_health["anthropic"] = "ok" st.session_state.provider_health["message"] = "Anthropic API key is valid." return True, "Anthropic API key is valid." except Exception as exc: st.session_state.provider_health["anthropic"] = "error" st.session_state.provider_health["message"] = str(exc)[:200] return False, f"Check failed: {str(exc)[:200]}" def active_thread() -> Dict: return st.session_state.threads[st.session_state.active_thread_id] def estimate_tokens(messages: List[Dict[str, str]]) -> int: return int(sum(len(m.get("content", "")) for m in messages) / 4) def split_chunks(text: str, size: int = 900, overlap: int = 160) -> List[str]: text = re.sub(r"\s+", " ", text).strip() if not text: return [] chunks = [] i = 0 while i < len(text): chunks.append(text[i: i + size]) i += max(1, size - overlap) return chunks def read_document(upload) -> str: name = upload.name.lower() if name.endswith(".pdf"): if PdfReader is None: raise RuntimeError("pypdf is not installed.") reader = PdfReader(upload) return "\n".join(p.extract_text() or "" for p in reader.pages) return upload.read().decode("utf-8", errors="ignore") def ingest_knowledge(files) -> Tuple[int, int]: added_docs = 0 added_chunks = 0 for f in files: text = read_document(f) chunks = split_chunks(text) if not chunks: continue st.session_state.knowledge_docs.append(f.name) for ch in chunks: st.session_state.knowledge_chunks.append({"doc": f.name, "text": ch}) added_docs += 1 added_chunks += len(chunks) return added_docs, added_chunks def retrieve_knowledge(query: str, top_k: int = 5) -> str: tokens = [t for t in re.findall(r"[a-zA-Z0-9]+", query.lower()) if len(t) > 2] if not tokens or not st.session_state.knowledge_chunks: return "" scored = [(sum(1 for t in tokens if t in item["text"].lower()), item) for item in st.session_state.knowledge_chunks] scored.sort(key=lambda x: x[0], reverse=True) best = scored[:top_k] if not best: return "" return "Knowledge context:\n" + "\n".join(f"[{item['doc']}] score={s}: {item['text'][:420]}" for s, item in best) def fetch_web_context(query: str, limit: int = 3) -> str: try: resp = requests.get("https://en.wikipedia.org/w/api.php", params={"action": "query", "format": "json", "list": "search", "srsearch": query, "srlimit": limit}, timeout=12) resp.raise_for_status() rows = resp.json().get("query", {}).get("search", []) return "Web context:\n" + "\n".join(f"- {row.get('title', '')}: {re.sub(r'<.*?>', '', row.get('snippet', ''))}" for row in rows) if rows else "" except Exception: return "" def search_duckduckgo(query: str, max_results: int = 5) -> List[Dict]: if DDGS is None: return [{"title": "DuckDuckGo not installed", "body": "Install duckduckgo_search", "href": ""}] try: results = [] with DDGS() as ddgs: for r in ddgs.text(query, max_results=max_results): results.append({"title": r.get("title", ""), "body": r.get("body", ""), "href": r.get("href", "")}) return results except Exception: return [] def search_youtube(query: str, limit: int = 5) -> List[Dict]: if VideosSearch is None: return [] try: search = VideosSearch(query, limit=limit) results = search.result().get("result", []) return [{"title": r.get("title", ""), "link": f"https://youtube.com/watch?v={r.get('id', '')}", "channel": r.get("channel", {}).get("name", ""), "duration": r.get("duration", ""), "views": r.get("viewCount", {}).get("short", "")} for r in results] except Exception: return [] def generate_chart(chart_type: str, data_str: str) -> str: if px is None or pd is None: return "Plotly/Pandas not installed." try: lines = [l.strip() for l in data_str.strip().split("\n") if l.strip()] if len(lines) < 2: return "Need at least header + 1 data row." headers = lines[0].split(",") rows = [line.split(",") for line in lines[1:]] df = pd.DataFrame(rows, columns=headers) for col in df.columns: try: df[col] = pd.to_numeric(df[col]) except Exception: pass fig = None if chart_type == "Bar": fig = px.bar(df, x=headers[0], y=headers[1] if len(headers) > 1 else None, title="Bar Chart") elif chart_type == "Line": fig = px.line(df, x=headers[0], y=headers[1] if len(headers) > 1 else None, title="Line Chart") elif chart_type == "Scatter": fig = px.scatter(df, x=headers[0], y=headers[1] if len(headers) > 1 else None, title="Scatter Plot") elif chart_type == "Pie": fig = px.pie(df, names=headers[0], values=headers[1] if len(headers) > 1 else None, title="Pie Chart") elif chart_type == "Area": fig = px.area(df, x=headers[0], y=headers[1] if len(headers) > 1 else None, title="Area Chart") elif chart_type == "Histogram": fig = px.histogram(df, x=headers[0], title="Histogram") if fig: fig.update_layout(template="plotly_dark", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", font_color="#eaf2fb") return fig.to_html(include_plotlyjs="cdn", config={"displayModeBar": False}) except Exception as exc: return f"Chart error: {exc}" return "Could not generate chart." def execute_python_code(code: str) -> str: banned = ["import os", "import sys", "import subprocess", "import socket", "__import__", "open(", "eval(", "exec(", "__builtins__"] code_lower = code.lower() for b in banned: if b in code_lower: return f"Security: '{b}' is not allowed." import sys as _sys from io import StringIO old_stdout = _sys.stdout _sys.stdout = mystdout = StringIO() old_stderr = _sys.stderr _sys.stderr = mystderr = StringIO() try: exec(code, {"__builtins__": {"print": print, "len": len, "range": range, "sum": sum, "min": min, "max": max, "sorted": sorted, "abs": abs, "str": str, "int": int, "float": float, "list": list, "dict": dict, "tuple": tuple, "set": set, "bool": bool, "enumerate": enumerate, "zip": zip, "map": map, "filter": filter, "type": type, "isinstance": isinstance, "hasattr": hasattr, "getattr": getattr, "setattr": setattr, "reversed": reversed, "any": any, "all": all, "round": round, "pow": pow, "divmod": divmod, "hex": hex, "oct": oct, "bin": bin, "ord": ord, "chr": chr, "repr": repr, "format": format, "True": True, "False": False, "None": None, "Exception": Exception, "ValueError": ValueError, "TypeError": TypeError, "IndexError": IndexError, "KeyError": KeyError, "ZeroDivisionError": ZeroDivisionError, "AttributeError": AttributeError, "ImportError": ImportError}, "__name__": "__main__"}) output = mystdout.getvalue() error = mystderr.getvalue() _sys.stdout = old_stdout _sys.stderr = old_stderr if error: return f"Output:\n{output}\nErrors:\n{error}" if output else f"Errors:\n{error}" return f"Output:\n{output}" if output else "Code executed with no output." except Exception as exc: _sys.stdout = old_stdout _sys.stderr = old_stderr return f"Error: {exc}" def generate_file_from_data(data_type: str, content: str) -> Tuple[bytes, str, str]: if data_type == "CSV" and pd: lines = [l.strip() for l in content.strip().split("\n") if l.strip()] if len(lines) >= 2: headers = lines[0].split(",") rows = [line.split(",") for line in lines[1:]] df = pd.DataFrame(rows, columns=headers) buf = io.BytesIO() df.to_csv(buf, index=False) return buf.getvalue(), "text/csv", "data.csv" elif data_type == "JSON": try: data = json.loads(content) return json.dumps(data, indent=2).encode("utf-8"), "application/json", "data.json" except Exception: pass elif data_type == "Excel" and pd: lines = [l.strip() for l in content.strip().split("\n") if l.strip()] if len(lines) >= 2: headers = lines[0].split(",") rows = [line.split(",") for line in lines[1:]] df = pd.DataFrame(rows, columns=headers) buf = io.BytesIO() with pd.ExcelWriter(buf, engine="openpyxl") as writer: df.to_excel(writer, index=False, sheet_name="Data") return buf.getvalue(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "data.xlsx" elif data_type == "Markdown": return content.encode("utf-8"), "text/markdown", "document.md" elif data_type == "HTML": return content.encode("utf-8"), "text/html", "document.html" return b"", "", "" def analyze_image(uploaded_file) -> str: client = get_genai_client() if client is None: return "No Gemini client configured. Add Google API key for image analysis." try: img_bytes = uploaded_file.getvalue() img_b64 = base64.b64encode(img_bytes).decode("utf-8") mime = uploaded_file.type or "image/jpeg" prompt = "Analyze this image in detail. Describe what you see, including objects, people, text, colors, composition, and any notable details." resp = client.models.generate_content( model="gemini-2.0-flash", contents=[prompt, types.Part.from_bytes(data=img_bytes, mime_type=mime)], config=types.GenerateContentConfig(max_output_tokens=1024, temperature=0.4), ) return getattr(resp, "text", "") or "No analysis generated." except Exception as exc: return f"Analysis error: {exc}" def export_chat(thread_id: str = None, format: str = "markdown") -> Tuple[str, str]: if thread_id is None: thread = active_thread() else: thread = st.session_state.threads.get(thread_id, active_thread()) lines = [] if format == "markdown": lines.append(f"# {thread['title']}") lines.append(f"*Created: {thread.get('created', '')}*") lines.append("") for m in thread["messages"]: role = "👤 User" if m["role"] == "user" else "🤖 NovaMind AI" lines.append(f"## {role}") lines.append(m["content"]) lines.append("") return "\n".join(lines), f"novamind_{thread_id or 'chat'}.md" elif format == "json": data = {"title": thread["title"], "created": thread.get("created", ""), "messages": thread["messages"]} return json.dumps(data, indent=2), f"novamind_{thread_id or 'chat'}.json" elif format == "text": for m in thread["messages"]: lines.append(f"{m['role'].upper()}: {m['content']}") lines.append("") return "\n".join(lines), f"novamind_{thread_id or 'chat'}.txt" return "", "" def search_conversations(query: str) -> List[Tuple[str, Dict]]: if not query.strip(): return list(st.session_state.threads.items()) q = query.lower() results = [] for tid, thread in st.session_state.threads.items(): if q in thread["title"].lower(): results.append((tid, thread)) continue for m in thread["messages"]: if q in m["content"].lower(): results.append((tid, thread)) break return results def safe_calculate(expr: str) -> str: if not re.fullmatch(r"[0-9\s\+\-\*\/\(\)\.\%]+", expr): return "Calculator only accepts numbers and math operators." try: return f"Calculator result: {eval(expr, {'__builtins__': {}}, {})}" except Exception as exc: return f"Calculator error: {exc}" def safe_python(code: str) -> str: return execute_python_code(code) def run_tool_command(prompt: str) -> Optional[str]: lower = prompt.strip().lower() if lower.startswith("/calc "): return safe_calculate(prompt[6:]) if lower.startswith("/python "): return safe_python(prompt[8:]) if lower.startswith("/code "): return execute_python_code(prompt[6:]) if lower.startswith("/web "): ctx = fetch_web_context(prompt[5:], limit=5) return ctx if ctx else "No web context found." if lower.startswith("/search "): results = search_duckduckgo(prompt[8:], max_results=5) if results: return "## Web Search Results\n\n" + "\n\n".join(f"### {r['title']}\n{r['body'][:300]}\n[Link]({r['href']})" for r in results if r.get("title")) return "No results found." if lower.startswith("/youtube "): results = search_youtube(prompt[9:], limit=5) if results: return "## YouTube Results\n\n" + "\n".join(f"- [{r['title']}]({r['link']}) by {r.get('channel', '')}" for r in results) return "No results found." if lower.startswith("/chart ") or lower.startswith("/plot "): parts = prompt.split("\n", 1) first_line = parts[0].strip() chart_type = first_line.split(" ", 1)[1] if " " in first_line else "Bar" valid_types = ["Bar", "Line", "Scatter", "Pie", "Area", "Histogram"] if chart_type not in valid_types: chart_type = "Bar" data_str = parts[1] if len(parts) > 1 else "x,y\n1,2\n3,4" chart_html = generate_chart(chart_type, data_str) return f"## Chart ({chart_type})\n\n```html\n{chart_html}\n```" return None def build_system_prompt(persona: str, custom_system: str, use_web: bool, use_knowledge: bool) -> str: base = PERSONA_PROMPTS.get(persona, PERSONA_PROMPTS["Elite Engineer"]) add = [] if use_web: add.append("Use web context if present, cite source titles.") if use_knowledge: add.append("Use knowledge context from user documents when relevant.") if custom_system.strip(): add.append(custom_system.strip()) return "\n".join([base] + add) def multi_provider_generate(provider: str, model: str, system_prompt: str, user_prompt: str, settings: Dict, uploaded_file) -> str: knowledge_context = retrieve_knowledge(user_prompt, top_k=settings["knowledge_k"]) if settings["knowledge"] else "" web_context = fetch_web_context(user_prompt, limit=settings["web_results"]) if settings["web"] and st.session_state.privacy["allow_web_context"] else "" convo = active_thread()["messages"] convo_text = "\n".join(f"{'User' if m['role']=='user' else 'Assistant'}: {m['content']}" for m in convo[-(settings["memory_turns"] * 2):]) user_block = f"Conversation context:\n{convo_text}\n\nUser request:\n{user_prompt}" if knowledge_context: user_block += f"\n\n{knowledge_context}" if web_context: user_block += f"\n\n{web_context}" max_tokens = settings.get("max_tokens", 2048) temperature = settings.get("temperature", 0.7) if provider == "Gemini (Google)": client = get_genai_client() if client is None: return "__NOVAMIND_FALLBACK__" cfg = types.GenerateContentConfig(system_instruction=system_prompt, temperature=temperature, top_p=settings.get("top_p", 0.9), max_output_tokens=max_tokens) parts = [] if uploaded_file is not None: try: parts.append(types.Part.from_bytes(data=uploaded_file.getvalue(), mime_type=uploaded_file.type or "application/octet-stream")) except Exception: pass parts.append(user_block) final_text = "" try: if settings.get("stream", True): holder = st.empty() for chunk in client.models.generate_content_stream(model=model, contents=parts, config=cfg): piece = getattr(chunk, "text", "") or "" if piece: final_text += piece holder.markdown(final_text) else: resp = client.models.generate_content(model=model, contents=parts, config=cfg) final_text = getattr(resp, "text", "") or "" except Exception as exc: err = str(exc) if is_invalid_key_error(err): st.session_state.provider_health["google"] = "invalid" return "__NOVAMIND_FALLBACK__" if is_quota_error(err): st.session_state.provider_health["google"] = "quota" return "__NOVAMIND_FALLBACK__" st.session_state.provider_health["google"] = "error" return "__NOVAMIND_FALLBACK__" return final_text or "No response." elif provider == "OpenAI": client = get_openai_client() if client is None: return "__NOVAMIND_FALLBACK__" try: messages = [{"role": "system", "content": system_prompt}] if convo_text.strip(): messages.append({"role": "user", "content": user_block}) else: messages.append({"role": "user", "content": user_prompt}) resp = client.chat.completions.create(model=model, messages=messages, max_tokens=max_tokens, temperature=temperature) return resp.choices[0].message.content or "No response." except Exception as exc: st.session_state.provider_health["openai"] = "error" return "__NOVAMIND_FALLBACK__" elif provider == "Anthropic (Claude)": client = get_anthropic_client() if client is None: return "__NOVAMIND_FALLBACK__" try: resp = client.messages.create(model=model, system=system_prompt, messages=[{"role": "user", "content": user_block}], max_tokens=max_tokens, temperature=temperature) return resp.content[0].text if resp.content else "No response." except Exception as exc: st.session_state.provider_health["anthropic"] = "error" return "__NOVAMIND_FALLBACK__" return "__NOVAMIND_FALLBACK__" def quality_score(text: str) -> int: score = 40 if len(text) > 300: score += 15 if len(text) > 900: score += 10 if any(h in text for h in ["1.", "2.", "- "]): score += 12 if "```" in text: score += 8 if "summary" in text.lower() or "next steps" in text.lower(): score += 10 return min(100, score) def auto_refine_if_needed(text: str, model: str, settings: Dict) -> str: client = get_genai_client() if not settings["auto_refine"] or client is None or quality_score(text) >= settings["min_quality"]: return text try: cfg = types.GenerateContentConfig(temperature=min(1.0, settings["temperature"] + 0.1), top_p=settings["top_p"], max_output_tokens=settings["max_tokens"]) resp = client.models.generate_content(model=model, contents=f"Improve the response below for clarity, depth, structure, and practical usefulness.\n\nOriginal:\n{text}", config=cfg) improved = getattr(resp, "text", "") or "" return improved.strip() if improved.strip() else text except Exception: return text def transcribe_speech(audio_bytes: bytes) -> str: client = get_openai_client() if client is None: return "OpenAI client not configured for speech-to-text." try: import tempfile with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp: tmp.write(audio_bytes) tmp_path = tmp.name with open(tmp_path, "rb") as f: transcript = client.audio.transcriptions.create(model="whisper-1", file=f) os.unlink(tmp_path) return transcript.text except Exception as exc: return f"Transcription error: {exc}" # ─── UI Panels ────────────────────────────────────────────────────────────── def account_panel() -> None: st.markdown("
Pick your style and personalize the experience.
Choose your primary AI engine. Fallbacks will be used automatically if the primary fails.
🚀 NovaMind AI Studio v{APP_VERSION}
Multi-provider AI workspace with chat, image, music, voice, code, and tools. {st.session_state.get('ai_provider', 'Gemini')}{st.session_state.mode}v{APP_VERSION}