diff --git a/docs/pending-test.md b/docs/pending-test.md
index 2db3ddf..94a9502 100644
--- a/docs/pending-test.md
+++ b/docs/pending-test.md
@@ -1,5 +1,6 @@
# 待测试
+- 生图工作台和画布生图会把参考图按当前顺序显示为 `图片1`、`图片2` 等编号,并在图生图请求的实际提示词中注入编号说明;需要验证 `/image` 参考图排序、画布配置节点输入顺序和画布助手参考图编号一致。
- GPT Image 生图请求会在前端把 `9:16`、`16:9` 等比例转换成合法 `WIDTHxHEIGHT` 尺寸,并在非法尺寸时直接显示中文错误,避免上游返回 `invalid_value Invalid size`。
- Docker 部署时,`DATABASE_DSN=data/infinite-canvas.db` 会在存在 `/app/data` 挂载目录时自动归一到 `/app/data/infinite-canvas.db`,需要验证后台模型配置不会再因为工作目录变为 `/app/web` 而读到空库。
- Seedance 参考视频被火山判定包含真人或隐私信息时,前端错误摘要会提示改用不含真人的视频、官方允许的模型产物或已授权的 `asset://` 素材;参考素材上传目录改为跟随 SQLite 数据目录,并补充公开素材的 HEAD 访问。
diff --git a/web/src/app/(user)/canvas/components/canvas-assistant-panel.tsx b/web/src/app/(user)/canvas/components/canvas-assistant-panel.tsx
index 8860bee..0b10338 100644
--- a/web/src/app/(user)/canvas/components/canvas-assistant-panel.tsx
+++ b/web/src/app/(user)/canvas/components/canvas-assistant-panel.tsx
@@ -16,6 +16,7 @@ import { requestEdit, requestGeneration, requestImageQuestion, type ChatCompleti
import { imageToDataUrl, uploadImage } from "@/services/image-storage";
import { useAssetStore } from "@/stores/use-asset-store";
import { useThemeStore } from "@/stores/use-theme-store";
+import { imageReferenceLabel } from "@/lib/image-reference-prompt";
import type { ReferenceImage } from "@/types/image";
import { DiaTextReveal } from "@/components/ui/dia-text-reveal";
import { CanvasImageSettingsPopover } from "./canvas-image-settings-popover";
@@ -398,8 +399,8 @@ function AssistantComposer({
event.stopPropagation()}>
{references.length ? (
- {references.map((item) => (
-
onRemoveReference(item.id)} />
+ {references.map((item, index) => (
+ onRemoveReference(item.id)} />
))}
) : null}
@@ -584,20 +585,23 @@ function AssistantHistory({
function MessageReferences({ message }: { message: CanvasAssistantMessage }) {
return (
- {message.references?.map((item) => (
-
+ {message.references?.map((item, index, references) => (
+
))}
);
}
-function AssistantReferenceChip({ item, onRemove }: { item: CanvasAssistantReference; onRemove?: () => void }) {
+function AssistantReferenceChip({ item, label, onRemove }: { item: CanvasAssistantReference; label?: string; onRemove?: () => void }) {
const theme = canvasThemes[useThemeStore((state) => state.theme)];
const text = (item.text || item.title).replace(/\s+/g, " ").trim().slice(0, 1) || "文";
return (
{item.dataUrl ? (
-

+
+
+ {label ? {label} : null}
+
) : (
{text}
@@ -618,6 +622,12 @@ function AssistantReferenceChip({ item, onRemove }: { item: CanvasAssistantRefer
);
}
+function assistantImageReferenceLabel(references: CanvasAssistantReference[], index: number) {
+ if (!references[index]?.dataUrl) return undefined;
+ const imageIndex = references.slice(0, index + 1).filter((item) => item.dataUrl).length - 1;
+ return imageIndex >= 0 ? imageReferenceLabel(imageIndex) : undefined;
+}
+
function nodeToReference(node: CanvasNodeData): CanvasAssistantReference | null {
if (node.type === CanvasNodeType.Image && node.metadata?.content) {
return { id: node.id, type: node.type, title: node.title, dataUrl: node.metadata.content, storageKey: node.metadata.storageKey };
diff --git a/web/src/app/(user)/canvas/components/canvas-config-node-panel.tsx b/web/src/app/(user)/canvas/components/canvas-config-node-panel.tsx
index f910ed7..4116989 100644
--- a/web/src/app/(user)/canvas/components/canvas-config-node-panel.tsx
+++ b/web/src/app/(user)/canvas/components/canvas-config-node-panel.tsx
@@ -9,6 +9,7 @@ import { ModelPicker } from "@/components/model-picker";
import { defaultConfig, useConfigStore, useEffectiveConfig, type AiConfig } from "@/stores/use-config-store";
import { CreditSymbol, requestCreditCost } from "@/constant/credits";
import { canvasThemes } from "@/lib/canvas-theme";
+import { imageReferenceLabel } from "@/lib/image-reference-prompt";
import { seedanceReferenceLabel } from "@/lib/seedance-video";
import { useThemeStore } from "@/stores/use-theme-store";
import { CanvasImageSettingsPopover } from "./canvas-image-settings-popover";
@@ -304,7 +305,7 @@ function ImageSortCard({

-
{seedanceReferenceLabel("image", imageIndex)}
+
{imageReferenceLabel(imageIndex)}
onMove(input, offset)} />
diff --git a/web/src/app/(user)/image/page.tsx b/web/src/app/(user)/image/page.tsx
index 34be005..44e06f5 100644
--- a/web/src/app/(user)/image/page.tsx
+++ b/web/src/app/(user)/image/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { BookOpen, CheckSquare, ClipboardPaste, Download, FolderPlus, History, ImagePlus, LoaderCircle, PenLine, Plus, SlidersHorizontal, Sparkles, Trash2, Upload } from "lucide-react";
+import { ArrowLeft, ArrowRight, BookOpen, CheckSquare, ClipboardPaste, Download, FolderPlus, History, ImagePlus, LoaderCircle, PenLine, Plus, SlidersHorizontal, Sparkles, Trash2, Upload } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { App, Button, Checkbox, Drawer, Empty, Image, Input, Modal, Tag, Typography } from "antd";
import localforage from "localforage";
@@ -11,6 +11,7 @@ import { ModelPicker } from "@/components/model-picker";
import { PromptSelectDialog } from "@/components/prompts/prompt-select-dialog";
import { AssetPickerModal, type InsertAssetPayload } from "@/app/(user)/canvas/components/asset-picker-modal";
import { canvasThemes } from "@/lib/canvas-theme";
+import { imageReferenceLabel } from "@/lib/image-reference-prompt";
import { useConfigStore, useEffectiveConfig, type AiConfig } from "@/stores/use-config-store";
import { useThemeStore } from "@/stores/use-theme-store";
import { nanoid } from "nanoid";
@@ -373,9 +374,11 @@ export default function ImagePage() {
event.currentTarget.scrollLeft += event.deltaY;
}}
>
- {references.map((item) => (
+ {references.map((item, index) => (

+
{imageReferenceLabel(index)}
+
setReferences((value) => moveListItem(value, index, offset))} />