docs: add batch image PR readiness notes

This commit is contained in:
Turtle_Li
2026-07-07 03:31:39 +08:00
parent 1b07fe821a
commit 3c43fdec11
20 changed files with 879 additions and 52 deletions
+253 -17
View File
@@ -587,9 +587,9 @@
</div>
<div>
<label class="input-label">Prompt 数量</label>
<label class="input-label">预计生成</label>
<div class="input flex items-center bg-gray-50 text-gray-600 dark:bg-dark-900 dark:text-gray-300">
{{ parsedItems.length }} 条
{{ estimatedOutputCount }} 张 / {{ promptRows.length }} 条
</div>
</div>
</div>
@@ -600,24 +600,65 @@
<span class="text-xs text-gray-500 dark:text-gray-400">已添加 {{ promptRows.length }} 条</span>
</div>
<div class="rounded-lg border border-gray-200 p-3 dark:border-dark-700">
<div class="grid gap-3 md:grid-cols-[180px_minmax(0,1fr)_96px] md:items-start">
<textarea
v-model="promptDraft"
rows="3"
class="h-[76px] w-full resize-y rounded-md border border-gray-300 px-3 py-2 text-sm leading-5 outline-none focus:border-primary-500 focus:ring-2 focus:ring-primary-100 dark:border-dark-600 dark:bg-dark-900 dark:text-gray-100 dark:focus:border-primary-500 dark:focus:ring-primary-900/40"
placeholder="粘贴 prompt,添加后进入下方列表"
/>
<div class="mt-2 grid gap-2 md:grid-cols-[minmax(0,1fr)_112px_132px_112px] md:items-center">
<input
v-model="customIdDraft"
type="text"
maxlength="255"
class="input"
class="input h-9 text-sm"
placeholder="Custom ID 可选"
/>
<textarea
v-model="promptDraft"
class="min-h-[120px] w-full resize-y rounded-md border border-gray-300 px-3 py-2 text-sm outline-none focus:border-primary-500 focus:ring-2 focus:ring-primary-100 dark:border-dark-600 dark:bg-dark-900 dark:text-gray-100 dark:focus:border-primary-500 dark:focus:ring-primary-900/40"
placeholder="输入一整段 prompt,点击添加后会进入下方列表"
/>
<button type="button" class="btn btn-secondary h-10 justify-center" :disabled="!promptDraft.trim()" @click="addPromptRow">
<select
v-model.number="outputCountDraft"
class="batch-output-count-select input h-9 text-sm"
title="每条生成张数"
aria-label="每条生成张数"
>
<option v-for="count in outputCountOptions" :key="count" :value="count">
{{ count }} 张
</option>
</select>
<label
class="btn btn-secondary h-9 cursor-pointer justify-center text-sm"
:class="referenceImageDrafts.length >= selectedModelReferenceLimit ? 'pointer-events-none opacity-60' : ''"
>
<Icon name="upload" size="sm" class="mr-1.5" />
参考图
<input
type="file"
accept="image/png,image/jpeg,image/webp"
multiple
class="hidden"
:disabled="referenceImageDrafts.length >= selectedModelReferenceLimit"
@change="handleReferenceImageFiles"
/>
</label>
<button type="button" class="btn btn-secondary h-9 justify-center whitespace-nowrap px-4 text-sm" :disabled="!promptDraft.trim()" @click="addPromptRow">
<Icon name="plus" size="sm" class="mr-1.5" />
添加
</button>
</div>
<div v-if="referenceImageDrafts.length" class="mt-3 flex flex-wrap gap-2">
<span
v-for="(ref, refIndex) in referenceImageDrafts"
:key="`${ref.name}-${refIndex}`"
class="inline-flex max-w-full items-center gap-1 rounded-md border border-gray-200 bg-gray-50 px-2 py-1 text-xs text-gray-700 dark:border-dark-700 dark:bg-dark-900 dark:text-gray-200"
>
<span class="max-w-[180px] truncate">{{ ref.name }}</span>
<button type="button" class="text-gray-400 hover:text-red-600" title="移除参考图" @click="removeReferenceImageDraft(refIndex)">
<Icon name="x" size="xs" />
</button>
</span>
</div>
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
每条最多 {{ BATCH_IMAGE_MAX_OUTPUTS_PER_ITEM }} 张,整组最多 {{ BATCH_IMAGE_MAX_OUTPUTS_PER_JOB }} 张;当前模型每条最多 {{ selectedModelReferenceLimit }} 张参考图,参考图按生成张数重复消耗输入 token。
</p>
</div>
<div v-if="promptRows.length" class="overflow-hidden rounded-lg border border-gray-200 dark:border-dark-700">
<div
@@ -627,6 +668,12 @@
>
<span class="w-20 flex-shrink-0 font-mono text-xs text-gray-500 dark:text-gray-400">{{ row.custom_id }}</span>
<p class="min-w-0 flex-1 truncate text-sm text-gray-800 dark:text-gray-100">{{ row.prompt }}</p>
<span v-if="row.output_count > 1" class="flex-shrink-0 text-xs text-gray-500 dark:text-gray-400">
x{{ row.output_count }}
</span>
<span v-if="row.reference_images.length" class="flex-shrink-0 text-xs text-gray-500 dark:text-gray-400">
{{ row.reference_images.length }} 参考图
</span>
<button type="button" class="btn-ghost btn-icon flex-shrink-0 text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20" title="删除" @click="removePromptRow(index)">
<Icon name="trash" size="sm" />
</button>
@@ -662,9 +709,9 @@
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">当前界面如何使用</h3>
<div class="rounded-lg border border-gray-200 bg-gray-50 p-3 text-sm leading-6 text-gray-700 dark:border-dark-700 dark:bg-dark-900/50 dark:text-gray-200">
<p>1. 选择已开启批量生图的 Gemini API Key,模型列表会按该 Key 所属分组可用模型展示。</p>
<p>2. 任务名称可以留空,提交时会自动使用当前时间;Prompt 需要一条条添加到列表里。</p>
<p>2. 任务名称可以留空,提交时会自动使用当前时间;Prompt 需要一条条添加到列表里,每条 Prompt 可附参考图,也可以设置重复生成张数。</p>
<p>3. 提交后任务会先排队,明细会展示已提交的 Prompt;图片预览默认不加载,点击明细里的预览按钮才会加载单张图。</p>
<p>4. 完成后可以下载 ZIP;部分失败时,更多菜单里可以只重试失败项。</p>
<p>4. 完成后可以下载 ZIP;部分失败时,更多菜单里可以只重试失败项。当前结算仍按成功输出图张数计算,不单独对参考图加价。</p>
</div>
</section>
<section class="space-y-3">
@@ -720,6 +767,7 @@ import {
type BatchImageItem,
type BatchImageJob,
type BatchImageJobsListOptions,
type BatchImageReferenceImage,
type BatchImageStatus,
type BatchImageSubmitItem,
} from '@/api/batchImage'
@@ -742,6 +790,13 @@ type PromptRow = {
localId: string
custom_id: string
prompt: string
output_count: number
reference_images: BatchImageReferenceImage[]
}
type ReferenceImageDraft = BatchImageReferenceImage & {
name: string
size: number
}
type PreviewCacheRecord = {
@@ -762,6 +817,9 @@ const PREVIEW_THUMBNAIL_QUALITY = 0.72
const PREVIEW_CACHE_MAX_AGE_MS = 3 * 24 * 60 * 60 * 1000
const PREVIEW_CACHE_MAX_ENTRIES = 120
const PREVIEW_CACHE_MAX_BYTES = 48 * 1024 * 1024
const BATCH_IMAGE_MAX_OUTPUTS_PER_ITEM = 4
const BATCH_IMAGE_MAX_OUTPUTS_PER_JOB = 200
const outputCountOptions = Array.from({ length: BATCH_IMAGE_MAX_OUTPUTS_PER_ITEM }, (_, index) => index + 1)
const batchPageSizeOptions: SelectOption[] = [20, 50, 100].map(size => ({ value: size, label: String(size) }))
const appStore = useAppStore()
@@ -844,6 +902,8 @@ const expandedParentIds = ref(new Set<string>())
const promptRows = ref<PromptRow[]>([])
const promptDraft = ref('')
const customIdDraft = ref('')
const outputCountDraft = ref(1)
const referenceImageDrafts = ref<ReferenceImageDraft[]>([])
const itemPreviewUrls = reactive<Record<string, string>>({})
const previewLoadingIds = ref(new Set<string>())
const previewErrorIds = ref(new Set<string>())
@@ -962,16 +1022,37 @@ const endpointBase = computed(() => {
return '<你的 Sub2API API 端点>'
})
const selectedModelReferenceLimit = computed(() => referenceImageLimitForModel(form.model))
const estimatedOutputCount = computed(() =>
promptRows.value.reduce((sum, row) => sum + normalizeOutputCount(row.output_count), 0),
)
const parsedItems = computed<BatchImageSubmitItem[]>(() => {
const used = new Set<string>()
return promptRows.value
.map((row, index) => {
const customID = uniqueCustomID(row.custom_id || `img_${String(index + 1).padStart(3, '0')}`, used, index)
return { custom_id: customID, prompt: row.prompt.trim() }
const item: BatchImageSubmitItem = { custom_id: customID, prompt: row.prompt.trim() }
const outputCount = normalizeOutputCount(row.output_count)
if (outputCount > 1) {
item.output_count = outputCount
}
if (row.reference_images.length) {
item.reference_images = row.reference_images
}
return item
})
.filter(item => item.prompt)
})
function referenceImageLimitForModel(model: string) {
const normalized = String(model || '').toLowerCase()
if (normalized.includes('pro-image')) return 14
if (normalized.includes('flash-image')) return 3
return 0
}
const agentInstruction = computed(() => `---
name: sub2api-batch-image
description: 当用户希望用 Gemini/Vertex 批量生成图片、批量跑提示词、下载批量生图结果、重试失败图片时使用。
@@ -986,8 +1067,11 @@ ${endpointBase.value}
1. 从用户聊天或附件中提取 prompt。每条 prompt 保留完整文本,按顺序生成稳定 custom_id,例如 img_001、img_002。
2. 从用户要求或上下文推断任务名称;没有明确名称时用当前时间生成任务名。
3. 从用户要求或上下文推断输出目录;如果用户没有说保存到哪里,才询问用户。
4. 选择 API Key 和模型:先获取当前可用的批量生图 Key/模型;如果用户指定模型且该 Key 支持,则使用用户指定模型;否则使用该 Key 可用模型中的默认/第一个。不要展示或询问内部 provider 名称。
5. 调用批量生图 API 提交、轮询、下载,不要求用户去页面里手填。
4. 提交前必须先计算 expected_output_count = 所有 item 的 output_count 之和。单个批量任务硬性最多 200 张输出图;超过 200 张必须拆成多组任务,不能提交一个超大任务,也不能把参考图附件上限当成生成张数上限。
5. 如果用户提供参考图,把参考图按用途绑定到具体 item。参考图只是输入附件,不是输出图数量。模型单条限制必须按模型执行:Gemini 2.5 Flash Image 每条最多 3 张参考图;Gemini 3 Pro Image 每条最多 14 张参考图。不要把后端附件风控理解成 Pro 单条能力:按 output_count 展开后,所有 item 的参考图附件总数还有内部保护阈值 1000 个,inline base64 参考图解码后总量最多 128MB。这个 1000 只是服务器拒绝异常请求的保护阈值,不是推荐规模;参考图很多或总请求体较大时应主动拆分任务。
6. 参考图会按 output_count 重复消耗输入 token;大量任务、重复复用同一张参考图或参考图总体积较大时,优先使用 gs:// file_uri 或拆分成多组任务。
7. 选择 API Key 和模型:先获取当前可用的批量生图 Key/模型;如果用户指定模型且该 Key 支持,则使用用户指定模型;否则使用该 Key 可用模型中的默认/第一个。不要展示或询问内部 provider 名称。
8. 调用批量生图 API 提交、轮询、下载,不要求用户去页面里手填。
API 调用规范:
- 模型:GET ${joinEndpointPath(endpointBase.value, '/v1/images/batches/models')}
@@ -1004,14 +1088,29 @@ API 调用规范:
"image_size": "1K",
"response_mime_type": "image/png",
"items": [
{ "custom_id": "img_001", "prompt": "<第一条完整 prompt>" }
{
"custom_id": "img_001",
"prompt": "<第一条完整 prompt>",
"output_count": 1,
"reference_images": [
{
"id": "face",
"type": "subject",
"mime_type": "image/png",
"data": "<base64,不含 data:image/png;base64, 前缀>"
}
]
}
]
}
必须遵守:
- 不要把 API Key 写入仓库、日志、提交记录或最终回复。
- 不要把参考图 base64 写入最终回复、日志或公开文件。恢复记录中只保存参考图文件名、用途、数量和请求 JSON 文件路径;若请求 JSON 文件包含 base64,应保存在用户指定输出目录且不要提交到仓库。
- output_count 表示同一 prompt 和参考图重复生成几张,默认 1,每条最多 4;这不是依赖 Gemini 单次请求返回多图,而是系统展开成多个真实任务项。提交前必须确认预计输出图总数不超过 200,超过就拆分成多组任务。绝不能因为参考图附件有更高的内部保护阈值,就提交会生成超过 200 张图的任务。
- 当前对用户的批量生图计费仍按成功输出图片数量结算,不单独对参考图加价。可以向用户说明:参考图会产生少量上游输入 token 和临时存储成本,且会随 output_count 重复计算;页面显示的冻结/结算金额按输出图片数量计算。
- 提交成功后,必须立刻在输出目录写入本地恢复记录,例如 batch-image-resume.json。不要在恢复记录里保存 API Key。
- 恢复记录至少包含:endpoint、task_name、batch_id、model、output_dir、request_file、submitted_at、last_status、status_url、items_url、download_url、prompt_count,以及可用于失败重试的 custom_id 到 prompt 映射或请求 JSON 文件路径。
- 恢复记录至少包含:endpoint、task_name、batch_id、model、output_dir、request_file、submitted_at、last_status、status_url、items_url、download_url、prompt_count、expected_output_count,以及可用于失败重试的 custom_id 到 prompt 映射或请求 JSON 文件路径。
- 每次查询状态后更新恢复记录,写入 last_checked_at、last_status、成功数、失败数、实际扣费和失败摘要。会话中断或暂停后,下次必须能凭该文件继续查询、下载或重试。
- 不要高频轮询。首次查询等待约 20 到 30 秒;queued 状态每 60 到 120 秒查询一次;如果连续 3 次仍是 queued,就先停止主动查询,告诉用户任务仍在排队,并保留恢复记录,之后可继续其他任务或等待用户稍后让你恢复。
- running 状态每约 60 秒查询一次,服务器压力大或大批量任务时可以更久;processing_results 等接近完成的状态可每 20 到 45 秒查询一次。
@@ -1037,9 +1136,16 @@ function uniqueCustomID(raw: string, used: Set<string>, index: number): string {
return candidate
}
function normalizeOutputCount(value: unknown): number {
const parsed = Math.floor(Number(value || 1))
if (!Number.isFinite(parsed)) return 1
return Math.min(BATCH_IMAGE_MAX_OUTPUTS_PER_ITEM, Math.max(1, parsed))
}
function addPromptRow() {
const prompt = promptDraft.value.trim()
if (!prompt) return
const outputCount = normalizeOutputCount(outputCountDraft.value)
const used = new Set(promptRows.value.map(row => row.custom_id))
const customID = uniqueCustomID(customIdDraft.value || `img_${String(promptRows.value.length + 1).padStart(3, '0')}`, used, promptRows.value.length)
promptRows.value = [
@@ -1048,16 +1154,78 @@ function addPromptRow() {
localId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
custom_id: customID,
prompt,
output_count: outputCount,
reference_images: referenceImageDrafts.value.map(({ name: _name, size: _size, ...ref }) => ref),
},
]
promptDraft.value = ''
customIdDraft.value = ''
outputCountDraft.value = 1
referenceImageDrafts.value = []
}
function removePromptRow(index: number) {
promptRows.value = promptRows.value.filter((_, currentIndex) => currentIndex !== index)
}
function removeReferenceImageDraft(index: number) {
referenceImageDrafts.value = referenceImageDrafts.value.filter((_, currentIndex) => currentIndex !== index)
}
async function handleReferenceImageFiles(event: Event) {
const input = event.target as HTMLInputElement
const files = Array.from(input.files || [])
input.value = ''
if (files.length === 0) return
const limit = selectedModelReferenceLimit.value
if (limit <= 0) {
appStore.showError('当前模型不支持参考图。')
return
}
const slots = Math.max(0, limit - referenceImageDrafts.value.length)
if (slots <= 0) {
appStore.showError(`当前模型每条最多 ${limit} 张参考图。`)
return
}
const accepted = files.slice(0, slots)
if (accepted.length < files.length) {
appStore.showError(`当前模型每条最多 ${limit} 张参考图,已忽略超出的文件。`)
}
const next: ReferenceImageDraft[] = []
for (const file of accepted) {
if (!['image/png', 'image/jpeg', 'image/webp'].includes(file.type)) {
appStore.showError('参考图仅支持 PNG、JPEG 或 WebP。')
continue
}
if (file.size > 10 * 1024 * 1024) {
appStore.showError(`${file.name} 超过 10MB,已忽略。`)
continue
}
const data = await readFileAsBase64(file)
next.push({
id: file.name,
type: 'reference',
mime_type: file.type,
data,
name: file.name,
size: file.size,
})
}
referenceImageDrafts.value = [...referenceImageDrafts.value, ...next]
}
function readFileAsBase64(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onerror = () => reject(reader.error || new Error('Failed to read file'))
reader.onload = () => {
const result = String(reader.result || '')
resolve(result.includes(',') ? result.slice(result.indexOf(',') + 1) : result)
}
reader.readAsDataURL(file)
})
}
async function loadApiKeys() {
loadingKeys.value = true
try {
@@ -1382,14 +1550,19 @@ function openCreateModal() {
}
function closeCreateModal() {
if (submitting.value) return
showCreateModal.value = false
resetCreateDraft()
}
function resetCreateDraft() {
form.taskName = ''
form.responseMimeType = 'image/png'
promptRows.value = []
promptDraft.value = ''
customIdDraft.value = ''
outputCountDraft.value = 1
referenceImageDrafts.value = []
}
function closeDetail() {
@@ -1427,6 +1600,15 @@ function validateForm(): boolean {
appStore.showError(batchImageText('promptRequired'))
return false
}
if (estimatedOutputCount.value > BATCH_IMAGE_MAX_OUTPUTS_PER_JOB) {
appStore.showError(batchImageText('tooManyOutputImages'))
return false
}
const refLimit = selectedModelReferenceLimit.value
if (promptRows.value.some(row => row.reference_images.length > refLimit)) {
appStore.showError(batchImageText('tooManyReferenceImages'))
return false
}
return true
}
@@ -2245,6 +2427,10 @@ type BatchImageTextKey =
| 'invalidItems'
| 'duplicateCustomId'
| 'promptTooLong'
| 'invalidReferenceImage'
| 'tooManyReferenceImages'
| 'referenceImagesTooLarge'
| 'tooManyOutputImages'
| 'idempotencyConflict'
| 'notReady'
| 'outputDeleted'
@@ -2252,6 +2438,7 @@ type BatchImageTextKey =
| 'itemFailed'
| 'itemImageIndexOutOfRange'
| 'downloadLimited'
| 'downloadTooLarge'
| 'deleteNotReady'
| 'disabled'
| 'authRequired'
@@ -2305,6 +2492,10 @@ function batchImageText(key: BatchImageTextKey) {
invalidItems: 'Prompt 列表格式不正确,请检查是否为空、是否超过数量限制,或图片尺寸是否仍为 1K。',
duplicateCustomId: 'Prompt 列表里的 custom_id 不能重复。',
promptTooLong: '单条 prompt 过长,请缩短后重试。',
invalidReferenceImage: '参考图格式不正确,请使用 10MB 以内的 PNG、JPEG 或 WebP。',
tooManyReferenceImages: '参考图数量超过限制:Flash Image 每条最多 3 张,Pro Image 每条最多 14 张,整组最多 1000 张。',
referenceImagesTooLarge: '参考图总量过大。inline 参考图整组最多 128MB;大量参考图请改用 gs:// file_uri 或拆分任务。',
tooManyOutputImages: '预计生成张数超过限制:每条最多 4 张,整组最多 200 张。',
idempotencyConflict: '这次提交和之前的请求标识冲突,请刷新页面后重新提交。',
notReady: '任务还没有完成,完成后才能下载。',
outputDeleted: '这个任务的结果文件已经被清理,无法下载。',
@@ -2312,6 +2503,7 @@ function batchImageText(key: BatchImageTextKey) {
itemFailed: '这条明细没有成功图片,无法预览。',
itemImageIndexOutOfRange: '这条明细没有可预览的图片。',
downloadLimited: '当前下载请求太多,请稍后再试。',
downloadTooLarge: '这个 ZIP 太大,已超过单次下载限制。请减少单次下载数量,或联系管理员调整批量下载上限。',
deleteNotReady: '任务结束后才能删除记录。正在生成或结算中的任务请先等待完成。',
disabled: '批量生图功能当前未开启。',
authRequired: '当前 API Key 不可用或已失效,请重新选择密钥。',
@@ -2360,6 +2552,10 @@ function batchImageText(key: BatchImageTextKey) {
invalidItems: 'The prompt list is invalid. Check that it is not empty, within the item limit, and still using 1K image size.',
duplicateCustomId: 'Custom IDs in the prompt list must be unique.',
promptTooLong: 'One prompt is too long. Shorten it and try again.',
invalidReferenceImage: 'A reference image is invalid. Use PNG, JPEG, or WebP under 10 MB.',
tooManyReferenceImages: 'Too many reference images. Flash Image allows up to 3 per item, Pro Image allows up to 14, and each job allows up to 1000 total.',
referenceImagesTooLarge: 'Reference images are too large. Inline reference images are limited to 128 MB per job; use gs:// file_uri or split the job for large batches.',
tooManyOutputImages: 'Too many expected output images. Each prompt can request up to 4 images, and each job can generate up to 200 images.',
idempotencyConflict: 'This submission conflicts with a previous request ID. Refresh the page and submit again.',
notReady: 'The job is not complete yet. Download will be available after completion.',
outputDeleted: 'The result files for this job have already been cleaned up.',
@@ -2367,6 +2563,7 @@ function batchImageText(key: BatchImageTextKey) {
itemFailed: 'This item has no successful image to preview.',
itemImageIndexOutOfRange: 'This item has no previewable image.',
downloadLimited: 'Too many download requests are active. Please try again later.',
downloadTooLarge: 'This ZIP is too large for a single download. Download fewer items at once or ask an administrator to raise the batch download limit.',
deleteNotReady: 'Job records can only be deleted after the job finishes.',
disabled: 'Batch image generation is currently disabled.',
authRequired: 'The current API key is unavailable or expired. Select the key again.',
@@ -2446,6 +2643,18 @@ function batchImageErrorMessage(error: any, fallback: string) {
if (code === 'BATCH_IMAGE_PROMPT_TOO_LONG') {
return batchImageText('promptTooLong')
}
if (code === 'BATCH_IMAGE_INVALID_REFERENCE_IMAGE') {
return batchImageText('invalidReferenceImage')
}
if (code === 'BATCH_IMAGE_TOO_MANY_REFERENCE_IMAGES') {
return batchImageText('tooManyReferenceImages')
}
if (code === 'BATCH_IMAGE_REFERENCE_IMAGES_TOO_LARGE') {
return batchImageText('referenceImagesTooLarge')
}
if (code === 'BATCH_IMAGE_TOO_MANY_OUTPUT_IMAGES') {
return batchImageText('tooManyOutputImages')
}
if (code === 'BATCH_IMAGE_IDEMPOTENCY_CONFLICT') {
return batchImagePlainError(batchImageText('idempotencyConflict'))
}
@@ -2467,6 +2676,9 @@ function batchImageErrorMessage(error: any, fallback: string) {
if (code === 'BATCH_IMAGE_DOWNLOAD_LIMITED') {
return batchImageText('downloadLimited')
}
if (code === 'BATCH_IMAGE_DOWNLOAD_TOO_LARGE') {
return batchImageText('downloadTooLarge')
}
if (code === 'BATCH_IMAGE_RECORD_DELETE_NOT_READY') {
return batchImagePlainError(batchImageText('deleteNotReady'))
}
@@ -2514,6 +2726,20 @@ watch(
},
)
watch(
() => form.model,
() => {
const limit = selectedModelReferenceLimit.value
if (limit <= 0) {
referenceImageDrafts.value = []
return
}
if (referenceImageDrafts.value.length > limit) {
referenceImageDrafts.value = referenceImageDrafts.value.slice(0, limit)
}
},
)
onBeforeUnmount(() => {
stopPolling()
if (previewCacheCleanupTimer) {
@@ -2560,4 +2786,14 @@ onBeforeUnmount(() => {
.batch-prompt-popover p {
scrollbar-width: thin;
}
.batch-output-count-select {
height: 36px;
min-height: 36px;
padding-top: 0;
padding-bottom: 0;
padding-left: 14px;
padding-right: 34px;
line-height: 36px;
}
</style>