import gradio as gr import uuid from datetime import datetime import pandas as pd from model_handler import ModelHandler from config import CHAT_MODEL_SPECS, LING_1T from recommand_config import RECOMMENDED_INPUTS from ui_components.model_selector import create_model_selector from i18n import get_text def get_history_df(history): if not history: # Provide explicit column names for an empty DataFrame return pd.DataFrame({'ID': pd.Series(dtype='str'), '对话': pd.Series(dtype='str')}) df = pd.DataFrame(history) # Ensure columns exist before renaming if 'id' in df.columns and 'title' in df.columns: return df[['id', 'title']].rename(columns={'id': 'ID', '对话': '对话'}) else: return pd.DataFrame({'ID': pd.Series(dtype='str'), '对话': pd.Series(dtype='str')}) def create_chat_tab(initial_lang: str, current_lang_state: gr.State): model_handler = ModelHandler() # Browser-side storage for conversation history and current ID conversation_store = gr.BrowserState(default_value=[], storage_key="ling_conversation_history") current_conversation_id = gr.BrowserState(default_value=None, storage_key="ling_current_conversation_id") def handle_new_chat(history, current_conv_id, lang): current_convo = next((c for c in history if c["id"] == current_conv_id), None) if history else None if current_convo and not current_convo.get("messages", []): return current_conv_id, history, [], gr.update(value=get_history_df(history)) conv_id = str(uuid.uuid4()) new_convo_title = get_text('chat_new_conversation_title', lang) new_convo = { "id": conv_id, "title": new_convo_title, "messages": [], "timestamp": datetime.now().isoformat() } updated_history = [new_convo] + (history or []) return conv_id, updated_history, [], gr.update(value=get_history_df(updated_history)) def load_conversation_from_df(df: pd.DataFrame, evt: gr.SelectData, history, lang): if evt.index is None or len(df) == 0: return None, [] selected_id = df.iloc[evt.index[0]]['ID'] for convo in history: if convo["id"] == selected_id: return selected_id, convo["messages"] new_id, _, new_msgs, _ = handle_new_chat(history, None, lang) return new_id, new_msgs with gr.Row(equal_height=False, elem_id="indicator-chat-tab"): with gr.Column(scale=1): new_chat_btn = gr.Button(get_text('chat_new_chat_button', initial_lang)) history_df = gr.DataFrame( value=get_history_df(conversation_store.value), headers=["ID", get_text('chat_history_dataframe_header', initial_lang)], datatype=["str", "str"], interactive=False, visible=True, column_widths=["0%", "99%"] ) with gr.Column(scale=4): chatbot = gr.Chatbot(height=500, placeholder=get_text('chat_chatbot_placeholder', initial_lang)) with gr.Row(): textbox = gr.Textbox(placeholder=get_text('chat_textbox_placeholder', initial_lang), container=False, scale=7) submit_btn = gr.Button(get_text('chat_submit_button', initial_lang), scale=1) recommended_title = gr.Markdown(get_text('chat_recommended_dialogues_title', initial_lang)) recommended_dataset = gr.Dataset( components=[gr.Textbox(visible=False)], samples=[[item["task"]] for item in RECOMMENDED_INPUTS], label=get_text('chat_recommended_dataset_label', initial_lang), headers=[get_text('chat_recommended_dataset_header', initial_lang)], ) with gr.Column(scale=1): model_dropdown, model_description_markdown = create_model_selector( model_specs=CHAT_MODEL_SPECS, default_model_constant=LING_1T ) system_prompt_textbox = gr.Textbox(label=get_text('chat_system_prompt_label', initial_lang), lines=5, placeholder=get_text('chat_system_prompt_placeholder', initial_lang)) temperature_slider = gr.Slider(minimum=0, maximum=1.0, value=0.7, step=0.1, label=get_text('chat_temperature_slider_label', initial_lang)) # --- Event Handlers --- # def on_select_recommendation(evt: gr.SelectData, history, current_conv_id, lang): selected_task = evt.value[0] item = next((i for i in RECOMMENDED_INPUTS if i["task"] == selected_task), None) if not item: return gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update() new_id, new_history, new_messages, history_df_update = handle_new_chat(history, current_conv_id, lang) return ( new_id, new_history, gr.update(value=item["model"]), gr.update(value=item["system_prompt"]), gr.update(value=item["temperature"]), gr.update(value=item["user_message"]), history_df_update, new_messages ) def chat_stream(conv_id, history, model_display_name, message, chat_history, system_prompt, temperature): if not message: yield chat_history return model_constant = next((k for k, v in CHAT_MODEL_SPECS.items() if v["display_name"] == model_display_name), LING_1T) response_generator = model_handler.get_response(model_constant, message, chat_history, system_prompt, temperature) for history_update in response_generator: yield history_update def on_chat_stream_complete(conv_id, history, final_chat_history, lang): current_convo = next((c for c in history if c["id"] == conv_id), None) if not current_convo: return history, gr.update() new_convo_title = get_text('chat_new_conversation_title', lang) if len(final_chat_history) > len(current_convo["messages"]) and current_convo["title"] == new_convo_title: user_message = final_chat_history[-2]["content"] if len(final_chat_history) > 1 else final_chat_history[0]["content"] current_convo["title"] = user_message[:50] current_convo["messages"] = final_chat_history current_convo["timestamp"] = datetime.now().isoformat() history = sorted([c for c in history if c["id"] != conv_id] + [current_convo], key=lambda x: x["timestamp"], reverse=True) return history, gr.update(value=get_history_df(history)) # Store all components that need i18n updates components = { "new_chat_btn": new_chat_btn, "history_df": history_df, "chatbot": chatbot, "textbox": textbox, "submit_btn": submit_btn, "recommended_title": recommended_title, "recommended_dataset": recommended_dataset, "system_prompt_textbox": system_prompt_textbox, "temperature_slider": temperature_slider, "model_dropdown": model_dropdown, "model_description_markdown": model_description_markdown, # Non-updatable components needed for event handlers and app.py "conversation_store": conversation_store, "current_conversation_id": current_conversation_id, } # Wire event handlers recommended_dataset.select(on_select_recommendation, inputs=[conversation_store, current_conversation_id, current_lang_state], outputs=[current_conversation_id, conversation_store, model_dropdown, system_prompt_textbox, temperature_slider, textbox, history_df, chatbot], show_progress="none") submit_btn.click( chat_stream, [current_conversation_id, conversation_store, model_dropdown, textbox, chatbot, system_prompt_textbox, temperature_slider], [chatbot] ).then( on_chat_stream_complete, [current_conversation_id, conversation_store, chatbot, current_lang_state], [conversation_store, history_df] ) textbox.submit( chat_stream, [current_conversation_id, conversation_store, model_dropdown, textbox, chatbot, system_prompt_textbox, temperature_slider], [chatbot] ).then( on_chat_stream_complete, [current_conversation_id, conversation_store, chatbot, current_lang_state], [conversation_store, history_df] ) new_chat_btn.click(handle_new_chat, inputs=[conversation_store, current_conversation_id, current_lang_state], outputs=[current_conversation_id, conversation_store, chatbot, history_df]) history_df.select(load_conversation_from_df, inputs=[history_df, conversation_store, current_lang_state], outputs=[current_conversation_id, chatbot]) return components def update_language(lang: str, components: dict): """ Returns a dictionary mapping components to their gr.update calls for language change. """ updates = { components["new_chat_btn"]: gr.update(value=get_text('chat_new_chat_button', lang)), components["history_df"]: gr.update(headers=["ID", get_text('chat_history_dataframe_header', lang)]), components["chatbot"]: gr.update(placeholder=get_text('chat_chatbot_placeholder', lang)), components["textbox"]: gr.update(placeholder=get_text('chat_textbox_placeholder', lang)), components["submit_btn"]: gr.update(value=get_text('chat_submit_button', lang)), components["recommended_title"]: gr.update(value=get_text('chat_recommended_dialogues_title', lang)), components["recommended_dataset"]: gr.update(label=get_text('chat_recommended_dataset_label', lang), headers=[get_text('chat_recommended_dataset_header', lang)]), components["system_prompt_textbox"]: gr.update(label=get_text('chat_system_prompt_label', lang), placeholder=get_text('chat_system_prompt_placeholder', lang)), components["temperature_slider"]: gr.update(label=get_text('chat_temperature_slider_label', lang)), } return updates