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="1430px", 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, ) if q.client.theme_dark: q.page["meta"].theme = "h2o-dark" else: q.page["meta"].theme = "light" 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 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/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("
"), 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("
"), 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) }) """ )