Create standalone browser port of LTX Athena with authenticated native API transport

This commit is contained in:
Mikei386
2026-09-29 17:59:23 +02:00
commit 1333b0c36b
202 changed files with 63054 additions and 0 deletions
+251
View File
@@ -0,0 +1,251 @@
import React, { useState, useRef, useEffect, useMemo } from 'react'
import { Search } from 'lucide-react'
// --- Types ---
export interface MenuItem {
id: string
label: string
shortcut?: string
action?: () => void
disabled?: boolean
separator?: boolean // renders a divider line
submenu?: MenuItem[]
}
export interface MenuDefinition {
id: string
label: string
items: MenuItem[]
}
interface MenuBarProps {
menus: MenuDefinition[]
rightContent?: React.ReactNode
}
// --- Component ---
export function MenuBar({ menus, rightContent }: MenuBarProps) {
const [openMenuId, setOpenMenuId] = useState<string | null>(null)
const [hoverMenuId, setHoverMenuId] = useState<string | null>(null)
const [searchQuery, setSearchQuery] = useState('')
const [highlightedResult, setHighlightedResult] = useState(0)
const menuBarRef = useRef<HTMLDivElement>(null)
const searchInputRef = useRef<HTMLInputElement>(null)
// The active open menu (follow hover once a menu is open)
const activeMenuId = openMenuId ? (hoverMenuId || openMenuId) : null
// Close menus on outside click
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (menuBarRef.current && !menuBarRef.current.contains(e.target as Node)) {
setOpenMenuId(null)
setHoverMenuId(null)
}
}
if (openMenuId) {
document.addEventListener('mousedown', handleClickOutside)
}
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [openMenuId])
// Close on Escape
useEffect(() => {
const handleKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
setOpenMenuId(null)
setHoverMenuId(null)
setSearchQuery('')
}
}
document.addEventListener('keydown', handleKey)
return () => document.removeEventListener('keydown', handleKey)
}, [])
const searchResults = useMemo(() => {
const query = searchQuery.trim().toLowerCase()
if (!query) return []
const results: { menuLabel: string; item: MenuItem }[] = []
for (const menu of menus) {
for (const item of menu.items) {
if (item.separator) continue
if (item.label.toLowerCase().includes(query)) {
results.push({ menuLabel: menu.label, item })
}
if (item.submenu) {
for (const sub of item.submenu) {
if (sub.separator) continue
if (sub.label.toLowerCase().includes(query)) {
results.push({ menuLabel: `${menu.label} > ${item.label}`, item: sub })
}
}
}
}
}
return results
}, [menus, searchQuery])
useEffect(() => {
setHighlightedResult(prev => {
if (searchResults.length === 0) return 0
return Math.min(prev, searchResults.length - 1)
})
}, [searchResults.length])
const handleItemClick = (item: MenuItem) => {
if (item.disabled || !item.action) return
item.action()
setOpenMenuId(null)
setHoverMenuId(null)
}
const handleSearchResultClick = (item: MenuItem) => {
if (item.disabled || !item.action) return
item.action()
setSearchQuery('')
setOpenMenuId(null)
setHoverMenuId(null)
}
// Handle search keyboard navigation
const handleSearchKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'ArrowDown') {
e.preventDefault()
setHighlightedResult(prev => Math.min(prev + 1, searchResults.length - 1))
} else if (e.key === 'ArrowUp') {
e.preventDefault()
setHighlightedResult(prev => Math.max(prev - 1, 0))
} else if (e.key === 'Enter' && searchResults[highlightedResult]) {
e.preventDefault()
handleSearchResultClick(searchResults[highlightedResult].item)
}
}
const renderMenuItem = (item: MenuItem, index: number) => {
if (item.separator) {
return <div key={`sep-${index}`} className="h-px bg-zinc-700 my-1 mx-2" />
}
return (
<button
key={item.id}
onClick={() => handleItemClick(item)}
disabled={item.disabled}
className={`w-full flex items-center justify-between px-3 py-1.5 text-left text-[13px] transition-colors ${
item.disabled
? 'text-zinc-600 cursor-not-allowed'
: 'text-zinc-200 hover:bg-blue-600 hover:text-white'
}`}
>
<span>{item.label}</span>
{item.shortcut && (
<span className={`ml-8 text-[11px] ${item.disabled ? 'text-zinc-700' : 'text-zinc-500'}`}>
{item.shortcut}
</span>
)}
</button>
)
}
return (
<div ref={menuBarRef} className="flex items-center bg-zinc-900 border-b border-zinc-800 select-none relative z-[60]">
<div className="flex items-center flex-1">
{menus.map(menu => {
const isActive = activeMenuId === menu.id
const isHelpMenu = menu.id === 'help'
return (
<div key={menu.id} className="relative">
<button
onMouseDown={() => {
if (openMenuId === menu.id) {
setOpenMenuId(null)
setHoverMenuId(null)
} else {
setOpenMenuId(menu.id)
setHoverMenuId(null)
if (isHelpMenu) {
setTimeout(() => searchInputRef.current?.focus(), 50)
}
}
}}
onMouseEnter={() => {
if (openMenuId) setHoverMenuId(menu.id)
}}
className={`px-3 py-1.5 text-[13px] font-medium transition-colors ${
isActive
? 'bg-zinc-800 text-white'
: 'text-zinc-400 hover:text-zinc-200'
}`}
>
{menu.label}
</button>
{/* Dropdown */}
{isActive && (
<div className="absolute top-full left-0 min-w-[240px] bg-zinc-900 border border-zinc-700 rounded-b-lg shadow-xl shadow-black/50 py-1 z-[60]">
{/* Help menu has search */}
{isHelpMenu && (
<div className="px-2 py-1.5 border-b border-zinc-700">
<div className="flex items-center gap-2 bg-zinc-800 rounded px-2 py-1">
<Search className="h-3.5 w-3.5 text-zinc-500 flex-shrink-0" />
<input
ref={searchInputRef}
type="text"
placeholder="Search menus..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={handleSearchKeyDown}
className="flex-1 bg-transparent text-[13px] text-white placeholder-zinc-500 outline-none"
autoFocus
/>
</div>
{/* Search results */}
{searchQuery && (
<div className="mt-1 max-h-48 overflow-y-auto">
{searchResults.length === 0 ? (
<div className="text-[12px] text-zinc-500 px-2 py-2 text-center">No results</div>
) : (
searchResults.map((result, i) => (
<button
key={`${result.item.id}-${i}`}
onClick={() => handleSearchResultClick(result.item)}
className={`w-full flex items-center justify-between px-2 py-1.5 text-left text-[12px] rounded transition-colors ${
i === highlightedResult
? 'bg-blue-600 text-white'
: 'text-zinc-300 hover:bg-zinc-800'
}`}
>
<div>
<span>{result.item.label}</span>
<span className="text-[10px] text-zinc-500 ml-2">{result.menuLabel}</span>
</div>
{result.item.shortcut && (
<span className="text-[10px] text-zinc-500">{result.item.shortcut}</span>
)}
</button>
))
)}
</div>
)}
</div>
)}
{/* Regular menu items */}
{menu.items.map((item, i) => renderMenuItem(item, i))}
</div>
)}
</div>
)
})}
</div>
{rightContent && (
<div className="flex items-center mr-2">
{rightContent}
</div>
)}
</div>
)
}