All checks were successful
Beta Release / beta (push) Successful in 46s
- Rewrite dashboard from 4 tabs to single grid view with 5s auto-refresh - Add live CPU/RAM/Network SVG graphs with rolling 30-point history - Add backend /api/system/metrics reading /proc/stat, /proc/meminfo, /proc/net/dev - Add backend /api/providers/quota for MiniMax and Z.AI quota monitoring - Add backend /api/recent-commands reading bash/zsh history - Add backend /api/running-processes filtering editors/IDEs/languages - Add sudo/root indicator (⚡ ROOT) in footer when running as root - Remove duplicate Ctrl+1-4 shortcut from page-specific footer (keep only right side) - Add Ctrl+R shortcut on dashboard for metrics-only refresh - Make API key mandatory in onboarding, auto-scan editors via AI chat - Remove manual editor input, only show AI-detected editors - Bump version to 0.3.3 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
166 lines
7.5 KiB
JavaScript
166 lines
7.5 KiB
JavaScript
const API_BASE = '/api'
|
|
|
|
async function request(path, options = {}) {
|
|
const res = await fetch(`${API_BASE}${path}`, {
|
|
...options,
|
|
headers: { 'Content-Type': 'application/json', ...(options.headers || {}) },
|
|
})
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ error: res.statusText }))
|
|
throw new Error(err.error || res.statusText)
|
|
}
|
|
return res.json()
|
|
}
|
|
|
|
const api = {
|
|
getInfo: () => request('/info'),
|
|
getSystem: () => request('/system'),
|
|
getTools: () => request('/tools'),
|
|
getConfig: () => request('/config'),
|
|
getProviders: () => request('/providers'),
|
|
getSkills: () => request('/skills'),
|
|
getLSP: () => request('/lsp'),
|
|
getMCP: () => request('/mcp'),
|
|
getUpdates: () => request('/updates'),
|
|
getEditors: () => request('/editors'),
|
|
runScan: () => request('/scan', { method: 'POST' }),
|
|
installTools: (tools) => request('/install', { method: 'POST', body: JSON.stringify({ tools }) }),
|
|
configureMCP: () => request('/mcp/configure', { method: 'POST' }),
|
|
configureMCPForEditor: (editor) => request('/mcp/configure', { method: 'POST', body: JSON.stringify({ editor }) }),
|
|
getMCPStatus: () => request('/mcp/status'),
|
|
getMCPRegistry: () => request('/mcp/registry'),
|
|
getLSPHealth: () => request('/lsp/health'),
|
|
autoInstallLSP: (projectDir) => request('/lsp/auto-install', { method: 'POST', body: JSON.stringify({ project_dir: projectDir || '' }) }),
|
|
generateLSPConfig: (editor, names) => request('/lsp/editor-config', { method: 'POST', body: JSON.stringify({ editor, names }) }),
|
|
validateSkill: (name) => request('/skills/validate', { method: 'POST', body: JSON.stringify({ name }) }),
|
|
testSkill: (name, sampleTask) => request('/skills/test', { method: 'POST', body: JSON.stringify({ name, sample_task: sampleTask || '' }) }),
|
|
exportSkill: (name) => request('/skills/export', { method: 'POST', body: JSON.stringify({ name }) }),
|
|
importSkill: (path) => request('/skills/import', { method: 'POST', body: JSON.stringify({ import_path: path }) }),
|
|
getDashboardStatus: () => request('/dashboard/status'),
|
|
getProvidersQuota: () => request('/providers/quota'),
|
|
getRecentCommands: () => request('/recent-commands'),
|
|
getRunningProcesses: () => request('/running-processes'),
|
|
getSystemMetrics: () => request('/system/metrics'),
|
|
savePreferences: (prefs) => request('/preferences', { method: 'PUT', body: JSON.stringify(prefs) }),
|
|
saveProfile: (profile) => request('/config/profile', { method: 'PUT', body: JSON.stringify(profile) }),
|
|
saveProvider: (provider) => request('/config/provider', { method: 'PUT', body: JSON.stringify(provider) }),
|
|
resetConfig: () => request('/config/reset', { method: 'POST' }),
|
|
applyStarshipTheme: (theme) => request('/starship/apply-theme', { method: 'POST', body: JSON.stringify({ theme }) }),
|
|
validateProvider: (provider) => request('/providers/validate', { method: 'POST', body: JSON.stringify(provider) }),
|
|
runUpdate: (tool) => request('/update/run', { method: 'POST', body: JSON.stringify({ tool: tool || '' }) }),
|
|
runCommand: (command, cwd) => request('/terminal', { method: 'POST', body: JSON.stringify({ command, cwd }) }),
|
|
getTerminalSessions: () => request('/terminal/sessions'),
|
|
addSSHConnection: (conn) => request('/terminal/sessions', { method: 'POST', body: JSON.stringify(conn) }),
|
|
deleteSSHConnection: (name) => request(`/terminal/sessions/${encodeURIComponent(name)}`, { method: 'DELETE' }),
|
|
getTerminalThemes: () => request('/terminal/themes'),
|
|
saveTerminalSettings: (settings) => request('/terminal/settings', { method: 'PUT', body: JSON.stringify(settings) }),
|
|
getChatHistory: () => request('/chat/history'),
|
|
clearChat: () => request('/chat/clear', { method: 'POST' }),
|
|
sendChat: (message, stream = true, onChunk, signal) => {
|
|
if (!stream) {
|
|
return request('/chat', { method: 'POST', body: JSON.stringify({ message, stream: false }) })
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
fetch(`${API_BASE}/chat`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ message, stream: true }),
|
|
signal,
|
|
}).then(async (res) => {
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ error: res.statusText }))
|
|
reject(new Error(err.error || res.statusText))
|
|
return
|
|
}
|
|
const reader = res.body.getReader()
|
|
const decoder = new TextDecoder()
|
|
let full = ''
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
const text = decoder.decode(value, { stream: true })
|
|
for (const line of text.split('\n')) {
|
|
if (!line.startsWith('data: ')) continue
|
|
try {
|
|
const data = JSON.parse(line.slice(6))
|
|
if (data.error) { reject(new Error(data.error)); return }
|
|
if (data.done) { resolve(full); return }
|
|
if (data.content) {
|
|
full += data.content
|
|
if (onChunk) onChunk(full, data)
|
|
} else if (data.thinking !== undefined || data.thinking_end) {
|
|
if (onChunk) onChunk(full, data)
|
|
} else if (data.tool_call || data.tool_result) {
|
|
if (onChunk) onChunk(full, data)
|
|
}
|
|
} catch {}
|
|
}
|
|
}
|
|
resolve(full)
|
|
}).catch(reject)
|
|
})
|
|
},
|
|
sendShellChat: (message, context = {}, stream = true, onChunk) => {
|
|
const payload = {
|
|
message,
|
|
context: context.context || '',
|
|
history: context.history || [],
|
|
cwd: context.cwd || '',
|
|
platform: context.platform || '',
|
|
stream,
|
|
}
|
|
if (!stream) {
|
|
return request('/shell/chat', { method: 'POST', body: JSON.stringify(payload) })
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
fetch(`${API_BASE}/shell/chat`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
}).then(async (res) => {
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ error: res.statusText }))
|
|
reject(new Error(err.error || res.statusText))
|
|
return
|
|
}
|
|
const reader = res.body.getReader()
|
|
const decoder = new TextDecoder()
|
|
let full = ''
|
|
let toolCalls = []
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
const text = decoder.decode(value, { stream: true })
|
|
for (const line of text.split('\n')) {
|
|
if (!line.startsWith('data: ')) continue
|
|
try {
|
|
const data = JSON.parse(line.slice(6))
|
|
if (data.error) { reject(new Error(data.error)); return }
|
|
if (data.done) {
|
|
resolve({ content: full, tool_calls: toolCalls })
|
|
return
|
|
}
|
|
if (data.content) {
|
|
full += data.content
|
|
if (onChunk) onChunk(full, data)
|
|
} else if (data.tool_call) {
|
|
toolCalls.push(data.tool_call)
|
|
if (onChunk) onChunk(full, data, toolCalls)
|
|
} else if (data.tool_result) {
|
|
const idx = toolCalls.findIndex(tc => tc.tool_call_id === data.tool_result.id)
|
|
if (idx >= 0) {
|
|
toolCalls[idx].result = data.tool_result
|
|
}
|
|
if (onChunk) onChunk(full, data, toolCalls)
|
|
}
|
|
} catch {}
|
|
}
|
|
}
|
|
resolve({ content: full, tool_calls: toolCalls })
|
|
}).catch(reject)
|
|
})
|
|
},
|
|
}
|
|
|
|
export default api
|