feat(ui): add fixed image completion summary and remove upgrade usage table
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-02-21 02:52:35 +08:00
parent 8d4a54de4a
commit 28847b5705
3 changed files with 47 additions and 85 deletions

View File

@ -240,6 +240,7 @@ function formatMessageDateLabel(timestamp: number): string {
} }
const CANVAS_IMAGE_ANALYSIS_TITLE = "图片分析"; const CANVAS_IMAGE_ANALYSIS_TITLE = "图片分析";
const CANVAS_IMAGE_DONE_SUMMARY = "已完成本次生成,如需微调可继续告诉我。";
const CANVAS_AGENT_SECTION_TITLES = new Set([ const CANVAS_AGENT_SECTION_TITLES = new Set([
"需求理解", "需求理解",
CANVAS_IMAGE_ANALYSIS_TITLE, CANVAS_IMAGE_ANALYSIS_TITLE,
@ -5106,30 +5107,37 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
</span> </span>
) : null} ) : null}
{imageUrls.length > 0 && ( {imageUrls.length > 0 && (
<div className={cn("flex flex-wrap gap-2", textWithoutImages ? "mt-3" : "mt-0")}> <div className={cn("space-y-2", textWithoutImages ? "mt-3" : "mt-0")}>
{imageUrls.map((url, idx) => ( <div className="flex flex-wrap gap-2">
<div {imageUrls.map((url, idx) => (
key={idx} <div
className={cn( key={idx}
"relative group overflow-hidden border transition-all", className={cn(
"rounded-2xl border-black/5 dark:border-white/10 bg-white/70 dark:bg-zinc-900/50", "relative group overflow-hidden border transition-all",
"shadow-[0_8px_24px_-16px_rgba(15,23,42,0.45)] hover:shadow-[0_12px_28px_-14px_rgba(15,23,42,0.5)]" "rounded-2xl border-black/5 dark:border-white/10 bg-white/70 dark:bg-zinc-900/50",
)} "shadow-[0_8px_24px_-16px_rgba(15,23,42,0.45)] hover:shadow-[0_12px_28px_-14px_rgba(15,23,42,0.5)]"
> )}
<img >
src={url} <img
alt="" src={url}
className="block max-w-full h-auto cursor-zoom-in transition-transform duration-300 group-hover:scale-[1.02]" alt=""
style={{ maxHeight: "260px" }} className="block max-w-full h-auto cursor-zoom-in transition-transform duration-300 group-hover:scale-[1.02]"
onClick={() => setFullscreenPreview({ url })} style={{ maxHeight: "260px" }}
onDoubleClick={(e) => { onClick={() => setFullscreenPreview({ url })}
e.preventDefault(); onDoubleClick={(e) => {
e.stopPropagation(); e.preventDefault();
setFullscreenPreview({ url }); e.stopPropagation();
}} setFullscreenPreview({ url });
/> }}
</div> />
))} </div>
))}
</div>
{!isDetailGenerationMode ? (
<p className="text-[14px] font-medium leading-6 text-[#2f3640]">
{CANVAS_IMAGE_DONE_SUMMARY}
</p>
) : null}
</div> </div>
)} )}
{videoUrls.length > 0 && ( {videoUrls.length > 0 && (

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import { Fragment, useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { signIn, signOut } from "next-auth/react"; import { signIn, signOut } from "next-auth/react";
import useSWR from "swr"; import useSWR from "swr";
@ -455,17 +455,6 @@ export function LovartHome({
const membershipPlans = membershipConfig?.plans || []; const membershipPlans = membershipConfig?.plans || [];
const currentPlan = membershipData?.currentPlan; const currentPlan = membershipData?.currentPlan;
const groupedModelRows = useMemo(() => {
const rows = membershipConfig?.modelRows || [];
return rows.reduce<Record<string, typeof rows>>((acc, row) => {
if (!acc[row.category]) {
acc[row.category] = [];
}
acc[row.category].push(row);
return acc;
}, {});
}, [membershipConfig?.modelRows]);
const imageModels = useMemo( const imageModels = useMemo(
() => (modelsData?.models || []).filter((model) => model.supportsImageGen), () => (modelsData?.models || []).filter((model) => model.supportsImageGen),
[modelsData?.models] [modelsData?.models]
@ -2422,53 +2411,6 @@ export function LovartHome({
</div> </div>
</section> </section>
<section className="mt-7">
<h4 className="mb-2 text-center text-2xl font-semibold text-black">
模型积分消耗 & 每月生成数量
</h4>
<p className="mb-3 text-center text-[11px] text-black/45">
以下表格基于系统设置中的会员配置,可直接在后台调整。
</p>
<div className="overflow-x-auto rounded-xl border border-black/10 bg-white">
<table className="w-full min-w-[820px] text-xs">
<thead>
<tr className="bg-black/[0.03]">
<th className="px-2.5 py-2.5 text-left font-semibold text-black/75">模型</th>
{membershipPlans.map((plan) => (
<th key={plan.id} className="px-2.5 py-2.5 text-left font-semibold text-black/75">
{plan.name}
</th>
))}
</tr>
</thead>
<tbody>
{Object.entries(groupedModelRows).map(([category, rows]) => (
<Fragment key={category}>
<tr className="bg-black/[0.045]">
<td
colSpan={membershipPlans.length + 1}
className="px-2.5 py-2 text-[11px] font-semibold text-black/60"
>
{category}
</td>
</tr>
{rows.map((row) => (
<tr key={`${category}-${row.model}`} className="border-t border-black/[0.06]">
<td className="px-2.5 py-2 text-black/70">{row.model}</td>
{membershipPlans.map((plan) => (
<td key={`${row.model}-${plan.id}`} className="px-2.5 py-2 text-black/60">
{row.values[plan.id] ?? "-"}
{row.values[plan.id] && row.values[plan.id] !== "-" ? row.unit : ""}
</td>
))}
</tr>
))}
</Fragment>
))}
</tbody>
</table>
</div>
</section>
</div> </div>
</div> </div>
</DialogContent> </DialogContent>

View File

@ -5,6 +5,7 @@ import { memo, useState } from "react";
import type { Vote } from "@/lib/db/schema"; import type { Vote } from "@/lib/db/schema";
import type { ChatMessage } from "@/lib/types"; import type { ChatMessage } from "@/lib/types";
import { cn, sanitizeText } from "@/lib/utils"; import { cn, sanitizeText } from "@/lib/utils";
import { extractImageUrlsFromText } from "@/lib/image-url";
import { useDataStream } from "./data-stream-provider"; import { useDataStream } from "./data-stream-provider";
import { DocumentToolResult } from "./document"; import { DocumentToolResult } from "./document";
import { DocumentPreview } from "./document-preview"; import { DocumentPreview } from "./document-preview";
@ -172,6 +173,17 @@ function filterAgentRoutingNoise(text: string): string {
return filtered; return filtered;
} }
const IMAGE_DONE_SUMMARY = "已完成本次生成,如需微调可继续告诉我。";
function appendImageDoneSummary(text: string): string {
if (!text) return text;
const normalized = String(text || "").trim();
if (!normalized) return normalized;
if (extractImageUrlsFromText(normalized).length === 0) return normalized;
if (normalized.includes(IMAGE_DONE_SUMMARY)) return normalized;
return `${normalized}\n\n${IMAGE_DONE_SUMMARY}`;
}
const PurePreviewMessage = ({ const PurePreviewMessage = ({
chatId, chatId,
message, message,
@ -315,7 +327,7 @@ const PurePreviewMessage = ({
// 应用视频内容过滤 // 应用视频内容过滤
const filteredText = const filteredText =
message.role === "assistant" message.role === "assistant"
? filterAgentRoutingNoise(filterVideoContent(part.text)) ? appendImageDoneSummary(filterAgentRoutingNoise(filterVideoContent(part.text)))
: part.text; : part.text;
const textParts = parseAnnotations(filteredText); const textParts = parseAnnotations(filteredText);
@ -567,7 +579,7 @@ export const ThinkingMessage = () => {
<div className="flex w-full flex-col gap-2"> <div className="flex w-full flex-col gap-2">
{/* 简洁明了的文本 + 动态点 */} {/* 简洁明了的文本 + 动态点 */}
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="text-base font-medium text-foreground">AI 正在思考中</span> <span className="text-base font-medium text-foreground">AI 正在分析中</span>
<span className="flex gap-1"> <span className="flex gap-1">
<span className="h-2 w-2 rounded-full bg-foreground animate-bounce" style={{ animationDelay: '0ms' }} /> <span className="h-2 w-2 rounded-full bg-foreground animate-bounce" style={{ animationDelay: '0ms' }} />
<span className="h-2 w-2 rounded-full bg-foreground animate-bounce" style={{ animationDelay: '150ms' }} /> <span className="h-2 w-2 rounded-full bg-foreground animate-bounce" style={{ animationDelay: '150ms' }} />