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_DONE_SUMMARY = "已完成本次生成,如需微调可继续告诉我。";
const CANVAS_AGENT_SECTION_TITLES = new Set([
"需求理解",
CANVAS_IMAGE_ANALYSIS_TITLE,
@ -5106,30 +5107,37 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
</span>
) : null}
{imageUrls.length > 0 && (
<div className={cn("flex flex-wrap gap-2", textWithoutImages ? "mt-3" : "mt-0")}>
{imageUrls.map((url, idx) => (
<div
key={idx}
className={cn(
"relative group overflow-hidden border transition-all",
"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}
alt=""
className="block max-w-full h-auto cursor-zoom-in transition-transform duration-300 group-hover:scale-[1.02]"
style={{ maxHeight: "260px" }}
onClick={() => setFullscreenPreview({ url })}
onDoubleClick={(e) => {
e.preventDefault();
e.stopPropagation();
setFullscreenPreview({ url });
}}
/>
</div>
))}
<div className={cn("space-y-2", textWithoutImages ? "mt-3" : "mt-0")}>
<div className="flex flex-wrap gap-2">
{imageUrls.map((url, idx) => (
<div
key={idx}
className={cn(
"relative group overflow-hidden border transition-all",
"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}
alt=""
className="block max-w-full h-auto cursor-zoom-in transition-transform duration-300 group-hover:scale-[1.02]"
style={{ maxHeight: "260px" }}
onClick={() => setFullscreenPreview({ url })}
onDoubleClick={(e) => {
e.preventDefault();
e.stopPropagation();
setFullscreenPreview({ url });
}}
/>
</div>
))}
</div>
{!isDetailGenerationMode ? (
<p className="text-[14px] font-medium leading-6 text-[#2f3640]">
{CANVAS_IMAGE_DONE_SUMMARY}
</p>
) : null}
</div>
)}
{videoUrls.length > 0 && (

View File

@ -1,6 +1,6 @@
"use client";
import { Fragment, useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import { signIn, signOut } from "next-auth/react";
import useSWR from "swr";
@ -455,17 +455,6 @@ export function LovartHome({
const membershipPlans = membershipConfig?.plans || [];
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(
() => (modelsData?.models || []).filter((model) => model.supportsImageGen),
[modelsData?.models]
@ -2422,53 +2411,6 @@ export function LovartHome({
</div>
</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>
</DialogContent>

View File

@ -5,6 +5,7 @@ import { memo, useState } from "react";
import type { Vote } from "@/lib/db/schema";
import type { ChatMessage } from "@/lib/types";
import { cn, sanitizeText } from "@/lib/utils";
import { extractImageUrlsFromText } from "@/lib/image-url";
import { useDataStream } from "./data-stream-provider";
import { DocumentToolResult } from "./document";
import { DocumentPreview } from "./document-preview";
@ -172,6 +173,17 @@ function filterAgentRoutingNoise(text: string): string {
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 = ({
chatId,
message,
@ -315,7 +327,7 @@ const PurePreviewMessage = ({
// 应用视频内容过滤
const filteredText =
message.role === "assistant"
? filterAgentRoutingNoise(filterVideoContent(part.text))
? appendImageDoneSummary(filterAgentRoutingNoise(filterVideoContent(part.text)))
: part.text;
const textParts = parseAnnotations(filteredText);
@ -567,7 +579,7 @@ export const ThinkingMessage = () => {
<div className="flex w-full flex-col gap-2">
{/* 简洁明了的文本 + 动态点 */}
<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="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' }} />