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:
@ -27,6 +27,11 @@ import type {
|
||||
} from "@/domain";
|
||||
import { useGateways } from "@/app/di";
|
||||
import { usePluginRuntime } from "@/features/plugins/PluginRuntimeProvider";
|
||||
import {
|
||||
markProjectCliInteractionFinished,
|
||||
markProjectCliInteractionStarted,
|
||||
markProjectCliInteractionStopped,
|
||||
} from "@/features/projects/projectCliInteractionActivity";
|
||||
import { Button, Spinner, cn } from "@/shared";
|
||||
import type { ChatAttachmentInput, ChatDraftScope } from "@/ports";
|
||||
|
||||
@ -638,6 +643,8 @@ export function CustomAgentChatView({
|
||||
detachAgentChatRef.current = agent.detachAgentChat;
|
||||
const slashQuerySeqRef = useRef(0);
|
||||
const draftSaveSeqRef = useRef(0);
|
||||
const interactionSeqRef = useRef(0);
|
||||
const activeInteractionIdRef = useRef<string | null>(null);
|
||||
const draftHydratedRef = useRef(false);
|
||||
const draftEditedBeforeHydrationRef = useRef(false);
|
||||
const lastPersistedDraftRef = useRef("");
|
||||
@ -731,10 +738,33 @@ export function CustomAgentChatView({
|
||||
const receive = useCallback(
|
||||
(chunk: ReplyChunk) => {
|
||||
scrollbackChunkCountRef.current += 1;
|
||||
const activeInteractionId = activeInteractionIdRef.current;
|
||||
if (
|
||||
activeInteractionId &&
|
||||
chunk.kind === "progress" &&
|
||||
chunk.progress.kind === "turn"
|
||||
) {
|
||||
if (
|
||||
chunk.progress.stage === "started" ||
|
||||
chunk.progress.stage === "delta"
|
||||
) {
|
||||
markProjectCliInteractionStarted(projectId, activeInteractionId);
|
||||
}
|
||||
if (chunk.progress.stage === "completed") {
|
||||
markProjectCliInteractionFinished(projectId, activeInteractionId);
|
||||
activeInteractionIdRef.current = null;
|
||||
}
|
||||
}
|
||||
updateChatViewState((prev) => foldChunk(prev, chunk));
|
||||
if (chunk.kind === "final" || chunk.kind === "error") setBusy(false);
|
||||
if (chunk.kind === "final" || chunk.kind === "error") {
|
||||
if (activeInteractionIdRef.current) {
|
||||
markProjectCliInteractionFinished(projectId, activeInteractionIdRef.current);
|
||||
activeInteractionIdRef.current = null;
|
||||
}
|
||||
setBusy(false);
|
||||
}
|
||||
},
|
||||
[updateChatViewState],
|
||||
[projectId, updateChatViewState],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -1328,6 +1358,14 @@ export function CustomAgentChatView({
|
||||
const attachmentInputs = outgoingAttachments.map((item) => item.input);
|
||||
const attachmentLabels = outgoingAttachments.map((item) => item.label);
|
||||
const displayText = text || "Pièce jointe";
|
||||
const interactionId = `${agentId}:${++interactionSeqRef.current}`;
|
||||
activeInteractionIdRef.current = interactionId;
|
||||
markProjectCliInteractionStarted(projectId, interactionId);
|
||||
const finishInteraction = () => {
|
||||
if (activeInteractionIdRef.current !== interactionId) return;
|
||||
markProjectCliInteractionFinished(projectId, interactionId);
|
||||
activeInteractionIdRef.current = null;
|
||||
};
|
||||
setDraft("");
|
||||
setAttachments([]);
|
||||
void clearPersistedDraft();
|
||||
@ -1356,6 +1394,7 @@ export function CustomAgentChatView({
|
||||
retryAttachNotFound: true,
|
||||
}));
|
||||
await sendTurn(sid);
|
||||
finishInteraction();
|
||||
} catch (e) {
|
||||
if (isNotFound(e)) {
|
||||
try {
|
||||
@ -1366,8 +1405,10 @@ export function CustomAgentChatView({
|
||||
retryAttachNotFound: true,
|
||||
});
|
||||
await sendTurn(recoveredSession);
|
||||
finishInteraction();
|
||||
return;
|
||||
} catch (recoveryError) {
|
||||
finishInteraction();
|
||||
setBusy(false);
|
||||
setError(describe(recoveryError));
|
||||
updateChatViewState((prev) => ({
|
||||
@ -1377,6 +1418,7 @@ export function CustomAgentChatView({
|
||||
return;
|
||||
}
|
||||
}
|
||||
finishInteraction();
|
||||
setBusy(false);
|
||||
setError(describe(e));
|
||||
updateChatViewState((prev) => ({
|
||||
@ -1393,6 +1435,10 @@ export function CustomAgentChatView({
|
||||
setError(null);
|
||||
try {
|
||||
const stopped = await agent.stopLiveAgent(projectId, agentId);
|
||||
if (activeInteractionIdRef.current) {
|
||||
markProjectCliInteractionStopped(projectId, activeInteractionIdRef.current);
|
||||
activeInteractionIdRef.current = null;
|
||||
}
|
||||
setBusy(false);
|
||||
setOpening(false);
|
||||
setCurrentSession(null);
|
||||
@ -1415,6 +1461,10 @@ export function CustomAgentChatView({
|
||||
});
|
||||
} catch (e) {
|
||||
if (isNotFound(e)) {
|
||||
if (activeInteractionIdRef.current) {
|
||||
markProjectCliInteractionStopped(projectId, activeInteractionIdRef.current);
|
||||
activeInteractionIdRef.current = null;
|
||||
}
|
||||
setBusy(false);
|
||||
setOpening(false);
|
||||
setCurrentSession(null);
|
||||
|
||||
Reference in New Issue
Block a user