@@ -57,13 +57,7 @@ import {
type ChoiceOptionId ,
} from "@/lib/rectification-agentic/v9/choice-action" ;
import { isRectificationCaseStatus , isResumableStatus , type RectificationCaseStatus } from "@/lib/rectification-agentic/v9/case-status" ;
import { composeCollectSpokenAssistantText } from "@/lib/rectification-agentic/v9/collect-prompt " ;
import {
isPersistedFocusId ,
parseRectificationChoiceCard ,
type ChoiceKey ,
type RectificationChoiceCard as ChoiceCardModel ,
} from "@/lib/rectification-agentic/v9/choice-card" ;
import { CHOICE_MODE , CHOICE_STOP_LABEL , isPersistedFocusId , parseRectificationChoiceCard , type ChoiceKey , type RectificationChoiceCard as ChoiceCardModel } from "@/lib/rectification-agentic/v9/choice-card " ;
import type { PublicLanguageModel } from "@/lib/public-models" ;
import { useConversationScrollAnchor } from "@/hooks/use-conversation-scroll-anchor" ;
import { CharacterRemaining } from "./character-remaining" ;
@@ -77,13 +71,15 @@ import {
import { ModelSelector } from "./model-selector" ;
import { RectificationBoard , RectificationBoardPeek } from "./rectification-board" ;
import { RectificationChoiceCard } from "./rectification-choice-card" ;
import { Button } from "./ui/butt on";
import { copyTextForMessage , parseTurnQuestion , questionIsAnswered , type TurnQuestion } from "@/lib/rectification-agentic/v9/turn-questi on";
import { Button } from "@/components/ui/button" ;
type PersistedTurn = Readonly < {
id : string ;
role : "user" | "assistant" ;
text : string | null ;
status : string ;
question? : unknown ;
receipt? : Readonly < {
status : string ;
phases : readonly string [ ] ;
@@ -106,40 +102,24 @@ type CandidateResult = RectificationCandidateResult | null;
type CurrentQuestionModel = Readonly < {
kind : "choice" | "collect_spoken" ;
prompt : string | null ;
focus_id : string | null ;
question_id : string | null ;
} > ;
function currentQuestionFromSnapshot ( value : unknown ) : CurrentQuestionModel | null {
if ( ! value || typeof value !== "object" || Array . isArray ( value ) ) return null ;
const question = value as { kind? : unknown ; prompt? : unknown } ;
const question = value as { kind? : unknown ; prompt? : unknown ; focus_id? : unknown ; question_id? : unknown } ;
if ( question . kind !== "choice" && question . kind !== "collect_spoken" ) return null ;
return {
kind : question.kind ,
prompt : typeof question . prompt === "string" && question . prompt . trim ( )
? question . prompt . trim ( )
: null ,
focus_id : typeof question . focus_id === "string" ? question.focus_id : null ,
question_id : typeof question . question_id === "string" ? question.question_id : null ,
} ;
}
function collectSpokenPromptFromQuestion ( question : CurrentQuestionModel | null ) : string | null {
return question ? . kind === "collect_spoken" ? question.prompt : null ;
}
function attachCollectSpokenStem ( current : RenderMessage [ ] , prompt : string ) : RenderMessage [ ] {
const last = [ . . . current ] . reverse ( ) . find ( ( message ) = > (
message . role === "assistant"
&& message . state === "settled"
&& ! message . failed
&& Boolean ( message . text )
) ) ;
if ( ! last ) return current ;
const combined = composeCollectSpokenAssistantText ( last . text , prompt ) ;
if ( combined === last . text ) return current ;
return current . map ( ( item ) = > (
item . renderKey === last . renderKey ? { . . . item , text : combined } : item
) ) ;
}
function RectificationCandidateCards ( {
result ,
acceptingCandidateId ,
@@ -207,20 +187,93 @@ type RectificationAgenticChatProps = Readonly<{
headerSlot : HTMLElement | null ;
} > ;
type SettledChoiceAttachment = Readonly < {
card : ChoiceCardModel ;
selectedKey : ChoiceKey | "stop" ;
} > ;
type RenderMessage = ChatMessageView & {
renderKey : string ;
completedReceipt? : CompletedActivityReceiptView ;
failed? : boolean ;
turnId? : string ;
activityTrace? : readonly AgentActivityTraceItem [ ] ;
choiceAttachment ? : SettledChoiceAttachment ;
question ? : TurnQuestion ;
} ;
function mergeTurnQuestions ( current : RenderMessage [ ] , turns : readonly unknown [ ] ) : RenderMessage [ ] {
const byId = new Map < string , TurnQuestion | null > ( ) ;
for ( const item of turns ) {
if ( ! item || typeof item !== "object" ) continue ;
const turn = item as { id? : unknown ; question? : unknown } ;
if ( typeof turn . id !== "string" ) continue ;
byId . set ( turn . id , parseTurnQuestion ( turn . question ) ) ;
}
return current . map ( ( message ) = > {
if ( ! message . turnId || ! byId . has ( message . turnId ) ) return message ;
const question = byId . get ( message . turnId ) ? ? undefined ;
return { . . . message , question : question ? ? undefined } ;
} ) ;
}
function markQuestionAnswered (
current : RenderMessage [ ] ,
focusId : string ,
selected : ChoiceKey | "stop" | "typed" ,
) : RenderMessage [ ] {
return current . map ( ( message ) = > {
const question = message . question ;
if ( ! question || question . focus_id !== focusId || questionIsAnswered ( question ) ) return message ;
return {
. . . message ,
question : {
. . . question ,
status : "resolved" ,
answer_option : selected === "typed" ? null : selected ,
} ,
} ;
} ) ;
}
function snapshotTurns ( payload : { turns? : unknown } | null | undefined ) : readonly unknown [ ] {
return Array . isArray ( payload ? . turns ) ? payload . turns : [ ] ;
}
function appendUnseenAssistantTurns ( current : RenderMessage [ ] , turns : readonly unknown [ ] ) : RenderMessage [ ] {
const known = new Set ( current . flatMap ( ( message ) = > message . turnId ? [ message . turnId ] : [ ] ) ) ;
const extras = messagesFromTurns ( turns as readonly PersistedTurn [ ] ) . filter ( ( message ) = > (
message . role === "assistant"
&& message . turnId
&& ! known . has ( message . turnId )
) ) ;
return extras . length ? [ . . . current , . . . extras ] : current ;
}
function choiceCardFromQuestion (
question : TurnQuestion ,
live : ChoiceCardModel | null ,
) : ChoiceCardModel | null {
if ( live && live . focus_id === question . focus_id && live . options . length === 4 ) {
return { . . . live , prompt : question.prompt } ;
}
if ( ! question . options || question . options . length !== 4 ) return null ;
return {
question_id : question.question_id ,
method_id : "" ,
prompt : question.prompt ,
why : "" ,
varga : null ,
choice_mode : CHOICE_MODE ,
options : question.options.map ( ( option ) = > ( {
key : option.key ,
label : option.label ,
answer_class : "unsure" ,
role : "primary" ,
} ) ) ,
stop_label : CHOICE_STOP_LABEL ,
stop_message : "先这样" ,
scoring : question.kind !== "reverse_verify" ,
probe_id : question.probe_id ,
case_revision : null ,
focus_id : question.focus_id ,
} ;
}
function completedReceiptFromPersisted ( receipt : PersistedTurn [ "receipt" ] ) : CompletedActivityReceiptView {
if ( ! receipt ) return { steps : [ ] , methods : [ ] } ;
if ( Array . isArray ( receipt . tool_activities ) ) {
@@ -282,6 +335,7 @@ function messagesFromTurns(initialTurns: readonly PersistedTurn[]): RenderMessag
activityTrace : activityTraceFromReceipt ( completedReceipt ) ,
failed ,
turnId : turn.id ,
question : parseTurnQuestion ( turn . question ) ? ? undefined ,
} ] ;
}
if ( isIncompleteRunBanner ( turn . text ? ? "" ) ) return [ ] ;
@@ -392,6 +446,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
latest_result? : unknown ;
current_question? : unknown ;
choice_card? : unknown ;
turns? : unknown ;
case ? : {
status? : unknown ;
accepted_time? : unknown ;
@@ -421,7 +476,10 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
}
} , [ ] ) ;
const loadCaseSnapshot = useCallback ( async ( ) : Promise < CurrentQuestionModel | null | undefined > = > {
const loadCaseSnapshot = useCallback ( async ( ) : Promise < {
question : CurrentQuestionModel | null ;
turns : readonly unknown [ ] ;
} | null | undefined > = > {
try {
const response = await fetch (
` /api/rectification/cases/ ${ encodeURIComponent ( caseId ) } ?sessionId= ${ encodeURIComponent ( sessionId ) } ` ,
@@ -430,7 +488,10 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
if ( ! response . ok ) return undefined ;
const payload = await response . json ( ) . catch ( ( ) = > null ) ;
applyCaseSnapshot ( payload ) ;
return currentQuestionFromSnapshot ( payload ? . current_question ) ;
return {
question : currentQuestionFromSnapshot ( payload ? . current_question ) ,
turns : snapshotTurns ( payload ) ,
} ;
} catch {
// Snapshot refresh is best-effort; the durable Case remains on the server.
return undefined ;
@@ -447,8 +508,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
. then ( ( payload ) = > {
if ( controller . signal . aborted ) return ;
applyCaseSnapshot ( payload ) ;
const prompt = collectSpokenPromptFromQuestion ( currentQuestionFromSnapshot ( payload ? . current_question ) ) ;
if ( prompt ) setMessages ( ( current ) = > attachCollectSpokenStem ( current , prompt ) ) ;
} )
. catch ( ( ) = > {
// Snapshot refresh is best-effort; the durable Case remains on the server.
@@ -468,10 +527,10 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
const userRenderKey = ` v9-user- ${ turnKey } ` ;
const assistantRenderKey = ` v9-assistant- ${ turnKey } ` ;
const pendingCollectPrompt = collectSpokenPromptFromQuestion ( currentQuestionRef . current ) ;
const pendingFocusId = currentQuestionRef . current ? . focus_id ;
setMessages ( ( current ) = > [
. . . ( action === "message" && pendingCollectPrompt
? attachCollectSpokenStem ( current , pendingCollectPrompt )
. . . ( action === "message" && pendingFocusId
? markQuestionAnswered ( current , pendingFocusId , "typed" )
: current ) ,
. . . ( action === "message"
? [ { role : "user" , text : trimmed , renderKey : userRenderKey , state : "settled" } satisfies RenderMessage ]
@@ -679,16 +738,12 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
const parsed = completed && ! streamFailed ? parseAgentReply ( raw ) : { text : "" , title : undefined } ;
const succeeded = completed && ! streamFailed && Boolean ( parsed . text ) ;
const collectPrompt = collectSpokenPromptFromQuestion ( currentQuestionRef . current ) ;
const settledText = succeeded && collectPrompt
? composeCollectSpokenAssistantText ( parsed . text , collectPrompt )
: parsed . text ;
setMessages ( ( current ) = > current . flatMap ( ( message ) : RenderMessage [ ] = > {
if ( message . renderKey !== assistantRenderKey ) return [ message ] ;
if ( succeeded ) {
return [ {
. . . message ,
text : settledText ,
text : parsed.text ,
activityTrace : completeActivityTrace ( activityTrace ) ,
state : "settled" ,
completedReceipt ,
@@ -718,18 +773,13 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
setError ( ( current ) = > current || userFacingRunFailure ( "run_failed" ) ) ;
}
if ( succeeded ) {
const snapshotPrompt = collectSpokenPromptFromQuestion ( await loadCaseSnapshot ( ) ? ? null ) ;
const attachedText = snapshotPrompt
? composeCollectSpokenAssistantText ( parsed . text , snapshotPrompt )
: settledText ;
if ( attachedText !== settledText ) {
setMessages ( ( current ) = > current . map ( ( message ) = > (
message . renderKey === assistantRenderKey ? { . . . message , text : attachedText } : message
) ) ) ;
const snapshot = await loadCaseSnapshot ( ) ;
if ( snapshot ? . turns . length ) {
setMessages ( ( current ) = > mergeTurnQuestions ( current , snapshot . turns ) ) ;
}
onMessagesChange ? . ( [
. . . ( action === "message" ? [ { role : "user" as const , text : trimmed } ] : [ ] ) ,
{ role : "assistant" , text : attachedText } ,
{ role : "assistant" , text : parsed.text } ,
] ) ;
onCompleted ? . ( ) ;
}
@@ -797,26 +847,12 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
}
const questionId = choiceCard . question_id ;
const actionId = actionIdForChoice ( focusId , optionId ) ;
const answeredCard = choiceCard ;
const offeringRenderKey = [ . . . messages ] . reverse ( ) . find ( ( message ) = > (
message . role === "assistant"
&& message . state === "settled"
&& Boolean ( message . text )
&& ! message . choiceAttachment
) ) ? . renderKey ? ? null ;
setError ( "" ) ;
keyCounter . current += 1 ;
const turnKey = keyCounter . current ;
const assistantRenderKey = ` v9-choice-assistant- ${ turnKey } ` ;
setMessages ( ( current ) = > [
. . . current . map ( ( message ) = > (
offeringRenderKey && message . renderKey === offeringRenderKey
? {
. . . message ,
choiceAttachment : { card : answeredCard , selectedKey : optionId } ,
}
: message
) ) ,
. . . markQuestionAnswered ( current , focusId , optionId ) ,
{
role : "assistant" ,
text : "" ,
@@ -852,13 +888,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
} ) ;
const payload = await response . json ( ) . catch ( ( ) = > null ) ;
if ( ! response . ok ) {
setMessages ( ( current ) = > current . flatMap ( ( message ) = > {
if ( message . renderKey === assistantRenderKey ) return [ ] ;
if ( offeringRenderKey && message . renderKey === offeringRenderKey ) {
return [ { . . . message , choiceAttachment : undefined } ] ;
}
return [ message ] ;
} ) ) ;
setMessages ( ( current ) = > current . filter ( ( message ) = > message . renderKey !== assistantRenderKey ) ) ;
setChoiceNonce ( ( current ) = > current + 1 ) ;
if ( payload ? . code === "profile_incomplete" ) {
onProfileIncomplete ? . ( ) ;
@@ -869,34 +899,42 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
}
const willContinue = shouldContinueAfterStructuredChoice ( payload ? . nextAction , payload ) ;
onCompleted ? . ( ) ;
await loadCaseSnapshot ( ) ;
const snapshot = await loadCaseSnapshot ( ) ;
const turns = snapshot ? . turns ? ? [ ] ;
if ( willContinue ) {
setMessages ( ( current ) = > current . filter ( ( message ) = > message . renderKey !== assistantRenderKey ) ) ;
setMessages ( ( current ) = > mergeTurnQuestions (
current . filter ( ( message ) = > message . renderKey !== assistantRenderKey ) ,
turns ,
) ) ;
choiceContinuationPending . current = true ;
} else {
const narration = typeof payload ? . narration === "string" && payload . narration . trim ( )
? payload . narration . trim ( )
: "已记录你的选择。" ;
setMessages ( ( current ) = > current . map ( ( message ) = > message . renderKey === assistantRenderKey
? {
. . . message ,
text : narration ,
state : "settled" as const ,
activity : undefined ,
}
: message ) ) ;
setMessages ( ( current ) = > {
const withoutPlaceholder = current . filter ( ( message ) = > message . renderKey !== assistantRenderKey ) ;
const withHistory = appendUnseenAssistantTurns (
mergeTurnQuestions ( withoutPlaceholder , turns ) ,
turns ,
) ;
if ( withHistory . length > withoutPlaceholder . length ) return withHistory ;
return [
. . . withHistory ,
{
role : "assistant" as const ,
text : narration ,
renderKey : assistantRenderKey ,
state : "settled" as const ,
activity : undefined ,
} ,
] ;
} ) ;
onMessagesChange ? . ( [
{ role : "assistant" , text : narration } ,
] ) ;
}
} catch {
setMessages ( ( current ) = > current . flatMap ( ( message ) = > {
if ( message . renderKey === assistantRenderKey ) return [ ] ;
if ( offeringRenderKey && message . renderKey === offeringRenderKey ) {
return [ { . . . message , choiceAttachment : undefined } ] ;
}
return [ message ] ;
} ) ) ;
setMessages ( ( current ) = > current . filter ( ( message ) = > message . renderKey !== assistantRenderKey ) ) ;
setChoiceNonce ( ( current ) = > current + 1 ) ;
setError ( "选择题处理失败,请稍后重试。" ) ;
} finally {
@@ -908,7 +946,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
caseId ,
choiceCard ,
loadCaseSnapshot ,
messages ,
onCompleted ,
onMessagesChange ,
onProfileIncomplete ,
@@ -997,7 +1034,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
async function copyMessage ( message : RenderMessage ) {
try {
await navigator . clipboard . writeText ( message . text ) ;
await navigator . clipboard . writeText ( copyTextForMessage ( message . text , message . question ) ) ;
setCopiedMessageKey ( message . renderKey ) ;
window . setTimeout ( ( ) = > setCopiedMessageKey ( ( current ) = > (
current === message . renderKey ? null : current
@@ -1028,10 +1065,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
if ( ! response . ok || payload ? . ok !== true || typeof payload . assistantMessage !== "string" ) {
throw new Error ( payload ? . message || payload ? . error || "暂时无法重新生成回答" ) ;
}
const collectPrompt = collectSpokenPromptFromQuestion ( currentQuestion ) ;
const nextText = collectPrompt
? composeCollectSpokenAssistantText ( payload . assistantMessage , collectPrompt )
: payload . assistantMessage ;
const nextText = payload . assistantMessage ;
setMessages ( ( current ) = > current . map ( ( item ) = > item . renderKey === message . renderKey
? { . . . item , text : nextText , state : "settled" }
: item ) ) ;
@@ -1067,15 +1101,15 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
&& ! message . failed
&& Boolean ( message . text )
) ) ;
const answeredQuestionIds = new Set (
messages . flatMap ( ( message ) = > message . choiceAttachment
? [ message . choiceAttachment . card . question_id ]
: [ ] ) ,
) ;
const latestLiveQuestion = [ . . . messages ] . reverse ( ) . find ( ( message ) = > (
message . role === "assistant"
&& message . question
&& ! questionIsAnswered ( message . question )
) ) ? . question ? ? null ;
const showLiveChoiceCard = Boolean (
currentQuestion ? . kind === "choice"
&& choiceCard
&& ! answeredQuestionIds . has ( choiceCard . question_id )
latestLiveQuestion
&& latestLiveQuestion . options ? . length === 4
&& currentQuestion ? . focus_id === latestLiveQuestion . focus_id
&& ! busy
&& ! readonly
&& regeneratingMessageKey === null ,
@@ -1097,6 +1131,14 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
? currentQuestion.prompt
: null ;
const resumableCase = caseStatus !== null && isResumableStatus ( caseStatus ) ;
const liveQuestionOnMessages = messages . some ( ( message ) = > (
message . role === "assistant"
&& message . state === "settled"
&& message . question
&& currentQuestion
&& message . question . focus_id === currentQuestion . focus_id
&& ! questionIsAnswered ( message . question )
) ) ;
const showMissingQuestion = Boolean (
caseSnapshotLoaded
&& resumableCase
@@ -1110,13 +1152,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
&& ! readonly
&& ! busy
&& currentQuestion !== null
&& ( ( currentQuestion . kind === "choice" && ! choiceCard )
|| ( currentQuestion . kind === "collect_spoken" && ! collectSpokenPrompt ) ) ,
) ;
const showQuestionSlot = Boolean (
showLiveChoiceCard
|| showMissingQuestion
|| showUnavailableQuestion ,
&& ! liveQuestionOnMessages ,
) ;
const canSend = ! busy && ! readonly && ! regeneratingMessageKey ;
@@ -1191,28 +1227,48 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
settled : message.state === "settled" ,
} ) ,
} ;
const settledChoice = message . choiceAttachment ;
const visibleChoiceCard = settledChoice ? . card ;
const vargaSentence = ! message . failed
? vargaSentenceFromMethods ( message . completedReceipt ? . methods )
: null ;
const isLatestMessage = message . renderKey === messages [ messages . length - 1 ] ? . renderKey ;
const bubbleText = ! regenerating
&& isLatestMessage
&& message . role === "assistant"
&& collectSpokenPrompt
? composeCollectSpokenAssistantText ( displayedMessage . text , collectSpokenPrompt )
: displayedMessage . text ;
const bubbleMessage = bubbleText === displayedMessage . text
? displayedMessage
: { . . . displayedMessage , text : bubbleText } ;
const question = regenerating ? undefined : message . question ;
const liveQuestion = Boolean (
question
&& currentQuestion ? . focus_id === question . focus_id
&& ! questionIsAnswered ( question )
&& choiceCard
&& choiceCard . focus_id === question . focus_id
&& ! busy
&& ! readonly
&& regeneratingMessageKey === null ,
) ;
const embeddedCard = question ? choiceCardFromQuestion ( question , liveQuestion ? choiceCard : null ) : null ;
const afterAnswer = question && displayedMessage . state === "settled"
? (
< div className = "rectification-message-question" >
< p className = "rectification-message-question__prompt" > { question . prompt } < / p >
{ embeddedCard && (
< RectificationChoiceCard
key = { ` ${ embeddedCard . question_id } : ${ question . answer_option ? ? choiceNonce } : ${ question . status } ` }
variant = "embedded"
card = { embeddedCard }
pending = { busy && liveQuestion }
disabled = { ! liveQuestion }
selectedKey = { question . answer_option ? ? "" }
onSelect = { submitChoice }
onStop = { submitStop }
/ >
) }
< / div >
)
: undefined ;
return (
< div key = { message . renderKey } className = "rectification-message-wrap rectification-message-entry" >
{ ( ! message . failed || Boolean ( bubbleMessage . text ) || regenerating ) && (
{ ( ! message . failed || Boolean ( displayedMessage . text ) || regenerating ) && (
< ChatMessageRow
message = { bubbleMessage }
message = { displayedMessage }
showActivity = { displayedMessage . state !== "settled" }
vargaSentence = { vargaSentence }
afterAnswer = { afterAnswer }
/ >
) }
{ showActions && ! regenerating && (
@@ -1224,7 +1280,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
. . . current ,
[ message . renderKey ] : toggleRectificationFeedback ( current [ message . renderKey ] , requested ) ,
} ) ) }
onCopy = { ( ) = > void copyMessage ( { . . . message , text : bubbleText } ) }
onCopy = { ( ) = > void copyMessage ( message ) }
onRegenerate = { ( ) = > void regenerateMessage ( message ) }
/ >
) }
@@ -1236,72 +1292,9 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
onAccept = { ( candidateId ) = > void acceptCandidate ( candidateId ) }
/ >
) }
{ visibleChoiceCard && (
< RectificationChoiceCard
key = { settledChoice
? ` ${ visibleChoiceCard . question_id } :answered: ${ settledChoice . selectedKey } `
: ` ${ visibleChoiceCard . question_id } : ${ choiceNonce } ` }
card = { visibleChoiceCard }
pending = { false }
disabled = { readonly || Boolean ( settledChoice ) }
selectedKey = { settledChoice ? . selectedKey ? ? "" }
onSelect = { submitChoice }
onStop = { submitStop }
/ >
) }
{ isLatestMessage && showQuestionSlot && (
< section className = "rectification-question-slot" aria-label = "当前问题" >
{ showLiveChoiceCard && choiceCard && (
< RectificationChoiceCard
key = { ` ${ choiceCard . question_id } : ${ choiceNonce } ` }
card = { choiceCard }
pending = { busy }
disabled = { readonly }
selectedKey = ""
onSelect = { submitChoice }
onStop = { submitStop }
/ >
) }
{ showMissingQuestion && (
< p className = "rectification-question-slot__status" role = "status" >
当 前 没 有 可 回 答 的 问 题 , 正 在 等 待 服 务 端 更 新 。
< / p >
) }
{ showUnavailableQuestion && (
< p className = "rectification-question-slot__status" role = "status" >
当 前 问 题 暂 时 无 法 显 示 , 请 等 待 服 务 端 更 新 。
< / p >
) }
< / section >
) }
< / div >
) ;
} ) }
{ messages . length === 0 && showQuestionSlot && (
< section className = "rectification-question-slot" aria-label = "当前问题" >
{ showLiveChoiceCard && choiceCard && (
< RectificationChoiceCard
key = { ` ${ choiceCard . question_id } : ${ choiceNonce } ` }
card = { choiceCard }
pending = { busy }
disabled = { readonly }
selectedKey = ""
onSelect = { submitChoice }
onStop = { submitStop }
/ >
) }
{ showMissingQuestion && (
< p className = "rectification-question-slot__status" role = "status" >
当 前 没 有 可 回 答 的 问 题 , 正 在 等 待 服 务 端 更 新 。
< / p >
) }
{ showUnavailableQuestion && (
< p className = "rectification-question-slot__status" role = "status" >
当 前 问 题 暂 时 无 法 显 示 , 请 等 待 服 务 端 更 新 。
< / p >
) }
< / section >
) }
{ savedTime && savedStatus === "confirmed" && (
< p className = "rectification-saved" role = "status" >
已 确 认 校 正 时 间 : { savedTime }
@@ -1328,6 +1321,13 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
{ ! conversationAnchor . anchored && (
< JumpToLatestButton onClick = { conversationAnchor . anchorToLatest } / >
) }
{ ( showMissingQuestion || showUnavailableQuestion ) && (
< p className = "rectification-composer-status" role = "status" >
{ showMissingQuestion
? "当前没有可回答的问题,正在等待服务端更新。"
: "当前问题暂时无法显示,请等待服务端更新。" }
< / p >
) }
< ChatComposer
inputRef = { composer }
value = { draft }