PMFlow/frontend/src/api/aiSteward.ts

366 lines
11 KiB
TypeScript

import { apiRequest } from "./base";
type ApiEnvelope<T> = { code: number; message: string; data: T };
function unwrap<T>(raw: ApiEnvelope<T> | T): T {
if (raw && typeof raw === "object" && "code" in (raw as ApiEnvelope<T>) && "data" in (raw as ApiEnvelope<T>)) {
return (raw as ApiEnvelope<T>).data;
}
return raw as T;
}
export interface ProjectFromTextRequest {
input_text: string;
}
export interface ProjectFromTextRequirementItem {
section: string;
title: string;
description: string;
priority?: string;
source_kind?: string;
source_name?: string;
source_identifier?: string;
}
export interface ProjectFromTextSignalSource {
registry_id: number | string;
id?: number | string;
project_id?: number | string;
signal_id?: number | string | null;
source_kind: string;
source_name: string;
source_identifier: string;
description?: string | null;
requested_via?: string;
status?: string;
onboarding_status?: string;
section?: string;
title?: string;
priority?: string;
origin?: string;
raw_context?: Record<string, unknown> | null;
config?: Record<string, unknown> | null;
}
export interface ProjectFromTextResponse {
project: { id: string; name: string; title: string; description: string; status: string };
signal: { id: string; content: string; impact_score: number };
charter: { id: string; title: string; content: string | Record<string, unknown> | null };
tasks: Array<{ id: string; title: string; type: string; estimated_hours: number; priority: string }>;
risks: Array<{ type: string; description: string; severity: string }>;
signal_sources: ProjectFromTextSignalSource[];
analysis: {
project_name: string;
project_title?: string;
tech_stack: string[];
timeline: string;
primary_role: string;
target_audience?: string;
domain_id?: number;
key_features?: string[];
competitors?: string[];
success_metrics?: string[];
core_pain_points?: string[];
mvp_features?: string[];
requirement_items?: ProjectFromTextRequirementItem[];
};
}
export interface SignalSourceRepairRequest {
project_id: number;
registry_ids: number[];
sender?: string;
}
export interface SignalSourceRepairItem {
registry_id: number;
signal_id: number | null;
capture_status: string;
work_item_id: number | null;
route_status: string;
}
export interface SignalSourceRepairResponse {
project_id: number;
items: SignalSourceRepairItem[];
}
export interface CognitiveConflict {
id: string;
type: string;
description: string;
severity: string;
related_users: string[];
}
export interface Blocker {
id: string;
type: string;
description: string;
severity: string;
detected_at: string;
}
export interface DailyBriefing {
date: string;
summary: string;
highlights: Array<{ type: string; priority: string; content: string }>;
your_tasks: Array<{ id: number; title: string; status: string; priority: string }>;
risks: Array<{ type: string; task: string; severity: string }>;
signals: Array<{ id: number; source: string; content: string; impact_score: number }>;
conflicts: Array<{ id: string; type: string; description: string }>;
action_items: Array<{ type: string; content: string; priority: string }>;
team_updates: Array<{ assignee_id: number; task: string; status: string }>;
}
export interface ExecutiveBriefingMetric {
label: string;
value: string | number;
tone: string;
}
export interface ExecutiveBriefingItem {
title: string;
detail: string;
tone: string;
kind?: string | null;
}
export interface ExecutiveBriefingSection {
key: string;
title: string;
summary: string;
metrics: ExecutiveBriefingMetric[];
items: ExecutiveBriefingItem[];
drilldown_tab?: string | null;
}
export interface ExecutiveBriefingHero {
title: string;
decision_count: number;
anomaly_count: number;
baseline_generated_at?: string | null;
live_generated_at?: string | null;
key_alerts: ExecutiveBriefingMetric[];
snapshot_state: "ready" | "missing" | string;
}
export interface ExecutiveBriefingConfig {
enabled: boolean;
send_time: string;
send_cycle: "workdays" | "daily";
delivery_target_type: "management_group";
delivery_target_ref: string | null;
timezone: string;
updated_at?: string | null;
updated_by?: number | null;
}
export interface ExecutiveBriefingPayload {
brief_date: string;
mode: "snapshot" | "live";
summary: Record<string, unknown>;
hero: ExecutiveBriefingHero;
sections: ExecutiveBriefingSection[];
delivery: ExecutiveBriefingConfig;
}
export type LLMProvider = "deepseek" | "glm" | "openai" | "ollama";
export interface LLMProviderPreset {
model: string;
base_url: string;
api_env: string;
}
export interface LLMProviderPresetResponse {
low: string;
high: string;
base_url: string;
providers: Record<LLMProvider, LLMProviderPreset>;
}
export interface LLMEngineSettings {
enable_local_kb_qa: boolean;
local_kb_qa_scope: "kb_qa_only";
}
export interface LLMEngine {
id: string;
name: string;
provider: LLMProvider;
model: string;
base_url: string;
api_key_masked: string;
updated_at?: string;
created_at?: string;
}
export interface LLMEngineListResponse {
active_engine_id: string | null;
engines: LLMEngine[];
settings: LLMEngineSettings;
}
export interface LLMEngineUpsertRequest {
name: string;
provider: LLMProvider;
api_key?: string;
model?: string;
base_url?: string;
set_active?: boolean;
}
export interface LLMEngineConnectionTestResponse {
ok: boolean;
engine_id: string;
name: string;
provider: LLMProvider;
model: string;
base_url: string;
api_key_masked: string;
latency_ms: number;
response_preview: string;
}
export const aiStewardApi = {
async listCognitiveConflicts(projectId?: number): Promise<CognitiveConflict[]> {
const query = typeof projectId === "number" ? `?project_id=${projectId}` : "";
return await apiRequest<CognitiveConflict[]>(`/ai-steward/cognitive-conflicts${query}`);
},
async detectConflicts(projectId: number): Promise<{ project_id: number; detected_count: number; conflicts: CognitiveConflict[] }> {
return await apiRequest<{ project_id: number; detected_count: number; conflicts: CognitiveConflict[] }>(
"/ai-steward/conflicts/detect",
{
method: "POST",
body: JSON.stringify({ project_id: projectId, detect_type: "all" }),
}
);
},
async listActiveBlockers(): Promise<{ total: number; blockers: Blocker[] }> {
return await apiRequest<{ total: number; blockers: Blocker[] }>("/ai-steward/blockers/active");
},
async detectTaskBlockers(): Promise<{ total_detected: number; blockers: Blocker[] }> {
return await apiRequest<{ total_detected: number; blockers: Blocker[] }>("/ai-steward/blockers/detect-tasks", {
method: "POST",
});
},
async getDailyBriefing(): Promise<DailyBriefing> {
return await apiRequest<DailyBriefing>("/ai-steward/daily-briefing");
},
async startHnMonitor(intervalMinutes: number = 30): Promise<Record<string, unknown>> {
return await apiRequest<Record<string, unknown>>(
`/ai-steward/signal/start-hn-monitor?interval_minutes=${intervalMinutes}`,
{
method: "POST",
}
);
},
async getExecutiveBriefing(mode: "snapshot" | "live"): Promise<ExecutiveBriefingPayload> {
const raw = await apiRequest<ApiEnvelope<ExecutiveBriefingPayload>>(`/ai-steward/executive-briefing?mode=${mode}`);
return unwrap(raw);
},
async getExecutiveBriefingConfig(): Promise<ExecutiveBriefingConfig> {
const raw = await apiRequest<ApiEnvelope<ExecutiveBriefingConfig>>("/ai-steward/executive-briefing/config");
return unwrap(raw);
},
async updateExecutiveBriefingConfig(body: ExecutiveBriefingConfig): Promise<ExecutiveBriefingConfig> {
const raw = await apiRequest<ApiEnvelope<ExecutiveBriefingConfig>>("/ai-steward/executive-briefing/config", {
method: "PUT",
body: JSON.stringify(body),
});
return unwrap(raw);
},
async getLlmProviderPresets(): Promise<LLMProviderPresetResponse> {
const raw = await apiRequest<ApiEnvelope<LLMProviderPresetResponse>>("/ai-steward/llm/providers");
return unwrap(raw);
},
async listLlmEngines(): Promise<LLMEngineListResponse> {
const raw = await apiRequest<ApiEnvelope<LLMEngineListResponse>>("/ai-steward/llm-engines");
return unwrap(raw);
},
async createLlmEngine(body: LLMEngineUpsertRequest): Promise<LLMEngineListResponse & { engine: LLMEngine }> {
const raw = await apiRequest<ApiEnvelope<LLMEngineListResponse & { engine: LLMEngine }>>("/ai-steward/llm-engines", {
method: "POST",
body: JSON.stringify(body),
});
return unwrap(raw);
},
async updateLlmEngine(
engineId: string,
body: Partial<LLMEngineUpsertRequest>,
): Promise<LLMEngineListResponse & { engine: LLMEngine }> {
const raw = await apiRequest<ApiEnvelope<LLMEngineListResponse & { engine: LLMEngine }>>(
`/ai-steward/llm-engines/${engineId}`,
{
method: "PUT",
body: JSON.stringify(body),
}
);
return unwrap(raw);
},
async activateLlmEngine(engineId: string): Promise<LLMEngineListResponse & { active_engine_id: string }> {
const raw = await apiRequest<ApiEnvelope<LLMEngineListResponse & { active_engine_id: string }>>(
`/ai-steward/llm-engines/${engineId}/activate`,
{
method: "POST",
}
);
return unwrap(raw);
},
async deleteLlmEngine(
engineId: string,
): Promise<LLMEngineListResponse & { deleted_engine_id: string; remaining_count: number }> {
const raw = await apiRequest<
ApiEnvelope<LLMEngineListResponse & { deleted_engine_id: string; remaining_count: number }>
>(`/ai-steward/llm-engines/${engineId}`, {
method: "DELETE",
});
return unwrap(raw);
},
async updateLlmEngineSettings(
body: Pick<LLMEngineSettings, "enable_local_kb_qa">,
): Promise<LLMEngineListResponse & { settings: LLMEngineSettings }> {
const raw = await apiRequest<ApiEnvelope<LLMEngineListResponse & { settings: LLMEngineSettings }>>(
"/ai-steward/llm-engines/settings",
{
method: "PUT",
body: JSON.stringify(body),
}
);
return unwrap(raw);
},
async testActiveLlmEngine(): Promise<LLMEngineConnectionTestResponse> {
const raw = await apiRequest<ApiEnvelope<LLMEngineConnectionTestResponse>>("/ai-steward/llm-engines/test-active", {
method: "POST",
body: JSON.stringify({}),
});
return unwrap(raw);
},
async testLlmEngine(engineId: string): Promise<LLMEngineConnectionTestResponse> {
const raw = await apiRequest<ApiEnvelope<LLMEngineConnectionTestResponse>>(
`/ai-steward/llm-engines/${engineId}/test`,
{
method: "POST",
body: JSON.stringify({}),
}
);
return unwrap(raw);
},
async repairDirtySignalSources(body: SignalSourceRepairRequest): Promise<SignalSourceRepairResponse> {
const raw = await apiRequest<ApiEnvelope<SignalSourceRepairResponse>>("/ai-steward/signal-sources/repair-dirty", {
method: "POST",
body: JSON.stringify(body),
});
return unwrap(raw);
},
async createProjectFromText(body: ProjectFromTextRequest): Promise<ProjectFromTextResponse> {
const raw = await apiRequest<ApiEnvelope<ProjectFromTextResponse>>("/ai-steward/project-from-text", {
method: "POST",
body: JSON.stringify(body),
});
return unwrap(raw);
},
};