import type { AgentDecomposeResponse, AgentDecomposeTarget } from '@ftb/shared'; export interface AiDecomposeCompletedPayload { result: AgentDecomposeResponse; dedupeSummary: { removedDevTaskCount: number; removedTestCaseCount: number }; } export type AiDecomposeButtonMode = 'generate' | 'generating' | 'view_result' | 'retry'; export type AiDecomposeSessionState = | { status: 'idle' } | { status: 'generating'; startedAt: number; promise: Promise } | { status: 'completed'; completedAt: number; payload: AiDecomposeCompletedPayload } | { status: 'error'; failedAt: number; error: string }; type Listener = () => void; export interface AiDecomposeSessionStore { get: (planId: string, target: AgentDecomposeTarget) => AiDecomposeSessionState; start: ( planId: string, target: AgentDecomposeTarget, run: () => Promise, ) => Promise; markReviewed: (planId: string, target: AgentDecomposeTarget) => void; subscribe: (listener: Listener) => () => void; } function getSessionKey(planId: string, target: AgentDecomposeTarget): string { return `${planId}::${target}`; } function normalizeError(error: unknown): string { return error instanceof Error ? error.message : String(error || 'AI 生成失败'); } export function createAiDecomposeSessionStore(): AiDecomposeSessionStore { const sessions = new Map(); const listeners = new Set(); const notify = () => { for (const listener of listeners) listener(); }; return { get(planId, target) { return sessions.get(getSessionKey(planId, target)) ?? { status: 'idle' }; }, start(planId, target, run) { const key = getSessionKey(planId, target); const existing = sessions.get(key); if (existing?.status === 'generating') return existing.promise; const promise = run().then( (payload) => { sessions.set(key, { status: 'completed', completedAt: Date.now(), payload }); notify(); return payload; }, (error) => { sessions.set(key, { status: 'error', failedAt: Date.now(), error: normalizeError(error) }); notify(); throw error; }, ); sessions.set(key, { status: 'generating', startedAt: Date.now(), promise }); notify(); return promise; }, markReviewed(planId, target) { sessions.delete(getSessionKey(planId, target)); notify(); }, subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); }, }; } export function getAiDecomposeButtonMode(session: AiDecomposeSessionState): AiDecomposeButtonMode { if (session.status === 'generating') return 'generating'; if (session.status === 'completed') return 'view_result'; if (session.status === 'error') return 'retry'; return 'generate'; } export const aiDecomposeSessionStore = createAiDecomposeSessionStore();