6cb6aeb168
Users reported no GUI switch for browser.use_real_profile — the only desktop home was the generic Settings → Config editor, which nobody found. The Browser toolset detail pane now renders a 'Use My Real Browser Profile' ToggleRow above the backend/provider matrix. - new BrowserRealProfilePanel: reads the shared profile-scoped config record cache, optimistic write-through, rollback on failure - saveHermesConfigRecord: capability-scoped PUT /api/config counterpart of getHermesConfigRecord, so the Capabilities scope selector writes the profile it points at (possibly another gateway) - i18n: en/ja/zh/zh-hant keys (ar inherits en via defineLocale) - docs: browser.md desktop pointer corrected to the real location Live E2E on the built app over CDP: clicking the switch flipped browser.use_real_profile true→false→true in the sandbox HERMES_HOME config.yaml, GET reflected it, no layout glitches (screenshots in PR).
252 lines
7.2 KiB
TypeScript
252 lines
7.2 KiB
TypeScript
import type {
|
|
ConfigSchemaResponse,
|
|
CustomEndpointsResponse,
|
|
CustomEndpointUpdate,
|
|
CustomEndpointValidationResponse,
|
|
EnvVarInfo,
|
|
HermesConfig,
|
|
HermesConfigRecord,
|
|
LogsResponse,
|
|
OAuthPollResponse,
|
|
OAuthProvidersResponse,
|
|
OAuthStartResponse,
|
|
OAuthSubmitResponse,
|
|
StatusResponse
|
|
} from '@/types/hermes'
|
|
|
|
import { capabilityScoped, hermesApi, type ProfileScope, profileScoped, STARTUP_REQUEST_TIMEOUT_MS } from './client'
|
|
|
|
export function getStatus(): Promise<StatusResponse> {
|
|
return hermesApi<StatusResponse>({
|
|
...profileScoped(),
|
|
path: '/api/status'
|
|
})
|
|
}
|
|
|
|
export function getLogs(params: {
|
|
component?: string
|
|
file?: string
|
|
level?: string
|
|
lines?: number
|
|
search?: string
|
|
}): Promise<LogsResponse> {
|
|
const query = new URLSearchParams()
|
|
|
|
if (params.file) {
|
|
query.set('file', params.file)
|
|
}
|
|
|
|
if (typeof params.lines === 'number') {
|
|
query.set('lines', String(params.lines))
|
|
}
|
|
|
|
if (params.level && params.level !== 'ALL') {
|
|
query.set('level', params.level)
|
|
}
|
|
|
|
if (params.component && params.component !== 'all') {
|
|
query.set('component', params.component)
|
|
}
|
|
|
|
if (params.search) {
|
|
query.set('search', params.search)
|
|
}
|
|
|
|
const suffix = query.toString()
|
|
|
|
return hermesApi<LogsResponse>({
|
|
...profileScoped(),
|
|
path: suffix ? `/api/logs?${suffix}` : '/api/logs'
|
|
})
|
|
}
|
|
|
|
export function getHermesConfig(profile?: string): Promise<HermesConfig> {
|
|
return hermesApi<HermesConfig>({
|
|
...profileScoped(profile),
|
|
path: '/api/config',
|
|
timeoutMs: STARTUP_REQUEST_TIMEOUT_MS
|
|
})
|
|
}
|
|
|
|
export function getHermesConfigRecord(profile?: ProfileScope): Promise<HermesConfigRecord> {
|
|
return window.hermesDesktop.api<HermesConfigRecord>({
|
|
...capabilityScoped(profile),
|
|
path: '/api/config'
|
|
})
|
|
}
|
|
|
|
export function getHermesConfigDefaults(): Promise<HermesConfigRecord> {
|
|
return hermesApi<HermesConfigRecord>({
|
|
...profileScoped(),
|
|
path: '/api/config/defaults',
|
|
timeoutMs: STARTUP_REQUEST_TIMEOUT_MS
|
|
})
|
|
}
|
|
|
|
export function getHermesConfigSchema(profile?: null | string): Promise<ConfigSchemaResponse> {
|
|
return hermesApi<ConfigSchemaResponse>({
|
|
...profileScoped(profile),
|
|
path: '/api/config/schema'
|
|
})
|
|
}
|
|
|
|
export function saveHermesConfig(config: HermesConfigRecord, profile?: null | string): Promise<{ ok: boolean }> {
|
|
return hermesApi<{ ok: boolean }>({
|
|
...profileScoped(profile),
|
|
path: '/api/config',
|
|
method: 'PUT',
|
|
body: { config }
|
|
})
|
|
}
|
|
|
|
/** Capability-scoped counterpart of saveHermesConfig — writes the config of
|
|
* the profile/connection the Capabilities scope selector points at (possibly
|
|
* on another registered gateway), mirroring getHermesConfigRecord. */
|
|
export function saveHermesConfigRecord(config: HermesConfigRecord, profile?: ProfileScope): Promise<{ ok: boolean }> {
|
|
return window.hermesDesktop.api<{ ok: boolean }>({
|
|
...capabilityScoped(profile),
|
|
path: '/api/config',
|
|
method: 'PUT',
|
|
body: { config }
|
|
})
|
|
}
|
|
|
|
export function getEnvVars(profile?: null | string): Promise<Record<string, EnvVarInfo>> {
|
|
return hermesApi<Record<string, EnvVarInfo>>({
|
|
...profileScoped(profile),
|
|
path: '/api/env'
|
|
})
|
|
}
|
|
|
|
export function setEnvVar(key: string, value: string, profile?: ProfileScope): Promise<{ ok: boolean }> {
|
|
return window.hermesDesktop.api<{ ok: boolean }>({
|
|
...capabilityScoped(profile),
|
|
path: '/api/env',
|
|
method: 'PUT',
|
|
body: { key, value }
|
|
})
|
|
}
|
|
|
|
export function deleteEnvVar(key: string, profile?: ProfileScope): Promise<{ ok: boolean }> {
|
|
return window.hermesDesktop.api<{ ok: boolean }>({
|
|
...capabilityScoped(profile),
|
|
path: '/api/env',
|
|
method: 'DELETE',
|
|
body: { key }
|
|
})
|
|
}
|
|
|
|
export function revealEnvVar(key: string, profile?: ProfileScope): Promise<{ key: string; value: string }> {
|
|
return window.hermesDesktop.api<{ key: string; value: string }>({
|
|
...capabilityScoped(profile),
|
|
path: '/api/env/reveal',
|
|
method: 'POST',
|
|
body: { key }
|
|
})
|
|
}
|
|
|
|
export function validateProviderCredential(
|
|
key: string,
|
|
value: string,
|
|
apiKey?: string
|
|
): Promise<{ ok: boolean; reachable: boolean; message: string; models?: string[] }> {
|
|
return hermesApi<{ ok: boolean; reachable: boolean; message: string; models?: string[] }>({
|
|
...profileScoped(),
|
|
path: '/api/providers/validate',
|
|
method: 'POST',
|
|
body: { key, value, api_key: apiKey ?? '' }
|
|
})
|
|
}
|
|
|
|
export function getCustomEndpoints(): Promise<CustomEndpointsResponse> {
|
|
return hermesApi<CustomEndpointsResponse>({
|
|
...profileScoped(),
|
|
path: '/api/providers/custom-endpoints'
|
|
})
|
|
}
|
|
|
|
export function saveCustomEndpoint(endpoint: CustomEndpointUpdate): Promise<CustomEndpointsResponse> {
|
|
return hermesApi<CustomEndpointsResponse>({
|
|
...profileScoped(),
|
|
path: '/api/providers/custom-endpoints',
|
|
method: 'POST',
|
|
body: endpoint
|
|
})
|
|
}
|
|
|
|
export function validateCustomEndpoint(endpoint: CustomEndpointUpdate): Promise<CustomEndpointValidationResponse> {
|
|
return hermesApi<CustomEndpointValidationResponse>({
|
|
path: '/api/providers/custom-endpoints/validate',
|
|
method: 'POST',
|
|
body: endpoint
|
|
})
|
|
}
|
|
|
|
export function activateCustomEndpoint(id: string): Promise<{ ok: boolean; provider: string; model: string }> {
|
|
return hermesApi<{ ok: boolean; provider: string; model: string }>({
|
|
...profileScoped(),
|
|
path: `/api/providers/custom-endpoints/${encodeURIComponent(id)}/activate`,
|
|
method: 'POST'
|
|
})
|
|
}
|
|
|
|
export function deleteCustomEndpoint(id: string): Promise<CustomEndpointsResponse> {
|
|
return hermesApi<CustomEndpointsResponse>({
|
|
...profileScoped(),
|
|
path: `/api/providers/custom-endpoints/${encodeURIComponent(id)}`,
|
|
method: 'DELETE'
|
|
})
|
|
}
|
|
|
|
export function listOAuthProviders(): Promise<OAuthProvidersResponse> {
|
|
return hermesApi<OAuthProvidersResponse>({
|
|
...profileScoped(),
|
|
path: '/api/providers/oauth'
|
|
})
|
|
}
|
|
|
|
export function disconnectOAuthProvider(providerId: string): Promise<{ ok: boolean; provider: string }> {
|
|
return hermesApi<{ ok: boolean; provider: string }>({
|
|
...profileScoped(),
|
|
path: `/api/providers/oauth/${encodeURIComponent(providerId)}`,
|
|
method: 'DELETE'
|
|
})
|
|
}
|
|
|
|
export function startOAuthLogin(providerId: string, profile?: ProfileScope): Promise<OAuthStartResponse> {
|
|
return window.hermesDesktop.api<OAuthStartResponse>({
|
|
...capabilityScoped(profile),
|
|
path: `/api/providers/oauth/${encodeURIComponent(providerId)}/start`,
|
|
method: 'POST',
|
|
body: {}
|
|
})
|
|
}
|
|
|
|
export function submitOAuthCode(providerId: string, sessionId: string, code: string): Promise<OAuthSubmitResponse> {
|
|
return hermesApi<OAuthSubmitResponse>({
|
|
...profileScoped(),
|
|
path: `/api/providers/oauth/${encodeURIComponent(providerId)}/submit`,
|
|
method: 'POST',
|
|
body: { session_id: sessionId, code }
|
|
})
|
|
}
|
|
|
|
export function pollOAuthSession(
|
|
providerId: string,
|
|
sessionId: string,
|
|
profile?: ProfileScope
|
|
): Promise<OAuthPollResponse> {
|
|
return window.hermesDesktop.api<OAuthPollResponse>({
|
|
...capabilityScoped(profile),
|
|
path: `/api/providers/oauth/${encodeURIComponent(providerId)}/poll/${encodeURIComponent(sessionId)}`
|
|
})
|
|
}
|
|
|
|
export function cancelOAuthSession(sessionId: string): Promise<{ ok: boolean }> {
|
|
return hermesApi<{ ok: boolean }>({
|
|
...profileScoped(),
|
|
path: `/api/providers/oauth/sessions/${encodeURIComponent(sessionId)}`,
|
|
method: 'DELETE'
|
|
})
|
|
}
|