Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

142 lines
4.4 KiB
TypeScript

import { useCallback, useMemo, useState } from "react";
import type { AgentAskUserQuestionVM } from "../contracts/agentAskUserQuestionItemVM";
import {
buildAskUserAnswerPayload,
readOwnAnswer,
writeOwnAnswer,
type InteractiveAnswerPayload
} from "../interactiveAnswerPayload";
export interface AskUserAnswerFlow {
allQuestionsAnswered: boolean;
answerPayload: InteractiveAnswerPayload;
currentIndex: number;
currentQuestion: AgentAskUserQuestionVM | null;
currentQuestionAnswered: boolean;
freeText: string;
isLastQuestion: boolean;
selectedOptions: string[];
goToNextQuestion: () => void;
goToPreviousQuestion: () => void;
setFreeText: (value: string) => void;
toggleOption: (optionLabel: string) => void;
}
export function useAskUserAnswerFlow({
isSubmitting,
questions
}: {
isSubmitting: boolean;
questions: AgentAskUserQuestionVM[];
}): AskUserAnswerFlow {
const [currentIndex, setCurrentIndex] = useState(0);
const [selectedByQuestionId, setSelectedByQuestionId] = useState<
Record<string, string[]>
>({});
const [freeTextByQuestionId, setFreeTextByQuestionId] = useState<
Record<string, string>
>({});
const currentQuestion = questions[currentIndex] ?? null;
const selectedOptions = currentQuestion
? readOwnAnswer(selectedByQuestionId, currentQuestion.id, [])
: [];
const freeText = currentQuestion
? readOwnAnswer(freeTextByQuestionId, currentQuestion.id, "")
: "";
const currentQuestionAnswered =
currentQuestion !== null &&
(selectedOptions.length > 0 || freeText.trim() !== "");
const isLastQuestion = currentIndex >= questions.length - 1;
const answerPayload = useMemo(() => {
const answersByQuestionId: Record<string, string | string[]> = {};
for (const question of questions) {
const selected = readOwnAnswer(selectedByQuestionId, question.id, []);
const customAnswer = readOwnAnswer(
freeTextByQuestionId,
question.id,
""
).trim();
if (question.multiSelect) {
const answers = customAnswer ? [...selected, customAnswer] : selected;
if (answers.length > 0) {
writeOwnAnswer(answersByQuestionId, question.id, answers);
}
continue;
}
const answer = customAnswer || selected[0];
if (answer) {
writeOwnAnswer(answersByQuestionId, question.id, answer);
}
}
return buildAskUserAnswerPayload(answersByQuestionId);
}, [freeTextByQuestionId, questions, selectedByQuestionId]);
const allQuestionsAnswered =
questions.length > 0 &&
questions.every((question) =>
Object.prototype.hasOwnProperty.call(
answerPayload.answersByQuestionId,
question.id
)
);
const toggleOption = useCallback(
(optionLabel: string) => {
if (isSubmitting || !currentQuestion) return;
setSelectedByQuestionId((current) => {
const existing = readOwnAnswer(current, currentQuestion.id, []);
const next = currentQuestion.multiSelect
? existing.includes(optionLabel)
? existing.filter((value) => value !== optionLabel)
: [...existing, optionLabel]
: existing.includes(optionLabel)
? []
: [optionLabel];
const updated = { ...current };
writeOwnAnswer(updated, currentQuestion.id, next);
return updated;
});
},
[currentQuestion, isSubmitting]
);
const setFreeText = useCallback(
(value: string) => {
if (isSubmitting || !currentQuestion) return;
setFreeTextByQuestionId((current) => {
const updated = { ...current };
writeOwnAnswer(updated, currentQuestion.id, value);
return updated;
});
},
[currentQuestion, isSubmitting]
);
const goToPreviousQuestion = useCallback(() => {
if (isSubmitting) return;
setCurrentIndex((current) => Math.max(current - 1, 0));
}, [isSubmitting]);
const goToNextQuestion = useCallback(() => {
if (isSubmitting || !currentQuestionAnswered) return;
setCurrentIndex((current) => Math.min(current + 1, questions.length - 1));
}, [currentQuestionAnswered, isSubmitting, questions.length]);
return {
allQuestionsAnswered,
answerPayload,
currentIndex,
currentQuestion,
currentQuestionAnswered,
freeText,
isLastQuestion,
selectedOptions,
goToNextQuestion,
goToPreviousQuestion,
setFreeText,
toggleOption
};
}