Spaces:
Sleeping
Sleeping
import hashlib | |
import logging | |
from typing import List | |
from h2o_wave import Q, ui | |
from llm_studio.app_utils.cards import card_zones | |
from llm_studio.app_utils.config import default_cfg | |
logger = logging.getLogger(__name__) | |
async def meta(q: Q) -> None: | |
if q.client["keep_meta"]: # Do not reset meta, keep current dialog opened | |
q.client["keep_meta"] = False | |
return | |
zones = card_zones(mode=q.client["mode_curr"]) | |
if q.client["notification_bar"]: | |
notification_bar = ui.notification_bar( | |
type="warning", | |
timeout=20, | |
text=q.client["notification_bar"], | |
position="top-right", | |
) | |
else: | |
notification_bar = None | |
# TODO remove `stylesheet` when wave makes message bars smaller | |
q.page["meta"] = ui.meta_card( | |
box="", | |
title="H2O LLM Studio", | |
layouts=[ | |
ui.layout(breakpoint="0px", width="100%", zones=zones), | |
ui.layout(breakpoint="1920px", width="1920px", zones=zones), | |
], | |
scripts=[ | |
ui.script(source, asynchronous=True) for source in q.app["script_sources"] | |
], | |
stylesheet=ui.inline_stylesheet( | |
""" | |
.ms-MessageBar { | |
padding-top: 3px; | |
padding-bottom: 3px; | |
min-height: 18px; | |
} | |
div[data-test="nav_bar"] .ms-Nav-groupContent { | |
margin-bottom: 0; | |
} | |
div[data-test="experiment/display/deployment/top_right"], | |
div[data-test="experiment/display/deployment/top_right"] | |
div[data-visible="true"]:last-child > div > div { | |
display: flex; | |
} | |
div[data-test="experiment/display/deployment/top_right"] | |
div[data-visible="true"]:last-child, | |
div[data-test="experiment/display/deployment/top_right"] | |
div[data-visible="true"]:last-child > div { | |
display: flex; | |
flex-grow: 1; | |
} | |
div[data-test="experiment/display/deployment/top_right"] | |
div[data-visible="true"]:last-child > div > div > div { | |
display: flex; | |
flex-grow: 1; | |
flex-direction: column; | |
} | |
div[data-test="experiment/display/deployment/top_right"] | |
div[data-visible="true"]:last-child > div > div > div > div { | |
flex-grow: 1; | |
} | |
""" | |
), | |
script=None, | |
notification_bar=notification_bar, | |
) | |
q.page["meta"].theme = "h2o-dark" | |
def heap_analytics( | |
userid, user_properties=None, event_properties=None | |
) -> ui.InlineScript: | |
script = ( | |
"window.heap=window.heap||[],heap.load=function(e,t)" | |
"{window.heap.appid=e,window.heap." | |
'config=t=t||{};var r=document.createElement("script");' | |
'r.type="text/javascript",' | |
'r.async=!0,r.src="https://cdn.heapanalytics.com/js/heap-"+e+".js";' | |
'var a=document.getElementsByTagName("script")[0];' | |
"a.parentNode.insertBefore(r,a);" | |
"for(var n=function(e){return function(){heap.push([e]." | |
"concat(Array.prototype.slice.call(arguments,0)))}}," | |
'p=["addEventProperties","addUserProperties","clearEventProperties","identify",' | |
'"resetIdentity","removeEventProperty","setEventProperties","track",' | |
'"unsetEventProperty"],o=0;o<p.length;o++)heap[p[o]]=n(p[o])};' | |
'heap.load("1090178399");' | |
) | |
identity = hashlib.sha256(userid.encode()).hexdigest() | |
script += f"heap.identify('{identity}');" | |
if user_properties is not None: | |
script += f"heap.addUserProperties({user_properties})" | |
if event_properties is not None: | |
script += f"heap.addEventProperties({event_properties})" | |
return ui.inline_script(content=script) | |
async def interface(q: Q) -> None: | |
"""Display interface cards.""" | |
await meta(q) | |
navigation_pages = ["Home", "Settings"] | |
if q.client["init_interface"] is None: | |
# to avoid flickering | |
q.page["header"] = ui.header_card( | |
box="header", | |
title=default_cfg.name, | |
image=q.app["icon_path"], | |
subtitle=f"v{default_cfg.version}", | |
) | |
if q.app.heap_mode: | |
logger.info("Heap on") | |
q.page["meta"].script = heap_analytics( | |
userid=q.auth.subject, | |
event_properties=( | |
f"{{version: '{q.app.version}'" + f", product: '{q.app.name}'}}" | |
), | |
) | |
# execute the heap inline script once in the initialization | |
await q.page.save() | |
else: | |
logger.info("Heap off") | |
q.page["nav_bar"] = ui.nav_card( | |
box="nav", | |
items=[ | |
ui.nav_group( | |
"Navigation", | |
items=[ | |
ui.nav_item(page.lower(), page) for page in navigation_pages | |
], | |
), | |
ui.nav_group( | |
"Datasets", | |
items=[ | |
ui.nav_item(name="dataset/import", label="Import dataset"), | |
ui.nav_item(name="dataset/list", label="View datasets"), | |
], | |
), | |
ui.nav_group( | |
"Experiments", | |
items=[ | |
ui.nav_item(name="experiment/start", label="Create experiment"), | |
ui.nav_item( | |
name="experiment/start/grid_search", | |
label="Create grid search", | |
), | |
ui.nav_item(name="experiment/list", label="View experiments"), | |
], | |
), | |
], | |
value=( | |
default_cfg.start_page | |
if q.client["nav/active"] is None | |
else q.client["nav/active"] | |
), | |
) | |
else: | |
# Only update menu properties to prevent from flickering | |
q.page["nav_bar"].value = ( | |
default_cfg.start_page | |
if q.client["nav/active"] is None | |
else q.client["nav/active"] | |
) | |
q.client["init_interface"] = True | |
async def clean_dashboard(q: Q, mode: str = "full", exclude: List[str] = []): | |
"""Drop cards from Q page.""" | |
logger.info(q.client.delete_cards) | |
for card_name in q.client.delete_cards: | |
if card_name not in exclude: | |
del q.page[card_name] | |
q.page["meta"].layouts[0].zones = card_zones(mode=mode) | |
q.client["mode_curr"] = mode | |
q.client["notification_bar"] = None | |
async def delete_dialog(q: Q, names: List[str], action, entity): | |
title = "Do you really want to delete " | |
n_datasets = len(names) | |
if n_datasets == 1: | |
title = f"{title} {entity} {names[0]}?" | |
else: | |
title = f"{title} {n_datasets} {entity}s?" | |
q.page["meta"].dialog = ui.dialog( | |
f"Delete {entity}", | |
items=[ | |
ui.text(title), | |
ui.markup("<br>"), | |
ui.buttons( | |
[ | |
ui.button(name=action, label="Delete", primary=True), | |
ui.button(name="abort", label="Abort", primary=False), | |
], | |
justify="end", | |
), | |
], | |
) | |
q.client["keep_meta"] = True | |
async def info_dialog(q: Q, title: str, message: str): | |
q.page["meta"].dialog = ui.dialog( | |
title, | |
items=[ | |
ui.text(message), | |
ui.markup("<br>"), | |
ui.buttons( | |
[ | |
ui.button(name="abort", label="Continue", primary=False), | |
], | |
justify="end", | |
), | |
], | |
blocking=True, | |
) | |
q.client["keep_meta"] = True | |
async def heap_redact(q: Q) -> None: | |
if q.app.heap_mode: | |
# Send the page to the browser, so the following js can be applied | |
await q.page.save() | |
# replace dataset names with **** | |
q.page["meta"].script = ui.inline_script( | |
""" | |
document.querySelectorAll('div[data-automation-key="name"]').forEach(a => { | |
a.setAttribute('data-heap-redact-text', '') | |
}) | |
document.querySelector('div[data-test="datasets_table"] \ | |
.ms-ScrollablePane--contentContainer').addEventListener('scroll', () => { | |
window.setTimeout(() => {{ | |
document.querySelectorAll('div[data-automation-key="name"]').forEach(a => { | |
a.setAttribute('data-heap-redact-text', '') | |
}) | |
}}, 100) | |
}) | |
""" | |
) | |