import gradio as gr
import torch
from typing import List, Dict, Generator, Tuple, Optional, Any
from codon.motif import MotifA1, MotifA1Tokenizer
from codon.utils.generate import chat, ChatChunk
from codon.utils.tokens import PackedTokenizer
LOCALIZATION = {
'English': {
'title': 'Motif-A1 Interactive Web Demo',
'subtitle': 'Explore the experimental 105.41M reasoning language model from the CodonProject. Built-in with native Chain-of-Thought (CoT) support.',
'model_control': 'Model Status',
'status_not_loaded': 'Loading model... Please wait...',
'status_loading': 'Loading model... Please wait...',
'status_loaded': 'Model and tokenizer successfully loaded!',
'gen_params': 'Generation Parameters',
'temp_label': 'Temperature',
'temp_info': 'Higher values increase creativity, lower values increase precision.',
'max_tokens_label': 'Max New Tokens',
'max_tokens_info': 'Maximum number of generated tokens.',
'top_p_label': 'Top P (Nucleus Sampling)',
'top_p_info': 'Set to 1.0 to disable. Keep below 1.0 for standard nucleus sampling.',
'top_k_label': 'Top K Sampling',
'top_k_info': 'Set to 0 to disable. Standard filter limit.',
'persona': 'Persona Setting',
'sys_prompt_label': 'System Prompt',
'sys_prompt_val': 'You are Motif-A1, an intelligent AI assistant developed by CodonProject. Think carefully step-by-step.',
'sys_prompt_placeholder': 'Type custom behavior or persona guidelines here...',
'chatbot_label': 'Motif-A1 Conversation Window',
'user_input_placeholder': 'Type your prompt here and press Enter (or click Send)...',
'submit_btn': 'Send',
'stop_btn': 'Stop',
'clear_btn': 'Clear History',
'thinking_active': 'Thinking Process (thinking...)',
'thinking_collapsed': 'Thinking Process (collapsed)',
'warning_not_loaded': 'Model is not loaded yet! Attempting auto-loading...',
'error_load_failed': 'Error: Model failed to load automatically.',
'error_gen_failed': 'Generation Error:'
},
'中文': {
'title': 'Motif-A1 交互式网页演示',
'subtitle': '探索由 CodonProject 开发的实验性 105.41M 推理大语言模型。内置原生思维链 (CoT) 支持。',
'model_control': '模型状态',
'status_not_loaded': '正在加载模型,请稍候...',
'status_loading': '正在加载模型,请稍候...',
'status_loaded': '模型和分词器已成功加载!',
'gen_params': '生成参数',
'temp_label': '温度 (Temperature)',
'temp_info': '较高的值增加创造力,较低的值提高精准度。',
'max_tokens_label': '最大生成长度 (Max New Tokens)',
'max_tokens_info': '生成文本的最大 Token 数量。',
'top_p_label': 'Top P (核采样)',
'top_p_info': '设置为 1.0 以禁用。保持小于 1.0 进行核采样。',
'top_k_label': 'Top K 采样',
'top_k_info': '设置为 0 以禁用。标准过滤器限制。',
'persona': '角色设定',
'sys_prompt_label': '系统提示词',
'sys_prompt_val': '你是由 CodonProject 开发的智能 AI 助手 Motif-A1。请一步步仔细思考。',
'sys_prompt_placeholder': '在此处输入自定义行为或角色设定指南...',
'chatbot_label': 'Motif-A1 对话窗口',
'user_input_placeholder': '在此输入您的内容并按回车(或点击发送)...',
'submit_btn': '发送',
'stop_btn': '停止',
'clear_btn': '清空历史',
'thinking_active': '思考过程 (思考中...)',
'thinking_collapsed': '思考过程 (已折叠)',
'warning_not_loaded': '模型尚未加载!正在尝试自动加载...',
'error_load_failed': '错误:模型自动加载失败。',
'error_gen_failed': '生成错误:'
}
}
class ModelManager:
'''
Manager class to handle model and tokenizer loading and generation.
Attributes:
model (Optional[MotifA1]): The language model instance.
tokenizer (Optional[PackedTokenizer]): The tokenizer instance.
device (torch.device): The device used for computation.
'''
def __init__(self) -> None:
self.model: Optional[MotifA1] = None
self.tokenizer: Optional[PackedTokenizer] = None
self.device: torch.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
def load_model(self) -> str:
'''
Loads the model and tokenizer from remote source.
Returns:
str: Loading status message.
'''
try:
self.model = MotifA1().from_remote().to(self.device)
self.tokenizer = MotifA1Tokenizer().from_remote()
return f'Model and tokenizer successfully loaded on {self.device}!'
except Exception as e:
return f'Error loading model: {str(e)}'
def is_loaded(self) -> bool:
'''
Checks if both the model and tokenizer are loaded.
Returns:
bool: True if loaded, False otherwise.
'''
return self.model is not None and self.tokenizer is not None
# Global instance of ModelManager
model_manager = ModelManager()
def handle_user_message(
user_message: str,
chat_history: List[Dict[str, Any]],
clean_messages: List[Dict[str, str]]
) -> Tuple[str, List[Dict[str, Any]], List[Dict[str, str]]]:
'''
Handles the user input, adding it to the history list and cleaning the input box.
Args:
user_message (str): The raw text entered by the user.
chat_history (List[Dict[str, Any]]): Chatbot display history with role and content.
clean_messages (List[Dict[str, str]]): Clean dialog list for model context.
Returns:
Tuple[str, List[Dict[str, Any]], List[Dict[str, str]]]: Cleaned text, updated display history, and clean history.
'''
if not user_message.strip():
return '', chat_history, clean_messages
chat_history.append({'role': 'user', 'content': user_message})
clean_messages.append({'role': 'user', 'content': user_message})
chat_history.append({'role': 'assistant', 'content': ''})
return '', chat_history, clean_messages
def generate_response(
chat_history: List[Dict[str, Any]],
clean_messages: List[Dict[str, str]],
system_prompt: str,
temperature: float,
max_new_tokens: int,
top_p: float,
top_k: int,
language: str
) -> Generator[Tuple[List[Dict[str, Any]], List[Dict[str, str]]], None, None]:
'''
Generates model response in a streaming way and updates the UI in real time.
Args:
chat_history (List[Dict[str, Any]]): Chat history with UI HTML formatting.
clean_messages (List[Dict[str, str]]): Clean message history.
system_prompt (str): Custom system instruction.
temperature (float): Generation temperature.
max_new_tokens (int): Max response length.
top_p (float): Sampling probability threshold.
top_k (int): Top-K sampling filter.
language (str): UI language choice ('English' or '中文').
Yields:
Tuple[List[Dict[str, Any]], List[Dict[str, str]]]: Streaming updates to chatbot and clean state.
'''
loc = LOCALIZATION[language]
if not model_manager.is_loaded():
gr.Warning(loc['warning_not_loaded'])
status = model_manager.load_model()
if not model_manager.is_loaded():
chat_history[-1]['content'] = f'{loc["error_load_failed"]} {status}'
yield chat_history, clean_messages
return
tk: Optional[int] = int(top_k) if top_k > 0 else None
tp: Optional[float] = float(top_p) if top_p < 1.0 else None
messages_to_send: List[Dict[str, str]] = []
if system_prompt.strip():
messages_to_send.append({'role': 'system', 'content': system_prompt.strip()})
messages_to_send.extend(clean_messages)
cot_buffer = ''
response_buffer = ''
has_cot = False
try:
generator = chat(
model=model_manager.model,
tokenizer=model_manager.tokenizer,
device=model_manager.device,
messages=messages_to_send,
max_new_tokens=max_new_tokens,
temperature=temperature,
top_k=tk,
top_p=tp
)
for chunk in generator:
if chunk.is_cot:
has_cot = True
cot_buffer += chunk.content
else:
response_buffer += chunk.content
display_text = ''
if has_cot:
if chunk.is_cot:
display_text += (
f'{loc["thinking_active"]}
\n\n'
f'{cot_buffer}\n\n'
f'{loc["thinking_collapsed"]}
\n\n'
f'{cot_buffer}\n\n'
f'
{loc["subtitle"]}
' f'Explore the experimental 105.41M reasoning language model from the CodonProject. Built-in with native Chain-of-Thought (CoT) support.