Spaces:
Runtime error
Runtime error
| import random | |
| import time | |
| from dataclasses import asdict, dataclass | |
| from typing import Callable, Literal | |
| import mesop.labs as mel | |
| import mesop as me | |
| import requests | |
| import json | |
| from dataclasses import dataclass, field | |
| from mongo_db_writer_main import log_prompt_and_response | |
| Role = Literal["user", "bot"] | |
| _INTRO_TEXT = """ | |
| # Mesop Markdown Editor Example | |
| This example shows how to make a simple markdown editor. | |
| """.strip() | |
| class Note: | |
| """Content of note.""" | |
| content: str = "" | |
| _APP_TITLE = "# YuvaRaj's LLM-Playground" | |
| _BOT_AVATAR_LETTER = "UV" | |
| _EMPTY_CHAT_MESSAGE = "Get started with an example" | |
| _EXAMPLE_USER_QUERIES = ( | |
| "Examples of SQL Commands?", | |
| "What a store proc? Provide an examples", | |
| "What is Java Class, provide few examples?", | |
| "What is quantum computing, and why is it important?" | |
| ) | |
| _CHAT_MAX_WIDTH = "800px" | |
| _MOBILE_BREAKPOINT = 640 | |
| class ChatMessage: | |
| """Chat message metadata.""" | |
| role: Role = "user" | |
| content: str = "You're ai expert, guided by yuvaraj" | |
| edited: bool = False | |
| # 1 is positive | |
| # -1 is negative | |
| # 0 is no rating | |
| rating: int = 0 | |
| class State: | |
| input: str | |
| output: list[ChatMessage] | |
| in_progress: bool | |
| sidebar_expanded: bool = False | |
| # Need to use dict instead of ChatMessage due to serialization bug. | |
| # See: https://github.com/google/mesop/issues/659 | |
| history: list[list[dict]] | |
| notes: list[Note] = field(default_factory=lambda: [Note(content=_INTRO_TEXT)]) | |
| selected_note_index: int = 0 | |
| selected_note_content: str = _INTRO_TEXT | |
| show_preview: bool = True | |
| def call_ollama_api(input_text: str, history: list[ChatMessage]) -> str: | |
| """ | |
| Sends the user input and chat history to the Ollama endpoint and retrieves the response. | |
| """ | |
| OLLAMA_URL = "http://96.228.37.139:4002/api/generate" | |
| MODEL_NAME = "mistral-small" | |
| # Format the chat history as part of the prompt | |
| formatted_history = "\n".join([f"{msg.role}: {msg.content}" for msg in history]) | |
| prompt = f"{formatted_history}\nuser: give me response is well formatted {input_text}" | |
| # Prepare the payload | |
| payload = { | |
| "model": MODEL_NAME, | |
| "prompt": prompt, # Include chat history in the prompt | |
| "stream": False, | |
| } | |
| try: | |
| response = requests.post(OLLAMA_URL, json=payload) | |
| response.raise_for_status() | |
| # Parse the JSON response | |
| response_data = response.json() | |
| log_reponse_data=response_data.get("response") | |
| # Access the 'response' field from the parsed JSON data | |
| if "response" in response_data: | |
| # Call the external logging function | |
| log_prompt_and_response(prompt, log_reponse_data) | |
| return response_data.get("response") | |
| else: | |
| return "Error: No response field found in Ollama API response" | |
| except requests.exceptions.RequestException as e: | |
| return f"Error connecting to Ollama: {e}" | |
| except json.JSONDecodeError as e: | |
| return f"JSON Parsing Error: {e}, Raw Response: {response.text}" | |
| def respond_to_chat(input: str, history: list[ChatMessage]): | |
| """Calls the Ollama API to get a response, including chat history.""" | |
| return call_ollama_api(input, history) | |
| def on_load(e: me.LoadEvent): | |
| me.set_theme_mode("system") | |
| def page(): | |
| state = me.state(State) | |
| with me.box( | |
| style=me.Style( | |
| background=me.theme_var("surface-container-lowest"), | |
| display="flex", | |
| flex_direction="column", | |
| height="100%", | |
| ) | |
| ): | |
| with me.box( | |
| style=me.Style( | |
| display="flex", flex_direction="row", flex_grow=1, overflow="hidden" | |
| ) | |
| ): | |
| with me.box( | |
| style=me.Style( | |
| background=me.theme_var("surface-container-low"), | |
| display="flex", | |
| flex_direction="column", | |
| flex_shrink=0, | |
| position="absolute" | |
| if state.sidebar_expanded and _is_mobile() | |
| else None, | |
| height="100%" if state.sidebar_expanded and _is_mobile() else None, | |
| width=300 if state.sidebar_expanded else None, | |
| z_index=2000, | |
| ) | |
| ): | |
| sidebar() | |
| with me.box( | |
| style=me.Style( | |
| display="flex", | |
| flex_direction="column", | |
| flex_grow=1, | |
| padding=me.Padding(left=60) | |
| if state.sidebar_expanded and _is_mobile() | |
| else None, | |
| ) | |
| ): | |
| header() | |
| with me.box(style=me.Style(flex_grow=1, overflow_y="scroll")): | |
| if state.output: | |
| chat_pane() | |
| else: | |
| examples_pane() | |
| chat_input() | |
| def sidebar(): | |
| state = me.state(State) | |
| with me.box( | |
| style=me.Style( | |
| display="flex", | |
| flex_direction="column", | |
| flex_grow=1, | |
| ) | |
| ): | |
| with me.box(style=me.Style(display="flex", gap=20)): | |
| menu_icon(icon="menu", tooltip="Menu", on_click=on_click_menu_icon) | |
| if state.sidebar_expanded: | |
| me.text( | |
| _APP_TITLE, | |
| style=me.Style(margin=me.Margin(bottom=0, top=14)), | |
| type="headline-6", | |
| ) | |
| if state.sidebar_expanded: | |
| menu_item(icon="add", label="New chat", on_click=on_click_new_chat) | |
| else: | |
| menu_icon(icon="add", tooltip="New chat", on_click=on_click_new_chat) | |
| if state.sidebar_expanded: | |
| history_pane() | |
| def history_pane(): | |
| state = me.state(State) | |
| for index, chat in enumerate(state.history): | |
| with me.box( | |
| key=f"chat-{index}", | |
| on_click=on_click_history, | |
| style=me.Style( | |
| background=me.theme_var("surface-container"), | |
| border=me.Border.all( | |
| me.BorderSide( | |
| width=1, color=me.theme_var("outline-variant"), style="solid" | |
| ) | |
| ), | |
| border_radius=5, | |
| cursor="pointer", | |
| margin=me.Margin.symmetric(horizontal=10, vertical=10), | |
| padding=me.Padding.all(10), | |
| text_overflow="ellipsis", | |
| ), | |
| ): | |
| me.text(_truncate_text(chat[0]["content"])) | |
| def header(): | |
| state = me.state(State) | |
| with me.box( | |
| style=me.Style( | |
| align_items="center", | |
| background=me.theme_var("surface-container-lowest"), | |
| display="flex", | |
| gap=5, | |
| justify_content="space-between", | |
| padding=me.Padding.symmetric(horizontal=20, vertical=10), | |
| font_family="Roboto" | |
| ) | |
| ): | |
| with me.box(style=me.Style(display="flex", gap=5)): | |
| if not state.sidebar_expanded: | |
| me.text( | |
| _APP_TITLE, | |
| style=me.Style(margin=me.Margin(bottom=0)), | |
| type="headline-6", | |
| ) | |
| with me.box(style=me.Style(display="flex", gap=5)): | |
| icon_button( | |
| key="", | |
| icon="dark_mode" if me.theme_brightness() == "light" else "light_mode", | |
| tooltip="Dark mode" | |
| if me.theme_brightness() == "light" | |
| else "Light mode", | |
| on_click=on_click_theme_brightness, | |
| ) | |
| def examples_pane(): | |
| with me.box( | |
| style=me.Style( | |
| margin=me.Margin.symmetric(horizontal="auto"), | |
| padding=me.Padding.all(15), | |
| width=f"min({_CHAT_MAX_WIDTH}, 100%)", | |
| ) | |
| ): | |
| with me.box(style=me.Style(margin=me.Margin(top=25), font_size=40,font_weight=70)): | |
| me.text(_EMPTY_CHAT_MESSAGE) | |
| with me.box( | |
| style=me.Style( | |
| display="flex", | |
| flex_direction="column" if _is_mobile() else "row", | |
| gap=20, | |
| margin=me.Margin(top=25), | |
| ) | |
| ): | |
| for index, query in enumerate(_EXAMPLE_USER_QUERIES): | |
| with me.box( | |
| key=f"query-{index}", | |
| on_click=on_click_example_user_query, | |
| style=me.Style( | |
| background=me.theme_var("surface-container-highest"), | |
| border_radius=30, | |
| padding=me.Padding.all(20), | |
| cursor="pointer", | |
| font_family="Roboto" | |
| ), | |
| ): | |
| me.text(query) | |
| def chat_pane(): | |
| state = me.state(State) | |
| with me.box( | |
| style=me.Style( | |
| background=me.theme_var("surface-container-lowest"), | |
| color=me.theme_var("on-surface"), | |
| display="flex", | |
| flex_direction="column", | |
| margin=me.Margin.symmetric(horizontal="auto"), | |
| padding=me.Padding.all(15), | |
| width=f"min({_CHAT_MAX_WIDTH}, 100%)", | |
| font_family="Roboto" #YR | |
| ) | |
| ): | |
| for index, msg in enumerate(state.output): | |
| if msg.role == "user": | |
| user_message(message=msg) | |
| else: | |
| bot_message(message_index=index, message=msg) | |
| if state.in_progress: | |
| with me.box(key="scroll-to", style=me.Style(height=250)): | |
| pass | |
| def user_message(*, message: ChatMessage): | |
| with me.box( | |
| style=me.Style( | |
| display="flex", | |
| gap=15, | |
| justify_content="end", | |
| margin=me.Margin.all(20), | |
| ) | |
| ): | |
| with me.box( | |
| style=me.Style( | |
| background=me.theme_var("surface-container-low"), | |
| border_radius=10, | |
| color=me.theme_var("on-surface-variant"), | |
| padding=me.Padding.symmetric(vertical=0, horizontal=10), | |
| width="66%", | |
| ) | |
| ): | |
| me.markdown(message.content) | |
| def bot_message(*, message_index: int, message: ChatMessage): | |
| with me.box(style=me.Style(display="flex", gap=20, margin=me.Margin.all(1))): | |
| text_avatar( | |
| background=me.theme_var("primary"), | |
| color=me.theme_var("on-primary"), | |
| label=_BOT_AVATAR_LETTER, | |
| ) | |
| # Bot message response | |
| with me.box(style=me.Style(display="flex", flex_direction="column")): | |
| me.markdown( | |
| message.content, | |
| style=me.Style(color=me.theme_var("on-surface")), | |
| ) | |
| # Actions panel | |
| with me.box(): | |
| icon_button( | |
| key=f"thumb_up-{message_index}", | |
| icon="thumb_up", | |
| is_selected=message.rating == 1, | |
| tooltip="Good response", | |
| on_click=on_click_thumb_up, | |
| ) | |
| icon_button( | |
| key=f"thumb_down-{message_index}", | |
| icon="thumb_down", | |
| is_selected=message.rating == -1, | |
| tooltip="Bad response", | |
| on_click=on_click_thumb_down, | |
| ) | |
| icon_button( | |
| key=f"restart-{message_index}", | |
| icon="restart_alt", | |
| tooltip="Regenerate answer", | |
| on_click=on_click_regenerate, | |
| ) | |
| def chat_input(): | |
| state = me.state(State) | |
| with me.box( | |
| style=me.Style( | |
| background=me.theme_var("surface-container") | |
| if _is_mobile() | |
| else me.theme_var("surface-container"), | |
| border_radius=16, | |
| display="flex", | |
| margin=me.Margin.symmetric(horizontal="auto", vertical=15), | |
| padding=me.Padding.all(8), | |
| width=f"min({_CHAT_MAX_WIDTH}, 100%)", | |
| ) | |
| ): | |
| with me.box( | |
| style=me.Style( | |
| flex_grow=1, | |
| font_family="Roboto" | |
| ) | |
| ): | |
| me.native_textarea( | |
| autosize=True, | |
| key="chat_input", | |
| min_rows=4, | |
| on_blur=on_chat_input, | |
| shortcuts={ | |
| me.Shortcut(key="Enter"): on_submit_chat_msg, # Handle Enter key | |
| me.Shortcut(shift=True, key="Enter"): lambda e: None, # Allow Shift+Enter for newline | |
| }, | |
| placeholder="Enter your prompt", | |
| style=me.Style( | |
| background=me.theme_var("surface-container") | |
| if _is_mobile() | |
| else me.theme_var("surface-container"), | |
| border=me.Border.all( | |
| me.BorderSide(style="none"), | |
| ), | |
| color=me.theme_var("on-surface-variant"), | |
| outline="none", | |
| overflow_y="auto", | |
| padding=me.Padding(top=16, left=16), | |
| width="100%", | |
| ), | |
| value=state.input, | |
| ) | |
| with me.content_button( | |
| disabled=state.in_progress, | |
| on_click=on_click_submit_chat_msg, | |
| type="icon", | |
| ): | |
| me.icon("send") | |
| def text_avatar(*, label: str, background: str, color: str): | |
| me.text( | |
| label, | |
| style=me.Style( | |
| background=background, | |
| border_radius="40%", | |
| color=color, | |
| font_size=20, | |
| height=40, | |
| line_height="1", | |
| margin=me.Margin(top=16), | |
| padding=me.Padding(top=10), | |
| text_align="center", | |
| width="45px", | |
| ), | |
| ) | |
| def icon_button( | |
| *, | |
| icon: str, | |
| tooltip: str, | |
| key: str = "", | |
| is_selected: bool = False, | |
| on_click: Callable | None = None, | |
| ): | |
| selected_style = me.Style( | |
| background=me.theme_var("surface-container-low"), | |
| color=me.theme_var("on-surface-variant"), | |
| ) | |
| with me.tooltip(message=tooltip): | |
| with me.content_button( | |
| type="icon", | |
| key=key, | |
| on_click=on_click, | |
| style=selected_style if is_selected else None, | |
| ): | |
| me.icon(icon) | |
| def menu_icon( | |
| *, icon: str, tooltip: str, key: str = "", on_click: Callable | None = None | |
| ): | |
| with me.tooltip(message=tooltip): | |
| with me.content_button( | |
| key=key, | |
| on_click=on_click, | |
| style=me.Style(margin=me.Margin.all(10)), | |
| type="icon", | |
| ): | |
| me.icon(icon) | |
| def menu_item( | |
| *, icon: str, label: str, key: str = "", on_click: Callable | None = None | |
| ): | |
| with me.box(on_click=on_click): | |
| with me.box( | |
| style=me.Style( | |
| background=me.theme_var("surface-container-high"), | |
| border_radius=20, | |
| cursor="pointer", | |
| display="inline-flex", | |
| gap=10, | |
| line_height=1, | |
| margin=me.Margin.all(10), | |
| padding=me.Padding(top=10, left=10, right=20, bottom=10), | |
| ), | |
| ): | |
| me.icon(icon) | |
| me.text(label, style=me.Style(height=24, line_height="24px")) | |
| # Event Handlers | |
| def on_click_example_user_query(e: me.ClickEvent): | |
| """Populates the user input with the example query""" | |
| state = me.state(State) | |
| _, example_index = e.key.split("-") | |
| state.input = _EXAMPLE_USER_QUERIES[int(example_index)] | |
| me.focus_component(key="chat_input") | |
| def on_click_thumb_up(e: me.ClickEvent): | |
| """Gives the message a positive rating""" | |
| state = me.state(State) | |
| _, msg_index = e.key.split("-") | |
| msg_index = int(msg_index) | |
| state.output[msg_index].rating = 1 | |
| def on_click_thumb_down(e: me.ClickEvent): | |
| """Gives the message a negative rating""" | |
| state = me.state(State) | |
| _, msg_index = e.key.split("-") | |
| msg_index = int(msg_index) | |
| state.output[msg_index].rating = -1 | |
| def on_click_new_chat(e: me.ClickEvent): | |
| """Resets messages and saves the current chat to history.""" | |
| state = me.state(State) | |
| if state.output: | |
| # Save the current chat to history | |
| state.history.insert(0, [asdict(message) for message in state.output]) | |
| # Reset the output for a new chat | |
| state.output = [] | |
| me.focus_component(key="chat_input") | |
| def on_click_history(e: me.ClickEvent): | |
| """Loads existing chat from history and saves current chat.""" | |
| state = me.state(State) | |
| _, chat_index = e.key.split("-") | |
| chat_index = int(chat_index) | |
| # Save the current chat to history if it exists | |
| if state.output: | |
| state.history.insert(0, [asdict(message) for message in state.output]) | |
| # Load the selected chat from history | |
| chat_messages = [ChatMessage(**chat) for chat in state.history.pop(chat_index)] | |
| state.output = chat_messages | |
| me.focus_component(key="chat_input") | |
| def on_click_theme_brightness(e: me.ClickEvent): | |
| """Toggles dark mode.""" | |
| if me.theme_brightness() == "light": | |
| me.set_theme_mode("dark") | |
| else: | |
| me.set_theme_mode("light") | |
| def on_click_menu_icon(e: me.ClickEvent): | |
| """Expands and collapses sidebar menu.""" | |
| state = me.state(State) | |
| state.sidebar_expanded = not state.sidebar_expanded | |
| def on_chat_input(e: me.InputBlurEvent): | |
| """Capture chat text input on blur.""" | |
| state = me.state(State) | |
| state.input = e.value | |
| def on_click_regenerate(e: me.ClickEvent): | |
| """Regenerates response from an existing message""" | |
| state = me.state(State) | |
| _, msg_index = e.key.split("-") | |
| msg_index = int(msg_index) | |
| # Get the user message which is the previous message | |
| user_message = state.output[msg_index - 1] | |
| # Get bot message to be regenerated | |
| assistant_message = state.output[msg_index] | |
| assistant_message.content = "" | |
| state.in_progress = True | |
| yield | |
| start_time = time.time() | |
| # Send in the old user input and chat history to get the bot response. | |
| # We make sure to only pass in the chat history up to this message. | |
| ollama_response = respond_to_chat( | |
| user_message.content, state.output[:msg_index] | |
| ) | |
| assistant_message.content = ollama_response | |
| yield | |
| state.in_progress = False | |
| me.focus_component(key="chat_input") | |
| yield | |
| def transform(input: str, history: list[mel.ChatMessage]): | |
| """ | |
| Takes user input, sends it to the Ollama API, and yields the complete response. | |
| """ | |
| # Call Ollama API with user input | |
| ollama_response = respond_to_chat(input, history) | |
| yield ollama_response | |
| def on_submit_chat_msg(e: me.TextareaShortcutEvent): | |
| """Handles submitting a chat message when Enter is pressed.""" | |
| state = me.state(State) | |
| state.input = e.value | |
| yield | |
| yield from _submit_chat_msg() | |
| def on_click_submit_chat_msg(e: me.ClickEvent): | |
| yield from _submit_chat_msg() | |
| def _submit_chat_msg(): | |
| """Handles submitting a chat message.""" | |
| state = me.state(State) | |
| if state.in_progress or not state.input: | |
| return | |
| input = state.input | |
| # Clear the text input. | |
| state.input = "" | |
| yield | |
| output = state.output | |
| if output is None: | |
| output = [] | |
| output.append(ChatMessage(role="user", content=input)) | |
| state.in_progress = True | |
| me.scroll_into_view(key="scroll-to") | |
| yield | |
| # Send user input and chat history to get the bot response. | |
| ollama_response = respond_to_chat(input, state.output) | |
| assistant_message = ChatMessage(role="bot", content=ollama_response) | |
| output.append(assistant_message) | |
| state.output = output | |
| state.in_progress = False | |
| me.focus_component(key="chat_input") | |
| yield | |
| # Helpers | |
| def _is_mobile(): | |
| return me.viewport_size().width < _MOBILE_BREAKPOINT | |
| def _truncate_text(text, char_limit=100): | |
| """Truncates text that is too long.""" | |
| if len(text) <= char_limit: | |
| return text | |
| truncated_text = text[:char_limit].rsplit(" ", 1)[0] | |
| return truncated_text.rstrip(".,!?;:") + "..." | |