koishi-plugin-chat-patch 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/vue/index.vue +397 -57
- package/dist/index.js +8 -8
- package/dist/style.css +1 -1
- package/lib/index.js +95 -51
- package/package.json +1 -1
- package/src/file-manager.ts +7 -1
- package/src/message-handler.ts +6 -6
- package/src/utils.ts +57 -0
package/client/vue/index.vue
CHANGED
|
@@ -222,7 +222,6 @@ function isFileUrl(url: string): boolean {
|
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
|
|
226
225
|
// 头像组件
|
|
227
226
|
const AvatarComponent = defineComponent({
|
|
228
227
|
props: {
|
|
@@ -899,13 +898,35 @@ interface ImageCacheItem {
|
|
|
899
898
|
|
|
900
899
|
// 内存中的URL缓存
|
|
901
900
|
const imageBlobUrls = ref<Record<string, string>>({})
|
|
902
|
-
const maxImagesPerChannel = 200 // 每个频道最大缓存图片数量
|
|
903
901
|
|
|
904
902
|
// 内存管理配置
|
|
905
903
|
const MAX_MEMORY_USAGE = 100 * 1024 * 1024 // 100MB 最大内存使用量
|
|
906
904
|
const MAX_BLOB_COUNT = 50 // 最大blob URL数量
|
|
907
905
|
let currentMemoryUsage = 0 // 当前内存使用量估算
|
|
908
906
|
|
|
907
|
+
// IndexedDB 配置和限制
|
|
908
|
+
let imageDB: IDBDatabase | null = null
|
|
909
|
+
const DB_NAME = 'ChatImageCache'
|
|
910
|
+
const DB_VERSION = 2 // 版本号
|
|
911
|
+
const STORE_NAME = 'images'
|
|
912
|
+
|
|
913
|
+
// 严格的存储限制
|
|
914
|
+
const MAX_DB_SIZE = 50 * 1024 * 1024 // 50MB 最大数据库大小
|
|
915
|
+
const MAX_IMAGES_PER_CHANNEL = 100 // 每个频道最多缓存100张图片
|
|
916
|
+
const MAX_TOTAL_IMAGES = 500 // 总共最多缓存500张图片
|
|
917
|
+
const MAX_IMAGE_SIZE = 2 * 1024 * 1024 // 单张图片最大2MB
|
|
918
|
+
const CLEANUP_THRESHOLD = 0.8 // 当达到80%限制时开始清理
|
|
919
|
+
const DB_HEALTH_CHECK_INTERVAL = 60 * 1000 // 每分钟检查一次数据库健康状态
|
|
920
|
+
|
|
921
|
+
// 数据库状态跟踪
|
|
922
|
+
let currentDbSize = 0
|
|
923
|
+
let currentImageCount = 0
|
|
924
|
+
let lastHealthCheck = 0
|
|
925
|
+
|
|
926
|
+
const selectedBot = ref<string>('')
|
|
927
|
+
const selectedChannel = ref<string>('')
|
|
928
|
+
const inputMessage = ref<string>('')
|
|
929
|
+
|
|
909
930
|
// 内存管理函数
|
|
910
931
|
function estimateBlobSize(blob: Blob): number {
|
|
911
932
|
return blob.size || 0
|
|
@@ -954,16 +975,6 @@ function checkAndCleanupMemory() {
|
|
|
954
975
|
}
|
|
955
976
|
}
|
|
956
977
|
|
|
957
|
-
// IndexedDB
|
|
958
|
-
let imageDB: IDBDatabase | null = null
|
|
959
|
-
const DB_NAME = 'ChatImageCache'
|
|
960
|
-
const DB_VERSION = 1
|
|
961
|
-
const STORE_NAME = 'images'
|
|
962
|
-
|
|
963
|
-
const selectedBot = ref<string>('')
|
|
964
|
-
const selectedChannel = ref<string>('')
|
|
965
|
-
const inputMessage = ref<string>('')
|
|
966
|
-
|
|
967
978
|
// 图片上传相关状态
|
|
968
979
|
const uploadedImages = ref<Array<{
|
|
969
980
|
tempId: string
|
|
@@ -971,6 +982,7 @@ const uploadedImages = ref<Array<{
|
|
|
971
982
|
preview: string
|
|
972
983
|
size: number
|
|
973
984
|
}>>([])
|
|
985
|
+
|
|
974
986
|
const showActionMenu = ref<boolean>(false)
|
|
975
987
|
const fileInput = ref<HTMLInputElement>()
|
|
976
988
|
|
|
@@ -1771,7 +1783,6 @@ function resetDragState() {
|
|
|
1771
1783
|
removeThresholdCircle()
|
|
1772
1784
|
}
|
|
1773
1785
|
|
|
1774
|
-
// getDragStyle 不再直接用于拖拽中的元素,而是用于原始元素隐藏
|
|
1775
1786
|
function getDragStyle(channelId: string) {
|
|
1776
1787
|
if (draggingChannel.value === channelId && isDragReady.value) {
|
|
1777
1788
|
// 当拖拽开始且准备就绪时,隐藏原始气泡
|
|
@@ -1951,30 +1962,319 @@ function removeThresholdCircle() {
|
|
|
1951
1962
|
}
|
|
1952
1963
|
}
|
|
1953
1964
|
|
|
1954
|
-
//
|
|
1955
|
-
async function
|
|
1956
|
-
|
|
1957
|
-
|
|
1965
|
+
// 数据库健康检查
|
|
1966
|
+
async function checkDatabaseHealth(): Promise<boolean> {
|
|
1967
|
+
try {
|
|
1968
|
+
if (!imageDB) return false
|
|
1969
|
+
|
|
1970
|
+
const now = Date.now()
|
|
1971
|
+
if (now - lastHealthCheck < DB_HEALTH_CHECK_INTERVAL) {
|
|
1972
|
+
return true // 跳过频繁检查
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
lastHealthCheck = now
|
|
1976
|
+
|
|
1977
|
+
// 获取数据库统计信息
|
|
1978
|
+
const stats = await getDatabaseStats()
|
|
1979
|
+
currentDbSize = stats.totalSize
|
|
1980
|
+
currentImageCount = stats.totalImages
|
|
1981
|
+
|
|
1982
|
+
console.log('数据库健康检查:', {
|
|
1983
|
+
大小: `${(currentDbSize / 1024 / 1024).toFixed(2)}MB / ${(MAX_DB_SIZE / 1024 / 1024).toFixed(2)}MB`,
|
|
1984
|
+
图片数量: `${currentImageCount} / ${MAX_TOTAL_IMAGES}`,
|
|
1985
|
+
使用率: `${(currentDbSize / MAX_DB_SIZE * 100).toFixed(1)}%`
|
|
1986
|
+
})
|
|
1987
|
+
|
|
1988
|
+
// 检查是否需要清理
|
|
1989
|
+
const sizeRatio = currentDbSize / MAX_DB_SIZE
|
|
1990
|
+
const countRatio = currentImageCount / MAX_TOTAL_IMAGES
|
|
1991
|
+
|
|
1992
|
+
if (sizeRatio > CLEANUP_THRESHOLD || countRatio > CLEANUP_THRESHOLD) {
|
|
1993
|
+
console.warn('数据库使用率过高,开始自动清理')
|
|
1994
|
+
await performAutomaticCleanup()
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// 检查是否超过硬限制
|
|
1998
|
+
if (sizeRatio > 0.95 || countRatio > 0.95) {
|
|
1999
|
+
console.error('数据库接近极限,执行紧急清理')
|
|
2000
|
+
await performEmergencyCleanup()
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
return true
|
|
2004
|
+
} catch (error) {
|
|
2005
|
+
console.error('数据库健康检查失败:', error)
|
|
2006
|
+
return false
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
// 获取数据库统计信息
|
|
2011
|
+
async function getDatabaseStats(): Promise<{ totalSize: number, totalImages: number, channelStats: Record<string, number> }> {
|
|
2012
|
+
if (!imageDB) return { totalSize: 0, totalImages: 0, channelStats: {} }
|
|
2013
|
+
|
|
2014
|
+
return new Promise((resolve) => {
|
|
2015
|
+
const transaction = imageDB!.transaction([STORE_NAME], 'readonly')
|
|
2016
|
+
const store = transaction.objectStore(STORE_NAME)
|
|
2017
|
+
const request = store.getAll()
|
|
2018
|
+
|
|
2019
|
+
request.onsuccess = () => {
|
|
2020
|
+
const items: ImageCacheItem[] = request.result || []
|
|
2021
|
+
let totalSize = 0
|
|
2022
|
+
const channelStats: Record<string, number> = {}
|
|
2023
|
+
|
|
2024
|
+
items.forEach(item => {
|
|
2025
|
+
totalSize += item.size || 0
|
|
2026
|
+
channelStats[item.channelKey] = (channelStats[item.channelKey] || 0) + 1
|
|
2027
|
+
})
|
|
2028
|
+
|
|
2029
|
+
resolve({
|
|
2030
|
+
totalSize,
|
|
2031
|
+
totalImages: items.length,
|
|
2032
|
+
channelStats
|
|
2033
|
+
})
|
|
2034
|
+
}
|
|
1958
2035
|
|
|
1959
2036
|
request.onerror = () => {
|
|
1960
|
-
console.error('
|
|
1961
|
-
resolve(
|
|
2037
|
+
console.error('获取数据库统计失败:', request.error)
|
|
2038
|
+
resolve({ totalSize: 0, totalImages: 0, channelStats: {} })
|
|
2039
|
+
}
|
|
2040
|
+
})
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
// 自动清理
|
|
2044
|
+
async function performAutomaticCleanup() {
|
|
2045
|
+
try {
|
|
2046
|
+
console.log('开始自动清理...')
|
|
2047
|
+
|
|
2048
|
+
// 获取所有图片,按时间排序
|
|
2049
|
+
const allImages = await getAllImagesFromDB()
|
|
2050
|
+
if (allImages.length === 0) return
|
|
2051
|
+
|
|
2052
|
+
// 按频道分组
|
|
2053
|
+
const channelGroups: Record<string, ImageCacheItem[]> = {}
|
|
2054
|
+
allImages.forEach(item => {
|
|
2055
|
+
if (!channelGroups[item.channelKey]) {
|
|
2056
|
+
channelGroups[item.channelKey] = []
|
|
2057
|
+
}
|
|
2058
|
+
channelGroups[item.channelKey].push(item)
|
|
2059
|
+
})
|
|
2060
|
+
|
|
2061
|
+
let cleanedCount = 0
|
|
2062
|
+
let freedSize = 0
|
|
2063
|
+
|
|
2064
|
+
// 清理每个频道超出限制的图片
|
|
2065
|
+
for (const [channelKey, images] of Object.entries(channelGroups)) {
|
|
2066
|
+
if (images.length > MAX_IMAGES_PER_CHANNEL) {
|
|
2067
|
+
// 按时间排序,删除最旧的
|
|
2068
|
+
images.sort((a, b) => a.timestamp - b.timestamp)
|
|
2069
|
+
const toDelete = images.slice(0, images.length - MAX_IMAGES_PER_CHANNEL)
|
|
2070
|
+
|
|
2071
|
+
for (const item of toDelete) {
|
|
2072
|
+
await deleteImageFromDB(item.url)
|
|
2073
|
+
cleanedCount++
|
|
2074
|
+
freedSize += item.size || 0
|
|
2075
|
+
|
|
2076
|
+
// 清理内存中的blob URL
|
|
2077
|
+
if (imageBlobUrls.value[item.url]) {
|
|
2078
|
+
URL.revokeObjectURL(imageBlobUrls.value[item.url])
|
|
2079
|
+
delete imageBlobUrls.value[item.url]
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
console.log(`自动清理完成: 清理了 ${cleanedCount} 张图片,释放了 ${(freedSize / 1024 / 1024).toFixed(2)}MB`)
|
|
2086
|
+
|
|
2087
|
+
// 更新统计
|
|
2088
|
+
currentImageCount -= cleanedCount
|
|
2089
|
+
currentDbSize -= freedSize
|
|
2090
|
+
|
|
2091
|
+
} catch (error) {
|
|
2092
|
+
console.error('自动清理失败:', error)
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
// 紧急清理 // 激进
|
|
2097
|
+
async function performEmergencyCleanup() {
|
|
2098
|
+
try {
|
|
2099
|
+
console.log('开始紧急清理...')
|
|
2100
|
+
|
|
2101
|
+
// 获取所有图片
|
|
2102
|
+
const allImages = await getAllImagesFromDB()
|
|
2103
|
+
if (allImages.length === 0) return
|
|
2104
|
+
|
|
2105
|
+
// 按时间排序,只保留最新的一部分
|
|
2106
|
+
allImages.sort((a, b) => b.timestamp - a.timestamp)
|
|
2107
|
+
const keepCount = Math.floor(MAX_TOTAL_IMAGES * 0.3) // 只保留30%
|
|
2108
|
+
const toDelete = allImages.slice(keepCount)
|
|
2109
|
+
|
|
2110
|
+
let cleanedCount = 0
|
|
2111
|
+
let freedSize = 0
|
|
2112
|
+
|
|
2113
|
+
for (const item of toDelete) {
|
|
2114
|
+
await deleteImageFromDB(item.url)
|
|
2115
|
+
cleanedCount++
|
|
2116
|
+
freedSize += item.size || 0
|
|
2117
|
+
|
|
2118
|
+
// 清理内存中的blob URL
|
|
2119
|
+
if (imageBlobUrls.value[item.url]) {
|
|
2120
|
+
URL.revokeObjectURL(imageBlobUrls.value[item.url])
|
|
2121
|
+
delete imageBlobUrls.value[item.url]
|
|
2122
|
+
}
|
|
1962
2123
|
}
|
|
1963
2124
|
|
|
2125
|
+
console.log(`紧急清理完成: 清理了 ${cleanedCount} 张图片,释放了 ${(freedSize / 1024 / 1024).toFixed(2)}MB`)
|
|
2126
|
+
|
|
2127
|
+
// 更新统计
|
|
2128
|
+
currentImageCount = keepCount
|
|
2129
|
+
currentDbSize -= freedSize
|
|
2130
|
+
|
|
2131
|
+
} catch (error) {
|
|
2132
|
+
console.error('紧急清理失败:', error)
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
// 获取所有图片
|
|
2137
|
+
async function getAllImagesFromDB(): Promise<ImageCacheItem[]> {
|
|
2138
|
+
if (!imageDB) return []
|
|
2139
|
+
|
|
2140
|
+
return new Promise((resolve) => {
|
|
2141
|
+
const transaction = imageDB!.transaction([STORE_NAME], 'readonly')
|
|
2142
|
+
const store = transaction.objectStore(STORE_NAME)
|
|
2143
|
+
const request = store.getAll()
|
|
2144
|
+
|
|
1964
2145
|
request.onsuccess = () => {
|
|
1965
|
-
|
|
1966
|
-
|
|
2146
|
+
resolve(request.result || [])
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
request.onerror = () => {
|
|
2150
|
+
console.error('获取所有图片失败:', request.error)
|
|
2151
|
+
resolve([])
|
|
2152
|
+
}
|
|
2153
|
+
})
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
// 清理所有IndexedDB数据 //紧急情况使用
|
|
2157
|
+
async function clearAllIndexedDBData(): Promise<boolean> {
|
|
2158
|
+
return new Promise((resolve) => {
|
|
2159
|
+
try {
|
|
2160
|
+
// 先关闭现有连接
|
|
2161
|
+
if (imageDB) {
|
|
2162
|
+
imageDB.close()
|
|
2163
|
+
imageDB = null
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
// 删除整个数据库
|
|
2167
|
+
const deleteRequest = indexedDB.deleteDatabase(DB_NAME)
|
|
2168
|
+
|
|
2169
|
+
deleteRequest.onsuccess = () => {
|
|
2170
|
+
console.log('IndexedDB数据库已完全清理')
|
|
2171
|
+
currentDbSize = 0
|
|
2172
|
+
currentImageCount = 0
|
|
2173
|
+
resolve(true)
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
deleteRequest.onerror = () => {
|
|
2177
|
+
console.error('清理IndexedDB数据库失败:', deleteRequest.error)
|
|
2178
|
+
resolve(false)
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
deleteRequest.onblocked = () => {
|
|
2182
|
+
console.warn('IndexedDB数据库删除被阻塞,可能有其他连接正在使用')
|
|
2183
|
+
// 等待一段时间后重试
|
|
2184
|
+
setTimeout(() => {
|
|
2185
|
+
resolve(false)
|
|
2186
|
+
}, 5000)
|
|
2187
|
+
}
|
|
2188
|
+
} catch (error) {
|
|
2189
|
+
console.error('清理数据库时出错:', error)
|
|
2190
|
+
resolve(false)
|
|
1967
2191
|
}
|
|
2192
|
+
})
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
// IndexedDB初始化
|
|
2196
|
+
async function initImageDB(): Promise<boolean> {
|
|
2197
|
+
try {
|
|
2198
|
+
// 首先尝试打开数据库
|
|
2199
|
+
const success = await openDatabase()
|
|
2200
|
+
if (!success) {
|
|
2201
|
+
console.warn('数据库打开失败,尝试清理后重新初始化')
|
|
2202
|
+
await clearAllIndexedDBData()
|
|
2203
|
+
return await openDatabase()
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
// 数据库打开成功,进行初始健康检查
|
|
2207
|
+
setTimeout(async () => {
|
|
2208
|
+
const stats = await getDatabaseStats()
|
|
2209
|
+
console.log('数据库初始状态:', {
|
|
2210
|
+
大小: `${(stats.totalSize / 1024 / 1024).toFixed(2)}MB`,
|
|
2211
|
+
图片数量: stats.totalImages,
|
|
2212
|
+
频道分布: stats.channelStats
|
|
2213
|
+
})
|
|
2214
|
+
|
|
2215
|
+
// 如果初始状态就超过限制,执行清理
|
|
2216
|
+
if (stats.totalSize > MAX_DB_SIZE * 0.9 || stats.totalImages > MAX_TOTAL_IMAGES * 0.9) {
|
|
2217
|
+
console.warn('数据库初始状态接近限制,执行清理')
|
|
2218
|
+
await performAutomaticCleanup()
|
|
2219
|
+
}
|
|
2220
|
+
}, 1000)
|
|
2221
|
+
|
|
2222
|
+
return true
|
|
2223
|
+
} catch (error) {
|
|
2224
|
+
console.error('IndexedDB初始化出错:', error)
|
|
2225
|
+
return false
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
// 打开数据库的内部函数
|
|
2230
|
+
async function openDatabase(): Promise<boolean> {
|
|
2231
|
+
return new Promise((resolve) => {
|
|
2232
|
+
try {
|
|
2233
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION)
|
|
1968
2234
|
|
|
1969
|
-
|
|
1970
|
-
|
|
2235
|
+
request.onerror = () => {
|
|
2236
|
+
console.error('IndexedDB打开失败:', request.error)
|
|
2237
|
+
resolve(false)
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
request.onsuccess = () => {
|
|
2241
|
+
imageDB = request.result
|
|
2242
|
+
|
|
2243
|
+
// 添加错误处理
|
|
2244
|
+
imageDB.onerror = (event) => {
|
|
2245
|
+
console.error('IndexedDB运行时错误:', event)
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
// 添加版本变更处理
|
|
2249
|
+
imageDB.onversionchange = () => {
|
|
2250
|
+
console.warn('IndexedDB版本变更,关闭连接')
|
|
2251
|
+
imageDB?.close()
|
|
2252
|
+
imageDB = null
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
resolve(true)
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
request.onupgradeneeded = (event) => {
|
|
2259
|
+
const db = (event.target as IDBOpenDBRequest).result
|
|
2260
|
+
|
|
2261
|
+
// 创建对象存储
|
|
2262
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
2263
|
+
const store = db.createObjectStore(STORE_NAME, { keyPath: 'url' })
|
|
2264
|
+
store.createIndex('channelKey', 'channelKey', { unique: false })
|
|
2265
|
+
store.createIndex('timestamp', 'timestamp', { unique: false })
|
|
2266
|
+
store.createIndex('size', 'size', { unique: false })
|
|
2267
|
+
console.log('IndexedDB对象存储创建完成')
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
1971
2270
|
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
store.createIndex('channelKey', 'channelKey', { unique: false })
|
|
1976
|
-
store.createIndex('timestamp', 'timestamp', { unique: false })
|
|
2271
|
+
request.onblocked = () => {
|
|
2272
|
+
console.warn('IndexedDB打开被阻塞')
|
|
2273
|
+
resolve(false)
|
|
1977
2274
|
}
|
|
2275
|
+
} catch (error) {
|
|
2276
|
+
console.error('打开数据库时出错:', error)
|
|
2277
|
+
resolve(false)
|
|
1978
2278
|
}
|
|
1979
2279
|
})
|
|
1980
2280
|
}
|
|
@@ -2003,20 +2303,60 @@ async function getImageFromDB(url: string): Promise<ImageCacheItem | null> {
|
|
|
2003
2303
|
async function saveImageToDB(item: ImageCacheItem): Promise<boolean> {
|
|
2004
2304
|
if (!imageDB) return false
|
|
2005
2305
|
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2306
|
+
try {
|
|
2307
|
+
// 检查单张图片大小
|
|
2308
|
+
if (item.size > MAX_IMAGE_SIZE) {
|
|
2309
|
+
console.warn(`图片过大,跳过缓存: ${(item.size / 1024 / 1024).toFixed(2)}MB > ${(MAX_IMAGE_SIZE / 1024 / 1024).toFixed(2)}MB`)
|
|
2310
|
+
return false
|
|
2311
|
+
}
|
|
2010
2312
|
|
|
2011
|
-
|
|
2012
|
-
|
|
2313
|
+
// 执行健康检查
|
|
2314
|
+
await checkDatabaseHealth()
|
|
2315
|
+
|
|
2316
|
+
// 检查是否会超过限制
|
|
2317
|
+
if (currentDbSize + item.size > MAX_DB_SIZE) {
|
|
2318
|
+
console.warn('添加图片会超过数据库大小限制,执行清理')
|
|
2319
|
+
await performAutomaticCleanup()
|
|
2320
|
+
|
|
2321
|
+
// 清理后再次检查
|
|
2322
|
+
if (currentDbSize + item.size > MAX_DB_SIZE) {
|
|
2323
|
+
console.warn('清理后仍会超过限制,跳过此图片')
|
|
2324
|
+
return false
|
|
2325
|
+
}
|
|
2013
2326
|
}
|
|
2014
2327
|
|
|
2015
|
-
|
|
2016
|
-
console.
|
|
2017
|
-
|
|
2328
|
+
if (currentImageCount >= MAX_TOTAL_IMAGES) {
|
|
2329
|
+
console.warn('图片数量已达上限,执行清理')
|
|
2330
|
+
await performAutomaticCleanup()
|
|
2331
|
+
|
|
2332
|
+
// 清理后再次检查
|
|
2333
|
+
if (currentImageCount >= MAX_TOTAL_IMAGES) {
|
|
2334
|
+
console.warn('清理后仍达上限,跳过此图片')
|
|
2335
|
+
return false
|
|
2336
|
+
}
|
|
2018
2337
|
}
|
|
2019
|
-
|
|
2338
|
+
|
|
2339
|
+
return new Promise((resolve) => {
|
|
2340
|
+
const transaction = imageDB!.transaction([STORE_NAME], 'readwrite')
|
|
2341
|
+
const store = transaction.objectStore(STORE_NAME)
|
|
2342
|
+
const request = store.put(item)
|
|
2343
|
+
|
|
2344
|
+
request.onsuccess = () => {
|
|
2345
|
+
// 更新统计
|
|
2346
|
+
currentDbSize += item.size
|
|
2347
|
+
currentImageCount += 1
|
|
2348
|
+
resolve(true)
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
request.onerror = () => {
|
|
2352
|
+
console.error('保存图片到IndexedDB失败:', request.error)
|
|
2353
|
+
resolve(false)
|
|
2354
|
+
}
|
|
2355
|
+
})
|
|
2356
|
+
} catch (error) {
|
|
2357
|
+
console.error('保存图片时出错:', error)
|
|
2358
|
+
return false
|
|
2359
|
+
}
|
|
2020
2360
|
}
|
|
2021
2361
|
|
|
2022
2362
|
// 从IndexedDB删除图片
|
|
@@ -2118,22 +2458,10 @@ async function cacheImage(channelKey: string, originalUrl: string): Promise<stri
|
|
|
2118
2458
|
const byteArray = new Uint8Array(byteNumbers)
|
|
2119
2459
|
const blob = new Blob([byteArray], { type: contentType })
|
|
2120
2460
|
|
|
2121
|
-
//
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
// 清理最旧的图片缓存
|
|
2126
|
-
const sortedImages = channelImages.sort((a, b) => a.timestamp - b.timestamp)
|
|
2127
|
-
const toDelete = sortedImages.slice(0, channelImages.length - maxImagesPerChannel + 1)
|
|
2128
|
-
|
|
2129
|
-
for (const item of toDelete) {
|
|
2130
|
-
await deleteImageFromDB(item.url)
|
|
2131
|
-
// 清理内存中的blob URL
|
|
2132
|
-
if (imageBlobUrls.value[item.url]) {
|
|
2133
|
-
URL.revokeObjectURL(imageBlobUrls.value[item.url])
|
|
2134
|
-
delete imageBlobUrls.value[item.url]
|
|
2135
|
-
}
|
|
2136
|
-
}
|
|
2461
|
+
// 检查blob大小
|
|
2462
|
+
if (blob.size > MAX_IMAGE_SIZE) {
|
|
2463
|
+
console.warn(`图片过大,跳过缓存: ${(blob.size / 1024 / 1024).toFixed(2)}MB`)
|
|
2464
|
+
return null
|
|
2137
2465
|
}
|
|
2138
2466
|
|
|
2139
2467
|
// 创建缓存项
|
|
@@ -2369,7 +2697,7 @@ function handleMessageEvent(messageEvent: any) {
|
|
|
2369
2697
|
chatData.value = { ...chatData.value }
|
|
2370
2698
|
}
|
|
2371
2699
|
|
|
2372
|
-
//
|
|
2700
|
+
// 处理机器人发送消息成功事件
|
|
2373
2701
|
function handleBotMessageSentEvent(sentEvent: any) {
|
|
2374
2702
|
const channelKey = `${sentEvent.selfId}:${sentEvent.channelId}`
|
|
2375
2703
|
if (!chatData.value.messages[channelKey]) {
|
|
@@ -2436,7 +2764,7 @@ function handleBotMessageSentEvent(sentEvent: any) {
|
|
|
2436
2764
|
chatData.value = { ...chatData.value }
|
|
2437
2765
|
}
|
|
2438
2766
|
|
|
2439
|
-
//
|
|
2767
|
+
// 处理机器人消息事件
|
|
2440
2768
|
function handleBotMessageEvent(botMessageEvent: any) {
|
|
2441
2769
|
// 更新机器人信息
|
|
2442
2770
|
if (!chatData.value.bots[botMessageEvent.selfId]) {
|
|
@@ -2562,7 +2890,7 @@ function handleBotMessageEvent(botMessageEvent: any) {
|
|
|
2562
2890
|
chatData.value = { ...chatData.value }
|
|
2563
2891
|
}
|
|
2564
2892
|
|
|
2565
|
-
//
|
|
2893
|
+
// 监听消息变化
|
|
2566
2894
|
watch(currentMessages, (newMessages, oldMessages) => {
|
|
2567
2895
|
// 只有在切换频道时(消息数组完全不同)才自动滚动
|
|
2568
2896
|
if (oldMessages.length === 0 && newMessages.length > 0) {
|
|
@@ -2830,6 +3158,18 @@ onMounted(async () => {
|
|
|
2830
3158
|
const dbInitialized = await initImageDB()
|
|
2831
3159
|
if (!dbInitialized) {
|
|
2832
3160
|
console.warn('IndexedDB初始化失败,图片缓存功能将不可用')
|
|
3161
|
+
} else {
|
|
3162
|
+
console.log('IndexedDB初始化成功')
|
|
3163
|
+
|
|
3164
|
+
// 启动时进行健康检查
|
|
3165
|
+
setTimeout(async () => {
|
|
3166
|
+
await checkDatabaseHealth()
|
|
3167
|
+
}, 2000) // 延迟2秒,避免影响页面加载
|
|
3168
|
+
|
|
3169
|
+
// 设置定期健康检查(每5分钟)
|
|
3170
|
+
setInterval(async () => {
|
|
3171
|
+
await checkDatabaseHealth()
|
|
3172
|
+
}, 5 * 60 * 1000)
|
|
2833
3173
|
}
|
|
2834
3174
|
|
|
2835
3175
|
// 首先加载插件配置
|