feat(ui): LED projet — exploite les signaux d'interaction CLI custom (#198, QA verte)
Projette l'état d'interaction agent→projet sur la LED via des signaux explicites de la CLI custom: - projectCliInteractionActivity: modèle d'activité d'interaction (InteractionStarted/Finished/Stopped, interactionId/seq, version). - useProjectActivity + ProjectTabs: projection live « interaction en cours » → LED, retour immédiat à l'état attendu au stop ou à la fin réelle de l'échange (pas d'état collant). - CustomAgentChatView: émet les signaux d'interaction CLI custom (début d'envoi, fin de tour, stop) consommés par l'activité projet — sans dériver la LED du texte streamé ou d'heuristiques. Tout côté frontend: aucun signal runtime manquant (les signaux proviennent de la CLI custom). Tests QA verts: projectActivity.test.ts, ProjectTabs.test.tsx (+ useProjectActivity.test.tsx, CustomAgentChatView.test.tsx).
This commit is contained in:
@ -0,0 +1,71 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type Listener = () => void;
|
||||
|
||||
const activeByProject = new Map<string, Set<string>>();
|
||||
const listeners = new Set<Listener>();
|
||||
|
||||
function emit(): void {
|
||||
for (const listener of listeners) listener();
|
||||
}
|
||||
|
||||
function activeSet(projectId: string): Set<string> {
|
||||
let set = activeByProject.get(projectId);
|
||||
if (!set) {
|
||||
set = new Set();
|
||||
activeByProject.set(projectId, set);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
export function markProjectCliInteractionStarted(
|
||||
projectId: string,
|
||||
interactionId: string,
|
||||
): void {
|
||||
const set = activeSet(projectId);
|
||||
const before = set.size;
|
||||
set.add(interactionId);
|
||||
if (set.size !== before) emit();
|
||||
}
|
||||
|
||||
export function markProjectCliInteractionFinished(
|
||||
projectId: string,
|
||||
interactionId: string,
|
||||
): void {
|
||||
const set = activeByProject.get(projectId);
|
||||
if (!set?.delete(interactionId)) return;
|
||||
if (set.size === 0) activeByProject.delete(projectId);
|
||||
emit();
|
||||
}
|
||||
|
||||
export function markProjectCliInteractionStopped(
|
||||
projectId: string,
|
||||
interactionId: string,
|
||||
): void {
|
||||
markProjectCliInteractionFinished(projectId, interactionId);
|
||||
}
|
||||
|
||||
export function hasProjectCliInteraction(projectId: string): boolean {
|
||||
return (activeByProject.get(projectId)?.size ?? 0) > 0;
|
||||
}
|
||||
|
||||
export function subscribeProjectCliInteractionActivity(
|
||||
listener: Listener,
|
||||
): () => void {
|
||||
listeners.add(listener);
|
||||
return () => listeners.delete(listener);
|
||||
}
|
||||
|
||||
export function useProjectCliInteractionActivityVersion(): number {
|
||||
const [version, setVersion] = useState(0);
|
||||
useEffect(
|
||||
() => subscribeProjectCliInteractionActivity(() => setVersion((v) => v + 1)),
|
||||
[],
|
||||
);
|
||||
return version;
|
||||
}
|
||||
|
||||
export function __resetProjectCliInteractionActivityForTests(): void {
|
||||
activeByProject.clear();
|
||||
emit();
|
||||
}
|
||||
Reference in New Issue
Block a user