Team Ai
Apppublic

WaveCut/Bonsai-Chat-WebGPU

sourceHugging Faceupdated 3mo agoView on Hugging Face
5likes
Header.tsx107 linesDownload Raw Back to components
1export type ChatWidth = 'centered' | 'wide';2 3interface HeaderProps {4  onOpenSettings: () => void;5  leftSidebarOpen: boolean;6  rightSidebarOpen: boolean;7  chatWidth: ChatWidth;8  onToggleLeftSidebar: () => void;9  onToggleRightSidebar: () => void;10  onToggleChatWidth: () => void;11  disabled?: boolean;12}13 14export function isSafariUserAgent(userAgent: string): boolean {15  return /\bSafari\//.test(userAgent) && !/(?:HeadlessChrome|\bChrome|\bChromium|\bCriOS|\bEdg|\bOPR)\//.test(userAgent);16}17 18export function Header({19  onOpenSettings,20  leftSidebarOpen,21  rightSidebarOpen,22  chatWidth,23  onToggleLeftSidebar,24  onToggleRightSidebar,25  onToggleChatWidth,26  disabled = false,27}: HeaderProps) {28  const safari = isSafariUserAgent(globalThis.navigator?.userAgent ?? '');29 30  return (31    <header className="station-header">32      <div className="brand-block" aria-label="Bonsai local WebGPU chat">33        <span className="brand-glyph" aria-hidden="true">B</span>34        <div>35          <h1>Bonsai</h1>36          <span>Local WebGPU chat</span>37        </div>38      </div>39 40      <div className={`browser-notice${safari ? ' is-warning' : ''}`} role={safari ? 'status' : undefined}>41        <span className="notice-mark" aria-hidden="true" />42        <span>43          <strong>{safari ? 'Safari compatibility mode' : 'Private local inference'}</strong>44          <span>{safari ? 'Use Chrome or Edge for substantially faster generation.' : 'Prompts and model output stay in this browser.'}</span>45        </span>46      </div>47 48      <div className="header-actions">49        <div className="layout-controls" role="group" aria-label="Workspace layout">50          <button51            type="button"52            aria-label={leftSidebarOpen ? 'Hide model sidebar' : 'Show model sidebar'}53            aria-pressed={leftSidebarOpen}54            onClick={onToggleLeftSidebar}55            title={leftSidebarOpen ? 'Hide models' : 'Show models'}56          >57            <PanelLeftIcon />58          </button>59          <button60            type="button"61            aria-label={chatWidth === 'wide' ? 'Center chat' : 'Use wide chat'}62            aria-pressed={chatWidth === 'wide'}63            onClick={onToggleChatWidth}64            title={chatWidth === 'wide' ? 'Centered chat' : 'Wide chat'}65          >66            <WidthIcon />67          </button>68          <button69            type="button"70            aria-label={rightSidebarOpen ? 'Hide telemetry sidebar' : 'Show telemetry sidebar'}71            aria-pressed={rightSidebarOpen}72            onClick={onToggleRightSidebar}73            title={rightSidebarOpen ? 'Hide telemetry' : 'Show telemetry'}74          >75            <PanelRightIcon />76          </button>77        </div>78        <button className="icon-button" type="button" disabled={disabled} onClick={onOpenSettings} aria-label="Open runtime settings">79          <SettingsIcon />80        </button>81      </div>82    </header>83  );84}85 86function PanelLeftIcon() {87  return <svg viewBox="0 0 20 20" aria-hidden="true"><rect x="2.5" y="3" width="15" height="14" rx="1.5" /><path d="M7.5 3v14" /></svg>;88}89 90function PanelRightIcon() {91  return <svg viewBox="0 0 20 20" aria-hidden="true"><rect x="2.5" y="3" width="15" height="14" rx="1.5" /><path d="M12.5 3v14" /></svg>;92}93 94function WidthIcon() {95  return <svg viewBox="0 0 20 20" aria-hidden="true"><path d="M3 6v8M17 6v8M7 10h6M8.5 8.5 7 10l1.5 1.5M11.5 8.5 13 10l-1.5 1.5" /></svg>;96}97 98function SettingsIcon() {99  return (100    <svg viewBox="0 0 24 24" aria-hidden="true">101      <path d="M4 7h10M18 7h2M4 17h2M10 17h10M14 4v6M10 14v6" />102      <circle cx="16" cy="7" r="2" />103      <circle cx="8" cy="17" r="2" />104    </svg>105  );106}107