feat: 聊天增加回到最新悬浮钮,智能体面板生成即收起
- 复用 ConversationScrollButton 接入 MessageList,滚离底部时显示一键回到最新问答的悬浮按钮;新增 scrollButtonClassName 以适配各页输入框高度 - 智能体详情面板改用 open+selectedArtifact 判定收起,流式写文件开始即收起让位预览,不再等生成结束 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
d82b5ed884
commit
4f0494a97a
4 files changed
+33
-8
No files matched your search
@@ -187,23 +187,30 @@ export function AgentDetailPanel({
|
|||||||
const isSaving = updateAgent.isPending || setFeatured.isPending;
|
const isSaving = updateAgent.isPending || setFeatured.isPending;
|
||||||
|
|
||||||
// 对话生成文件(md/txt 等 artifact)并在右侧展开预览时,自动收起智能体
|
// 对话生成文件(md/txt 等 artifact)并在右侧展开预览时,自动收起智能体
|
||||||
// 信息面板给预览让位;新文件落地时同样收起。编辑中不打断用户,用户随时
|
// 信息面板给预览让位;切换到新文件时同样收起。编辑中不打断用户,用户随时
|
||||||
// 可手动重新展开。
|
// 可手动重新展开。
|
||||||
|
//
|
||||||
|
// 关键:判定用 open + selectedArtifact,而非 artifacts.length。流式写文件时
|
||||||
|
// message-group 一开始就调用 select()+setOpen(true) 打开预览并选中该文件,但
|
||||||
|
// 该文件要等生成结束、artifact 入库后才会进 thread.values.artifacts。若按
|
||||||
|
// 数量判定就只能等生成结束才收起——正是之前「生成完才收起」的 bug 根因。
|
||||||
const artifactsCtx = useArtifactsOptional();
|
const artifactsCtx = useArtifactsOptional();
|
||||||
const artifactCount = artifactsCtx?.artifacts.length ?? 0;
|
const artifactCount = artifactsCtx?.artifacts.length ?? 0;
|
||||||
const artifactPanelVisible = !!artifactsCtx?.open && artifactCount > 0;
|
const selectedArtifact = artifactsCtx?.selectedArtifact ?? null;
|
||||||
|
const artifactPanelVisible =
|
||||||
|
!!artifactsCtx?.open && (!!selectedArtifact || artifactCount > 0);
|
||||||
const prevArtifactPanelVisible = useRef(false);
|
const prevArtifactPanelVisible = useRef(false);
|
||||||
const prevArtifactCount = useRef(0);
|
const prevSelectedArtifact = useRef<string | null>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const opened = artifactPanelVisible && !prevArtifactPanelVisible.current;
|
const opened = artifactPanelVisible && !prevArtifactPanelVisible.current;
|
||||||
const grew =
|
const switched =
|
||||||
artifactPanelVisible && artifactCount > prevArtifactCount.current;
|
artifactPanelVisible && selectedArtifact !== prevSelectedArtifact.current;
|
||||||
prevArtifactPanelVisible.current = artifactPanelVisible;
|
prevArtifactPanelVisible.current = artifactPanelVisible;
|
||||||
prevArtifactCount.current = artifactCount;
|
prevSelectedArtifact.current = selectedArtifact;
|
||||||
if ((opened || grew) && !editing) {
|
if ((opened || switched) && !editing) {
|
||||||
setCollapsed(true);
|
setCollapsed(true);
|
||||||
}
|
}
|
||||||
}, [artifactPanelVisible, artifactCount, editing]);
|
}, [artifactPanelVisible, selectedArtifact, editing]);
|
||||||
|
|
||||||
const displayName = desensitize(agent?.name ?? agentName);
|
const displayName = desensitize(agent?.name ?? agentName);
|
||||||
const questions = useMemo(
|
const questions = useMemo(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|||||||
import {
|
import {
|
||||||
Conversation,
|
Conversation,
|
||||||
ConversationContent,
|
ConversationContent,
|
||||||
|
ConversationScrollButton,
|
||||||
} from "@/components/ai-elements/conversation";
|
} from "@/components/ai-elements/conversation";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
@@ -61,6 +62,7 @@ export function MessageList({
|
|||||||
paddingBottom = MESSAGE_LIST_DEFAULT_PADDING_BOTTOM,
|
paddingBottom = MESSAGE_LIST_DEFAULT_PADDING_BOTTOM,
|
||||||
tokenUsageEnabled = false,
|
tokenUsageEnabled = false,
|
||||||
extraRehypePlugins,
|
extraRehypePlugins,
|
||||||
|
scrollButtonClassName,
|
||||||
onClarificationSubmit,
|
onClarificationSubmit,
|
||||||
onEditResubmit,
|
onEditResubmit,
|
||||||
onSetupAgentApprove,
|
onSetupAgentApprove,
|
||||||
@@ -75,6 +77,8 @@ export function MessageList({
|
|||||||
thread: BaseStream<AgentThreadState>;
|
thread: BaseStream<AgentThreadState>;
|
||||||
paddingBottom?: number;
|
paddingBottom?: number;
|
||||||
tokenUsageEnabled?: boolean;
|
tokenUsageEnabled?: boolean;
|
||||||
|
/** 额外定位「回到最新」悬浮按钮——不同页面输入框高度不同,需上移让位。 */
|
||||||
|
scrollButtonClassName?: string;
|
||||||
extraRehypePlugins?: ReturnType<typeof useRehypeSplitWordsIntoSpans>;
|
extraRehypePlugins?: ReturnType<typeof useRehypeSplitWordsIntoSpans>;
|
||||||
onClarificationSubmit?: (answer: string) => void | Promise<void>;
|
onClarificationSubmit?: (answer: string) => void | Promise<void>;
|
||||||
onEditResubmit?: (text: string) => void;
|
onEditResubmit?: (text: string) => void;
|
||||||
@@ -543,6 +547,16 @@ export function MessageList({
|
|||||||
<div style={{ height: `${paddingBottom}px` }} />
|
<div style={{ height: `${paddingBottom}px` }} />
|
||||||
</MessageMetricsProvider>
|
</MessageMetricsProvider>
|
||||||
</ConversationContent>
|
</ConversationContent>
|
||||||
|
{/* ChatGPT 式「回到最新」悬浮钮:仅在向上滚离底部时出现,点击平滑回到
|
||||||
|
最新问答。默认贴底,调用方可用 scrollButtonClassName 上移避开输入框。 */}
|
||||||
|
<ConversationScrollButton
|
||||||
|
className={cn(
|
||||||
|
"pointer-events-auto z-20 size-9 shadow-md",
|
||||||
|
scrollButtonClassName,
|
||||||
|
)}
|
||||||
|
aria-label="回到最新"
|
||||||
|
title="回到最新"
|
||||||
|
/>
|
||||||
</Conversation>
|
</Conversation>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,6 +285,8 @@ export default function AgentChatPage() {
|
|||||||
thread={thread}
|
thread={thread}
|
||||||
paddingBottom={showFollowups ? 96 : 24}
|
paddingBottom={showFollowups ? 96 : 24}
|
||||||
tokenUsageEnabled={tokenUsageEnabled}
|
tokenUsageEnabled={tokenUsageEnabled}
|
||||||
|
// 输入框在流内(MessageList 下方),按钮贴对话区底部即可。
|
||||||
|
scrollButtonClassName="bottom-2"
|
||||||
onClarificationSubmit={handleClarificationSubmit}
|
onClarificationSubmit={handleClarificationSubmit}
|
||||||
onEditResubmit={(text) => handleSubmit({ text, files: [] })}
|
onEditResubmit={(text) => handleSubmit({ text, files: [] })}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -493,6 +493,8 @@ export default function ChatPage() {
|
|||||||
thread={thread}
|
thread={thread}
|
||||||
paddingBottom={messageListPaddingBottom}
|
paddingBottom={messageListPaddingBottom}
|
||||||
tokenUsageEnabled={tokenUsageEnabled}
|
tokenUsageEnabled={tokenUsageEnabled}
|
||||||
|
// 输入框为底部绝对浮层(约 160px 高),按钮上移到其上方居中。
|
||||||
|
scrollButtonClassName="bottom-40"
|
||||||
onClarificationSubmit={handleClarificationSubmit}
|
onClarificationSubmit={handleClarificationSubmit}
|
||||||
onEditResubmit={(text) =>
|
onEditResubmit={(text) =>
|
||||||
handleSubmit({ text, files: [] })
|
handleSubmit({ text, files: [] })
|
||||||
|
|||||||
Reference in new issue
Block a user