/** A provisional spike choice in Chat, separate from the card's final decision. */ import { useState } from "react"; import { DecisionIcon } from "@chopin/icons"; import { capitalize } from "./model"; import type { Chat, ConversationPlan } from "@chopin/protocol"; import type { Transport } from "@chopin/question/react"; import type { Question } from "@chopin/protocol"; import type { Questionnaire } from "@chopin/dialect"; type ScopedChoice = Extract, { kind: "scoped-choice" }>; export function ScopedChoicePrompt({ canEdit, connected, decision, latest, meta, state, value, wire, }: { canEdit: boolean; connected: boolean; decision: ScopedChoice; latest: boolean; meta?: Question.CardMeta; state?: ConversationPlan.State; value?: Questionnaire; wire?: Transport; }) { let [submitting, setSubmitting] = useState(false); let [acknowledged, setAcknowledged] = useState(false); let [error, setError] = useState(""); let thread = state?.threads.find(item => item.id === decision.threadId); let options = value?.id === decision.cardId ? value.questions[0]?.options.filter(item => item.id === decision.optionId) : undefined; let option = options?.length === 1 ? options[0] : undefined; let pending = thread?.pendingScopedChoice; let saved = state?.events.some(event => event.type === "scoped-choice.saved" && event.proposalId === decision.proposalId && event.cardId === decision.cardId && event.expectedGeneration === decision.generation ); let current = latest && !saved && !!thread && !!pending && pending.proposalId === decision.proposalId && pending.cardId === decision.cardId && pending.optionId === decision.optionId && pending.label === decision.label && pending.scope === decision.scope && option?.label === decision.label && thread.questionnaireId === decision.cardId && (meta?.status === "open" || meta?.status === "reopened") && meta.history.length === decision.generation; let enabled = current && canEdit && connected && !!wire && !submitting && !acknowledged; let actionId = `scoped-choice:${decision.proposalId}:${decision.generation}`; let save = async () => { if (!enabled || !thread || !wire) return; setSubmitting(true); setError(""); try { await wire.ask("conversation-plan:scoped-choice-save", { actionId, threadId: decision.threadId, expectedVersion: thread.version, proposalId: decision.proposalId, cardId: decision.cardId, optionId: decision.optionId, expectedGeneration: decision.generation, }); setAcknowledged(true); } catch { setError("Could not save this choice. Check the card and connection, then try again."); } finally { setSubmitting(false); } }; return (

Save {decision.label} for this spike?

{decision.sources.map(source => (

{source.author.kind === "member" ? capitalize(source.author.handle) : "Chopin"} {" “"} {source.quote} {"”"}

))}
{!saved && !current && (

{latest ? "This choice has changed." : "Updated below."}

)} {current && !canEdit && (

This document is read-only.

)} {current && canEdit && !connected && (

Reconnect to save this choice.

)} {error &&

{error}

} {(canEdit || saved) && (
)}
); }