nvidiaclass2 / app.py
qwendashcoder's picture
Update app.py
0bdb794 verified
Raw History Blame Contribute Delete
22.7 kB
import os
import time
from openai import OpenAI
import gradio as gr
from auth import register_user, login_user, logout_user, save_chats, delete_account
from response_modes import chat_stream
import mymsg
from retry import retry # Add to requirements.txt: retry
# ================== SETTINGS ==================
API_KEY = os.environ.get("API_KEY")
HF_TOKEN = os.environ.get("HF_TOKEN2")
username = os.environ.get("username")
password = os.environ.get("password")
USER_DATA_REPO = "qwendashcoder/fluade_1"
auth_globals = {"hf_token": HF_TOKEN, "user_repo": USER_DATA_REPO}
if not HF_TOKEN:
raise ValueError("HF_TOKEN secret not set in HF Spaces settings!")
if not API_KEY:
print("WARNING: API_KEY not setβ€”model loading will fail!") # Log for debugging
client = OpenAI(base_url="https://integrate.api.nvidia.com/v1", api_key=API_KEY)
# Model list (deduplicated, sorted, removed potential invalids)
model_choices = sorted(set([
"meta/llama-3.1-405b-instruct", "meta/llama-3.1-70b-instruct", "meta/llama-3.1-8b-instruct",
"google/gemma-2-27b-it", "google/gemma-2-9b-it", "google/codegemma-7b-it",
"mistralai/mistral-large-3-675b-instruct-2512",
"mistralai/mixtral-8x22b-v0.1", "mistralai/devstral-2-123b-instruct-2512",
"qwen/qwen3-coder-480b-a35b-instruct", "qwen/qwen3.5-397b-a17b", "qwen/qwen3-235b-a22b"
]))
@retry(tries=2, delay=1) # Retry on transient failures
def load_model(selected_model):
print(f"[DEBUG] Loading model: {selected_model}") # Log to confirm function call
try:
# Test with timeout and stream=False
test = client.chat.completions.create(
model=selected_model,
messages=[{"role": "user", "content": "Say 'test'"}],
max_tokens=5,
stream=False,
timeout=15 # Added: Prevent hangs
)
response = test.choices[0].message.content.strip()
print(f"[DEBUG] Model response: {response}") # Log success
if response:
gr.Info("βœ… Model loaded successfully!", duration=5) # Longer duration
return selected_model, f"**βœ… Loaded & Ready: {selected_model}**"
except Exception as e: # Catch all, but log specifics
error_msg = str(e)
print(f"[ERROR] Model load failed: {error_msg}") # Log for HF Spaces debugging
if "authentication" in error_msg.lower():
error_msg = "Invalid or missing API_KEYβ€”check HF Secrets!"
elif "timeout" in error_msg.lower():
error_msg = "API timeoutβ€”try again or check NVIDIA status."
elif "not found" in error_msg.lower():
error_msg = "Model not available on NVIDIAβ€”select another."
gr.Warning(f"❌ Failed to load model: {error_msg}", duration=5)
return None, f"**❌ Unavailable: {error_msg}**"
# ================== MULTI-CHAT HELPERS ==================
def create_new_chat(user_chats, logged_in_username, coding_files):
if not logged_in_username:
return user_chats, None, [], gr.update(choices=[], value=None), "⚠️ Please login first!", ""
new_num = len(user_chats) + 1
new_title = f"Chat {new_num}"
new_chat = {"title": new_title, "history": [], "context": ""} # Added: Initialize empty context
new_user_chats = user_chats + [new_chat]
choices = [chat["title"] for chat in new_user_chats]
save_chats(logged_in_username, new_user_chats, auth_globals) # Persist changes
return new_user_chats, new_title, [], gr.update(choices=choices, value=new_title), f"βœ… New chat created: {new_title}", ""
def switch_chat(selected_title, user_chats, coding_files):
if not selected_title or not user_chats:
return [], None, ""
for chat in user_chats:
if chat["title"] == selected_title:
return chat["history"], selected_title, chat.get("context", "")
return [], None, ""
def delete_current_chat(current_title, user_chats, logged_in_username, coding_files):
if not logged_in_username or not current_title:
return user_chats, current_title, [], gr.update(), "⚠️ Nothing to delete!", ""
new_user_chats = [chat for chat in user_chats if chat["title"] != current_title]
save_chats(logged_in_username, new_user_chats, auth_globals) # Persist changes
choices = [chat["title"] for chat in new_user_chats]
# Inside delete_current_chat, replace ONLY the new_history part:
if new_user_chats:
new_title = new_user_chats[-1]["title"]
new_history = new_user_chats[-1]["history"] # ← direct dict history
new_context = new_user_chats[-1].get("context", "")
else:
new_title = None
new_history = []
new_context = ""
return new_user_chats, new_title, new_history, gr.update(choices=choices, value=new_title), f"πŸ—‘οΈ Deleted {current_title}", new_context
def save_chat_context(new_context, user_chats, current_chat_title, logged_in_username):
if not logged_in_username or not current_chat_title:
return user_chats, "⚠️ No chat selected!"
if len(new_context) > 2000:
return user_chats, "⚠️ Context too long (max 2000 chars)!"
for chat in user_chats:
if chat["title"] == current_chat_title:
chat["context"] = new_context
break
save_chats(logged_in_username, user_chats, auth_globals)
return user_chats, "βœ… Context saved!"
def enforce_cooldown(last_time):
now = time.time()
if now - last_time < 3:
gr.Warning("⏳ Please wait 3 seconds before clicking again.")
return False, last_time
return True, now
def delete_last_message(user_chats, current_title, logged_in_username, auth_globals):
if not current_title or not user_chats:
return user_chats, "Nothing to delete"
for chat in user_chats:
if chat["title"] == current_title and len(chat["history"]) >= 2:
chat["history"] = chat["history"][:-2] # remove last user + assistant
break
save_chats(logged_in_username, user_chats, auth_globals)
return user_chats, "πŸ—‘οΈ Last message deleted"
def edit_last_prompt(user_chats, current_title):
if not current_title or not user_chats:
return "", "No previous prompt"
for chat in user_chats:
if chat["title"] == current_title and len(chat["history"]) >= 2:
last_user_msg = chat["history"][-2]["content"]
return last_user_msg, "Ready to edit & re-run"
return "", "No previous prompt"
def update_token_display(user_chats, current_title):
total = 0
for chat in user_chats or []:
if chat["title"] == current_title:
for msg in chat.get("history", []):
total += msg.get("tokens", 0)
return f"**Tokens used this chat:** {total:,}"
# ================== HELPER: No conversion needed (Gradio expects messages format) ==================
def format_history_for_chatbot(history):
"""Return history as-is (already dict format)"""
return history or []
def refresh_file_list(user_chats, current_title):
if not current_title or not user_chats:
return gr.update(choices=[], value=None)
for chat in user_chats:
if chat["title"] == current_title:
files = list(chat.get("coding_files", {}).keys())
return gr.update(choices=files, value=files[0] if files else None)
return gr.update(choices=[], value=None)
def load_file_to_editor(selected_file, user_chats, current_title):
if not selected_file or not current_title:
return "", "python"
for chat in user_chats:
if chat["title"] == current_title:
f = chat.get("coding_files", {}).get(selected_file, {})
return f.get("content", ""), f.get("language", "python")
return "", "python"
def save_file_content(selected_file, new_content, new_language, user_chats, current_title, logged_in_username, auth_globals):
if not selected_file or not current_title or not logged_in_username:
return user_chats, "❌ No file selected"
for chat in user_chats:
if chat["title"] == current_title:
if "coding_files" not in chat:
chat["coding_files"] = {}
chat["coding_files"][selected_file] = {"content": new_content, "language": new_language}
break
save_chats(logged_in_username, user_chats, auth_globals)
return user_chats, f"βœ… Saved {selected_file}"
def add_uploaded_files(uploaded_files, user_chats, current_title, logged_in_username, auth_globals):
if not uploaded_files or not current_title:
return user_chats, gr.update(), "❌ No chat selected"
for chat in user_chats:
if chat["title"] == current_title:
if "coding_files" not in chat:
chat["coding_files"] = {}
for path in uploaded_files if isinstance(uploaded_files, list) else [uploaded_files]:
name = os.path.basename(path)
with open(path, "r", encoding="utf-8") as f:
content = f.read()
lang = "python" if name.endswith(".py") else "javascript" if name.endswith(".js") else "html" if name.endswith(".html") else "text"
chat["coding_files"][name] = {"content": content, "language": lang}
break
save_chats(logged_in_username, user_chats, auth_globals)
return user_chats, refresh_file_list(user_chats, current_title), "βœ… Files added to persistent storage"
def delete_selected_file(selected_file, user_chats, current_title, logged_in_username, auth_globals):
if not selected_file or not current_title:
return user_chats, gr.update(), "❌ Nothing to delete"
for chat in user_chats:
if chat["title"] == current_title and "coding_files" in chat:
chat["coding_files"].pop(selected_file, None)
break
save_chats(logged_in_username, user_chats, auth_globals)
return user_chats, refresh_file_list(user_chats, current_title), f"πŸ—‘οΈ Deleted {selected_file}"
# ================== CHAT HANDLER WRAPPER (with file limit) ==================
def handle_chat(msg, chatbot, current_model, logged_in_username, user_chats_state, current_chat_title, mode_radio, auth_globals_state, file_upload, current_chat_context):
if not current_model:
gr.Warning("⚠️ Load a model first!")
return chatbot
# 3-second cooldown
ok, new_time = enforce_cooldown(last_interaction_time.value)
if not ok:
return chatbot
last_interaction_time.value = new_time
is_generating.value = True
stop_btn.visible = True
internal_history = chatbot[:] if chatbot else []
try:
stream_gen = chat_stream(
msg, internal_history, current_model, logged_in_username,
user_chats_state, current_chat_title, mode_radio,
auth_globals_state, file_upload, current_chat_context
)
for updated_history in stream_gen:
yield updated_history # ← This makes streaming work
except Exception as e:
gr.Warning(f"⚠️ Error: {str(e)[:250]}")
yield chatbot
finally:
is_generating.value = False
stop_btn.visible = False
# Must return the chatbot at the end (Gradio requirement)
return chatbot
# ================== GUI ==================
with gr.Blocks(title="NVIDIA AI Chat") as demo:
gr.Markdown("# 🟒 NVIDIA AI Chat\n**School Project β€’ Pre-approved usernames only β€’ Max 20 Accounts globally**")
gr.Markdown(f"{mymsg.welcome_caution()}")
with gr.Accordion("πŸ“– How to Use", open=True):
gr.Markdown(f"{mymsg.welcome_message(0)}")
current_model = gr.State(None)
logged_in_username = gr.State(None)
user_chats_state = gr.State([])
current_chat_title = gr.State(None)
current_chat_context = gr.State("") # Added: State for current chat's context
auth_globals_state = gr.State(auth_globals)
coding_files_state = gr.State({}) # { "filename.py": {"content": "...", "language": "python"} }
current_file_name = gr.State(None)
last_interaction_time = gr.State(0.0) # for 3-second cooldown
is_generating = gr.State(False) # for Stop button
status_label = gr.Markdown("⚠️ No model loaded. Login + create/select a chat to begin.", elem_id="status-bold") # Made more prominent
with gr.Row():
with gr.Column(scale=1, min_width=200):
gr.Markdown("### πŸ“‹ Your Chats")
new_chat_btn = gr.Button("βž• New Chat", variant="primary", size="large")
delete_chat_btn = gr.Button("πŸ—‘οΈ Delete Current", variant="stop", size="large")
chat_dropdown = gr.Dropdown(choices=[], label="Switch Chat", interactive=True, value=None)
with gr.Column(scale=4):
with gr.Row():
model_dropdown = gr.Dropdown(choices=model_choices, value=model_choices[0], label="🎯 Select Model", interactive=True, scale=4)
load_btn = gr.Button("Load & Test Model", variant="primary", scale=1)
mode_radio = gr.Radio(
choices=["Default Chat", "Fast Search", "Deep Think", "πŸ’» Coding Mode"],
value="Default Chat",
label="🧠 Response Mode",
interactive=True
)
chatbot = gr.Chatbot(height=480, buttons=["copy", "copy_all"])
with gr.Row(): # Added: Context editor section
chat_context_tb = gr.Textbox(label="Instructions - Additional instruction for each prompt (saved per chat)",
lines=3, max_lines=10,
placeholder="e.g. 'Use Filipino and be funny' or 'Always answer in bullet points and be super concise'",
interactive=True, visible=False)
save_context_btn = gr.Button("πŸ’Ύ Save Context", variant="secondary", visible=False)
# ================== CODING MODE FILE MANAGER (visible only in Coding Mode) ==================
with gr.Row(visible=False) as coding_manager_row:
with gr.Column(scale=1, min_width=280):
gr.Markdown("### πŸ“ Persistent Project Files\n(saved per chat β€’ only in Coding Mode)")
file_upload_coding = gr.File(
label="Upload files",
file_types=[".py",".js",".html",".css",".txt",".md",".json",".ts"],
file_count="multiple",
type="filepath"
)
add_files_btn = gr.Button("βž• Add / Replace files", variant="primary")
file_list_dropdown = gr.Dropdown(choices=[], label="Select file to edit", interactive=True)
delete_file_btn = gr.Button("πŸ—‘οΈ Delete selected file", variant="stop", size="sm")
with gr.Column(scale=4):
file_editor = gr.Code(
label="Editor",
value="",
language="python",
lines=24,
interactive=True,
show_label=True
)
with gr.Row():
language_selector = gr.Dropdown(
choices=["python", "javascript", "typescript", "html", "css", "markdown", "json", "text"],
value="python",
label="Language",
scale=1
)
save_file_btn = gr.Button("πŸ’Ύ Save file", variant="primary", scale=2)
with gr.Row():
msg = gr.Textbox(placeholder="Ask anything...", show_label=False, scale=9)
file_upload = gr.File(
label="Attach up to 5 files (.txt, .pdf, .docx)",
file_types=[".txt", ".pdf", ".docx"],
file_count="multiple",
type="filepath"
)
send_btn = gr.Button("➀", variant="primary", scale=1)
with gr.Row():
delete_last_btn = gr.Button("πŸ—‘οΈ Delete Last Message", variant="stop", size="sm")
edit_last_btn = gr.Button("✏️ Edit & Re-run Last Prompt", variant="secondary", size="sm")
stop_btn = gr.Button("⏹️ Stop Generation", variant="stop", visible=False)
# Token display (optional but useful)
token_display = gr.Markdown("**Tokens used this chat:** 0", visible=False)
# Authentication (clean version - no phone/OTP)
with gr.Row():
username_input = gr.Textbox(label="Username (will be forced lowercase)", scale=2, placeholder="admin")
password_input = gr.Textbox(label="Password", type="password", scale=2)
with gr.Row():
register_btn = gr.Button("Register", variant="secondary")
login_btn = gr.Button("Login", variant="primary")
logout_btn = gr.Button("Logout", visible=False, variant="stop")
with gr.Accordion("⚠️ Danger Zone", open=False):
delete_acc_btn = gr.Button("πŸ—‘οΈ Delete My Account", variant="stop", size="large", visible=False)
with gr.Row(visible=False) as confirm_row:
confirm_username_input = gr.Textbox(label="Type exact username to confirm", placeholder="yourusername")
confirm_delete_btn = gr.Button("YES, DELETE FOREVER", variant="stop")
# ================== EVENTS ==================
load_btn.click(load_model, model_dropdown, [current_model, status_label])
# New Chat
new_chat_btn.click(
create_new_chat,
[user_chats_state, logged_in_username],
[user_chats_state, current_chat_title, chatbot, chat_dropdown, status_label, current_chat_context]
)
# Delete Current Chat
delete_chat_btn.click(
delete_current_chat,
[current_chat_title, user_chats_state, logged_in_username],
[user_chats_state, current_chat_title, chatbot, chat_dropdown, status_label, current_chat_context]
)
# Switch Chat
chat_dropdown.change(
switch_chat,
[chat_dropdown, user_chats_state],
[chatbot, current_chat_title, current_chat_context]
)
register_btn.click(
register_user,
[username_input, password_input, logged_in_username, auth_globals_state],
[user_chats_state, current_chat_title, chatbot, chat_dropdown, logged_in_username, status_label, logout_btn, delete_acc_btn, confirm_row, chat_context_tb, save_context_btn]
).then(lambda: gr.Info("βœ… Registration successful!", duration=4), None, None).then(lambda: ("",""), None, [username_input, password_input])
login_btn.click(
login_user,
[username_input, password_input, logged_in_username, auth_globals_state],
[user_chats_state, current_chat_title, chatbot, chat_dropdown, logged_in_username, status_label, logout_btn, delete_acc_btn, confirm_row, chat_context_tb, save_context_btn]
).then(lambda: gr.Info("βœ… Login successful!", duration=4), None, None).then(lambda: ("",""), None, [username_input, password_input])
logout_btn.click(logout_user, [], [user_chats_state, current_chat_title, chatbot, chat_dropdown, logged_in_username, status_label, logout_btn, delete_acc_btn, confirm_row, chat_context_tb, save_context_btn])
delete_acc_btn.click(lambda: gr.update(visible=True), None, confirm_row)
confirm_delete_btn.click(delete_account, [confirm_username_input, logged_in_username, auth_globals_state], [status_label, logged_in_username, logout_btn, delete_acc_btn, confirm_row, user_chats_state, current_chat_title, chatbot, chat_dropdown, chat_context_tb, save_context_btn])
save_context_btn.click(save_chat_context, [chat_context_tb, user_chats_state, current_chat_title, logged_in_username], [user_chats_state, status_label])
# Cooldown + action buttons
delete_last_btn.click(
delete_last_message,
[user_chats_state, current_chat_title, logged_in_username, auth_globals_state],
[user_chats_state, status_label]
).then(
lambda: gr.update(visible=True), None, token_display
).then(update_token_display, [user_chats_state, current_chat_title], token_display)
edit_last_btn.click(
edit_last_prompt,
[user_chats_state, current_chat_title],
[msg, status_label]
)
stop_btn.click(
lambda: (False, gr.update(visible=False)),
None,
[is_generating, stop_btn]
)
# Show token display in Coding Mode and after first message
mode_radio.change(
lambda m: gr.update(visible=(m == "πŸ’» Coding Mode")),
mode_radio,
token_display
)
# Send button + Enter
# Enter key
msg.submit(
handle_chat,
[msg, chatbot, current_model, logged_in_username, user_chats_state,
current_chat_title, mode_radio, auth_globals_state, file_upload, current_chat_context],
chatbot
).then(lambda: "", None, msg) \
.then(update_token_display, [user_chats_state, current_chat_title], token_display)
# Send button
send_btn.click(
handle_chat,
[msg, chatbot, current_model, logged_in_username, user_chats_state,
current_chat_title, mode_radio, auth_globals_state, file_upload, current_chat_context],
chatbot
).then(lambda: "", None, msg) \
.then(update_token_display, [user_chats_state, current_chat_title], token_display)
# File manager events
add_files_btn.click(
add_uploaded_files,
[file_upload_coding, user_chats_state, current_chat_title, logged_in_username, auth_globals_state],
[user_chats_state, file_list_dropdown, status_label]
)
file_list_dropdown.change(
load_file_to_editor,
[file_list_dropdown, user_chats_state, current_chat_title],
[file_editor, language_selector]
)
save_file_btn.click(
save_file_content,
[file_list_dropdown, file_editor, language_selector, user_chats_state, current_chat_title, logged_in_username, auth_globals_state],
[user_chats_state, status_label]
).then(refresh_file_list, [user_chats_state, current_chat_title], file_list_dropdown)
delete_file_btn.click(
delete_selected_file,
[file_list_dropdown, user_chats_state, current_chat_title, logged_in_username, auth_globals_state],
[user_chats_state, file_list_dropdown, status_label]
)
language_selector.change(
lambda lang: gr.update(language=lang),
language_selector,
file_editor
)
# Show/hide file manager when mode changes
mode_radio.change(
lambda m: gr.update(visible=(m == "πŸ’» Coding Mode")),
mode_radio,
coding_manager_row
).then(
refresh_file_list,
[user_chats_state, current_chat_title],
file_list_dropdown
)
demo.launch(auth=(username, password), theme=gr.themes.Soft(), server_name="0.0.0.0", debug=True, show_error=True)