MiniMax-M1 / app.py
Cherrytest
Update app.py
fa891b1
import base64
import gradio as gr
import json
import mimetypes
import os
import requests
import time
import modelscope_studio.components.antd as antd
import modelscope_studio.components.antdx as antdx
import modelscope_studio.components.base as ms
import modelscope_studio.components.pro as pro
from modelscope_studio.components.pro.chatbot import (
ChatbotActionConfig, ChatbotBotConfig, ChatbotMarkdownConfig,
ChatbotPromptsConfig, ChatbotUserConfig, ChatbotWelcomeConfig)
from config import DEFAULT_PROMPTS, EXAMPLES, SystemPrompt
import re
MODEL_VERSION = os.environ['MODEL_VERSION']
API_URL = os.environ['API_URL']
API_KEY = os.environ['API_KEY']
SYSTEM_PROMPT = os.environ.get('SYSTEM_PROMPT')
MULTIMODAL_FLAG = os.environ.get('MULTIMODAL')
MODEL_CONTROL_DEFAULTS = json.loads(os.environ['MODEL_CONTROL_DEFAULTS'])
NAME_MAP = {
'system': os.environ.get('SYSTEM_NAME'),
'user': os.environ.get('USER_NAME'),
}
MODEL_NAME = 'MiniMax-M1'
def prompt_select(e: gr.EventData):
return gr.update(value=e._data["payload"][0]["value"]["description"])
def clear():
return gr.update(value=None)
def retry(chatbot_value, e: gr.EventData):
index = e._data["payload"][0]["index"]
chatbot_value = chatbot_value[:index]
yield gr.update(loading=True), gr.update(value=chatbot_value), gr.update(
disabled=True)
for chunk in submit(None, chatbot_value):
yield chunk
def cancel(chatbot_value):
chatbot_value[-1]["loading"] = False
chatbot_value[-1]["status"] = "done"
chatbot_value[-1]["footer"] = "Chat completion paused"
return gr.update(value=chatbot_value), gr.update(loading=False), gr.update(
disabled=False)
def add_name_for_message(message):
name = NAME_MAP.get(message['role'])
if name is not None:
message['name'] = name
def convert_content(content):
if isinstance(content, str):
return content
if isinstance(content, tuple):
return [{
'type': 'image_url',
'image_url': {
'url': encode_base64(content[0]),
},
}]
content_list = []
for key, val in content.items():
if key == 'text':
content_list.append({
'type': 'text',
'text': val,
})
elif key == 'files':
for f in val:
content_list.append({
'type': 'image_url',
'image_url': {
'url': encode_base64(f),
},
})
return content_list
def encode_base64(path):
guess_type = mimetypes.guess_type(path)[0]
if not guess_type.startswith('image/'):
raise gr.Error('not an image ({}): {}'.format(guess_type, path))
with open(path, 'rb') as handle:
data = handle.read()
return 'data:{};base64,{}'.format(
guess_type,
base64.b64encode(data).decode(),
)
def format_history(history):
"""Convert chatbot history format to API call format"""
messages = []
if SYSTEM_PROMPT is not None:
messages.append({
'role': 'system',
'content': SYSTEM_PROMPT,
})
for item in history:
if item["role"] == "user":
messages.append({
'role': 'user',
'content': convert_content(item["content"]),
})
elif item["role"] == "assistant":
# Extract reasoning content and main content
reasoning_content = ""
main_content = ""
if isinstance(item["content"], list):
for content_item in item["content"]:
if content_item.get("type") == "tool":
reasoning_content = content_item.get("content", "")
elif content_item.get("type") == "text":
main_content = content_item.get("content", "")
else:
main_content = item["content"]
messages.append({
'role':
'assistant',
'content':
convert_content(main_content),
'reasoning_content':
convert_content(reasoning_content),
})
return messages
def submit(sender_value, chatbot_value):
if sender_value is not None:
chatbot_value.append({
"role": "user",
"content": sender_value,
})
api_messages = format_history(chatbot_value)
for message in api_messages:
add_name_for_message(message)
chatbot_value.append({
"role": "assistant",
"content": [],
"loading": True,
"status": "pending"
})
yield {
sender: gr.update(value=None, loading=True),
clear_btn: gr.update(disabled=True),
chatbot: gr.update(value=chatbot_value)
}
try:
data = {
'model': MODEL_VERSION,
'messages': api_messages,
'stream': True,
'max_tokens': MODEL_CONTROL_DEFAULTS['tokens_to_generate'],
'temperature': MODEL_CONTROL_DEFAULTS['temperature'],
'top_p': MODEL_CONTROL_DEFAULTS['top_p'],
}
r = requests.post(
API_URL,
headers={
'Content-Type': 'application/json',
'Authorization': 'Bearer {}'.format(API_KEY),
},
data=json.dumps(data),
stream=True,
)
thought_done = False
start_time = time.time()
message_content = chatbot_value[-1]["content"]
# Reasoning content (tool type)
message_content.append({
"type": "tool",
"content": "",
"options": {
"title": "🤔 Thinking..."
}
})
# Main content (text type)
message_content.append({
"type": "text",
"content": "",
})
reasoning_start_time = None
reasoning_duration = None
for row in r.iter_lines():
if row.startswith(b'data:'):
data = json.loads(row[5:])
if 'choices' not in data:
raise gr.Error('request failed')
choice = data['choices'][0]
if 'delta' in choice:
delta = choice['delta']
reasoning_content = delta.get('reasoning_content', '')
content = delta.get('content', '')
chatbot_value[-1]["loading"] = False
# Handle reasoning content
if reasoning_content:
if reasoning_start_time is None:
reasoning_start_time = time.time()
message_content[-2]["content"] += reasoning_content
# Handle main content
if content:
message_content[-1]["content"] += content
if not thought_done:
thought_done = True
if reasoning_start_time is not None:
reasoning_duration = time.time(
) - reasoning_start_time
thought_cost_time = "{:.2f}".format(
reasoning_duration)
else:
reasoning_duration = 0.0
thought_cost_time = "0.00"
message_content[-2]["options"] = {
"title":
f"End of Thought ({thought_cost_time}s)",
"status": "done"
}
yield {chatbot: gr.update(value=chatbot_value)}
elif 'message' in choice:
message_data = choice['message']
reasoning_content = message_data.get(
'reasoning_content', '')
main_content = message_data.get('content', '')
message_content[-2]["content"] = reasoning_content
message_content[-1]["content"] = main_content
if reasoning_content and main_content:
if reasoning_duration is None:
if reasoning_start_time is not None:
reasoning_duration = time.time(
) - reasoning_start_time
thought_cost_time = "{:.2f}".format(
reasoning_duration)
else:
reasoning_duration = 0.0
thought_cost_time = "0.00"
else:
thought_cost_time = "{:.2f}".format(
reasoning_duration)
message_content[-2]["options"] = {
"title": f"End of Thought ({thought_cost_time}s)",
"status": "done"
}
chatbot_value[-1]["loading"] = False
yield {chatbot: gr.update(value=chatbot_value)}
chatbot_value[-1]["footer"] = "{:.2f}s".format(time.time() -
start_time)
chatbot_value[-1]["status"] = "done"
yield {
clear_btn: gr.update(disabled=False),
sender: gr.update(loading=False),
chatbot: gr.update(value=chatbot_value),
}
except Exception as e:
chatbot_value[-1]["loading"] = False
chatbot_value[-1]["status"] = "done"
chatbot_value[-1]["content"] = "Request failed, please try again."
yield {
clear_btn: gr.update(disabled=False),
sender: gr.update(loading=False),
chatbot: gr.update(value=chatbot_value),
}
raise e
def remove_code_block(text):
# Try to match code blocks with language markers
patterns = [
r'```(?:html|HTML)\n([\s\S]+?)\n```', # Match ```html or ```HTML
r'```\n([\s\S]+?)\n```', # Match code blocks without language markers
r'```([\s\S]+?)```' # Match code blocks without line breaks
]
for pattern in patterns:
match = re.search(pattern, text, re.DOTALL)
if match:
extracted = match.group(1).strip()
print("Successfully extracted code block:", extracted)
return extracted
# If no code block is found, check if the entire text is HTML
if text.strip().startswith('<!DOCTYPE html>') or text.strip().startswith(
'<html'):
print("Text appears to be raw HTML, using as is")
return text.strip()
print("No code block found in text:", text)
return text.strip()
def send_to_sandbox(code):
# Add a wrapper to inject necessary permissions
wrapped_code = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>
// Create a safe storage alternative
const safeStorage = {{
_data: {{}},
getItem: function(key) {{
return this._data[key] || null;
}},
setItem: function(key, value) {{
this._data[key] = value;
}},
removeItem: function(key) {{
delete this._data[key];
}},
clear: function() {{
this._data = {{}};
}}
}};
// Replace native localStorage
Object.defineProperty(window, 'localStorage', {{
value: safeStorage,
writable: false
}});
// Add error handling without using alert
window.onerror = function(message, source, lineno, colno, error) {{
console.error('Error:', message);
}};
</script>
</head>
<body>
{code}
</body>
</html>
"""
encoded_html = base64.b64encode(
wrapped_code.encode('utf-8')).decode('utf-8')
data_uri = f"data:text/html;charset=utf-8;base64,{encoded_html}"
iframe = f'<iframe src="{data_uri}" width="100%" height="920px" sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-presentation" allow="display-capture"></iframe>'
print("Generated iframe:", iframe)
return iframe
def select_example(example):
if isinstance(example, dict):
return example.get("description", "")
return ""
def generate_code(query: str):
if not query:
return {
code_output: gr.update(value=None),
reasoning_output: gr.update(value=None),
sandbox: gr.update(value=None),
state_tab: gr.update(active_key="empty"),
output_tabs: gr.update(active_key="reasoning", visible=False),
loading: gr.update(tip="Thinking...")
}
print("Starting code generation with query:", query)
messages = [{
'role': 'system',
'content': SystemPrompt
}, {
'role': 'user',
'content': query
}]
max_retries = 3
retry_count = 0
while retry_count < max_retries:
try:
data = {
'model': MODEL_VERSION,
'messages': messages,
'stream': True,
'max_tokens': MODEL_CONTROL_DEFAULTS['tokens_to_generate'],
'temperature': MODEL_CONTROL_DEFAULTS['temperature'],
'top_p': MODEL_CONTROL_DEFAULTS['top_p'],
}
print(
f"Attempt {retry_count + 1}: Sending request to API with data:",
json.dumps(data, indent=2))
r = requests.post(
API_URL,
headers={
'Content-Type': 'application/json',
'Authorization': 'Bearer {}'.format(API_KEY),
},
data=json.dumps(data),
stream=True,
timeout=60 # Set 60 seconds timeout
)
content = ""
reasoning_content = ""
loading_text = "Thinking..."
for row in r.iter_lines():
if row.startswith(b'data:'):
data = json.loads(row[5:])
print("Received data from API:", json.dumps(data,
indent=2))
if 'choices' not in data:
raise gr.Error('request failed')
choice = data['choices'][0]
if 'delta' in choice:
delta = choice['delta']
content += delta.get('content', '')
reasoning_content += delta.get('reasoning_content', '')
# Update loading text based on content
if content and not loading_text == "Generating code...":
loading_text = "Generating code..."
yield {
code_output:
gr.update(value=content),
reasoning_output:
gr.update(value=reasoning_content + "\n"),
sandbox:
gr.update(value=None),
state_tab:
gr.update(active_key="loading"),
output_tabs:
gr.update(active_key="reasoning",
visible=True),
loading:
gr.update(tip=loading_text)
}
else:
yield {
code_output:
gr.update(value=content),
reasoning_output:
gr.update(value=reasoning_content + "\n"),
sandbox:
gr.update(value=None),
state_tab:
gr.update(active_key="loading"),
output_tabs:
gr.update(active_key="reasoning",
visible=True),
loading:
gr.update(tip=loading_text)
}
elif 'message' in choice:
message_data = choice['message']
content = message_data.get('content', '')
reasoning_content = message_data.get(
'reasoning_content', '')
print("Final content:", content)
print("Final reasoning:", reasoning_content)
html_content = remove_code_block(content)
print("Extracted HTML:", html_content)
yield {
code_output:
gr.update(value=content),
reasoning_output:
gr.update(value=reasoning_content + "\n"),
sandbox:
gr.update(value=send_to_sandbox(html_content)),
state_tab:
gr.update(active_key="render"),
output_tabs:
gr.update(active_key="code", visible=True),
loading:
gr.update(tip="Done")
}
# If successful, break out of retry loop
break
except (requests.exceptions.Timeout,
requests.exceptions.ConnectionError) as e:
retry_count += 1
if retry_count == max_retries:
print(f"Failed after {max_retries} attempts:", str(e))
raise gr.Error(
f"Request failed after {max_retries} attempts: {str(e)}")
print(f"Attempt {retry_count} failed, retrying...")
time.sleep(1) # Wait 1 second before retrying
except Exception as e:
print("Error occurred:", str(e))
raise gr.Error(str(e))
css = """
/* Add styles for the main container */
.ant-tabs-content {
height: calc(100vh - 200px);
overflow: hidden;
}
.ant-tabs-tabpane {
height: 100%;
overflow-y: auto;
}
/* Modify existing styles */
.output-empty,.output-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
min-height: 680px;
position: relative;
}
.output-html {
display: flex;
flex-direction: column;
width: 100%;
min-height: 680px;
}
.output-html > iframe {
flex: 1;
}
.right_content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
min-height: unset;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
/* Add styles for the code playground container */
.code-playground-container {
height: 100%;
overflow-y: auto;
padding-right: 8px;
}
.code-playground-container::-webkit-scrollbar {
width: 6px;
}
.code-playground-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.code-playground-container::-webkit-scrollbar-thumb {
background: #888;
border-radius: 3px;
}
.code-playground-container::-webkit-scrollbar-thumb:hover {
background: #555;
}
.render_header {
display: flex;
align-items: center;
padding: 8px 16px;
background: #f5f5f5;
border-bottom: 1px solid #e8e8e8;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.header_btn {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
display: inline-block;
}
.header_btn:nth-child(1) {
background: #ff5f56;
}
.header_btn:nth-child(2) {
background: #ffbd2e;
}
.header_btn:nth-child(3) {
background: #27c93f;
}
.output-html > iframe {
flex: 1;
border: none;
background: #fff;
}
.reasoning-box {
max-height: 300px;
overflow-y: auto;
border-radius: 4px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 14px;
line-height: 1.6;
width: 100%;
scroll-behavior: smooth;
display: flex;
flex-direction: column-reverse;
}
.reasoning-box .ms-markdown {
padding: 0 12px;
}
.reasoning-box::-webkit-scrollbar {
width: 6px;
}
.reasoning-box::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.reasoning-box::-webkit-scrollbar-thumb {
background: #888;
border-radius: 3px;
}
.reasoning-box::-webkit-scrollbar-thumb:hover {
background: #555;
}
.markdown-container {
max-height: 300px;
overflow-y: auto;
border-radius: 4px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 14px;
line-height: 1.6;
width: 100%;
scroll-behavior: smooth;
display: flex;
flex-direction: column-reverse;
}
/* Example card styles */
.example-card {
flex: 1 1 calc(50% - 20px);
max-width: calc(50% - 20px);
margin: 6px;
transition: all 0.3s;
cursor: pointer;
border: 1px solid #e8e8e8;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.example-card:hover {
transform: translateY(-4px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
border-color: #d9d9d9;
}
.example-card .ant-card-meta-title {
font-size: 16px;
font-weight: 500;
margin-bottom: 8px;
color: #262626;
}
.example-card .ant-card-meta-description {
color: #666;
font-size: 14px;
line-height: 1.5;
}
/* Example tabs styles */
.example-tabs .ant-tabs-nav {
margin-bottom: 16px;
}
.example-tabs .ant-tabs-tab {
padding: 8px 16px;
font-size: 15px;
}
.example-tabs .ant-tabs-tab-active {
font-weight: 500;
}
/* Empty state styles */
.right_content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
min-height: 620px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
/* Add styles for the example cards container */
.example-tabs .ant-tabs-content {
padding: 0 8px;
}
.example-tabs .ant-flex {
margin: 0 -8px;
width: calc(100% + 16px);
}
"""
def scroll_to_bottom():
return """
function() {
setTimeout(() => {
const reasoningBox = document.querySelector('.reasoning-box');
if (reasoningBox) {
reasoningBox.scrollTop = reasoningBox.scrollHeight;
}
const markdownContainer = document.querySelector('.markdown-container');
if (markdownContainer) {
markdownContainer.scrollTop = markdownContainer.scrollHeight;
}
}, 100);
}
"""
with gr.Blocks(css=css) as demo, ms.Application(), antdx.XProvider(
), ms.AutoLoading():
with antd.Tabs() as tabs:
with antd.Tabs.Item(key="chat", label="Chatbot"):
with antd.Flex(vertical=True,
gap="middle",
elem_style=dict(height="calc(100vh - 150px)")):
chatbot = pro.Chatbot(
elem_style=dict(flex=1, maxHeight=1200, height=0),
markdown_config=ChatbotMarkdownConfig(
allow_tags=["think"]),
welcome_config=ChatbotWelcomeConfig(
variant="borderless",
icon="./assets/minimax-logo.png",
title="Hello, I'm MiniMax-M1",
description="You can input text to get started.",
prompts=ChatbotPromptsConfig(
title="How can I help you today?",
styles={
"list": {
"width": '100%',
},
"item": {
"flex": 1,
},
},
items=DEFAULT_PROMPTS)),
user_config=ChatbotUserConfig(actions=["copy", "edit"]),
bot_config=ChatbotBotConfig(
header=MODEL_NAME,
avatar="./assets/minimax-logo.png",
actions=["copy", "retry"]))
with antdx.Sender() as sender:
with ms.Slot("prefix"):
with antd.Button(value=None,
color="default",
variant="text") as clear_btn:
with ms.Slot("icon"):
antd.Icon("ClearOutlined")
clear_btn.click(fn=clear, outputs=[chatbot])
submit_event = sender.submit(
fn=submit,
inputs=[sender, chatbot],
outputs=[sender, chatbot, clear_btn])
sender.cancel(fn=cancel,
inputs=[chatbot],
outputs=[chatbot, sender, clear_btn],
cancels=[submit_event],
queue=False)
chatbot.retry(fn=retry,
inputs=[chatbot],
outputs=[sender, chatbot, clear_btn])
chatbot.welcome_prompt_select(fn=prompt_select,
outputs=[sender])
with antd.Tabs.Item(key="code", label="Code Playground (WebDev)"):
with antd.Row(gutter=[32, 12],
elem_classes="code-playground-container"):
with antd.Col(span=24, md=12):
with antd.Flex(vertical=True, gap="middle"):
code_input = antd.Input.Textarea(
size="large",
allow_clear=True,
auto_size=dict(minRows=2, maxRows=6),
placeholder=
"Please enter what kind of application you want or choose an example below and click the button"
)
code_btn = antd.Button("Generate Code",
type="primary",
size="large")
with antd.Tabs(active_key="reasoning",
visible=False) as output_tabs:
with antd.Tabs.Item(key="reasoning",
label="🤔 Thinking Process"):
reasoning_output = ms.Markdown(
elem_classes="reasoning-box")
with antd.Tabs.Item(key="code",
label="💻 Generated Code"):
code_output = ms.Markdown(
elem_classes="markdown-container")
antd.Divider("Examples")
# Examples with categories
with antd.Tabs(
elem_classes="example-tabs") as example_tabs:
for category, examples in EXAMPLES.items():
with antd.Tabs.Item(key=category,
label=category):
with antd.Flex(gap="small", wrap=True):
for example in examples:
with antd.Card(
elem_classes="example-card",
hoverable=True
) as example_card:
antd.Card.Meta(
title=example['title'],
description=example[
'description'])
example_card.click(
fn=select_example,
inputs=[gr.State(example)],
outputs=[code_input])
with antd.Col(span=24, md=12):
with antd.Card(title="Output",
elem_style=dict(height="100%"),
styles=dict(body=dict(height="100%")),
elem_id="output-container"):
with antd.Tabs(
active_key="empty",
render_tab_bar="() => null") as state_tab:
with antd.Tabs.Item(key="empty"):
empty = antd.Empty(
description=
"Enter your request to generate code",
elem_classes="output-empty")
with antd.Tabs.Item(key="loading"):
with antd.Spin(True,
tip="Thinking and coding...",
size="large",
elem_classes="output-loading"
) as loading:
# placeholder
ms.Div()
with antd.Tabs.Item(key="render"):
sandbox = gr.HTML(elem_classes="output-html")
code_btn.click(generate_code,
inputs=[code_input],
outputs=[
code_output, reasoning_output, sandbox,
state_tab, output_tabs, loading
])
# Add auto-scroll functionality
reasoning_output.change(
fn=scroll_to_bottom,
inputs=[],
outputs=[],
)
code_output.change(
fn=scroll_to_bottom,
inputs=[],
outputs=[],
)
def on_tab_change(e: gr.EventData):
tab_key = e._data["payload"][0]
return gr.update(active_key=tab_key, visible=True)
output_tabs.change(
fn=on_tab_change,
outputs=[output_tabs],
)
if __name__ == '__main__':
demo.queue(default_concurrency_limit=50).launch(ssr_mode=False)