Spaces:
Sleeping
Sleeping
import subprocess | |
import sys | |
import traceback | |
from typing import Literal, 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): | |
primary: str | |
background_color: str | |
class WaveTheme: | |
_theme_colors: ThemeColors = { | |
"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_primary_color(self, q: Q): | |
primary_color = self._theme_colors["primary"] | |
return primary_color | |
def get_background_color(self, q: Q): | |
background_color = self._theme_colors["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: list = 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: Literal["tooltip", "wrap"] = "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=["number"]).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 | |