feat: improve image analysis collapse and keep model badge visible
This commit is contained in:
parent
9a1cbc4d15
commit
7cc489a510
@ -8,6 +8,11 @@ import useSWR, { useSWRConfig } from "swr";
|
||||
import { Send, Loader2, ImagePlus, Paperclip, X, Plus, History, LayoutGrid, Maximize2, RefreshCw, Globe, Box, ArrowUp, Video, Sparkles, Check, ChevronDown, ChevronUp, AlertCircle, CheckCircle2, Brain, ThumbsUp, ThumbsDown } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@ -232,6 +237,24 @@ function normalizeCanvasSectionTitle(line: string): string {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function shouldSplitCanvasAnalysisAfterLine(line: string): boolean {
|
||||
const normalized = String(line || "")
|
||||
.replace(/^[-*]\s*/, "")
|
||||
.trim();
|
||||
if (!normalized) return false;
|
||||
|
||||
if (
|
||||
/^(现在我将|接下来|下一步|我将|现在开始|下面我|随后|Then\b|Next\b|Now\b)/i.test(
|
||||
normalized
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 模型徽标行(例如:◈ Nano Banana Pro)
|
||||
return /^[◈◇◆🔹⬡✦•·]\s*[A-Za-z][A-Za-z0-9 .:+\-_/()]{2,}\s*$/i.test(normalized);
|
||||
}
|
||||
|
||||
function extractCanvasImageAnalysisSection(content: string): CanvasAnalysisSection | null {
|
||||
const lines = String(content || "").split(/\r?\n/);
|
||||
let startIndex = -1;
|
||||
@ -260,6 +283,10 @@ function extractCanvasImageAnalysisSection(content: string): CanvasAnalysisSecti
|
||||
|
||||
let endIndex = lines.length;
|
||||
for (let i = startIndex + 1; i < lines.length; i += 1) {
|
||||
if (shouldSplitCanvasAnalysisAfterLine(lines[i])) {
|
||||
endIndex = i;
|
||||
break;
|
||||
}
|
||||
const title = normalizeCanvasSectionTitle(lines[i]);
|
||||
if (!title || title.startsWith(CANVAS_IMAGE_ANALYSIS_TITLE)) continue;
|
||||
if (CANVAS_AGENT_SECTION_TITLES.has(title)) {
|
||||
@ -954,6 +981,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
||||
const [lastImageModelId, setLastImageModelId] = useState<string | null>(null);
|
||||
const [lastVideoModelId, setLastVideoModelId] = useState<string | null>(null);
|
||||
const [deepThinkingEnabled, setDeepThinkingEnabled] = useState(false);
|
||||
const [analysisCollapseState, setAnalysisCollapseState] = useState<Record<string, boolean>>({});
|
||||
const [insufficientPointsState, setInsufficientPointsState] = useState<{
|
||||
balance: number;
|
||||
expectedCost: number;
|
||||
@ -4959,6 +4987,8 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
||||
const textWithoutImages = stripMediaMarkup(rawText);
|
||||
const analysisSection = extractCanvasImageAnalysisSection(textWithoutImages);
|
||||
const plainText = normalizeCanvasAgentText(textWithoutImages);
|
||||
const analysisToggleKey = `${message.id}-${i}-analysis`;
|
||||
const analysisOpen = analysisCollapseState[analysisToggleKey] ?? true;
|
||||
const renderPlainText = (text: string, key: string) => (
|
||||
<div
|
||||
key={key}
|
||||
@ -4975,8 +5005,17 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
||||
{analysisSection.before
|
||||
? renderPlainText(analysisSection.before, `before-${i}`)
|
||||
: null}
|
||||
<div className="my-2 rounded-[20px] bg-[#f4f5f7] px-5 py-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<Collapsible
|
||||
className="my-2 rounded-[20px] bg-[#f4f5f7] px-5 py-4"
|
||||
open={analysisOpen}
|
||||
onOpenChange={(open) =>
|
||||
setAnalysisCollapseState((prev) => ({
|
||||
...prev,
|
||||
[analysisToggleKey]: open,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<CollapsibleTrigger className="flex w-full items-center justify-between gap-3 text-left">
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<span className="inline-flex h-6 min-w-6 items-center justify-center rounded-full bg-white text-[11px] font-semibold text-[#98a2b3] shadow-[inset_0_0_0_1px_rgba(148,163,184,0.45)]">
|
||||
Q.
|
||||
@ -4985,12 +5024,23 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
||||
图片分析
|
||||
</span>
|
||||
</span>
|
||||
<ChevronUp className="h-4 w-4 text-[#9aa1ad]" />
|
||||
</div>
|
||||
<div className="pt-3 text-[15px] font-medium leading-7 text-[#2f3640]">
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
"h-4 w-4 text-[#9aa1ad] transition-transform duration-200",
|
||||
analysisOpen ? "rotate-180" : "rotate-0"
|
||||
)}
|
||||
/>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent
|
||||
className={cn(
|
||||
"pt-3 text-[15px] font-medium leading-7 text-[#2f3640]",
|
||||
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-1 data-[state=open]:slide-in-from-top-1",
|
||||
"outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in"
|
||||
)}
|
||||
>
|
||||
{analysisSection.analysis}
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
{analysisSection.after
|
||||
? renderPlainText(analysisSection.after, `after-${i}`)
|
||||
: null}
|
||||
|
||||
@ -137,29 +137,36 @@ function filterVideoContent(text: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤 Agent 里“模型路由说明”这类冗余展示,避免在对话里暴露模型名称行
|
||||
* 规范化 Agent 路由文本:
|
||||
* - 保留“模型名”展示
|
||||
* - 去掉原始路由日志行(Routed to / Using model)
|
||||
*/
|
||||
function filterAgentRoutingNoise(text: string): string {
|
||||
if (!text) return text;
|
||||
|
||||
let filtered = text;
|
||||
let routedModelName = "";
|
||||
|
||||
// 1) 去掉“现在我将使用 XXX ...”这类过渡句
|
||||
filtered = filtered.replace(/^现在我将使用.+$/gim, "");
|
||||
|
||||
// 2) 去掉独立的模型名称行(常见为带图标的单行)
|
||||
// 提取并移除“路由日志行”,稍后转成用户可见的模型名行
|
||||
filtered = filtered.replace(
|
||||
/^\s*[◈◇◆🔹⬡✦•·]\s*[A-Za-z][A-Za-z0-9 .:+\-_/()]{2,}\s*$/gm,
|
||||
"",
|
||||
/^\s*(?:Routed to|Using model)\s*[::]\s*([^\n(]+).*$/gim,
|
||||
(_match, modelName: string) => {
|
||||
if (!routedModelName) {
|
||||
routedModelName = String(modelName || "").trim();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
);
|
||||
|
||||
// 3) 去掉纯模型路由标识行(不带图标)
|
||||
filtered = filtered.replace(
|
||||
/^\s*(?:Routed to|Using model|模型|Model)\s*[::].*$/gim,
|
||||
"",
|
||||
// 如果文本里没有现成的模型展示行,则补一行友好的模型名
|
||||
const hasModelBadgeLine = /^\s*[◈◇◆🔹⬡✦•·]\s*[A-Za-z][A-Za-z0-9 .:+\-_/()]{2,}\s*$/im.test(
|
||||
filtered
|
||||
);
|
||||
if (routedModelName && !hasModelBadgeLine) {
|
||||
filtered = `◈ ${routedModelName}\n\n${filtered}`;
|
||||
}
|
||||
|
||||
// 4) 清理多余空行
|
||||
// 清理多余空行
|
||||
filtered = filtered.replace(/\n{3,}/g, "\n\n").trim();
|
||||
|
||||
return filtered;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user