import pickle from datetime import datetime from pathlib import Path import gradio as gr from theflow.storage import storage from kotaemon.chatbot import ChatConversation from kotaemon.contribs.promptui.base import get_component from kotaemon.contribs.promptui.export import export from kotaemon.contribs.promptui.ui.blocks import ChatBlock from ..logs import ResultLog USAGE_INSTRUCTION = """## How to use: 1. Set the desired parameters. 2. Click "New chat" to start a chat session with the supplied parameters. This set of parameters will persist until the end of the chat session. During an ongoing chat session, changing the parameters will not take any effect. 3. Chat and interact with the chat bot on the right panel. You can add any additional input (if any), and they will be supplied to the chatbot. 4. During chat, the log of the chat will show up in the "Output" tabs. This is empty by default, so if you want to show the log here, tell the AI developers to configure the UI settings. 5. When finishing chat, select your preference in the radio box. Click "End chat". This will save the chat log and the preference to disk. 6. To compare the result of different run, click "Export" to get an Excel spreadsheet summary of different run. ## Support: In case of errors, you can: - PromptUI instruction: https://github.com/Cinnamon/kotaemon/wiki/Utilities#prompt-engineering-ui - Create bug fix and make PR at: https://github.com/Cinnamon/kotaemon - Ping any of @john @tadashi @ian @jacky in Slack channel #llm-productization ## Contribute: - Follow installation at: https://github.com/Cinnamon/kotaemon/ """ def construct_chat_ui( config, func_new_chat, func_chat, func_end_chat, func_export_to_excel ) -> gr.Blocks: """Construct the prompt engineering UI for chat Args: config: the UI config func_new_chat: the function for starting a new chat session func_chat: the function for chatting interaction func_end_chat: the function for ending and saving the chat func_export_to_excel: the function to export the logs to excel Returns: the UI object """ inputs, outputs, params = [], [], [] for name, component_def in config.get("inputs", {}).items(): if "params" not in component_def: component_def["params"] = {} component_def["params"]["interactive"] = True component = get_component(component_def) if hasattr(component, "label") and not component.label: # type: ignore component.label = name # type: ignore inputs.append(component) for name, component_def in config.get("params", {}).items(): if "params" not in component_def: component_def["params"] = {} component_def["params"]["interactive"] = True component = get_component(component_def) if hasattr(component, "label") and not component.label: # type: ignore component.label = name # type: ignore params.append(component) for idx, component_def in enumerate(config.get("outputs", [])): if "params" not in component_def: component_def["params"] = {} component_def["params"]["interactive"] = False component = get_component(component_def) if hasattr(component, "label") and not component.label: # type: ignore component.label = f"Output {idx}" # type: ignore outputs.append(component) sess = gr.State(value=None) chatbot = gr.Chatbot(label="Chatbot", show_copy_button=True) chat = ChatBlock( func_chat, chatbot=chatbot, additional_inputs=[sess], additional_outputs=outputs ) param_state = gr.Textbox(interactive=False) with gr.Blocks(analytics_enabled=False, title="Welcome to PromptUI") as demo: sess.render() with gr.Accordion(label="HOW TO", open=False): gr.Markdown(USAGE_INSTRUCTION) with gr.Row(): run_btn = gr.Button("New chat") run_btn.click( func_new_chat, inputs=params, outputs=[ chat.chatbot, chat.chatbot_state, chat.saved_input, param_state, sess, *outputs, ], ) with gr.Accordion(label="End chat", open=False): likes = gr.Radio(["like", "dislike", "neutral"], value="neutral") save_log = gr.Checkbox( value=True, label="Save log", info="If saved, log can be exported later", show_label=True, ) end_btn = gr.Button("End chat") end_btn.click( func_end_chat, inputs=[likes, save_log, sess], outputs=[param_state, sess], ) with gr.Accordion(label="Export", open=False): exported_file = gr.File( label="Output file", show_label=True, height=100 ) export_btn = gr.Button("Export") export_btn.click( func_export_to_excel, inputs=None, outputs=exported_file ) with gr.Row(): with gr.Column(): with gr.Tab("Params"): for component in params: component.render() with gr.Accordion(label="Session state", open=False): param_state.render() with gr.Tab("Outputs"): for component in outputs: component.render() with gr.Column(): chat.render() return demo.queue() def build_chat_ui(config, pipeline_def): """Build the chat UI Args: config: the UI config pipeline_def: the pipeline definition Returns: the UI object """ output_dir: Path = Path(storage.url(pipeline_def().config.store_result)) exported_dir = output_dir.parent / "exported" exported_dir.mkdir(parents=True, exist_ok=True) resultlog = getattr(pipeline_def, "_promptui_resultlog", ResultLog) allowed_resultlog_callbacks = {i for i in dir(resultlog) if not i.startswith("__")} def new_chat(*args): """Start a new chat function Args: *args: the pipeline init params Returns: new empty states """ gr.Info("Starting new session...") param_dicts = { name: value for name, value in zip(config["params"].keys(), args) } for key in param_dicts.keys(): if config["params"][key].get("component").lower() == "file": param_dicts[key] = param_dicts[key].name # TODO: currently hard-code as ChatConversation pipeline = pipeline_def() session = ChatConversation(bot=pipeline) session.set(param_dicts) session.start_session() param_state_str = "\n".join( f"- {name}: {value}" for name, value in param_dicts.items() ) gr.Info("New chat session started.") return ( [], [], None, param_state_str, session, *[None] * len(config.get("outputs", [])), ) def chat(message, history, session, *args): """The chat interface # TODO: wrap the input and output of this chat function so that it work with more types of chat conversation than simple text Args: message: the message from the user history: the gradio history of the chat session: the chat object session *args: the additional inputs Returns: the response from the chatbot """ if session is None: raise gr.Error( "No active chat session. Please set the params and click New chat" ) pred = session(message) text_response = pred.content additional_outputs = [] for output_def in config.get("outputs", []): value = session.last_run.logs(output_def["step"]) getter = output_def.get("getter", None) if getter and getter in allowed_resultlog_callbacks: value = getattr(resultlog, getter)(value) additional_outputs.append(value) return text_response, *additional_outputs def end_chat(preference: str, save_log: bool, session): """End the chat session Args: preference: the preference of the user save_log: whether to save the result session: the chat object session Returns: the new empty state """ gr.Info("Ending session...") session.end_session() output_dir: Path = ( Path(storage.url(session.config.store_result)) / session.last_run.id() ) if not save_log: if output_dir.exists(): import shutil shutil.rmtree(output_dir) session = None param_state = "" gr.Info("End session without saving log.") return param_state, session # add preference result to progress with (output_dir / "progress.pkl").open("rb") as fi: progress = pickle.load(fi) progress["preference"] = preference with (output_dir / "progress.pkl").open("wb") as fo: pickle.dump(progress, fo) # get the original params param_dicts = {name: session.getx(name) for name in config["params"].keys()} with (output_dir / "params.pkl").open("wb") as fo: pickle.dump(param_dicts, fo) session = None param_state = "" gr.Info("End session and save log.") return param_state, session def export_func(): name = ( f"{pipeline_def.__module__}.{pipeline_def.__name__}_{datetime.now()}.xlsx" ) path = str(exported_dir / name) gr.Info(f"Begin exporting {name}...") try: export(config=config, pipeline_def=pipeline_def, output_path=path) except Exception as e: raise gr.Error(f"Failed to export. Please contact project's AIR: {e}") gr.Info(f"Exported {name}. Please go to the `Exported file` tab to download") return path demo = construct_chat_ui( config=config, func_new_chat=new_chat, func_chat=chat, func_end_chat=end_chat, func_export_to_excel=export_func, ) return demo