fix(webui): show quoted context after follow-up send (#5071)

This commit is contained in:
chengyongru
2026-07-24 14:19:33 +08:00
committed by GitHub
parent cad368f585
commit 9957de5226
7 changed files with 182 additions and 8 deletions
+33 -4
View File
@@ -12,6 +12,7 @@ import {
Clock3,
Copy,
ImageIcon,
Quote,
Wrench,
} from "lucide-react";
import { useTranslation } from "react-i18next";
@@ -33,6 +34,7 @@ import { copyTextToClipboard } from "@/lib/clipboard";
import { formatTurnLatency } from "@/lib/format";
import { toMediaAttachment } from "@/lib/media";
import { matchingSlashCommand } from "@/lib/slash-command";
import { parseQuotedUserMessage } from "@/lib/user-message-quote";
import type {
CliAppInfo,
McpPresetInfo,
@@ -158,20 +160,23 @@ export function MessageBubble({
const media = message.media ?? [];
const hasImages = images.length > 0;
const hasMedia = media.length > 0;
const hasText = message.content.trim().length > 0;
const slashCommand = matchingSlashCommand(message.content, slashCommands);
const parsedMessage = parseQuotedUserMessage(message.content);
const userContent = parsedMessage.content;
const hasText = userContent.trim().length > 0;
const quotedContext = parsedMessage.quotedContext;
const slashCommand = matchingSlashCommand(userContent, slashCommands);
const messageText = slashCommand ? (
<>
<SlashCommandText command={slashCommand.command} />
<UserMessageText
text={message.content.slice(slashCommand.command.length)}
text={userContent.slice(slashCommand.command.length)}
cliApps={mentionCliApps}
mcpPresets={mentionMcpPresets}
/>
</>
) : (
<UserMessageText
text={message.content}
text={userContent}
cliApps={mentionCliApps}
mcpPresets={mentionMcpPresets}
/>
@@ -187,6 +192,12 @@ export function MessageBubble({
{!hasImages && hasMedia ? (
<MessageMedia media={media} align="right" />
) : null}
{quotedContext ? (
<UserQuotedContext
text={quotedContext}
label={t("thread.composer.quotedContext")}
/>
) : null}
{hasText ? (
<p
className={cn(
@@ -305,6 +316,24 @@ export function MessageBubble({
);
}
function UserQuotedContext({ text, label }: { text: string; label: string }) {
return (
<blockquote
className={cn(
"ml-auto flex w-fit max-w-full min-w-0 items-start gap-2 rounded-[14px]",
"border border-border/60 bg-muted/35 px-3 py-2 text-left text-muted-foreground",
)}
aria-label={label}
title={text}
>
<Quote className="mt-0.5 h-3.5 w-3.5 shrink-0" aria-hidden />
<p className="min-w-0 line-clamp-3 whitespace-pre-wrap text-[13px]/[1.45] [overflow-wrap:anywhere]">
{text}
</p>
</blockquote>
);
}
function AutomationSourceBadge({ label, triggerLabel }: { label: string; triggerLabel: string }) {
return (
<div
@@ -95,6 +95,7 @@ import {
isSideChannelLifecycle,
slashCommandLifecycle,
} from "@/lib/slash-command";
import { formatQuotedUserMessage } from "@/lib/user-message-quote";
import { cn } from "@/lib/utils";
const VOICE_SHORTCUT_CODE = "KeyD";
@@ -1466,7 +1467,7 @@ export function ThreadComposer({
const queueGuidancePrompt = useCallback(() => {
const text = value.trim();
if (!canQueueGuidance || (!text && readyImages.length === 0)) return;
if (utf8Bytes(text) > maxTextBytes) {
if (utf8Bytes(formatQuotedUserMessage(text, normalizedQuotedContext)) > maxTextBytes) {
setInlineError(textTooLargeMessage());
return;
}
@@ -1608,7 +1609,7 @@ export function ThreadComposer({
if (!canSend) return;
const trimmed = value.trim();
const content = trimmed;
if (utf8Bytes(content) > maxTextBytes) {
if (utf8Bytes(formatQuotedUserMessage(content, normalizedQuotedContext)) > maxTextBytes) {
setInlineError(textTooLargeMessage());
return;
}