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('') or text.strip().startswith( ' {code} """ 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'' 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)