refactor(api): api.ts 拆分为 10 个域模块(Phase 4.22)
909 行的单文件 api.ts 按域拆为 10 个模块 + 共享 client;barrel 保持 import from '../api' 不变,调用点零改动。 - api/client.ts:axios 实例 + 拦截器 + authToken + setOnUnauthorized (唯一住处)。 - 10 个域模块:auth / targets / scenarios / modelConfigs / runs / reports / stats / campaigns / intelligentEvals / files,每个 自包含类型声明与 API 方法。 - api/index.ts:barrel 再导出,调用点 import 路径全部不变。 - 原 api.ts 中的 Blob 导出仪式已在 Commit 4 下沉至 utils/download.ts,本 commit 仅做拆分与搬运。
This commit is contained in:
parent
f458897ab5
commit
eca7ccebe2
@ -1,909 +0,0 @@
|
|||||||
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' | 'deleted'
|
|
||||||
|
|
||||||
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
|
|
||||||
// 归一后的规范结构(后端 submit 边界归一,见 ADR-0011)
|
|
||||||
scores: { overall: number | null; dimensions: 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 interface ExecutionSlotProgress {
|
|
||||||
time_slot: string
|
|
||||||
planned: number
|
|
||||||
created: number
|
|
||||||
completed: number
|
|
||||||
is_current: boolean
|
|
||||||
is_past: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExecutionProgress {
|
|
||||||
current_stage: 'planning' | 'approval' | 'executing' | 'done'
|
|
||||||
abnormal_outcome: 'cancelled' | 'failed' | null
|
|
||||||
blocker: string | null
|
|
||||||
next_action: string | null
|
|
||||||
slots: ExecutionSlotProgress[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const intelligentEvalsApi = {
|
|
||||||
list: (params?: { page?: number; page_size?: number; status?: string }) =>
|
|
||||||
api.get<{
|
|
||||||
intelligent_evals: IntelligentEval[]
|
|
||||||
total?: number
|
|
||||||
stats?: Record<string, number>
|
|
||||||
page?: number
|
|
||||||
page_size?: number
|
|
||||||
}>(
|
|
||||||
'/intelligent-evals',
|
|
||||||
{ params },
|
|
||||||
),
|
|
||||||
listTasks: (params?: { status?: TaskQueueStatus; limit?: number; eval_id?: string }) =>
|
|
||||||
api.get<TaskQueueList>('/intelligent-evals/tasks', { params }),
|
|
||||||
get: (id: string) => api.get<IntelligentEval>(`/intelligent-evals/${id}`),
|
|
||||||
getExecutionProgress: (id: string) =>
|
|
||||||
api.get<ExecutionProgress>(`/intelligent-evals/${id}/execution-progress`),
|
|
||||||
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`),
|
|
||||||
remove: (id: string) => api.delete<{ ok: boolean }>(`/intelligent-evals/${id}`),
|
|
||||||
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`),
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── 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}`),
|
|
||||||
}
|
|
||||||
11
frontend/web/src/api/auth.ts
Normal file
11
frontend/web/src/api/auth.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import api from './client'
|
||||||
|
|
||||||
|
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 }),
|
||||||
|
}
|
||||||
291
frontend/web/src/api/campaigns.ts
Normal file
291
frontend/web/src/api/campaigns.ts
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
import api from './client'
|
||||||
|
import { downloadBlob } from '../utils/download'
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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' })
|
||||||
|
downloadBlob(res.data as Blob, `campaign-report-${id.slice(0, 8)}.md`)
|
||||||
|
},
|
||||||
|
}
|
||||||
42
frontend/web/src/api/client.ts
Normal file
42
frontend/web/src/api/client.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
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
|
||||||
51
frontend/web/src/api/files.ts
Normal file
51
frontend/web/src/api/files.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import api from './client'
|
||||||
|
|
||||||
|
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}`),
|
||||||
|
}
|
||||||
15
frontend/web/src/api/index.ts
Normal file
15
frontend/web/src/api/index.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* API 单一出口:axios 实例住在 client.ts,各域类型与端点在
|
||||||
|
* api/<domain>.ts;消费方统一从本 barrel 导入('../api' 路径不变)。
|
||||||
|
*/
|
||||||
|
export { default, authToken, setOnUnauthorized } from './client'
|
||||||
|
export * from './auth'
|
||||||
|
export * from './targets'
|
||||||
|
export * from './scenarios'
|
||||||
|
export * from './modelConfigs'
|
||||||
|
export * from './runs'
|
||||||
|
export * from './reports'
|
||||||
|
export * from './stats'
|
||||||
|
export * from './campaigns'
|
||||||
|
export * from './intelligentEvals'
|
||||||
|
export * from './files'
|
||||||
248
frontend/web/src/api/intelligentEvals.ts
Normal file
248
frontend/web/src/api/intelligentEvals.ts
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
import api from './client'
|
||||||
|
import { downloadBlob } from '../utils/download'
|
||||||
|
|
||||||
|
export type IntelligentEvalStatus =
|
||||||
|
| 'draft' | 'planning' | 'pending_approval'
|
||||||
|
| 'executing' | 'completed' | 'cancelled' | 'failed' | 'deleted'
|
||||||
|
|
||||||
|
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
|
||||||
|
// 归一后的规范结构(后端 submit 边界归一,见 ADR-0011)
|
||||||
|
scores: { overall: number | null; dimensions: 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 interface ExecutionSlotProgress {
|
||||||
|
time_slot: string
|
||||||
|
planned: number
|
||||||
|
created: number
|
||||||
|
completed: number
|
||||||
|
is_current: boolean
|
||||||
|
is_past: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExecutionProgress {
|
||||||
|
current_stage: 'planning' | 'approval' | 'executing' | 'done'
|
||||||
|
abnormal_outcome: 'cancelled' | 'failed' | null
|
||||||
|
blocker: string | null
|
||||||
|
next_action: string | null
|
||||||
|
slots: ExecutionSlotProgress[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const intelligentEvalsApi = {
|
||||||
|
list: (params?: { page?: number; page_size?: number; status?: string }) =>
|
||||||
|
api.get<{
|
||||||
|
intelligent_evals: IntelligentEval[]
|
||||||
|
total?: number
|
||||||
|
stats?: Record<string, number>
|
||||||
|
page?: number
|
||||||
|
page_size?: number
|
||||||
|
}>(
|
||||||
|
'/intelligent-evals',
|
||||||
|
{ params },
|
||||||
|
),
|
||||||
|
listTasks: (params?: { status?: TaskQueueStatus; limit?: number; eval_id?: string }) =>
|
||||||
|
api.get<TaskQueueList>('/intelligent-evals/tasks', { params }),
|
||||||
|
get: (id: string) => api.get<IntelligentEval>(`/intelligent-evals/${id}`),
|
||||||
|
getExecutionProgress: (id: string) =>
|
||||||
|
api.get<ExecutionProgress>(`/intelligent-evals/${id}/execution-progress`),
|
||||||
|
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`),
|
||||||
|
remove: (id: string) => api.delete<{ ok: boolean }>(`/intelligent-evals/${id}`),
|
||||||
|
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' })
|
||||||
|
downloadBlob(res.data as Blob, `intelligent-eval-report-${id.slice(0, 8)}.md`)
|
||||||
|
},
|
||||||
|
// 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`),
|
||||||
|
}
|
||||||
74
frontend/web/src/api/modelConfigs.ts
Normal file
74
frontend/web/src/api/modelConfigs.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import api from './client'
|
||||||
|
|
||||||
|
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 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`),
|
||||||
|
}
|
||||||
13
frontend/web/src/api/reports.ts
Normal file
13
frontend/web/src/api/reports.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import api from './client'
|
||||||
|
import { downloadBlob } from '../utils/download'
|
||||||
|
|
||||||
|
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' })
|
||||||
|
downloadBlob(res.data as Blob, `report-${runId.slice(0, 8)}.${ext}`)
|
||||||
|
},
|
||||||
|
compare: (run1: string, run2: string) =>
|
||||||
|
api.get(`/reports/compare`, { params: { run1, run2 } }),
|
||||||
|
}
|
||||||
89
frontend/web/src/api/runs.ts
Normal file
89
frontend/web/src/api/runs.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import api from './client'
|
||||||
|
|
||||||
|
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 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 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`),
|
||||||
|
}
|
||||||
24
frontend/web/src/api/scenarios.ts
Normal file
24
frontend/web/src/api/scenarios.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import api from './client'
|
||||||
|
|
||||||
|
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 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}`),
|
||||||
|
}
|
||||||
34
frontend/web/src/api/stats.ts
Normal file
34
frontend/web/src/api/stats.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import api from './client'
|
||||||
|
import type { Run, RunTrigger } from './runs'
|
||||||
|
|
||||||
|
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 statsApi = {
|
||||||
|
dashboard: () => api.get<DashboardStats>('/stats/dashboard'),
|
||||||
|
trend: (days?: number) => api.get<TrendPoint[]>('/stats/trend', { params: { days } }),
|
||||||
|
}
|
||||||
24
frontend/web/src/api/targets.ts
Normal file
24
frontend/web/src/api/targets.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import api from './client'
|
||||||
|
import type { Run } from './runs'
|
||||||
|
|
||||||
|
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 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`),
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user