refactor: unify into single muyue binary with embedded desktop mode
All checks were successful
Beta Release / beta (push) Successful in 37s
All checks were successful
Beta Release / beta (push) Successful in 37s
- Merge muyue + muyue-desktop into one binary (13MB) - `muyue` starts TUI, `muyue desktop` launches web UI in browser - Move frontend from cmd/muyue-desktop/frontend/ to web/ (standard Go layout) - Add web/embed.go with //go:embed all:dist for frontend assets - Add internal/desktop/ package (server, browser open, SPA routing, signals) - Split internal/api/api.go into server.go + handlers.go - Add internal/desktop/desktop.go with SPA fallback and --port/--no-open flags - Clean package.json: remove unused @xterm/xterm, switch to ESM - Fix vite.config.js proxy to use port 8095 for dev mode - Add Makefile targets: frontend, desktop, dev-desktop - Update all CI workflows: single binary build, web/ paths - Remove cmd/muyue-desktop/ entirely 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
98
web/src/components/Config.jsx
Normal file
98
web/src/components/Config.jsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { getThemeNames, applyTheme, getTheme } from '../themes'
|
||||
|
||||
export default function Config({ api, theme, onThemeChange }) {
|
||||
const [config, setConfig] = useState(null)
|
||||
const [providers, setProviders] = useState([])
|
||||
const [skillList, setSkillList] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
api.getConfig().then(d => setConfig(d)).catch(() => {})
|
||||
api.getProviders().then(d => setProviders(d.providers || [])).catch(() => {})
|
||||
api.getSkills().then(d => setSkillList(d.skills || [])).catch(() => {})
|
||||
}, [])
|
||||
|
||||
const themes = getThemeNames()
|
||||
|
||||
const handleThemeChange = (themeId) => {
|
||||
const t = getTheme(themeId)
|
||||
applyTheme(t)
|
||||
onThemeChange(themeId)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="config-container">
|
||||
<div className="config-section">
|
||||
<div className="section-header">Profile</div>
|
||||
{config?.profile && (
|
||||
<div>
|
||||
<Field label="Name" value={config.profile.name} />
|
||||
<Field label="Pseudo" value={config.profile.pseudo} />
|
||||
<Field label="Email" value={config.profile.email} />
|
||||
<Field label="Editor" value={config.profile.preferences?.editor} />
|
||||
<Field label="Shell" value={config.profile.preferences?.shell} />
|
||||
<Field label="Default AI" value={config.profile.preferences?.defaultAI} />
|
||||
<Field label="Languages" value={config.profile.languages?.join(', ')} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="config-section">
|
||||
<div className="section-header">AI Providers</div>
|
||||
{providers.map((p, i) => (
|
||||
<div key={i} className="config-field" style={{ flexDirection: 'column', alignItems: 'flex-start', gap: 4 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<span style={{ color: 'var(--text-bright)', fontWeight: 700 }}>{p.name}</span>
|
||||
{p.active && <span style={{ color: 'var(--cyber-red)', fontSize: 11, fontWeight: 700 }}>{'>>'}</span>}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 16, fontSize: 12 }}>
|
||||
<span style={{ color: 'var(--dim-red)' }}>model={p.model}</span>
|
||||
<span style={{ color: p.apiKey ? 'var(--success)' : 'var(--error)' }}>
|
||||
key={p.apiKey ? 'configured' : 'no key'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="config-section">
|
||||
<div className="section-header">Theme</div>
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||
{themes.map(t => (
|
||||
<button
|
||||
key={t.id}
|
||||
className={theme === t.id ? 'primary' : ''}
|
||||
onClick={() => handleThemeChange(t.id)}
|
||||
>
|
||||
{t.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="config-section">
|
||||
<div className="section-header">Skills ({skillList.length})</div>
|
||||
{skillList.length === 0 ? (
|
||||
<span style={{ color: 'var(--text-muted)', fontSize: 12 }}>No skills. Run `muyue skills init`.</span>
|
||||
) : (
|
||||
skillList.map((s, i) => (
|
||||
<div key={i} className="tool-item">
|
||||
<span className="tool-name">{s.name}</span>
|
||||
<span style={{ color: 'var(--cyber-red)', fontSize: 11 }}>[{s.target || 'both'}]</span>
|
||||
<span style={{ color: 'var(--dim-red)', fontSize: 11 }}>{s.description}</span>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Field({ label, value }) {
|
||||
return (
|
||||
<div className="config-field">
|
||||
<span className="config-label">{label}:</span>
|
||||
<span className="config-value">{value || '-'}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user