feat(agent): render local interactive answers and fold completed process
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
import type { AnswerUiMapTarget } from "../answer-ui/answer-ui-view";
|
||||
import { AnswerPartsView } from "../answer-ui/answer-parts-view";
|
||||
import { getAnswerPresentation } from "../answer-ui/answer-presentation";
|
||||
import { Disclosure } from "@/shared/ui/disclosure";
|
||||
import {
|
||||
Activity,
|
||||
CheckCircle2,
|
||||
@@ -68,9 +72,7 @@ import {
|
||||
} from "./agent-motion";
|
||||
import { AgentHistoryPanel } from "./agent-history-panel";
|
||||
import { AgentMessageDetails, AgentMessageProgress } from "./agent-message-details";
|
||||
import { AgentOperationalBrief } from "./agent-operational-brief";
|
||||
import { AgentUiEnvelopeRenderer } from "./agent-ui-envelope-renderer";
|
||||
import { StreamingTokenResponse } from "./streaming-token-response";
|
||||
import { useAgentSpeechRecognition, useAgentSpeechSynthesis } from "../hooks/use-agent-voice";
|
||||
import {
|
||||
findSpeechSelectionStartOffset,
|
||||
@@ -130,6 +132,7 @@ type AgentCommandPanelProps = {
|
||||
onSelectModel?: (model: string) => void;
|
||||
onApprovalModeChange?: (mode: AgentApprovalMode) => void;
|
||||
onSubmitPrompt: (prompt: string) => Promise<void> | void;
|
||||
onLocateAnswerFeatures?: (target: AnswerUiMapTarget) => Promise<unknown> | void;
|
||||
onStopPrompt?: () => void;
|
||||
onReplyPermission?: (
|
||||
permission: AgentPermissionRequest,
|
||||
@@ -164,7 +167,6 @@ export function AgentCommandPanel({
|
||||
sessionHistoryLoading = false,
|
||||
activeSessionId,
|
||||
qaOnly = false,
|
||||
statusLabel = "Agent 后端连接中",
|
||||
connectionStatus = "connecting",
|
||||
streaming = false,
|
||||
messages = [],
|
||||
@@ -185,6 +187,7 @@ export function AgentCommandPanel({
|
||||
onSelectModel,
|
||||
onApprovalModeChange,
|
||||
onSubmitPrompt,
|
||||
onLocateAnswerFeatures,
|
||||
onStopPrompt,
|
||||
onReplyPermission,
|
||||
onReplyQuestion,
|
||||
@@ -327,15 +330,6 @@ export function AgentCommandPanel({
|
||||
onDeleteSession={onDeleteHistorySession}
|
||||
/>
|
||||
);
|
||||
const operationalBrief = (
|
||||
<AgentOperationalBrief
|
||||
messages={messages}
|
||||
statusLabel={statusLabel}
|
||||
streaming={streaming}
|
||||
onSubmitCommand={submitPrompt}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<MotionConfig reducedMotion="user">
|
||||
<aside
|
||||
@@ -468,45 +462,9 @@ export function AgentCommandPanel({
|
||||
</header>
|
||||
</div>
|
||||
|
||||
{floatingPresentation ? (
|
||||
<AnimatePresence initial={false}>
|
||||
{hasConversation && !historyOpen ? (
|
||||
<motion.div
|
||||
key="operational-brief"
|
||||
className="agent-panel-operational-float absolute z-20 overflow-hidden rounded-2xl"
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
exit="exit"
|
||||
variants={agentPanelSectionVariants}
|
||||
>
|
||||
{operationalBrief}
|
||||
</motion.div>
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
) : (
|
||||
<AnimatePresence initial={false}>
|
||||
{hasConversation ? (
|
||||
<motion.div
|
||||
key="operational-brief"
|
||||
aria-hidden={historyOpen}
|
||||
className={cn(
|
||||
"space-y-2 px-3 pb-3",
|
||||
historyOpen && "invisible pointer-events-none"
|
||||
)}
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
exit="exit"
|
||||
variants={agentPanelSectionVariants}
|
||||
style={{ overflow: "hidden" }}
|
||||
>
|
||||
{operationalBrief}
|
||||
</motion.div>
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
)}
|
||||
|
||||
<AgentContent className="flex min-h-0 flex-1 flex-col gap-0 p-0">
|
||||
<AgentContent className="agent-panel-main-content flex min-h-0 flex-1 flex-col gap-0 p-0">
|
||||
<Conversation
|
||||
data-agent-conversation-state={hasConversation ? "active" : "empty"}
|
||||
className={cn(
|
||||
"agent-panel-conversation min-h-0",
|
||||
floatingPresentation && "agent-panel-conversation-canvas"
|
||||
@@ -517,9 +475,6 @@ export function AgentCommandPanel({
|
||||
"gap-4 p-3",
|
||||
messages.length === 0 && "min-h-full",
|
||||
floatingPresentation && "agent-panel-conversation-content",
|
||||
floatingPresentation &&
|
||||
hasConversation &&
|
||||
"agent-panel-conversation-content-has-brief",
|
||||
floatingPresentation &&
|
||||
!hasConversation &&
|
||||
"agent-panel-conversation-content-empty"
|
||||
@@ -544,6 +499,8 @@ export function AgentCommandPanel({
|
||||
onResumeSpeech={resumeSpeech}
|
||||
onStopSpeech={stopSpeech}
|
||||
qaOnly={qaOnly}
|
||||
onLocateAnswerFeatures={onLocateAnswerFeatures}
|
||||
onSubmitAnswerForm={onSubmitPrompt}
|
||||
/>
|
||||
) : (
|
||||
<motion.div
|
||||
@@ -1233,7 +1190,9 @@ function BackendMessageList({
|
||||
onPauseSpeech,
|
||||
onResumeSpeech,
|
||||
onStopSpeech,
|
||||
qaOnly
|
||||
qaOnly,
|
||||
onLocateAnswerFeatures,
|
||||
onSubmitAnswerForm
|
||||
}: {
|
||||
presentation: AgentCommandPanelPresentation;
|
||||
messages: AgentChatMessage[];
|
||||
@@ -1253,6 +1212,8 @@ function BackendMessageList({
|
||||
onResumeSpeech: () => void;
|
||||
onStopSpeech: () => void;
|
||||
qaOnly: boolean;
|
||||
onLocateAnswerFeatures?: (target: AnswerUiMapTarget) => Promise<unknown> | void;
|
||||
onSubmitAnswerForm: (text: string) => Promise<void> | void;
|
||||
}) {
|
||||
return (
|
||||
<motion.div
|
||||
@@ -1261,7 +1222,9 @@ function BackendMessageList({
|
||||
animate="animate"
|
||||
>
|
||||
<AnimatePresence initial={false} mode="popLayout">
|
||||
{messages.map((message, index) => (
|
||||
{messages.map((message, index) => {
|
||||
const answer = getAnswerPresentation(message);
|
||||
return (
|
||||
<motion.div
|
||||
key={message.id}
|
||||
className={cn("w-full", message.role === "user" && "flex justify-end")}
|
||||
@@ -1287,7 +1250,7 @@ function BackendMessageList({
|
||||
依据:{message.taskContext.execution_generation == null ? "历史资料 · 轮次未记录" : `第 ${message.taskContext.execution_generation} 轮`}
|
||||
{message.taskContext.report_revision == null ? " · 尚无报告" : ` · 报告第 ${message.taskContext.report_revision} 版`}
|
||||
</p> : null}
|
||||
{message.role === "assistant" && !qaOnly ? (
|
||||
{message.role === "assistant" && !qaOnly && !answer.final ? (
|
||||
<AgentMessageProgress
|
||||
progress={message.progress}
|
||||
running={streaming && index === messages.length - 1}
|
||||
@@ -1307,12 +1270,14 @@ function BackendMessageList({
|
||||
onResume={onResumeSpeech}
|
||||
onStop={onStopSpeech}
|
||||
>
|
||||
<StreamingTokenResponse
|
||||
className="leading-6"
|
||||
content={message.content}
|
||||
<AnswerPartsView
|
||||
parts={answer.report}
|
||||
messageId={message.id}
|
||||
streamDone={streamRenderState[message.id]?.done ?? !streaming}
|
||||
streaming={streaming && index === messages.length - 1}
|
||||
disabled={streaming}
|
||||
streaming={!answer.final && streaming && index === messages.length - 1}
|
||||
streamDone={answer.final || (streamRenderState[message.id]?.done ?? !streaming)}
|
||||
onLocate={onLocateAnswerFeatures}
|
||||
onSubmit={onSubmitAnswerForm}
|
||||
/>
|
||||
</AgentSpeechMessage>
|
||||
) : (
|
||||
@@ -1321,7 +1286,16 @@ function BackendMessageList({
|
||||
) : streaming && index === messages.length - 1 ? (
|
||||
<span className="text-[var(--ink-muted)]">正在生成...</span>
|
||||
) : null}
|
||||
{message.role === "assistant" && !qaOnly ? (
|
||||
{message.role === "assistant" && answer.final && (answer.process.length > 0 || (!qaOnly && message.progress?.length)) ? (
|
||||
<Disclosure key="completed-process" label="查看处理过程" className="border-t border-[var(--surface-divider)]">
|
||||
<div className="space-y-3 py-2">
|
||||
{!qaOnly ? <AgentMessageProgress progress={message.progress} running={false} /> : null}
|
||||
<AnswerPartsView parts={answer.process} messageId={`${message.id}-process`} disabled streaming={false} streamDone />
|
||||
{!qaOnly ? <AgentMessageDetails message={message} /> : null}
|
||||
</div>
|
||||
</Disclosure>
|
||||
) : null}
|
||||
{message.role === "assistant" && !qaOnly && !answer.final ? (
|
||||
<AgentMessageDetails
|
||||
message={message}
|
||||
onReplyPermission={onReplyPermission}
|
||||
@@ -1333,7 +1307,8 @@ function BackendMessageList({
|
||||
</MessageContent>
|
||||
</Message>
|
||||
</motion.div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user