AgentEvalTool/frontend/web/src/api.ts
sinohqb b4f9c887f4
All checks were successful
CI / test (push) Successful in 4m1s
feat(intelligent-eval): task queue monitor (方案③可视化)
方案③的定时触发(scan loop 每 60s 入队 + 触发 worker)此前只有 Worker
消费端 API,无可查看的列表。新增:
- GET /api/intelligent-evals/tasks:任务明细(含评估名/状态)+ 状态分布统计
  (注册在 /{eval_id} 之前避免被捕获为 eval_id="tasks")
- 前端 TaskQueueMonitor 组件 + 智能评估页任务队列入口:5s 轮询
  (usePolling),状态卡 + 状态筛选 + 明细表
测试:+3(列表/筛选/不被 {eval_id} 遮蔽),892 passed,tsc 通过
2026-08-17 13:57:00 +08:00

932 lines
26 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import axios from 'axios'
import { message } from 'antd'
const AUTH_TOKEN_KEY = 'agenteval_auth_token'
export const authToken = {
get: () => sessionStorage.getItem(AUTH_TOKEN_KEY),
set: (token: string) => sessionStorage.setItem(AUTH_TOKEN_KEY, token),
clear: () => sessionStorage.removeItem(AUTH_TOKEN_KEY),
}
/** Registered by App to switch back to the login screen on 401. */
let onUnauthorized: (() => void) | null = null
export const setOnUnauthorized = (handler: (() => void) | null) => { onUnauthorized = handler }
const api = axios.create({
baseURL: '/api',
timeout: 30000,
})
api.interceptors.request.use((config) => {
const token = authToken.get()
if (token) config.headers['X-Auth-Token'] = token
return config
})
api.interceptors.response.use(
(response) => response,
(error) => {
const isLoginCall = error.config?.url?.includes('/auth/login')
if (error.response?.status === 401 && !isLoginCall && onUnauthorized) {
authToken.clear()
onUnauthorized()
return Promise.reject(error)
}
const msg = error.response?.data?.detail || error.message || '请求失败'
message.error(msg)
return Promise.reject(error)
},
)
export default api
export interface AuthStatus {
auth_required: boolean
authenticated: boolean
}
export const authApi = {
status: () => api.get<AuthStatus>('/auth/status'),
login: (password: string) => api.post<{ token: string }>('/auth/login', { password }),
}
export interface Target {
id: string
name: string
description: string
platform: string
channel_type: string
channel_config: Record<string, unknown>
status: string
created_at: string
updated_at: string
}
export interface Scenario {
id: string
name: string
description: string
tags: string[]
cases: any[]
model_bindings: Record<string, string>
version: number
created_at: string
updated_at: string
}
export type ModelCapability = 'chat' | 'embedding' | 'moderation'
export type ModelProtocol = 'openai_compatible' | 'anthropic' | 'google_gemini' | 'dashscope'
export type ModelModality = 'text' | 'image' | 'audio' | 'video'
export interface ModelConfig {
id: string
name: string
provider: ModelProtocol
capability: ModelCapability
endpoint_url: string
model_name: string | null
vendor_name: string
input_modalities: ModelModality[]
output_modalities: ModelModality[]
context_window: number | null
max_output_tokens: number | null
supports_streaming: boolean
supports_tool_calling: boolean
supports_structured_output: boolean
supports_reasoning: boolean
region: string
documentation_url: string | null
has_api_key: boolean
enabled: boolean
is_default: boolean
is_analysis_default: boolean
description: string
created_at: string | null
updated_at: string | null
}
export interface ModelConfigPayload {
name: string
provider: ModelProtocol
capability: ModelCapability
endpoint_url: string
model_name?: string | null
vendor_name: string
input_modalities: ModelModality[]
output_modalities: ModelModality[]
context_window?: number | null
max_output_tokens?: number | null
supports_streaming: boolean
supports_tool_calling: boolean
supports_structured_output: boolean
supports_reasoning: boolean
region: string
documentation_url?: string | null
api_key?: string | null
clear_api_key?: boolean
enabled: boolean
is_default: boolean
is_analysis_default?: boolean
description: string
}
export interface ModelConfigReference {
scenario_id: string
scenario_name: string
purpose: string
}
export type RunTrigger = 'manual' | 'ai_assistant' | 'cli' | 'campaign'
export interface RunError {
code: string
message: string
}
export interface RunSummary {
total_cases: number
passed_cases: number
failed_cases: number
total_rules: number
passed_rules: number
pass_rate: number | null
avg_latency_ms: number | null
case_outcomes: Record<string, { passed: boolean; connectivity: boolean }>
case_errors: Array<Record<string, string>>
error: RunError | null
}
export interface Run {
id: string
target_id: string
scenario_id: string
scenario_version?: number
campaign_id?: string | null
status: string
triggered_by?: RunTrigger
scenario_name?: string | null
target_name?: string | null
started_at: string
completed_at: string | null
summary: RunSummary | null
}
export interface ScenarioStat {
scenario_id: string
scenario_name: string
run_count: number
avg_pass_rate: number | null
last_run_at: string | null
}
export interface DashboardStats {
targets_count: number
scenarios_count: number
runs_count: number
model_configs_count: number
today_runs: number
running_count: number
overall_pass_rate: number | null
trigger_breakdown: Partial<Record<RunTrigger, number>>
scenario_stats: ScenarioStat[]
recent_runs: Run[]
}
export interface TrendPoint {
date: string
pass_rate: number
run_count: number
}
export const targetsApi = {
list: () => api.get<Target[]>('/targets'),
get: (id: string) => api.get<Target>(`/targets/${id}`),
create: (data: Partial<Target>) => api.post<Target>('/targets', data),
update: (id: string, data: Partial<Target>) => api.put<Target>(`/targets/${id}`, data),
delete: (id: string) => api.delete(`/targets/${id}`),
test: (id: string) => api.post<{ ok: boolean; message: string }>(`/targets/${id}/test`),
runs: (id: string) => api.get<Run[]>(`/targets/${id}/runs`),
}
export const scenariosApi = {
list: () => api.get<Scenario[]>('/scenarios'),
get: (id: string) => api.get<Scenario>(`/scenarios/${id}`),
create: (data: Partial<Scenario>) => api.post<Scenario>('/scenarios', data),
update: (id: string, data: Partial<Scenario>) => api.put<Scenario>(`/scenarios/${id}`, data),
delete: (id: string) => api.delete(`/scenarios/${id}`),
validate: (data: any) => api.post<{ valid: boolean; errors: string[] }>('/scenarios/validate', data),
listTemplates: () => api.get<any[]>('/scenarios/templates'),
getTemplate: (id: string) => api.get<any>(`/scenarios/templates/${id}`),
}
export const modelConfigsApi = {
list: (params?: { capability?: ModelCapability; enabled?: boolean }) =>
api.get<ModelConfig[]>('/model-configs', { params }),
get: (id: string) => api.get<ModelConfig>(`/model-configs/${id}`),
create: (data: ModelConfigPayload) => api.post<ModelConfig>('/model-configs', data),
update: (id: string, data: ModelConfigPayload) => api.put<ModelConfig>(`/model-configs/${id}`, data),
delete: (id: string) => api.delete(`/model-configs/${id}`),
test: (id: string) => api.post<{ ok: boolean; message: string; tested_at: string }>(`/model-configs/${id}/test`),
references: (id: string) => api.get<ModelConfigReference[]>(`/model-configs/${id}/references`),
}
export const runsApi = {
list: () => api.get<Run[]>('/runs'),
start: (target_id: string, scenario_id: string) =>
api.post<Run>('/runs', { target_id, scenario_id }),
get: (id: string) => api.get<Run>(`/runs/${id}`),
logs: (id: string) => api.get<RunLogsResponse>(`/runs/${id}/logs`),
cancel: (id: string) => api.post(`/runs/${id}/cancel`),
}
export interface RunLogsTurn {
id: string
case_id: string
round_index: number
latency_ms: number | null
sent_text: string
reply_text: string
sent_at: string | null
received_at: string | null
}
export interface RunLogsResult {
case_id: string
rule_type: string
passed: boolean
score: number | null
reason: string | null
}
export interface CaseSnapshotExpectations {
intent?: string | null
keywords_include: string[]
keywords_exclude: string[]
response_time_max_ms?: number | null
coherence_min_score?: number | null
}
export interface CaseSnapshot {
id: string
type: string
messages: string[]
prompt?: string | null
turns?: number | null
expectations: CaseSnapshotExpectations
eval_rules: { type: string; params: Record<string, unknown> }[]
}
export interface RunLogsResponse {
turns: RunLogsTurn[]
results: RunLogsResult[]
case_verdicts: Record<string, { passed: boolean; connectivity: boolean }>
scenario_snapshot: Record<string, CaseSnapshot>
}
export const reportsApi = {
get: (runId: string) => api.get(`/reports/${runId}`),
download: async (runId: string, format: 'html' | 'json' | 'markdown') => {
const ext = format === 'markdown' ? 'md' : format
const res = await api.get(`/reports/${runId}/${format}`, { responseType: 'blob' })
const url = URL.createObjectURL(res.data as Blob)
const a = document.createElement('a')
a.href = url
a.download = `report-${runId.slice(0, 8)}.${ext}`
a.click()
URL.revokeObjectURL(url)
},
compare: (run1: string, run2: string) =>
api.get(`/reports/compare`, { params: { run1, run2 } }),
}
export const statsApi = {
dashboard: () => api.get<DashboardStats>('/stats/dashboard'),
trend: (days?: number) => api.get<TrendPoint[]>('/stats/trend', { params: { days } }),
}
// ── Campaigns评估活动 ──────────────────────────────────────────
export interface CampaignPlanEntry {
scenario_id: string
offset_seconds: number
count: number
}
export interface ExplorationSeeds {
personas: string[]
goals: string[]
}
export interface ExplorationBudgetConfig {
max_sessions?: number | null
max_turns?: number | null
min_interval_seconds?: number | null
}
export interface ExplorationIssueCount {
issue: string
count: number
}
export interface ExplorationJudgeReview {
reviewed_sessions: number
findings: { dimension: string; rating: string; comment: string }[]
summaries: string[]
}
export interface ExplorationSummary {
session_count: number
sessions_with_experience: number
goal_achieved_count: number
goal_achievement_rate: number | null
issues: ExplorationIssueCount[]
misled: ExplorationIssueCount[]
judge_review: ExplorationJudgeReview | null
}
export interface ExplorationExperience {
goal_achieved: boolean
blockers: string[]
misled: string[]
emotion: string
notes: string
}
export interface ExplorationSession {
id: string
campaign_id: string
target_id: string
persona: Record<string, unknown>
goal: string
seed_ref: Record<string, unknown> | null
status: string
triggered_by: string
experience: ExplorationExperience | null
judge_review: Record<string, unknown> | null
turn_count: number
error: string | null
created_at: string | null
closed_at: string | null
}
export interface ExplorationMessage {
id: string
session_id: string
round_index: number
role: string
content: string
latency_ms: number | null
created_at: string | null
}
export const explorationApi = {
listSessions: (campaignId: string) =>
api.get<{ sessions: ExplorationSession[] }>(`/exploration/campaigns/${campaignId}/sessions`),
listMessages: (sessionId: string) =>
api.get<{ messages: ExplorationMessage[] }>(`/exploration/sessions/${sessionId}/messages`),
}
export interface CampaignProgress {
current_offset_seconds: number
spawned_runs: number
completed_runs: number
}
export interface CampaignListProgress {
completed_runs: number
planned_total: number
overall_pass_rate: number | null
}
export interface Campaign {
id: string
name: string
target_id: string
window_seconds: number
time_scale: number
plan: CampaignPlanEntry[]
status: string
started_at: string | null
completed_at: string | null
summary: Record<string, unknown> | null
analysis_model_config_id: string | null
exploration_seeds: ExplorationSeeds | null
exploration_budget: ExplorationBudgetConfig | null
progress?: CampaignProgress
}
export interface CampaignListItem extends Omit<Campaign, 'progress'> {
progress: CampaignListProgress
}
export interface CampaignTrendBucket {
bucket_index: number
start_seconds: number
end_seconds: number
run_count: number
pass_rate: number | null
availability: number | null
avg_latency_ms: number | null
}
export interface CampaignCapability {
scenario_id: string
scenario_name: string
run_count: number
pass_rate: number | null
availability: number | null
avg_latency_ms: number | null
}
export interface CampaignReport {
campaign_id: string
name: string
target_id: string
status: string
window_seconds: number
time_scale: number
started_at: string | null
completed_at: string | null
summary: {
total_runs: number
completed_runs: number
overall_pass_rate: number | null
overall_availability: number | null
avg_latency_ms: number | null
}
time_trend: CampaignTrendBucket[]
capability_summary: CampaignCapability[]
exploration?: ExplorationSummary | null
}
export interface CampaignTimelineEntry {
run_id: string
scenario_id: string
scenario_name: string
offset_seconds: number
status: string
pass_rate: number | null
avg_latency_ms: number | null
started_at: string | null
}
export type CampaignAnalysisStatus = 'none' | 'queued' | 'generating' | 'completed' | 'failed'
export interface CampaignAnalysisProblem {
severity: string
title: string
description: string
scenario_ids: string[]
evidence_run_ids: string[]
}
export interface CampaignAnalysisResult {
overall: string
problems: CampaignAnalysisProblem[]
scenario_narratives: { scenario_id: string; narrative: string }[]
suggestions: { priority: number; text: string }[]
}
export interface CampaignAnalysis {
status: CampaignAnalysisStatus
result?: CampaignAnalysisResult | null
error?: string | null
model_config_id?: string | null
triggered_by?: string
updated_at?: string | null
}
export interface CreateCampaignPayload {
name: string
target_id: string
window_seconds: number
time_scale: number
plan: CampaignPlanEntry[]
analysis_model_config_id?: string | null
exploration_seeds?: ExplorationSeeds | null
exploration_budget?: ExplorationBudgetConfig | null
}
// ── Period comparison (v0.8) ────────────────────────────────────
export type ComparisonTrend = 'improving' | 'stable' | 'regressing'
export interface MetricDeltaPair {
baseline: number | null
current: number | null
delta: number | null
}
export interface MetricDiffScenario {
scenario_id: string
scenario_name: string
pass_rate: MetricDeltaPair
availability: MetricDeltaPair
avg_latency_ms: MetricDeltaPair
}
export interface MetricDiff {
overall: {
pass_rate: MetricDeltaPair
availability: MetricDeltaPair
avg_latency_ms: MetricDeltaPair
}
scenarios: MetricDiffScenario[]
}
export interface ComparisonBaselineInfo {
id: string
name: string
completed_at: string | null
}
export interface ComparisonProblemEvolution {
status: string
title: string
detail: string
scenario_ids: string[]
}
export interface ComparisonSuggestionTracking {
text: string
status: string
note: string
}
export interface ComparisonResult {
trend: ComparisonTrend
summary: string
problem_evolution: ComparisonProblemEvolution[]
suggestion_tracking: ComparisonSuggestionTracking[]
}
export interface CampaignComparison {
status: CampaignAnalysisStatus
auto_baseline: ComparisonBaselineInfo | null
metric_diff: MetricDiff | null
comparison: {
baseline_campaign_id: string
baseline: ComparisonBaselineInfo | null
result: ComparisonResult | null
error: string | null
model_config_id: string | null
triggered_by: string
updated_at: string | null
} | null
}
export const campaignsApi = {
list: () => api.get<CampaignListItem[]>('/campaigns'),
get: (id: string) => api.get<Campaign>(`/campaigns/${id}`),
create: (data: CreateCampaignPayload) => api.post<Campaign>('/campaigns', data),
cancel: (id: string) => api.post<Campaign>(`/campaigns/${id}/cancel`),
report: (id: string) => api.get<CampaignReport>(`/campaigns/${id}/report`),
timeline: (id: string) =>
api.get<{ entries: CampaignTimelineEntry[] }>(`/campaigns/${id}/timeline`),
getAnalysis: (id: string) => api.get<CampaignAnalysis>(`/campaigns/${id}/analysis`),
generateAnalysis: (id: string) => api.post<{ status: string }>(`/campaigns/${id}/analysis`),
getComparison: (id: string) => api.get<CampaignComparison>(`/campaigns/${id}/comparison`),
generateComparison: (id: string, baselineCampaignId?: string) =>
api.post<{ status: string }>(
`/campaigns/${id}/comparison`,
baselineCampaignId ? { baseline_campaign_id: baselineCampaignId } : {},
),
downloadReport: async (id: string) => {
const res = await api.get(`/campaigns/${id}/report/markdown`, { responseType: 'blob' })
const url = URL.createObjectURL(res.data as Blob)
const a = document.createElement('a')
a.href = url
a.download = `campaign-report-${id.slice(0, 8)}.md`
a.click()
URL.revokeObjectURL(url)
},
}
// ── Intelligent Evaluation智能评估 v1.0 ────────────────────────
export type IntelligentEvalStatus =
| 'draft' | 'planning' | 'pending_approval'
| 'executing' | 'completed' | 'cancelled' | 'failed'
export type IntelligentEvalSessionStatus = 'running' | 'completed' | 'failed' | 'expired'
export interface IntelligentEvalMessage {
id: string
session_id: string
role: string
content: string
latency_ms: number | null
created_at: string | null
}
export interface IntelligentEvalSession {
id: string
eval_id: string
target_id: string
persona: Record<string, unknown>
goal: string
dimension: string | null
status: IntelligentEvalSessionStatus
verdict: Record<string, unknown> | null
turn_count: number
created_at: string | null
closed_at: string | null
}
export interface IntelligentEvalVirtualUser {
persona: Record<string, unknown>
goal: string
}
export interface IntelligentEvalTimeSlot {
time_slot: string
sessions: number
scenario: string
}
export interface IntelligentEvalPlan {
dimensions: string[]
virtual_users: IntelligentEvalVirtualUser[]
time_distribution: IntelligentEvalTimeSlot[]
estimated_sessions: number
budget: { max_turns_per_session: number; total_max_turns: number }
completion_criteria: string
}
export interface ReportEvidence {
session_id?: string
turn_index?: number | null
user_said?: string
assistant_replied?: string
}
export interface ReportFinding {
issue: string
severity: string
dimension: string
evidence: ReportEvidence[]
suggestion?: string | null
related_sop?: string | null
}
export interface ReportHighlight {
description: string
dimension?: string | null
}
export interface IntelligentEvalReport {
summary: string
scores: Record<string, number> | null
findings: ReportFinding[]
highlights: ReportHighlight[]
priority_recommendations: string[]
}
export interface IntelligentEval {
id: string
name: string
target_id: string
status: IntelligentEvalStatus
goal: string
seeds: Record<string, unknown>
intent: string
role_description: string
plan: IntelligentEvalPlan | null
plan_feedback: string | null
time_window_hours: number
report: IntelligentEvalReport | null
created_at: string | null
updated_at: string | null
started_at: string | null
completed_at: string | null
session_count: number
completed_sessions: number
sessions?: IntelligentEvalSession[]
}
export interface CreateIntelligentEvalPayload {
name: string
target_id: string
goal: string
seeds: Record<string, unknown>
intent: string
role_description: string
time_window_hours: number
}
export interface ConfigSnapshot {
id: string
eval_id: string
snapshot_type: 'created' | 'plan_submitted' | 'config_updated'
goal: string
seeds: Record<string, unknown>
intent: string
role_description: string
time_window_hours: number
plan: IntelligentEvalPlan | null
created_at: string | null
created_by: string
}
export interface ConfigSnapshotComparison {
snapshot_1: {
id: string
snapshot_type: string
created_at: string | null
}
snapshot_2: {
id: string
snapshot_type: string
created_at: string | null
}
differences: Record<string, { old: unknown; new: unknown }>
}
export interface DecisionLog {
id: string
eval_id: string
decision_type: 'execute_session' | 'wait' | 'start_analysis'
reason: string
context: Record<string, unknown>
cron_id: string
created_at: string | null
}
export type TaskQueueStatus = 'pending' | 'assigned' | 'completed' | 'failed'
export interface TaskQueueStats {
pending: number
assigned: number
completed: number
failed: number
unresolved: number
}
export interface TaskQueueItem {
id: string
eval_id: string
eval_name: string | null
eval_status: string | null
status: TaskQueueStatus
priority: number
reason: string
assigned_cron_id: string | null
assigned_at: string | null
completed_at: string | null
created_at: string | null
updated_at: string | null
error: string | null
}
export interface TaskQueueList {
tasks: TaskQueueItem[]
stats: TaskQueueStats
}
export const intelligentEvalsApi = {
list: () => api.get<{ intelligent_evals: IntelligentEval[] }>('/intelligent-evals'),
listTasks: (params?: { status?: TaskQueueStatus; limit?: number }) =>
api.get<TaskQueueList>('/intelligent-evals/tasks', { params }),
get: (id: string) => api.get<IntelligentEval>(`/intelligent-evals/${id}`),
create: (data: CreateIntelligentEvalPayload) => api.post<IntelligentEval>('/intelligent-evals', data),
submitPlan: (id: string, plan: Record<string, unknown>) =>
api.put<IntelligentEval>(`/intelligent-evals/${id}/plan`, { plan }),
approve: (id: string) => api.post<IntelligentEval>(`/intelligent-evals/${id}/approve`),
reject: (id: string, feedback: string) =>
api.post<IntelligentEval>(`/intelligent-evals/${id}/reject`, { feedback }),
cancel: (id: string) => api.post<IntelligentEval>(`/intelligent-evals/${id}/cancel`),
listSessions: (id: string) =>
api.get<{ sessions: IntelligentEvalSession[] }>(`/intelligent-evals/${id}/sessions`),
listMessages: (id: string, sessionId: string) =>
api.get<{ messages: IntelligentEvalMessage[] }>(`/intelligent-evals/${id}/sessions/${sessionId}/messages`),
getReport: (id: string) => api.get<IntelligentEvalReport>(`/intelligent-evals/${id}/report`),
downloadReportMarkdown: async (id: string) => {
const res = await api.get(`/intelligent-evals/${id}/report/markdown`, { responseType: 'blob' })
const url = URL.createObjectURL(res.data as Blob)
const a = document.createElement('a')
a.href = url
a.download = `intelligent-eval-report-${id.slice(0, 8)}.md`
a.click()
URL.revokeObjectURL(url)
},
// Config Snapshots
listConfigSnapshots: (id: string) =>
api.get<{ snapshots: ConfigSnapshot[] }>(`/intelligent-evals/${id}/config-snapshots`),
getConfigSnapshot: (id: string, snapshotId: string) =>
api.get<ConfigSnapshot>(`/intelligent-evals/${id}/config-snapshots/${snapshotId}`),
compareConfigSnapshots: (id: string, snapshotId1: string, snapshotId2: string) =>
api.post<ConfigSnapshotComparison>(`/intelligent-evals/${id}/config-snapshots/compare`, {
snapshot_id_1: snapshotId1,
snapshot_id_2: snapshotId2,
}),
// Decision Logs
listDecisionLogs: (id: string) =>
api.get<{ logs: DecisionLog[] }>(`/intelligent-evals/${id}/decision-logs`),
}
// ── OpenClaw Cron Pool ──────────────────────────────────────────────
export interface CronPoolStatus {
total: number
idle: number
busy: number
stuck: number
min_size: number
max_size: number
}
export interface CronPoolMetrics {
pool_utilization: number
task_backlog: number
stuck_rate: number
avg_processing_time_seconds: number | null
eval_completion_rate: number
timestamp: string
}
export interface CronPoolAlert {
id: string
alert_type: string
severity: string
message: string
metric_value: number
threshold: number
created_at: string
resolved_at: string | null
webhook_sent: boolean
}
export const openclawCronPoolApi = {
getStatus: () => api.get<{ pool: CronPoolStatus }>('/openclaw/cron-pool'),
scale: (targetSize: number) =>
api.post<{ success: boolean; scaled_up?: number; scaled_down?: number; current_size: number }>(
'/openclaw/cron-pool/scale',
{ target_size: targetSize },
),
sync: () => api.post<{ success: boolean; synced: number }>('/openclaw/cron-pool/sync'),
autoScale: () =>
api.post<{ success: boolean; scaled_up: number; scaled_down: number }>('/openclaw/cron-pool/auto-scale'),
getMetrics: () => api.get<{ metrics: CronPoolMetrics }>('/openclaw/cron-pool/metrics'),
checkAlerts: () =>
api.post<{ success: boolean; alerts_triggered: number; alerts: CronPoolAlert[] }>('/openclaw/cron-pool/check-alerts'),
getAlerts: (limit = 100, unresolvedOnly = false) =>
api.get<{ alerts: CronPoolAlert[] }>('/openclaw/cron-pool/alerts', {
params: { limit, unresolved_only: unresolvedOnly },
}),
resolveAlert: (alertId: string) =>
api.post<{ success: boolean }>(`/openclaw/cron-pool/alerts/${alertId}/resolve`),
}
// ── File Management ──────────────────────────────────────────────
export interface FileCategory {
id: string
name: string
parent_id: string | null
children: FileCategory[]
}
export interface FileRecord {
id: string
original_name: string
file_size: number
mime_type: string
file_ext: string
category_id: string | null
storage_directory: string
created_at: string | null
}
export interface FileUploadConfig {
allowed_extensions: string[]
max_upload_size_mb: number
}
export const filesApi = {
// Categories
getConfig: () => api.get<FileUploadConfig>('/files/config'),
listCategories: () => api.get<FileCategory[]>('/files/categories'),
createCategory: (data: { name: string; parent_id?: string | null }) =>
api.post<FileCategory>('/files/categories', data),
updateCategory: (id: string, data: { name: string }) =>
api.put(`/files/categories/${id}`, data),
deleteCategory: (id: string) => api.delete(`/files/categories/${id}`),
// Files
list: (categoryId?: string | null) =>
api.get<FileRecord[]>('/files', { params: categoryId ? { category_id: categoryId } : {} }),
upload: (file: File, categoryId?: string | null) => {
const form = new FormData()
form.append('file', file)
if (categoryId) {
form.append('category_id', categoryId)
}
return api.post<FileRecord>('/files/upload', form, {
timeout: 120000,
})
},
download: (id: string) => api.get<Blob>(`/files/${id}/download`, { responseType: 'blob' }),
delete: (id: string) => api.delete(`/files/${id}`),
}