H2OTest / llm_studio /app_utils /wave_utils.py
elineve's picture
Upload 301 files
07423df
import subprocess
import sys
import traceback
from typing import TypedDict
import pandas as pd
from h2o_wave import Q, expando_to_dict, ui
from h2o_wave.types import Component
from llm_studio.app_utils.sections.common import clean_dashboard
from .config import default_cfg
class ThemeColors(TypedDict):
light: dict
dark: dict
class WaveTheme:
_theme_colors: ThemeColors = {
"light": {
"primary": "#000000",
"background_color": "#ffffff",
},
"dark": {
"primary": "#FEC925",
"background_color": "#121212",
},
}
states = {
"zombie": "#E0E0E0",
"queued": "#B8B8B8",
"running": "#FFE52B",
"finished": "#92E95A",
"failed": "#DA0000",
"stopped": "#DA0000",
}
color = "#2196F3"
color_range = "#2196F3 #CC7722 #2CA02C #D62728 #9467BD #17BECF #E377C2 #DDAA22"
def __repr__(self) -> str:
return "WaveTheme"
def get_value_by_key(self, q: Q, key: str):
value = (
self._theme_colors["dark"][key]
if q.client.theme_dark
else self._theme_colors["light"][key]
)
return value
def get_primary_color(self, q: Q):
primary_color = self.get_value_by_key(q, "primary")
return primary_color
def get_background_color(self, q: Q):
background_color = self.get_value_by_key(q, "background_color")
return background_color
wave_theme = WaveTheme()
def ui_table_from_df(
q: Q,
df: pd.DataFrame,
name: str,
sortables: list = None,
filterables: list = None,
searchables: list = None,
markdown_cells=None,
numerics: list = None,
times: list = None,
tags: list = None,
progresses: list = None,
min_widths: dict = None,
max_widths: dict = None,
link_col: str = None,
multiple: bool = False,
groupable: bool = False,
downloadable: bool = False,
resettable: bool = False,
height: str = None,
checkbox_visibility: str = None,
actions: dict = None,
max_char_length: int = 500,
cell_overflow="tooltip",
) -> Component:
"""
Convert a Pandas dataframe into Wave ui.table format.
"""
df = df.reset_index(drop=True)
sortables = sortables or []
filterables = filterables or []
searchables = searchables or []
numerics = numerics or []
times = times or []
tags = tags or []
progresses = progresses or []
markdown_cells = markdown_cells or []
min_widths = min_widths or {}
max_widths = max_widths or {}
if numerics == []:
numerics = df.select_dtypes(include=["float64", "float32"]).columns.tolist()
cell_types = {}
for col in tags:
cell_types[col] = ui.tag_table_cell_type(
name="tags",
tags=[
ui.tag(label=state, color=wave_theme.states[state])
for state in wave_theme.states
],
)
for col in progresses:
cell_types[col] = ui.progress_table_cell_type(
wave_theme.get_primary_color(q),
)
for col in markdown_cells:
# enables rendering of code in wave table
cell_types[col] = ui.markdown_table_cell_type()
columns = [
ui.table_column(
name=str(col),
label=str(col),
sortable=True if col in sortables else False,
filterable=True if col in filterables else False,
searchable=True if col in searchables else False,
data_type=(
"number" if col in numerics else ("time" if col in times else "string")
),
cell_type=cell_types[col] if col in cell_types else None,
min_width=min_widths[col] if col in min_widths else None,
max_width=max_widths[col] if col in max_widths else None,
link=True if col == link_col else False,
cell_overflow=cell_overflow,
)
for col in df.columns.values
]
if actions:
commands = [ui.command(name=key, label=val) for key, val in actions.items()]
action_column = ui.table_column(
name="actions",
label="action" if int(min_widths["actions"]) > 30 else "",
cell_type=ui.menu_table_cell_type(name="commands", commands=commands),
min_width=min_widths["actions"],
)
columns.append(action_column)
rows = []
for i, row in df.iterrows():
cells = []
for cell in row:
str_repr = str(cell)
if len(str_repr) >= max_char_length:
str_repr = str_repr[:max_char_length] + "..."
cells.append(str_repr)
rows.append(ui.table_row(name=str(i), cells=cells))
table = ui.table(
name=name,
columns=columns,
rows=rows,
multiple=multiple,
groupable=groupable,
downloadable=downloadable,
resettable=resettable,
height=height,
checkbox_visibility=checkbox_visibility,
)
return table
def wave_utils_error_card(
q: Q,
box: str,
app_name: str,
github: str,
q_app: dict,
error: Exception,
q_user: dict,
q_client: dict,
q_events: dict,
q_args: dict,
) -> ui.FormCard:
"""
Card for handling crash.
"""
q_app_str = (
"### q.app\n```"
+ "\n".join(
[
f"{k}: {v}"
for k, v in q_app.items()
if "_key" not in k and "_token not in k"
]
)
+ "\n```"
)
q_user_str = (
"### q.user\n```"
+ "\n".join(
[
f"{k}: {v}"
for k, v in q_user.items()
if "_key" not in k and "_token" not in k
]
)
+ "\n```"
)
q_client_str = (
"### q.client\n```"
+ "\n".join(
[
f"{k}: {v}"
for k, v in q_client.items()
if "_key" not in k and "_token" not in k
]
)
+ "\n```"
)
q_events_str = (
"### q.events\n```"
+ "\n".join(
[
f"{k}: {v}"
for k, v in q_events.items()
if "_key" not in k and "_token" not in k
]
)
+ "\n```"
)
q_args_str = (
"### q.args\n```"
+ "\n".join(
[
f"{k}: {v}"
for k, v in q_args.items()
if "_key" not in k and "_token" not in k
]
)
+ "\n```"
)
type_, value_, traceback_ = sys.exc_info()
stack_trace = traceback.format_exception(type_, value_, traceback_)
git_version = subprocess.getoutput("git rev-parse HEAD")
if not q.app.wave_utils_stack_trace_str:
q.app.wave_utils_stack_trace_str = "### stacktrace\n" + "\n".join(stack_trace)
card = ui.form_card(
box=box,
items=[
ui.stats(
items=[
ui.stat(
label="",
value="Oops!",
caption="Something went wrong",
icon="Error",
icon_color="#CDDD38",
)
],
justify="center",
),
ui.separator(),
ui.text_l(content="<center>Apologies for the inconvenience!</center>"),
ui.buttons(
items=[
ui.button(name="home", label="Restart", primary=True),
ui.button(name="report_error", label="Report", primary=True),
],
justify="center",
),
ui.separator(visible=False),
ui.text(
content=f"""<center>
To report this error,
please open an issues on Github <a href={github}>{github}</a>
with the details below:</center>""",
visible=False,
),
ui.text_l(content=f"Report Issue: {app_name}", visible=False),
ui.text_xs(content=q_app_str, visible=False),
ui.text_xs(content=q_user_str, visible=False),
ui.text_xs(content=q_client_str, visible=False),
ui.text_xs(content=q_events_str, visible=False),
ui.text_xs(content=q_args_str, visible=False),
ui.text_xs(content=q.app.wave_utils_stack_trace_str, visible=False),
ui.text_xs(content=f"### Error\n {error}", visible=False),
ui.text_xs(content=f"### Git Version\n {git_version}", visible=False),
],
)
return card
async def wave_utils_handle_error(q: Q, error: Exception):
"""
Handle any app error.
"""
await clean_dashboard(q, mode="error")
card_name = "wave_utils_error"
q.page[card_name] = wave_utils_error_card(
q,
box="content",
error=error,
app_name=f"{default_cfg.name} at {default_cfg.url}",
github=default_cfg.github,
q_app=expando_to_dict(q.app),
q_user=expando_to_dict(q.user),
q_client=expando_to_dict(q.client),
q_events=expando_to_dict(q.events),
q_args=expando_to_dict(q.args),
)
q.client.delete_cards.add("wave_utils_error")
await q.page.save()
async def report_error(q: Q):
"""
Report error details.
"""
card_name = "wave_utils_error"
# Show card again. Required since card can be cleared
await wave_utils_handle_error(
q,
error=q.app.wave_utils_error_str,
)
q.page[card_name].items[4].separator.visible = True
q.page[card_name].items[5].text.visible = True
q.page[card_name].items[6].text_l.visible = True
q.page[card_name].items[7].text_xs.visible = True
q.page[card_name].items[8].text_xs.visible = True
q.page[card_name].items[9].text_xs.visible = True
q.page[card_name].items[10].text_xs.visible = True
q.page[card_name].items[11].text_xs.visible = True
q.page[card_name].items[12].text_xs.visible = True
q.page[card_name].items[13].text_xs.visible = True
q.page[card_name].items[14].text_xs.visible = True
await q.page.save()
async def busy_dialog(
q: Q, title: str = "", text: str = "", force_wait: bool = False
) -> None:
"""Creates busy dialog"""
q.page["meta"].dialog = ui.dialog(
title=title,
primary=True,
items=[
ui.progress(label=text),
],
blocking=True,
)
await q.page.save()
if force_wait:
await q.sleep(1)
q.page["meta"].dialog = None