feat(teacher): stream Yuxi reasoning and answer previews

This commit is contained in:
2026-09-29 18:18:56 +08:00
parent f443f3bc66
commit 8ccc4650a2
12 changed files with 364 additions and 42 deletions

View File

@@ -530,6 +530,10 @@ function ProjectTeacherChatPanel({
: <div className="flex justify-end"><p className="consultation-user max-w-[92%] whitespace-pre-wrap break-words rounded-2xl rounded-br-sm px-4 py-3 text-[13px] leading-7">{request.text}</p></div>}
{request.references.length > 0 && <details className="text-xs text-muted-foreground"><summary>本轮引用 {request.references.length} 项</summary>{request.references.map((ref, index) => <pre className="whitespace-pre-wrap" key={index}>{ref.text}</pre>)}</details>}
<div className="flex items-center gap-2 text-[11px] text-muted-foreground"><TeacherAvatar definition={definition} className="h-6 w-6 rounded-lg" />{definition?.name ?? label}</div>
{request.reasoning && <details open={['running', 'preparing'].includes(request.status)} className="min-w-0 text-xs text-muted-foreground" data-testid="teacher-reasoning">
<summary className="cursor-pointer">思考过程</summary>
<div className="mt-2 whitespace-pre-wrap break-words border-l-2 pl-3 leading-6 [overflow-wrap:anywhere]" aria-label="智能体思考过程">{request.reasoning}</div>
</details>}
{!!request.toolActivity?.length && <details className="text-xs text-muted-foreground">
<summary className="cursor-pointer">使用了 {request.toolActivity.length} 项工具</summary>
<ul className="mt-2 space-y-2" aria-label="智能体工具活动">
@@ -545,10 +549,10 @@ function ProjectTeacherChatPanel({
{request.intent !== 'check-in' && request.text.trim() && <button type="button" disabled={helpUnavailable || !retryScopeMatches(request)} onClick={() => void send({ text: request.text, intent: request.intent }, request)} className="consultation-retry">重新回答</button>}
{!retryScopeMatches(request) && <p className="text-xs text-muted-foreground">回到原项目和操作对话后,可以重新回答。</p>}
</div> : request.intent === 'suggestions' ? <>
{request.response && (request.status === 'completed' || request.presentation === 'reply-v1') && <TeacherReply text={request.response} />}
{['running', 'preparing'].includes(request.status)
? <p role="status" className="flex items-center gap-2 text-[13px] leading-7 text-muted-foreground"><Loader2 className="h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />我看看你最近做到了哪里…</p>
: request.status === 'completed' && <>
<TeacherReply text={request.response} />
<div className="consultation-follow-ups" aria-label="可以和智能体聊的问题">
{request.suggestedQuestions?.map((question, index) => <button key={`${request.id}:${index}`} type="button" disabled={helpUnavailable} onClick={() => void send({ text: question })} className="consultation-follow-up"><span className="min-w-0 flex-1">{question}</span><ChevronRight className="h-4 w-4 shrink-0 opacity-70" aria-hidden="true" /></button>)}
</div>