接入项目聊天 Markdown 渲染

统一三个项目聊天视图的 assistant 消息展示

让 Direct Codex 流式回复复用 Markdown 组件

移除纯聊天视图的无条件滚动到底部行为
This commit is contained in:
2026-09-04 11:50:05 +08:00
parent 141eff203d
commit dcdd13bc1c
3 changed files with 68 additions and 33 deletions
@@ -16,6 +16,7 @@ import type {
PlanGddDecisionAction,
PlanGddStateViewV1,
} from '../../app/types';
import { ChatMarkdownMessage } from '../../components/ChatMarkdownMessage';
import {
projectProfessionalAgentLabel,
projectRuntimeVisibleError,
@@ -142,15 +143,22 @@ export function ProjectSupervisorView({
</button>
) : null}
{visibleMessages.map((message, index) => (
<p
<div
key={message.messageId ?? `${message.role}-${index}`}
className={`message message--${message.role}`}
>
{projectSupervisorVisibleConversationText(
message.text,
message.role,
)}
</p>
<ChatMarkdownMessage
role={message.role}
text={
message.role === 'assistant'
? projectSupervisorVisibleConversationText(
message.text,
message.role,
)
: message.text
}
/>
</div>
))}
{directCodex &&
(runtimePanelProps.controlBusy || Boolean(transientReply)) ? (
@@ -167,18 +175,26 @@ export function ProjectSupervisorView({
<strong>{directActivity || '陶泥儿正在处理'}</strong>
</header>
{transientReply ? (
<p aria-label="陶泥儿实时回复">{transientReply}</p>
<ChatMarkdownMessage
role="assistant"
text={transientReply}
streaming
/>
) : null}
</section>
) : transientReply ? (
<p
<div
className="message message--assistant"
aria-label="项目总控 Agent 实时回复"
aria-live="polite"
data-runtime-owned="true"
>
{transientReply}
</p>
<ChatMarkdownMessage
role="assistant"
text={transientReply}
streaming
/>
</div>
) : null}
</div>
{directCodex ? null : isPlanningLaneRuntime(
@@ -26,6 +26,7 @@ import type {
PendingCommand,
PendingUiConfirmation,
} from '../../app/types';
import { ChatMarkdownMessage } from '../../components/ChatMarkdownMessage';
import type { ProjectAgentResultSummary } from '../../view/project-development';
import {
formatAgentRecentRuntimeTask,
@@ -804,12 +805,19 @@ export function ProjectWorkspaceChatPane({
) : null}
{visibleMessages.map((message, index) => (
<Fragment key={index}>
<p className={`message message--${message.role}`}>
{projectSupervisorVisibleConversationText(
message.text,
message.role,
)}
</p>
<div className={`message message--${message.role}`}>
<ChatMarkdownMessage
role={message.role}
text={
message.role === 'assistant'
? projectSupervisorVisibleConversationText(
message.text,
message.role,
)
: message.text
}
/>
</div>
{message.draftCommand ? (
<button
type="button"
@@ -822,14 +830,18 @@ export function ProjectWorkspaceChatPane({
</Fragment>
))}
{projectSupervisorTransientReply ? (
<p
<div
className="message message--assistant"
aria-label="项目总控 Agent 实时回复"
aria-live="polite"
data-runtime-owned="true"
>
{projectSupervisorTransientReply}
</p>
<ChatMarkdownMessage
role="assistant"
text={projectSupervisorTransientReply}
streaming
/>
</div>
) : null}
</div>
<ProjectSupervisorRuntimePanel
@@ -7,7 +7,6 @@ import type {
SetStateAction,
UIEventHandler,
} from 'react';
import { useLayoutEffect } from 'react';
import type {
AgentRuntimeState,
@@ -15,6 +14,7 @@ import type {
PendingCommand,
PendingUiConfirmation,
} from '../../app/types';
import { ChatMarkdownMessage } from '../../components/ChatMarkdownMessage';
import {
isAgentRuntimeTerminalState,
projectRuntimeVisibleError,
@@ -107,10 +107,6 @@ export function SupervisorChatOnlyView({
: runtime
? projectSupervisorChatRuntimeStatus(runtime)
: projectWorkspaceStatusForDisplay(workspaceStatus);
useLayoutEffect(() => {
if (running && messagesRef.current)
messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
}, [messagesRef, running, transientReply, runtime?.updatedAt]);
const conversationLabel = directCodex
? '陶泥儿项目对话'
: '项目总控 Agent 纯聊天';
@@ -153,18 +149,25 @@ export function SupervisorChatOnlyView({
>{`显示更早 · 还有 ${hiddenConversationCount} 条对话`}</button>
) : null}
{visibleMessages.map((message, index) => (
<p
<div
key={message.messageId ?? `${message.role}-${index}`}
className={`message message--${message.role}`}
>
{projectSupervisorVisibleConversationText(
message.text,
message.role,
)}
</p>
<ChatMarkdownMessage
role={message.role}
text={
message.role === 'assistant'
? projectSupervisorVisibleConversationText(
message.text,
message.role,
)
: message.text
}
/>
</div>
))}
{transientReply ? (
<p
<div
className="message message--assistant supervisor-chat-only-stream"
aria-label={
directCodex ? '陶泥儿实时回复' : '项目总控 Agent 实时回复'
@@ -172,8 +175,12 @@ export function SupervisorChatOnlyView({
aria-live="polite"
data-runtime-owned="true"
>
{transientReply}
</p>
<ChatMarkdownMessage
role="assistant"
text={transientReply}
streaming
/>
</div>
) : null}
{running && !transientReply ? (
<div