Skip to content

Commit 2dc4696

Browse files
authored
feat(ai): clean expired images (#1021)
1 parent 4382412 commit 2dc4696

File tree

1 file changed

+197
-16
lines changed

1 file changed

+197
-16
lines changed

apps/web/src/components/ai/image-generator/AIImageGeneratorPanel.vue

Lines changed: 197 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
Settings,
1010
Trash2,
1111
} from 'lucide-vue-next'
12+
import { storeToRefs } from 'pinia'
13+
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
1214
import { Button } from '@/components/ui/button'
1315
import {
1416
Dialog,
@@ -17,7 +19,7 @@ import {
1719
DialogTitle,
1820
} from '@/components/ui/dialog'
1921
import { Textarea } from '@/components/ui/textarea'
20-
import { useStore } from '@/stores'
22+
import { useDisplayStore, useStore } from '@/stores'
2123
import useAIImageConfigStore from '@/stores/AIImageConfig'
2224
import { copyPlain } from '@/utils/clipboard'
2325
import AIImageConfig from './AIImageConfig.vue'
@@ -34,7 +36,13 @@ const { toggleAIDialog } = displayStore
3436
3537
/* ---------- 弹窗开关 ---------- */
3638
const dialogVisible = ref(props.open)
37-
watch(() => props.open, val => (dialogVisible.value = val))
39+
watch(() => props.open, (val) => {
40+
dialogVisible.value = val
41+
// 每次打开面板时检查并清理过期图片
42+
if (val) {
43+
cleanExpiredImages()
44+
}
45+
})
3846
watch(dialogVisible, val => emit(`update:open`, val))
3947
4048
/* ---------- 状态管理 ---------- */
@@ -43,38 +51,131 @@ const loading = ref(false)
4351
const prompt = ref<string>(``)
4452
const generatedImages = ref<string[]>([])
4553
const imagePrompts = ref<string[]>([]) // 存储每张图片对应的prompt
54+
const imageTimestamps = ref<number[]>([]) // 存储每张图片的生成时间戳
4655
const abortController = ref<AbortController | null>(null)
4756
const currentImageIndex = ref(0)
57+
const timeUpdateInterval = ref<NodeJS.Timeout | null>(null)
4858
4959
/* ---------- AI 配置 ---------- */
5060
const AIImageConfigStore = useAIImageConfigStore()
5161
const { apiKey, endpoint, model, type, size, quality, style } = storeToRefs(AIImageConfigStore)
5262
53-
/* ---------- 初始数据 ---------- */
54-
onMounted(() => {
63+
/* ---------- 过期检查函数 ---------- */
64+
function isImageExpired(timestamp: number): boolean {
65+
const EXPIRY_TIME = 60 * 60 * 1000 // 1小时,单位毫秒
66+
const now = Date.now()
67+
return now - timestamp > EXPIRY_TIME
68+
}
69+
70+
function cleanExpiredImages() {
5571
const savedImages = localStorage.getItem(`ai_generated_images`)
5672
const savedPrompts = localStorage.getItem(`ai_image_prompts`)
57-
if (savedImages) {
58-
generatedImages.value = JSON.parse(savedImages)
73+
const savedTimestamps = localStorage.getItem(`ai_image_timestamps`)
74+
75+
if (!savedImages) {
76+
return
5977
}
60-
if (savedPrompts) {
61-
imagePrompts.value = JSON.parse(savedPrompts)
78+
79+
const images = JSON.parse(savedImages)
80+
const prompts = savedPrompts ? JSON.parse(savedPrompts) : []
81+
const timestamps = savedTimestamps ? JSON.parse(savedTimestamps) : []
82+
83+
// 如果没有时间戳数据,说明是旧版本,默认清除所有数据
84+
if (!savedTimestamps || timestamps.length === 0) {
85+
console.log(`🧹 检测到旧版本数据,清除所有过期图片`)
86+
generatedImages.value = []
87+
imagePrompts.value = []
88+
imageTimestamps.value = []
89+
localStorage.removeItem(`ai_generated_images`)
90+
localStorage.removeItem(`ai_image_prompts`)
91+
localStorage.removeItem(`ai_image_timestamps`)
92+
return
93+
}
94+
95+
// 过滤掉过期的图片
96+
const validIndices: number[] = []
97+
timestamps.forEach((timestamp: number, index: number) => {
98+
if (!isImageExpired(timestamp)) {
99+
validIndices.push(index)
100+
}
101+
})
102+
103+
const validImages = validIndices.map(i => images[i]).filter(Boolean)
104+
const validPrompts = validIndices.map(i => prompts[i] || ``).filter((_, index) => validImages[index])
105+
const validTimestamps = validIndices.map(i => timestamps[i]).filter(Boolean)
106+
107+
// 更新数据
108+
generatedImages.value = validImages
109+
imagePrompts.value = validPrompts
110+
imageTimestamps.value = validTimestamps
111+
112+
// 如果有数据被清除,更新localStorage
113+
if (validImages.length < images.length) {
114+
console.log(`🧹 清除了 ${images.length - validImages.length} 张过期图片`)
115+
if (validImages.length > 0) {
116+
localStorage.setItem(`ai_generated_images`, JSON.stringify(validImages))
117+
localStorage.setItem(`ai_image_prompts`, JSON.stringify(validPrompts))
118+
localStorage.setItem(`ai_image_timestamps`, JSON.stringify(validTimestamps))
119+
}
120+
else {
121+
localStorage.removeItem(`ai_generated_images`)
122+
localStorage.removeItem(`ai_image_prompts`)
123+
localStorage.removeItem(`ai_image_timestamps`)
124+
}
62125
}
63126
127+
console.log(`📊 过期检查完成,有效图片数量:`, validImages.length)
128+
}
129+
130+
/* ---------- 初始数据 ---------- */
131+
onMounted(() => {
132+
// 先进行过期检查和清理
133+
cleanExpiredImages()
134+
64135
// 确保数组长度一致
65136
const imagesLength = generatedImages.value.length
66137
const promptsLength = imagePrompts.value.length
67-
68-
if (imagesLength > promptsLength) {
69-
// 如果图片多于提示词,用空字符串填充
70-
imagePrompts.value = [...imagePrompts.value, ...Array.from({ length: imagesLength - promptsLength }, () => ``)]
138+
const timestampsLength = imageTimestamps.value.length
139+
140+
const maxLength = Math.max(imagesLength, promptsLength, timestampsLength)
141+
142+
if (imagesLength < maxLength) {
143+
// 如果图片少于其他数组,说明数据不一致,清除所有数据
144+
console.warn(`⚠️ 数据不一致,清除所有数据`)
145+
generatedImages.value = []
146+
imagePrompts.value = []
147+
imageTimestamps.value = []
148+
localStorage.removeItem(`ai_generated_images`)
149+
localStorage.removeItem(`ai_image_prompts`)
150+
localStorage.removeItem(`ai_image_timestamps`)
71151
}
72-
else if (promptsLength > imagesLength) {
73-
// 如果提示词多于图片,截断提示词数组
74-
imagePrompts.value = imagePrompts.value.slice(0, imagesLength)
152+
else {
153+
// 补齐较短的数组
154+
if (promptsLength < imagesLength) {
155+
imagePrompts.value = [...imagePrompts.value, ...Array.from({ length: imagesLength - promptsLength }, () => ``)]
156+
}
157+
if (timestampsLength < imagesLength) {
158+
imageTimestamps.value = [...imageTimestamps.value, ...Array.from({ length: imagesLength - timestampsLength }, () => Date.now())]
159+
}
75160
}
76161
77-
console.log(`📊 数据加载完成,图片数量:`, imagesLength, `提示词数量:`, imagePrompts.value.length)
162+
console.log(`📊 数据加载完成,图片数量:`, generatedImages.value.length, `提示词数量:`, imagePrompts.value.length, `时间戳数量:`, imageTimestamps.value.length)
163+
164+
// 启动定时器,每30秒检查一次过期图片并更新时间显示
165+
timeUpdateInterval.value = setInterval(() => {
166+
// 检查并清理过期图片
167+
if (generatedImages.value.length > 0) {
168+
cleanExpiredImages()
169+
}
170+
}, 30000) // 30秒
171+
})
172+
173+
onBeforeUnmount(() => {
174+
// 清除定时器
175+
if (timeUpdateInterval.value) {
176+
clearInterval(timeUpdateInterval.value)
177+
timeUpdateInterval.value = null
178+
}
78179
})
79180
80181
/* ---------- 事件处理 ---------- */
@@ -155,18 +256,23 @@ async function generateImage() {
155256
? imageUrl
156257
: `data:image/png;base64,${imageUrl}`
157258
259+
const currentTimestamp = Date.now()
260+
158261
generatedImages.value.unshift(finalUrl)
159262
imagePrompts.value.unshift(prompt.value.trim()) // 保存对应的prompt
263+
imageTimestamps.value.unshift(currentTimestamp) // 保存生成时间戳
160264
currentImageIndex.value = 0
161265
162266
// 限制存储的图片数量,避免占用过多存储空间
163267
if (generatedImages.value.length > 20) {
164268
generatedImages.value = generatedImages.value.slice(0, 20)
165269
imagePrompts.value = imagePrompts.value.slice(0, 20)
270+
imageTimestamps.value = imageTimestamps.value.slice(0, 20)
166271
}
167272
168273
localStorage.setItem(`ai_generated_images`, JSON.stringify(generatedImages.value))
169274
localStorage.setItem(`ai_image_prompts`, JSON.stringify(imagePrompts.value))
275+
localStorage.setItem(`ai_image_timestamps`, JSON.stringify(imageTimestamps.value))
170276
}
171277
}
172278
else {
@@ -201,9 +307,11 @@ function cancelGeneration() {
201307
function clearImages() {
202308
generatedImages.value = []
203309
imagePrompts.value = []
310+
imageTimestamps.value = []
204311
currentImageIndex.value = 0
205312
localStorage.removeItem(`ai_generated_images`)
206313
localStorage.removeItem(`ai_image_prompts`)
314+
localStorage.removeItem(`ai_image_timestamps`)
207315
}
208316
209317
/* ---------- 下载图像 ---------- */
@@ -346,6 +454,73 @@ function viewFullImage(imageUrl: string) {
346454
console.error(`❌ 打开图片失败:`, error)
347455
}
348456
}
457+
458+
/* ---------- 时间相关函数 ---------- */
459+
const currentTime = ref(Date.now())
460+
461+
// 每秒更新当前时间,用于实时显示剩余时间
462+
onMounted(() => {
463+
const updateTime = () => {
464+
currentTime.value = Date.now()
465+
}
466+
467+
// 启动定时器更新时间显示
468+
const timeDisplayInterval = setInterval(updateTime, 1000)
469+
470+
// 组件卸载时清理定时器
471+
onBeforeUnmount(() => {
472+
clearInterval(timeDisplayInterval)
473+
})
474+
})
475+
476+
function getTimeRemaining(index: number): string {
477+
if (!imageTimestamps.value[index]) {
478+
return `未知`
479+
}
480+
481+
const EXPIRY_TIME = 60 * 60 * 1000 // 1小时
482+
const timestamp = imageTimestamps.value[index]
483+
const elapsed = currentTime.value - timestamp
484+
const remaining = EXPIRY_TIME - elapsed
485+
486+
if (remaining <= 0) {
487+
return `已过期`
488+
}
489+
490+
const minutes = Math.floor(remaining / (60 * 1000))
491+
const seconds = Math.floor((remaining % (60 * 1000)) / 1000)
492+
493+
if (minutes > 0) {
494+
return `${minutes}分${seconds}秒`
495+
}
496+
else {
497+
return `${seconds}秒`
498+
}
499+
}
500+
501+
function getTimeRemainingClass(index: number): string {
502+
if (!imageTimestamps.value[index]) {
503+
return `text-muted-foreground`
504+
}
505+
506+
const EXPIRY_TIME = 60 * 60 * 1000 // 1小时
507+
const timestamp = imageTimestamps.value[index]
508+
const elapsed = currentTime.value - timestamp
509+
const remaining = EXPIRY_TIME - elapsed
510+
511+
if (remaining <= 0) {
512+
return `text-red-500 font-medium`
513+
}
514+
else if (remaining < 10 * 60 * 1000) { // 少于10分钟
515+
return `text-orange-500 font-medium`
516+
}
517+
else if (remaining < 30 * 60 * 1000) { // 少于30分钟
518+
return `text-yellow-600`
519+
}
520+
else {
521+
return `text-green-600`
522+
}
523+
}
349524
</script>
350525

351526
<template>
@@ -473,6 +648,12 @@ function viewFullImage(imageUrl: string) {
473648
<span class="font-medium">提示词:</span>
474649
<span class="ml-1">{{ imagePrompts[currentImageIndex] || '无关联提示词' }}</span>
475650
</div>
651+
<div class="text-xs text-muted-foreground text-center">
652+
<span class="font-medium">有效期:</span>
653+
<span class="ml-1" :class="getTimeRemainingClass(currentImageIndex)">
654+
{{ getTimeRemaining(currentImageIndex) }}
655+
</span>
656+
</div>
476657
</div>
477658

478659
<!-- 图像操作按钮 -->

0 commit comments

Comments
 (0)