feat(azure-ai+gpu-ui): per-tenant Azure AI config + GPU health panel
- Per-tenant Azure AI config stored in tenants.tenant_config JSONB
- GET/PUT /api/tenants/{id}/ai-config + POST .../test connection
- api_key never returned to frontend (has_api_key: bool pattern)
- azure_ai.py resolves creds from tenant config when ai_enabled=True
- ai_tasks.py loads tenant config and passes it to validate_thumbnail
- Admin GPU Status section: probe button + status badge + last-checked time
- Notifications: _BELL_CHANNELS filter (notification+alert only in bell)
- Tenants.tsx: per-row Azure AI Config modal with URL auto-parse helper
- Remove duplicate in-memory /gpu-probe endpoints (kept DB-backed /probe/gpu)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,28 @@ export interface TenantUpdate {
|
||||
is_active?: boolean
|
||||
}
|
||||
|
||||
export interface TenantAIConfig {
|
||||
ai_enabled: boolean
|
||||
ai_endpoint: string | null
|
||||
ai_deployment: string
|
||||
ai_api_version: string
|
||||
has_api_key: boolean
|
||||
ai_max_tokens: number
|
||||
ai_temperature: number
|
||||
ai_validation_prompt: string | null
|
||||
}
|
||||
|
||||
export interface TenantAIConfigUpdate {
|
||||
ai_enabled: boolean
|
||||
ai_endpoint?: string | null
|
||||
ai_deployment?: string
|
||||
ai_api_version?: string
|
||||
ai_api_key?: string | null
|
||||
ai_max_tokens?: number
|
||||
ai_temperature?: number
|
||||
ai_validation_prompt?: string | null
|
||||
}
|
||||
|
||||
export async function getTenants(): Promise<Tenant[]> {
|
||||
const res = await api.get<Tenant[]>('/tenants/')
|
||||
return res.data
|
||||
@@ -44,3 +66,25 @@ export async function updateTenant(id: string, data: TenantUpdate): Promise<Tena
|
||||
export async function deleteTenant(id: string): Promise<void> {
|
||||
await api.delete(`/tenants/${id}`)
|
||||
}
|
||||
|
||||
export async function getTenantAIConfig(tenantId: string): Promise<TenantAIConfig> {
|
||||
const res = await api.get<TenantAIConfig>(`/tenants/${tenantId}/ai-config`)
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function updateTenantAIConfig(
|
||||
tenantId: string,
|
||||
config: TenantAIConfigUpdate,
|
||||
): Promise<TenantAIConfig> {
|
||||
const res = await api.put<TenantAIConfig>(`/tenants/${tenantId}/ai-config`, config)
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function testTenantAIConfig(
|
||||
tenantId: string,
|
||||
): Promise<{ ok: boolean; error?: string }> {
|
||||
const res = await api.post<{ ok: boolean; error?: string }>(
|
||||
`/tenants/${tenantId}/ai-config/test`,
|
||||
)
|
||||
return res.data
|
||||
}
|
||||
|
||||
@@ -197,3 +197,24 @@ export async function updateWorkerConfig(
|
||||
const res = await api.put<WorkerConfig>(`/worker/configs/${queueName}`, update)
|
||||
return res.data
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GPU probe
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface GPUProbeResult {
|
||||
status: 'ok' | 'failed' | 'error' | 'unknown'
|
||||
device_type?: string | null
|
||||
error?: string | null
|
||||
probed_at?: string | null
|
||||
}
|
||||
|
||||
export async function getGpuProbeResult(): Promise<GPUProbeResult> {
|
||||
const res = await api.get<GPUProbeResult>('/worker/gpu-probe')
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function triggerGpuProbe(): Promise<{ task_id: string; queued: boolean }> {
|
||||
const res = await api.post<{ task_id: string; queued: boolean }>('/worker/gpu-probe')
|
||||
return res.data
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user