@@ -67,6 +67,7 @@ type RenderMessage = ChatMessageView & {
renderKey : string ;
activeActivity? : PublicActivity ;
completedReceipt? : CompletedActivityReceiptView ;
failed? : boolean ;
turnId? : string ;
} ;
@@ -114,16 +115,30 @@ function activityPhase(tool: PublicRectificationTool): NonNullable<ChatMessageVi
return "evidence-validation" ;
}
function hasActivityReceipt ( receipt : CompletedActivityReceiptView ) : boolean {
return receipt . steps . length > 0 || receipt . methods . length > 0 || Boolean ( receipt . failedTool ) ;
}
function persistedTurnFailed ( turn : PersistedTurn ) : boolean {
return turn . status === "failed"
|| turn . status === "retryable"
|| turn . receipt ? . status === "failed"
|| turn . receipt ? . status === "degraded"
|| turn . receipt ? . status === "blocked" ;
}
function messagesFromTurns ( initialTurns : readonly PersistedTurn [ ] ) : RenderMessage [ ] {
return initialTurns . flatMap ( ( turn , index ) : RenderMessage [ ] = > {
const key = ` persisted- ${ turn . id } - ${ index } ` ;
if ( turn . role === "assistant" ) {
const failed = persistedTurnFailed ( turn ) ;
return [ {
role : "assistant" ,
text : turn.text ? ? "" ,
text : failed ? "" : turn . text ? ? "" ,
renderKey : key ,
state : turn.status === "completed" ? "settled" : "thinking" ,
state : turn.status === "completed" || failed ? "settled" : "thinking" ,
completedReceipt : completedReceiptFromPersisted ( turn . receipt ) ,
failed ,
turnId : turn.id ,
} ] ;
}
@@ -251,7 +266,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
if ( ! response . ok ) {
const payload = await response . json ( ) . catch ( ( ) = > null ) ;
const message = payload ? . message || payload ? . error || ` 请求失败( ${ response . status } ) ` ;
setMessages ( ( current ) = > current . filter ( ( item ) = > item . renderKey !== assistantRenderKey ) ) ;
setMessages ( ( current ) = > current . filter ( ( message ) = > message . renderKey !== assistantRenderKey ) ) ;
if ( payload ? . code === "profile_incomplete" ) {
onProfileIncomplete ? . ( ) ;
return ;
@@ -265,7 +280,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
return ;
}
if ( ! response . body ) {
setMessages ( ( current ) = > current . filter ( ( item ) = > item . renderKey !== assistantRenderKey ) ) ;
setMessages ( ( current ) = > current . filter ( ( message ) = > message . renderKey !== assistantRenderKey ) ) ;
setError ( "服务暂时不可用,请稍后再试。" ) ;
return ;
}
@@ -302,7 +317,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
raw += event . text ;
const parsed = parseAgentReplyBody ( raw ) ;
setMessages ( ( current ) = > current . map ( ( message ) = > message . renderKey === assistantRenderKey
? { . . . message , text : parsed.text , state : "streaming" , activeActivity : undefined }
? { . . . message , text : parsed.text , state : "streaming" }
: message ) ) ;
} else if ( event . type === "run.failed" ) {
streamFailed = true ;
@@ -343,18 +358,31 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
const parsed = parseAgentReplyBody ( raw ) ;
const succeeded = completed && ! streamFailed && Boolean ( parsed . text ) ;
setMessages ( ( current ) = > succeeded
? current . map ( ( message ) = > message . renderKey === assistantRenderKey
? {
setMessages ( ( current ) = > current . flatMap ( ( message ) : RenderMessage [ ] = > {
if ( message . renderKey ! == assistantRenderKey ) return [ message ] ;
if ( succeeded ) {
return [ {
. . . message ,
text : parsed.text ,
state : "settled" ,
activeActivity : undefined ,
completedReceipt ,
failed : false ,
turnId : completedTurnId ,
}
: message )
: current . filter ( ( message ) = > message . renderKey !== assistantRenderKey ) ) ;
} ] ;
}
if ( streamFailed || hasActivityReceipt ( completedReceipt ) ) {
return [ {
. . . message ,
text : "" ,
state : "settled" ,
activeActivity : undefined ,
completedReceipt ,
failed : true ,
} ] ;
}
return [ ] ;
} ) ) ;
if ( ! succeeded && completedReceipt . failedTool ) {
setError ( completedReceipt . failedTool === "rectification-compare-candidates"
? "候选比较未完成,当前进度已保留。"
@@ -372,7 +400,19 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
}
} catch {
setError ( "生时校正暂时不可用,请稍后再试。" ) ;
setMessages ( ( current ) = > current . filter ( ( message ) = > message . renderKey !== assistantRenderKey ) ) ;
setMessages ( ( current ) = > current . flatMap ( ( message ) : RenderMessage [ ] = > {
if ( message . renderKey !== assistantRenderKey ) return [ message ] ;
return hasActivityReceipt ( completedReceipt )
? [ {
. . . message ,
text : "" ,
state : "settled" ,
activeActivity : undefined ,
completedReceipt ,
failed : true ,
} ]
: [ ] ;
} ) ) ;
} finally {
setPending ( false ) ;
}
@@ -474,6 +514,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
. find ( ( message ) = > (
message . role === "assistant"
&& message . state === "settled"
&& ! message . failed
&& Boolean ( message . turnId )
&& Boolean ( message . text )
) ) ? . renderKey ;
@@ -491,6 +532,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
{ messages . map ( ( message ) = > {
const showActions = message . role === "assistant"
&& message . state === "settled"
&& ! message . failed
&& Boolean ( message . text ) ;
const regenerating = regeneratingMessageKey === message . renderKey ;
const canRegenerate = message . renderKey === latestRegeneratableKey
@@ -502,18 +544,25 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
: message ;
return (
< div key = { message . renderKey } className = "rectification-message-wrap rectification-message-entry" >
{ message . state === "settled" && message . failed && ! message . completedReceipt ? . failedTool && (
< p className = "rectification-activity-failure" role = "status" >
本 轮 处 理 未 完 成 , 已 保 留 服 务 端 记 录 的 执 行 进 度 。
< / p >
) }
{ message . state === "settled" && message . completedReceipt && (
< CompletedActivityReceipt receipt = { message . completedReceipt } / >
) }
< ChatMessageRow
message = { {
. . . displayedMessage ,
activity : displayedMessage.activeActivity
? { phase : activityPhase ( displayedMessage . activeActivity . tool ) , label : displayedMessage.activeActivity.label }
: undefined ,
} }
showActivity = { displayedMessage . state === "thinking" || Boolean ( displayedMessage . activeActivity ) }
/ >
{ ( ! message . failed || Boolean ( displayedMessage . text ) || regenerating ) && (
< ChatMessageRow
message = { {
. . . displayedMessage ,
activity : displayedMessage.activeActivity
? { phase : activityPhase ( displayedMessage . activeActivity . tool ) , label : displayedMessage.activeActivity.label }
: undefined ,
} }
showActivity = { displayedMessage . state === "thinking" || Boolean ( displayedMessage . activeActivity ) }
/ >
) }
{ showActions && ! regenerating && (
< div className = "rectification-message-actions" aria-label = "Agent 回答操作" >
< button
@@ -564,9 +613,9 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
{ candidateResult ? . selectionAllowed && (
< section className = "rectification-candidates" aria-label = "生时校正候选时间" >
< div className = "rectification-candidates-heading" >
< strong > { candidateResult . confirmationAllowed ? "确认校正时间" : "当前可能的出生时间" } < / strong >
< strong > 当 前 可 能 的 出 生 时 间 < / strong >
< span > 这 是 根 据 你 目 前 提 供 的 人 生 事 件 推 算 出 的 几 种 可 能 时 间 。 可 以 先 采 用 一 个 作 为 当 前 排 盘 时 间 , 也 可 以 继 续 补 充 事 件 ; 新 增 证 据 后 , 候 选 和 相 对 支 持 度 会 重 新 计 算 , 准 确 度 还 可 以 继 续 提 高 。 < / span >
< span > 相 对 支 持 度 不 是 统 计 概 率 , 采 用 不 会 覆 盖 原 始 填 报 时 间 。 < / span >
< span > 相 对 支 持 度 不 是 统 计 概 率 ; 采 用 不 等 于 确 认 出 生 时 间 , 也 不 会 覆 盖 原 始 填 报 时 间 。 < / span >
< / div >
< div className = "rectification-candidate-list" >
{ candidateResult . candidates . map ( ( candidate ) = > {
@@ -597,7 +646,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
) }
{ savedTime && (
< p className = "rectification-saved" role = "status" >
{ savedStatus === "confirmed" ? "已确认校正时间" : "校正采用时间 " } : { savedTime } 。 后 续 排 盘 将 使 用 该 时 间 ; 你 仍 可 继 续 补 充 事 件 或 改 选 其 他 候 选 。
{ savedStatus === "confirmed" ? "已确认校正时间" : "已采用校正时间(未确认) " } : { savedTime } 。 后 续 排 盘 将 使 用 该 时 间 ; 你 仍 可 继 续 补 充 事 件 或 改 选 其 他 候 选 。
< / p >
) }
{ error && < p className = "error-message" role = "alert" > { error } < / p > }