Some checks failed
PR Check / check (pull_request) Failing after 11s
New desktop application that launches a local HTTP server with embedded React frontend. Opens in the user's browser automatically. Architecture: - internal/api/: REST API exposing all internal/ packages to frontend - cmd/muyue-desktop/: entry point, serves embedded frontend + API - cmd/muyue-desktop/frontend/: React + Vite SPA Frontend features: - 4 tabs: Dashboard, Studio, Shell, Config - Cyberpunk red theme with CSS custom properties - Theme system: 4 built-in themes (Cyberpunk Red, Pink, Midnight Blue, Matrix Green) - Terminal with command execution via API - Chat interface with sidebar (agents, workflows, commands) - Live clock, status indicators, update badges - Glitch/scanline/fade animations between tabs - xterm.js included for future full terminal integration Backend API endpoints: - GET /api/info, /api/system, /api/tools, /api/config - GET /api/providers, /api/skills, /api/lsp, /api/mcp, /api/updates - POST /api/scan, /api/install, /api/terminal, /api/mcp/configure Build: cd cmd/muyue-desktop/frontend && npm run build && go build ./cmd/muyue-desktop/ Binary: ~11MB single binary with embedded frontend Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
106 lines
3.5 KiB
JavaScript
106 lines
3.5 KiB
JavaScript
import { useState, useEffect, useCallback } from 'react'
|
|
import { useAPI } from '../hooks/useAPI'
|
|
import { getTheme, getThemeNames, applyTheme } from '../themes'
|
|
import Dashboard from './Dashboard'
|
|
import Studio from './Studio'
|
|
import Shell from './Shell'
|
|
import Config from './Config'
|
|
|
|
const TABS = [
|
|
{ id: 'dash', label: 'DASH', icon: '[■]' },
|
|
{ id: 'studio', label: 'STUDIO', icon: '[<>]' },
|
|
{ id: 'shell', label: 'SHELL', icon: '[$]' },
|
|
{ id: 'config', label: 'CONFIG', icon: '[//]' },
|
|
]
|
|
|
|
export default function App() {
|
|
const [activeTab, setActiveTab] = useState('dash')
|
|
const [info, setInfo] = useState({})
|
|
const [clock, setClock] = useState(new Date())
|
|
const [updates, setUpdates] = useState([])
|
|
const [tools, setTools] = useState([])
|
|
const [transition, setTransition] = useState(false)
|
|
const [currentTheme, setCurrentTheme] = useState('cyberpunk-red')
|
|
const api = useAPI()
|
|
|
|
useEffect(() => {
|
|
api.getInfo().then(setInfo).catch(() => {})
|
|
api.getTools().then(d => setTools(d.tools || [])).catch(() => {})
|
|
api.getUpdates().then(d => setUpdates(d.updates || [])).catch(() => {})
|
|
|
|
const theme = getTheme(currentTheme)
|
|
applyTheme(theme)
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
const timer = setInterval(() => setClock(new Date()), 1000)
|
|
return () => clearInterval(timer)
|
|
}, [])
|
|
|
|
const switchTab = useCallback((tabId) => {
|
|
if (tabId === activeTab) return
|
|
setTransition(true)
|
|
setTimeout(() => {
|
|
setActiveTab(tabId)
|
|
setTimeout(() => setTransition(false), 150)
|
|
}, 100)
|
|
}, [activeTab])
|
|
|
|
const hasUpdates = updates.some(u => u.needsUpdate)
|
|
|
|
const renderContent = () => {
|
|
switch (activeTab) {
|
|
case 'dash': return <Dashboard tools={tools} updates={updates} api={api} onRescan={(t) => setTools(t)} />
|
|
case 'studio': return <Studio api={api} />
|
|
case 'shell': return <Shell api={api} />
|
|
case 'config': return <Config api={api} theme={currentTheme} onThemeChange={setCurrentTheme} />
|
|
default: return null
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="app-layout">
|
|
<header className="header">
|
|
<span className="header-logo">MUYUE</span>
|
|
<span className="header-version">v{info.version || '...'}</span>
|
|
|
|
<div className="header-tabs">
|
|
{TABS.map(tab => (
|
|
<div
|
|
key={tab.id}
|
|
className={`header-tab ${activeTab === tab.id ? 'active' : ''}`}
|
|
onClick={() => switchTab(tab.id)}
|
|
>
|
|
{tab.icon} {tab.label}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="header-spacer" />
|
|
|
|
<div className="header-status">
|
|
<span className={`status-dot ${tools.length > 0 ? 'ok' : 'off'}`} title="System" />
|
|
<span className={`status-dot ${hasUpdates ? 'warn' : 'ok'}`} title="Updates" />
|
|
</div>
|
|
|
|
<span className="header-date">{clock.toLocaleDateString('fr-FR')}</span>
|
|
<span className="header-clock">{clock.toLocaleTimeString('fr-FR')}</span>
|
|
</header>
|
|
|
|
<div className={`content ${transition ? 'glitch-text' : 'fade-in tab-transition'}`}>
|
|
{renderContent()}
|
|
</div>
|
|
|
|
<footer className="footer">
|
|
<span className="footer-shortcuts">
|
|
<kbd>1-4</kbd> tabs · <kbd>Ctrl+T</kbd> switcher · <kbd>Ctrl+C</kbd> quit
|
|
</span>
|
|
<span className={`footer-update ${hasUpdates ? 'available' : 'uptodate'}`}>
|
|
{hasUpdates ? '[UPD] Updates available' : '[OK] Up to date'}
|
|
</span>
|
|
<span className="footer-version">v{info.version || '...'}</span>
|
|
</footer>
|
|
</div>
|
|
)
|
|
}
|