fix: 对话区域显示问题修复
This commit is contained in:
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}
|
||||
|
||||
@@ -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={
|
||||
|
||||
Reference in new issue
Block a user