fix: 对话区域显示问题修复

This commit is contained in:
ZyGuo committed 2026-06-02 11:26:28 +08:00
1 parent 2e6eff43ad
commit 49cfc687f9
4 files changed
+126 -82

No files matched your search

@@ -1,13 +1,13 @@
import type { Message } from "@langchain/langgraph-sdk";
import { ClockIcon, FileIcon, Loader2Icon } from "lucide-react";
import {
memo,
useCallback,
useContext,
useMemo,
type AnchorHTMLAttributes,
type ImgHTMLAttributes,
} from "react";
import {
memo,
useCallback,
useContext,
useMemo,
type AnchorHTMLAttributes,
type ImgHTMLAttributes,
} from "react";
import rehypeKatex from "rehype-katex";
import { Loader } from "@/components/ai-elements/loader";
@@ -116,19 +116,21 @@ function useArtifactResolver(threadId: string): (path: string) => string {
);
}
export function MessageListItem({
className,
message,
isLoading,
threadId,
tokenUsageEnabled = false,
export function MessageListItem({
className,
fitRowToContent = false,
message,
isLoading,
threadId,
tokenUsageEnabled = false,
displaySources = [],
extraRehypePlugins,
}: {
className?: string;
message: Message;
isLoading?: boolean;
threadId: string;
}: {
className?: string;
fitRowToContent?: boolean;
message: Message;
isLoading?: boolean;
threadId: string;
tokenUsageEnabled?: boolean;
displaySources?: RuntimeDisplaySource[];
extraRehypePlugins?: ReturnType<typeof useRehypeSplitWordsIntoSpans>;
@@ -138,11 +140,16 @@ export function MessageListItem({
extractContentFromMessage(message) ??
extractReasoningContentFromMessage(message) ??
"";
return (
<AIElementMessage
className={cn("group/conversation-message relative w-full", className)}
from={isHuman ? "user" : "assistant"}
>
return (
<AIElementMessage
className={cn(
"group/conversation-message relative",
fitRowToContent ? "max-w-full w-fit" : "w-full",
isHuman && fitRowToContent && "ml-auto",
className,
)}
from={isHuman ? "user" : "assistant"}
>
<MessageContent
className={isHuman ? "w-fit" : "w-full"}
message={message}
@@ -343,20 +350,20 @@ function MessageContent_({
</AIElementMessageResponse>
) : null;
return (
<div className={cn("ml-auto flex flex-col gap-2", className)}>
<div className={cn("ml-auto flex max-w-full min-w-0 flex-col items-end gap-2", className)}>
{filesList}
{parsedHumanContent.pageContext ? (
<EmbeddedPageContextCard pageContext={parsedHumanContent.pageContext} />
) : null}
{messageResponse && (
<AIElementMessageContent className="w-fit">
<AIElementMessageContent className="min-w-0 max-w-full w-fit">
{messageResponse}
</AIElementMessageContent>
)}
<MessageTimeStamp time={messageTime} align="right" />
</div>
);
}
{parsedHumanContent.pageContext ? (
<EmbeddedPageContextCard pageContext={parsedHumanContent.pageContext} />
) : null}
<MessageTimeStamp time={messageTime} align="right" />
</div>
);
}
return (
<AIElementMessageContent className={className}>
@@ -388,33 +395,22 @@ function EmbeddedPageContextCard({
}: {
pageContext: EmbeddedPageContext;
}) {
const previewText = [pageContext.title, pageContext.url, pageContext.content]
.filter(Boolean)
.join(" | ");
return (
<div className="bg-muted/70 ml-auto flex max-w-sm flex-col gap-2 rounded-2xl border px-3 py-2 text-sm shadow-sm">
{pageContext.title ? (
<div className="truncate font-medium" title={pageContext.title}>
{pageContext.title}
</div>
) : null}
{pageContext.url ? (
<a
className="text-muted-foreground block truncate text-xs underline underline-offset-2"
href={pageContext.url}
target="_blank"
rel="noopener noreferrer"
title={pageContext.url}
>
{pageContext.url}
</a>
) : null}
{pageContext.content ? (
<div className="text-muted-foreground truncate text-xs" title={pageContext.content}>
{pageContext.content}
</div>
) : null}
<div className="bg-muted/70 ml-auto flex w-fit min-w-0 max-w-[min(24rem,calc(100vw-2rem),100%)] flex-col gap-2 rounded-2xl border px-3 py-2 text-sm shadow-sm">
<div
className="text-muted-foreground line-clamp-2 min-w-0 [overflow-wrap:anywhere] break-words text-xs"
title={previewText}
>
{previewText}
</div>
</div>
);
}
/**
* Format a wall-clock message time as ``YYYY-MM-DD HH:mm`` for display, plus a
* full localized string for the hover title. Returns ``null`` when no/invalid
@@ -506,7 +502,7 @@ function isImageFile(filename: string): boolean {
* Format bytes to human-readable size string
*/
function formatBytes(bytes: number): string {
if (bytes === 0) return "—";
if (bytes === 0) return "0 B";
const kb = bytes / 1024;
if (kb < 1024) return `${kb.toFixed(1)} KB`;
return `${(kb / 1024).toFixed(1)} MB`;
@@ -795,3 +791,4 @@ function groupDisplaySources(sources: RuntimeDisplaySource[]) {
function normalizeName(name: string) {
return name.toLowerCase().replace(/[^a-z0-9]+/g, "");
}
@@ -50,22 +50,26 @@ import { SubtaskCard } from "./subtask-card";
export const MESSAGE_LIST_DEFAULT_PADDING_BOTTOM = 160;
export const MESSAGE_LIST_FOLLOWUPS_EXTRA_PADDING_BOTTOM = 80;
export function MessageList({
className,
threadId,
thread,
paddingBottom = MESSAGE_LIST_DEFAULT_PADDING_BOTTOM,
tokenUsageEnabled = false,
export function MessageList({
className,
contentClassName,
fitMessageRowsToContent = false,
threadId,
thread,
paddingBottom = MESSAGE_LIST_DEFAULT_PADDING_BOTTOM,
tokenUsageEnabled = false,
extraRehypePlugins,
onClarificationSubmit,
onSetupAgentApprove,
onSetupAgentReject,
}: {
className?: string;
threadId: string;
thread: BaseStream<AgentThreadState>;
paddingBottom?: number;
tokenUsageEnabled?: boolean;
}: {
className?: string;
contentClassName?: string;
fitMessageRowsToContent?: boolean;
threadId: string;
thread: BaseStream<AgentThreadState>;
paddingBottom?: number;
tokenUsageEnabled?: boolean;
extraRehypePlugins?: ReturnType<typeof useRehypeSplitWordsIntoSpans>;
onClarificationSubmit?: (answer: string) => void | Promise<void>;
onSetupAgentApprove?: (payload: SetupAgentApprovalPayload) => void | Promise<void>;
@@ -213,12 +217,17 @@ export function MessageList({
if (thread.isThreadLoading && messages.length === 0) {
return <MessageListSkeleton />;
}
return (
<Conversation
className={cn("flex size-full flex-col justify-center", className)}
>
<ConversationContent className="mx-auto w-full max-w-(--container-width-lg) gap-8 pt-12">
<MessageMetricsProvider messages={messages} isLoading={thread.isLoading}>
return (
<Conversation
className={cn("flex size-full flex-col justify-center", className)}
>
<ConversationContent
className={cn(
"mx-auto w-full max-w-(--container-width-lg) gap-8 pt-12",
contentClassName,
)}
>
<MessageMetricsProvider messages={messages} isLoading={thread.isLoading}>
{groupMessages(messages, (group) => {
if (group.type === "human" || group.type === "assistant") {
return group.messages.map((msg) => {
@@ -233,11 +242,12 @@ export function MessageList({
})
: [];
return (
<MessageListItem
key={`${group.id}/${msg.id}`}
message={msg}
isLoading={thread.isLoading}
threadId={threadId}
<MessageListItem
key={`${group.id}/${msg.id}`}
fitRowToContent={fitMessageRowsToContent}
message={msg}
isLoading={thread.isLoading}
threadId={threadId}
tokenUsageEnabled={tokenUsageEnabled}
displaySources={displaySources}
extraRehypePlugins={extraRehypePlugins}
+3 -2
View File
@@ -313,10 +313,11 @@ export default function IframeChatPage() {
</div>
</header>
<main className="flex min-h-0 max-w-full grow flex-col overflow-hidden">
<div className="flex size-full justify-center">
<div className="flex size-full min-w-0">
{!isLandingState && (
<MessageList
className={cn("size-full", "pt-10")}
contentClassName="mx-0 max-w-full px-3 sm:px-4"
threadId={threadId}
thread={thread}
paddingBottom={messageListPaddingBottom}
@@ -332,7 +333,7 @@ export default function IframeChatPage() {
isLandingState && "-translate-y-[calc(50vh-96px)]",
isLandingState
? "max-w-(--container-width-md)"
: "max-w-(--container-width-lg)",
: "max-w-full",
)}
>
<div className="absolute -top-4 right-0 left-0 z-0">
@@ -43,6 +43,14 @@ import RoundtablePlanningPage from '@/roundtable-planning/pages/RoundtablePlanni
import '@/roundtable-planning/styles/roundtable-planning.css';
import { cn } from '@/lib/utils';
function isInIframe(): boolean {
try {
return window.self !== window.top;
} catch {
return true;
}
}
export function WorkspaceLayout({
children,
pageSidebarCollapsed,
@@ -116,6 +124,30 @@ export function ChatRuntime({ children }: { children: ReactNode }) {
);
}
function NewChatEntryRoute({
layoutProps,
}: {
layoutProps: {
pageSidebarCollapsed: boolean;
pageSidebarHidden: boolean;
headerHidden: boolean;
compactWorkspaceSidebar: boolean;
pageSidebarDark: boolean;
};
}) {
if (isInIframe()) {
return <Navigate to="/page/workspace/chats_iframe/new" replace />;
}
return (
<WorkspaceLayout {...layoutProps}>
<ChatRuntime>
<ChatPage />
</ChatRuntime>
</WorkspaceLayout>
);
}
export default function WorkspaceRoutes({
pageSidebarCollapsed = false,
pageSidebarHidden = false,
@@ -149,6 +181,10 @@ export default function WorkspaceRoutes({
</WorkspaceLayout>
}
/>
<Route
path="chats/new"
element={<NewChatEntryRoute layoutProps={layoutProps} />}
/>
<Route
path="chats/:thread_id"
element={