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