init: hermes-web-ui v0.1.0
Hermes Agent Web 管理面板,支持对话交互和定时任务管理。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import { request, getBaseUrlValue } from './client'
|
||||
|
||||
export interface ChatMessage {
|
||||
role: 'user' | 'assistant' | 'system'
|
||||
content: string
|
||||
}
|
||||
|
||||
export interface StartRunRequest {
|
||||
input: string | ChatMessage[]
|
||||
instructions?: string
|
||||
conversation_history?: ChatMessage[]
|
||||
session_id?: string
|
||||
}
|
||||
|
||||
export interface StartRunResponse {
|
||||
run_id: string
|
||||
status: string
|
||||
}
|
||||
|
||||
// SSE event types from /v1/runs/{id}/events
|
||||
export interface RunEvent {
|
||||
event: string
|
||||
run_id?: string
|
||||
delta?: string
|
||||
tool?: string
|
||||
name?: string
|
||||
preview?: string
|
||||
timestamp?: number
|
||||
error?: string
|
||||
}
|
||||
|
||||
export async function startRun(body: StartRunRequest): Promise<StartRunResponse> {
|
||||
return request<StartRunResponse>('/v1/runs', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
}
|
||||
|
||||
export function streamRunEvents(
|
||||
runId: string,
|
||||
onEvent: (event: RunEvent) => void,
|
||||
onDone: () => void,
|
||||
onError: (err: Error) => void,
|
||||
) {
|
||||
const baseUrl = getBaseUrlValue()
|
||||
const url = `${baseUrl}/v1/runs/${runId}/events`
|
||||
|
||||
let closed = false
|
||||
const source = new EventSource(url)
|
||||
|
||||
source.onmessage = (e) => {
|
||||
if (closed) return
|
||||
try {
|
||||
const parsed = JSON.parse(e.data)
|
||||
onEvent(parsed)
|
||||
|
||||
if (parsed.event === 'run.completed' || parsed.event === 'run.failed') {
|
||||
closed = true
|
||||
source.close()
|
||||
onDone()
|
||||
}
|
||||
} catch {
|
||||
onEvent({ event: 'message', delta: e.data })
|
||||
}
|
||||
}
|
||||
|
||||
source.onerror = () => {
|
||||
if (closed) return
|
||||
closed = true
|
||||
source.close()
|
||||
onError(new Error('SSE connection error'))
|
||||
}
|
||||
|
||||
// Return AbortController-compatible object
|
||||
return {
|
||||
abort: () => {
|
||||
if (!closed) {
|
||||
closed = true
|
||||
source.close()
|
||||
}
|
||||
},
|
||||
} as unknown as AbortController
|
||||
}
|
||||
|
||||
export async function fetchModels(): Promise<{ data: Array<{ id: string }> }> {
|
||||
return request('/v1/models')
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
const DEFAULT_BASE_URL = ''
|
||||
|
||||
function getBaseUrl(): string {
|
||||
return localStorage.getItem('hermes_server_url') || DEFAULT_BASE_URL
|
||||
}
|
||||
|
||||
function getApiKey(): string {
|
||||
return localStorage.getItem('hermes_api_key') || ''
|
||||
}
|
||||
|
||||
export function setServerUrl(url: string) {
|
||||
localStorage.setItem('hermes_server_url', url)
|
||||
}
|
||||
|
||||
export function setApiKey(key: string) {
|
||||
localStorage.setItem('hermes_api_key', key)
|
||||
}
|
||||
|
||||
export async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
||||
const base = getBaseUrl()
|
||||
const url = `${base}${path}`
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers as Record<string, string>,
|
||||
}
|
||||
|
||||
const apiKey = getApiKey()
|
||||
if (apiKey) {
|
||||
headers['Authorization'] = `Bearer ${apiKey}`
|
||||
}
|
||||
|
||||
const res = await fetch(url, { ...options, headers })
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => '')
|
||||
throw new Error(`API Error ${res.status}: ${text || res.statusText}`)
|
||||
}
|
||||
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export function getBaseUrlValue(): string {
|
||||
return getBaseUrl()
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
import { request } from './client'
|
||||
|
||||
export interface Job {
|
||||
job_id: string
|
||||
id: string
|
||||
name: string
|
||||
prompt: string
|
||||
prompt_preview?: string
|
||||
skills: string[]
|
||||
skill: string | null
|
||||
model: string | null
|
||||
provider: string | null
|
||||
base_url: string | null
|
||||
script: string | null
|
||||
schedule: string | { kind: string; expr: string; display: string }
|
||||
schedule_display: string
|
||||
repeat: string | { times: number | null; completed: number }
|
||||
enabled: boolean
|
||||
state: string
|
||||
paused_at: string | null
|
||||
paused_reason: string | null
|
||||
created_at: string
|
||||
next_run_at: string | null
|
||||
last_run_at: string | null
|
||||
last_status: string | null
|
||||
last_error: string | null
|
||||
deliver: string
|
||||
origin: {
|
||||
platform: string
|
||||
chat_id: string
|
||||
chat_name: string
|
||||
thread_id: string | null
|
||||
} | null
|
||||
last_delivery_error: string | null
|
||||
}
|
||||
|
||||
export interface CreateJobRequest {
|
||||
name: string
|
||||
schedule: string
|
||||
prompt?: string
|
||||
deliver?: string
|
||||
skills?: string[]
|
||||
repeat?: number
|
||||
}
|
||||
|
||||
export interface UpdateJobRequest {
|
||||
name?: string
|
||||
schedule?: string
|
||||
prompt?: string
|
||||
deliver?: string
|
||||
skills?: string[]
|
||||
skill?: string
|
||||
repeat?: number
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
function unwrap(res: { job: Job }): Job {
|
||||
return res.job
|
||||
}
|
||||
|
||||
export async function listJobs(): Promise<Job[]> {
|
||||
const res = await request<{ jobs: Job[] }>('/api/jobs?include_disabled=true')
|
||||
return res.jobs
|
||||
}
|
||||
|
||||
export async function getJob(jobId: string): Promise<Job> {
|
||||
return unwrap(await request<{ job: Job }>(`/api/jobs/${jobId}`))
|
||||
}
|
||||
|
||||
export async function createJob(data: CreateJobRequest): Promise<Job> {
|
||||
return unwrap(await request<{ job: Job }>('/api/jobs', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
}))
|
||||
}
|
||||
|
||||
export async function updateJob(jobId: string, data: UpdateJobRequest): Promise<Job> {
|
||||
return unwrap(await request<{ job: Job }>(`/api/jobs/${jobId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
}))
|
||||
}
|
||||
|
||||
export async function deleteJob(jobId: string): Promise<{ ok: boolean }> {
|
||||
return request<{ ok: boolean }>(`/api/jobs/${jobId}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
export async function pauseJob(jobId: string): Promise<Job> {
|
||||
return unwrap(await request<{ job: Job }>(`/api/jobs/${jobId}/pause`, { method: 'POST' }))
|
||||
}
|
||||
|
||||
export async function resumeJob(jobId: string): Promise<Job> {
|
||||
return unwrap(await request<{ job: Job }>(`/api/jobs/${jobId}/resume`, { method: 'POST' }))
|
||||
}
|
||||
|
||||
export async function runJob(jobId: string): Promise<Job> {
|
||||
return unwrap(await request<{ job: Job }>(`/api/jobs/${jobId}/run`, { method: 'POST' }))
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { request } from './client'
|
||||
|
||||
export interface HealthResponse {
|
||||
status: string
|
||||
version?: string
|
||||
}
|
||||
|
||||
export interface Model {
|
||||
id: string
|
||||
object: string
|
||||
owned_by: string
|
||||
}
|
||||
|
||||
export interface ModelsResponse {
|
||||
object: string
|
||||
data: Model[]
|
||||
}
|
||||
|
||||
export async function checkHealth(): Promise<HealthResponse> {
|
||||
return request<HealthResponse>('/health')
|
||||
}
|
||||
|
||||
export async function fetchModels(): Promise<ModelsResponse> {
|
||||
return request<ModelsResponse>('/v1/models')
|
||||
}
|
||||
Reference in New Issue
Block a user