feat: integrate pixel teacher presence and resilient classroom preview
This commit is contained in:
1 parent
12d800ec51
commit
7951cca700
60 files changed
+3313
-180
No files matched your search
@@ -1,3 +1,6 @@
|
||||
import { OPEN_WORK_PROMPT } from '../../../shared/coding-work-preview';
|
||||
import { CONTINUE_CODING_PROMPT, type CodingRecoveryAction } from '../../../shared/coding-recovery';
|
||||
import { CodingRecoveryCard } from './CodingRecoveryCard';
|
||||
import {
|
||||
memo,
|
||||
type ReactNode,
|
||||
@@ -488,6 +491,10 @@ const MessageNode = memo(function MessageNode({
|
||||
}).catch(() => undefined);
|
||||
}, [answerText]);
|
||||
|
||||
if (user && (answerText === OPEN_WORK_PROMPT || answerText === CONTINUE_CODING_PROMPT)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<article
|
||||
data-node-id={node.id}
|
||||
@@ -1282,6 +1289,9 @@ const AssistantCommentary = memo(function AssistantCommentary({
|
||||
total + (block.kind === 'image' ? 1 : block.text.length)
|
||||
), 0);
|
||||
|
||||
// The turn-level recovery card carries the next action; an empty error adds no useful detail.
|
||||
if (blocks.length === 0 && node.status === 'error') return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-node-id={node.id}
|
||||
@@ -1305,7 +1315,7 @@ const AssistantCommentary = memo(function AssistantCommentary({
|
||||
preview={contentBlocksProgressPreview(
|
||||
blocks,
|
||||
node.status === 'error'
|
||||
? '本次处理失败。'
|
||||
? '这一步当时没有完成。'
|
||||
: node.status === 'aborted'
|
||||
? '本次处理已中止。'
|
||||
: streaming
|
||||
@@ -1317,7 +1327,6 @@ const AssistantCommentary = memo(function AssistantCommentary({
|
||||
{blocks.map((block) => (
|
||||
<ContentBlock key={block.id} block={block} markdown compactMarkdown />
|
||||
))}
|
||||
{blocks.length === 0 && node.status === 'error' && <p className="text-destructive">本次处理失败。</p>}
|
||||
{blocks.length === 0 && node.status === 'aborted' && <p className="text-muted-foreground">本次处理已中止。</p>}
|
||||
</div>
|
||||
</CompactProcessText>
|
||||
@@ -1439,7 +1448,7 @@ const ProcessGroup = memo(function ProcessGroup({
|
||||
const summary = active
|
||||
? `处理中${duration ? ` ${duration}` : ''}`
|
||||
: failed
|
||||
? `${failureMessage ?? '处理失败'}${duration ? ` · ${duration}` : ''}`
|
||||
? `${failureMessage ?? (latest ? '这一步还没有完成' : '这一步当时没有完成')}${duration ? ` · ${duration}` : ''}`
|
||||
: `已处理${duration ? ` ${duration}` : ''}`;
|
||||
|
||||
return (
|
||||
@@ -1560,19 +1569,29 @@ const TimelineTurnView = memo(function TimelineTurnView({
|
||||
latest,
|
||||
onFork,
|
||||
assistantName,
|
||||
onRecovery,
|
||||
workReady,
|
||||
}: {
|
||||
turn: TimelineTurn;
|
||||
run: ConversationRunState;
|
||||
latest: boolean;
|
||||
onFork?(sourceEntryId: string): void;
|
||||
assistantName?: string;
|
||||
onRecovery?(action: CodingRecoveryAction, work: boolean): Promise<void> | void;
|
||||
workReady?: boolean;
|
||||
}) {
|
||||
const work = turn.user?.blocks.some((block) => block.kind === 'text' && block.text === OPEN_WORK_PROMPT) ?? false;
|
||||
const failed = processGroupIsFailed(turn.process, run, latest, turn.final) && !runIsActive(run);
|
||||
const resolvedWork = work && workReady;
|
||||
const persistedError = [...turn.process].reverse().find((item) => item.kind === 'notice' && item.node.level === 'error');
|
||||
const code = run.error?.code ?? (persistedError?.kind === 'notice' ? persistedError.node.code : undefined);
|
||||
return (
|
||||
<section className="space-y-3.5" data-testid="coding-conversation-turn">
|
||||
{turn.user && <MessageNode node={turn.user} onFork={onFork} />}
|
||||
{turn.process.length > 0 && (
|
||||
{turn.process.length > 0 && !resolvedWork && (
|
||||
<ProcessGroup items={turn.process} run={run} latest={latest} final={turn.final} />
|
||||
)}
|
||||
{latest && failed && !resolvedWork && onRecovery && <CodingRecoveryCard key={`${turn.id}:${code}`} code={code} work={work} onRecovery={onRecovery} />}
|
||||
{turn.final && (
|
||||
<MessageNode
|
||||
node={turn.final}
|
||||
@@ -1587,10 +1606,14 @@ export const CodingConversationTimeline = memo(function CodingConversationTimeli
|
||||
conversationId,
|
||||
onFork,
|
||||
assistantName,
|
||||
onRecovery,
|
||||
workReady,
|
||||
}: {
|
||||
conversationId: string;
|
||||
onFork?(sourceEntryId: string): void;
|
||||
assistantName?: string;
|
||||
onRecovery?(action: CodingRecoveryAction, work: boolean): Promise<void> | void;
|
||||
workReady?: boolean;
|
||||
}) {
|
||||
const selectNodes = useCallback((state: CodingConversationStoreState) => (
|
||||
selectCodingConversationSnapshot(conversationId)(state)?.nodes ?? EMPTY_NODES
|
||||
@@ -1670,6 +1693,8 @@ export const CodingConversationTimeline = memo(function CodingConversationTimeli
|
||||
latest={index === turns.length - 1}
|
||||
onFork={onFork}
|
||||
assistantName={assistantName}
|
||||
onRecovery={onRecovery}
|
||||
workReady={workReady}
|
||||
/>
|
||||
))}
|
||||
{nodes.length === 0 && (
|
||||
|
||||
Reference in new issue
Block a user