import base64
import os
import uuid
import time
import gradio as gr
from gradio_client import utils as client_utils
from config import DEFAULT_SYS_PROMPT, MODEL, api_key, base_url
from openai import OpenAI
# Initialize OpenAI client
client = OpenAI(
api_key=api_key,
base_url=base_url,
)
def encode_file_to_base64(file_path):
"""Encode file to base64 data URL."""
with open(file_path, "rb") as file:
mime_type = client_utils.get_mimetype(file_path)
base64_data = base64.b64encode(file.read()).decode("utf-8")
return f"data:{mime_type};base64,{base64_data}"
def is_video_file(file_path):
"""Check if file is a video."""
video_extensions = {'.mp4', '.avi', '.mov', '.mkv', '.webm', '.flv', '.wmv'}
ext = os.path.splitext(file_path)[1].lower()
return ext in video_extensions
def is_image_file(file_path):
"""Check if file is an image."""
image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'}
ext = os.path.splitext(file_path)[1].lower()
return ext in image_extensions
def format_messages(history, sys_prompt=None):
"""Format chat history to OpenAI message format."""
messages = [{
"role": "system",
"content": sys_prompt or DEFAULT_SYS_PROMPT,
}]
for item in history:
if item["role"] == "user":
content_parts = []
# Add files (images and videos)
if item.get("files"):
for file_path in item["files"]:
if file_path.startswith("http"):
# URL - determine type by extension
if is_video_file(file_path):
content_parts.append({
"type": "video_url",
"video_url": {"url": file_path}
})
else:
content_parts.append({
"type": "image_url",
"image_url": {"url": file_path}
})
elif os.path.exists(file_path):
data_url = encode_file_to_base64(file_path)
if is_video_file(file_path):
content_parts.append({
"type": "video_url",
"video_url": {"url": data_url}
})
else:
content_parts.append({
"type": "image_url",
"image_url": {"url": data_url}
})
# Add text
if item.get("content"):
content_parts.append({
"type": "text",
"text": item.get("content", "")
})
messages.append({
"role": "user",
"content": content_parts if len(content_parts) > 1 else item.get("content", "")
})
elif item["role"] == "assistant":
content = item.get("content", "").strip()
if content:
messages.append({
"role": "assistant",
"content": content
})
return messages
def chat_stream(messages, model, enable_thinking=True):
"""Stream chat completion from API."""
try:
kwargs = {
"model": model,
"messages": messages,
"stream": True,
"top_p": 0.95,
}
if enable_thinking:
kwargs["temperature"] = 1
kwargs["extra_body"] = {"thinking": {"type": "enabled"}}
else:
kwargs["temperature"] = 0.6
kwargs["extra_body"] = {"thinking": {"type": "disabled"}}
print(f"[DEBUG] enable_thinking={enable_thinking}, model={model}")
response = client.chat.completions.create(**kwargs)
for chunk in response:
if chunk and chunk.choices:
delta = chunk.choices[0].delta
if hasattr(delta, 'content') and delta.content:
yield delta.content, None
elif hasattr(delta, 'reasoning_content') and delta.reasoning_content:
yield None, delta.reasoning_content
except Exception as e:
yield f"\n\n**Error:** {str(e)}", None
# Global state
chat_history = []
conversations = {}
current_conversation_id = None
enable_thinking = True
uploaded_files = []
def create_new_conversation():
"""Create a new conversation."""
global current_conversation_id, chat_history, enable_thinking, uploaded_files
current_conversation_id = str(uuid.uuid4())
chat_history = []
uploaded_files = []
conversations[current_conversation_id] = {
"history": chat_history,
"enable_thinking": True
}
enable_thinking = True
return [], None, gr.update(value="β‘ Thinking: ON")
def toggle_thinking():
"""Toggle thinking mode."""
global enable_thinking
enable_thinking = not enable_thinking
if current_conversation_id and current_conversation_id in conversations:
conversations[current_conversation_id]["enable_thinking"] = enable_thinking
if enable_thinking:
return gr.update(value="β‘ Thinking: ON")
else:
return gr.update(value="π€ Thinking: OFF")
def format_chat_display(history):
"""Format history for Gradio Chatbot display."""
messages = []
for item in history:
if item["role"] == "user":
content_parts = []
# Add files first
if item.get("files"):
for f in item["files"]:
if is_image_file(f):
content_parts.append({"type": "image", "path": f})
else:
content_parts.append({"type": "text", "text": f"π₯ {os.path.basename(f)}"})
# Add text
if item.get("content"):
content_parts.append({"type": "text", "text": item["content"]})
if len(content_parts) == 1 and content_parts[0].get("type") == "text":
messages.append({"role": "user", "content": content_parts[0]["text"]})
else:
messages.append({"role": "user", "content": content_parts})
elif item["role"] == "assistant":
messages.append({"role": "assistant", "content": item.get("content", "")})
return messages
def handle_upload(files):
"""Handle file upload."""
global uploaded_files
if files:
if isinstance(files, list):
for f in files:
if isinstance(f, dict) and 'name' in f:
uploaded_files.append(f['name'])
elif isinstance(f, str):
uploaded_files.append(f)
elif isinstance(files, dict) and 'name' in files:
uploaded_files.append(files['name'])
elif isinstance(files, str):
uploaded_files.append(files)
# Return preview text
if uploaded_files:
preview = "π " + ", ".join([os.path.basename(p) for p in uploaded_files[-3:]])
if len(uploaded_files) > 3:
preview += f" (+{len(uploaded_files) - 3} more)"
return preview
return ""
def user_message(message, files):
"""Add user message to history."""
global chat_history, current_conversation_id, uploaded_files
if not current_conversation_id:
create_new_conversation()
# Handle newly uploaded files
if files:
for f in files:
if isinstance(f, str) and f not in uploaded_files:
uploaded_files.append(f)
chat_history.append({
"role": "user",
"content": message,
"files": uploaded_files.copy()
})
conversations[current_conversation_id]["history"] = chat_history
conversations[current_conversation_id]["enable_thinking"] = enable_thinking
# Clear uploaded files after sending
uploaded_files = []
return format_chat_display(chat_history), "", None
def bot_response():
"""Generate bot response."""
global chat_history
if not chat_history or chat_history[-1]["role"] != "user":
return format_chat_display(chat_history)
messages = format_messages(chat_history)
model = MODEL
reasoning_text = ""
answer_text = ""
is_thinking = False
assistant_msg = {"role": "assistant", "content": ""}
chat_history.append(assistant_msg)
try:
for content, reasoning in chat_stream(messages, model, enable_thinking):
if reasoning:
reasoning_text += reasoning
is_thinking = True
if content:
answer_text += content
# Build response with thinking section
final_response = ""
if is_thinking and reasoning_text.strip():
if content:
# Thinking done, show in collapsed details
final_response += f"""π§ Thought for a moment...