koishi-plugin-chat-patch 1.1.0 → 1.2.0

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.
@@ -601,7 +601,7 @@ export function useChatLogic() {
601
601
  return h('span', {
602
602
  class: 'message-at',
603
603
  title: element.attrs.name
604
- }, `${element.attrs.name || element.attrs.id}`)
604
+ }, `@${(element.attrs.name || element.attrs.id).replace("@", '')}`)
605
605
 
606
606
  case 'json':
607
607
  return h('div', { class: 'message-image-container' }, [
@@ -661,6 +661,7 @@ export function useChatLogic() {
661
661
  loading: boolean
662
662
  }>>({})
663
663
 
664
+ // Config默认值,实际值将从后端获取
664
665
  const pluginConfig = ref<{
665
666
  maxMessagesPerChannel: number
666
667
  keepMessagesOnClear: number
@@ -670,12 +671,14 @@ export function useChatLogic() {
670
671
  exactMatch: boolean
671
672
  }>
672
673
  chatContainerHeight: number
674
+ clearIndexedDBOnStart: boolean
673
675
  }>({
674
676
  maxMessagesPerChannel: 1000,
675
677
  keepMessagesOnClear: 50,
676
678
  loggerinfo: false,
677
679
  blockedPlatforms: [],
678
- chatContainerHeight: 80
680
+ chatContainerHeight: 80,
681
+ clearIndexedDBOnStart: true
679
682
  })
680
683
 
681
684
  // 图片缓存 - IndexedDB
@@ -745,7 +748,7 @@ export function useChatLogic() {
745
748
  const showScrollButton = ref<boolean>(false)
746
749
  const isUserScrolling = ref<boolean>(false)
747
750
  const isSending = ref<boolean>(false)
748
- const isLoadingMore = ref<boolean>(false)
751
+ const isLoadingMore = ref<boolean>(false)
749
752
 
750
753
  // 拖拽相关状态
751
754
  const draggingChannel = ref<string>('')
@@ -764,16 +767,18 @@ export function useChatLogic() {
764
767
  show: boolean
765
768
  x: number
766
769
  y: number
767
- type: 'bot' | 'channel' | null
770
+ type: 'bot' | 'channel' | 'message'
768
771
  targetId: string
769
772
  isSecondClick: boolean
773
+ message?: MessageInfo
770
774
  }>({
771
775
  show: false,
772
776
  x: 0,
773
777
  y: 0,
774
- type: null,
778
+ type: 'bot',
775
779
  targetId: '',
776
- isSecondClick: false
780
+ isSecondClick: false,
781
+ message: undefined
777
782
  })
778
783
 
779
784
  // 置顶状态管理
@@ -931,6 +936,125 @@ export function useChatLogic() {
931
936
  }
932
937
  }
933
938
 
939
+ // 解析内联引用消息
940
+ function parseQuoteMessage(content: string, allMessages: MessageInfo[]): { quotedMessage: MessageInfo | null, restContent: string } {
941
+ // 匹配 <quote id="..."/> 格式
942
+ const quoteRegex = /^<quote\s+id="([^"]+)"\s*\/>(.*)/;
943
+ const match = content.match(quoteRegex);
944
+
945
+ if (match) {
946
+ const quotedMessageId = match[1];
947
+ const restContent = match[2];
948
+
949
+ // 在所有消息中查找被引用的消息
950
+ const quotedMessage = allMessages.find(msg => msg.id === quotedMessageId) || null;
951
+
952
+ return { quotedMessage, restContent };
953
+ }
954
+
955
+ return { quotedMessage: null, restContent: content };
956
+ }
957
+
958
+ // 在计算属性中添加解析内联引用消息的函数
959
+ function parseInlineQuote(content: string, allMessages: MessageInfo[]): { quotedMessage: MessageInfo | null, restContent: string } {
960
+ return parseQuoteMessage(content, allMessages);
961
+ }
962
+
963
+ // 获取内联引用消息
964
+ function getInlineQuoteMessage(message: MessageInfo, allMessages: MessageInfo[]): MessageInfo | null {
965
+ if (!message || !message.content) return null;
966
+
967
+ // 使用 parseInlineQuote 函数解析内联引用
968
+ const parsed = parseInlineQuote(message.content, allMessages);
969
+ return parsed.quotedMessage;
970
+ }
971
+
972
+ // 获取引用消息的用户信息
973
+ function getQuoteUser(message: MessageInfo, allMessages: MessageInfo[]): { avatar: string, username: string } {
974
+ // 如果有现成的引用消息,直接使用
975
+ if (message.quote && message.quote.user) {
976
+ return {
977
+ avatar: message.quote.user.avatar || '',
978
+ username: message.quote.user.username || ''
979
+ };
980
+ }
981
+
982
+ // 检查是否有内联引用消息
983
+ const inlineQuote = getInlineQuoteMessage(message, allMessages);
984
+ if (inlineQuote) {
985
+ return {
986
+ avatar: inlineQuote.avatar || '',
987
+ username: inlineQuote.username || ''
988
+ };
989
+ }
990
+
991
+ // 默认返回空对象
992
+ return {
993
+ avatar: '',
994
+ username: ''
995
+ };
996
+ }
997
+
998
+ // 获取引用消息的时间戳
999
+ function getQuoteTimestamp(message: MessageInfo, allMessages: MessageInfo[]): number {
1000
+ // 如果有现成的引用消息,直接使用
1001
+ if (message.quote && message.quote.timestamp) {
1002
+ return message.quote.timestamp;
1003
+ }
1004
+
1005
+ // 检查是否有内联引用消息
1006
+ const inlineQuote = getInlineQuoteMessage(message, allMessages);
1007
+ if (inlineQuote) {
1008
+ return inlineQuote.timestamp;
1009
+ }
1010
+
1011
+ // 默认返回当前时间
1012
+ return Date.now();
1013
+ }
1014
+
1015
+ // 获取引用消息的内容
1016
+ function getQuoteContent(message: MessageInfo, allMessages: MessageInfo[]): string {
1017
+ // 如果有现成的引用消息,直接使用
1018
+ if (message.quote && message.quote.content) {
1019
+ return message.quote.content;
1020
+ }
1021
+
1022
+ // 检查是否有内联引用消息
1023
+ const inlineQuote = getInlineQuoteMessage(message, allMessages);
1024
+ if (inlineQuote) {
1025
+ return inlineQuote.content;
1026
+ }
1027
+
1028
+ // 默认返回空字符串
1029
+ return '';
1030
+ }
1031
+
1032
+ // 获取引用消息的元素
1033
+ function getQuoteElements(message: MessageInfo, allMessages: MessageInfo[]): MessageElement[] {
1034
+ // 如果有现成的引用消息,直接使用
1035
+ if (message.quote && message.quote.elements) {
1036
+ return message.quote.elements;
1037
+ }
1038
+
1039
+ // 检查是否有内联引用消息
1040
+ const inlineQuote = getInlineQuoteMessage(message, allMessages);
1041
+ if (inlineQuote) {
1042
+ return inlineQuote.elements || [];
1043
+ }
1044
+
1045
+ // 默认返回空数组
1046
+ return [];
1047
+ }
1048
+
1049
+ // 获取不包含引用部分的消息内容
1050
+ function getMessageContentWithoutQuote(message: MessageInfo, allMessages: MessageInfo[]): string {
1051
+ if (!message || !message.content) return '';
1052
+
1053
+ // 使用 parseInlineQuote 函数解析内联引用
1054
+ const parsed = parseInlineQuote(message.content, allMessages);
1055
+ return parsed.restContent || message.content;
1056
+ }
1057
+
934
1058
  // 右键菜单相关方法
935
1059
  function handleBotRightClick(event: MouseEvent, botId: string) {
936
1060
  event.preventDefault()
@@ -966,6 +1090,23 @@ export function useChatLogic() {
966
1090
  showContextMenu(event, 'channel', channelId)
967
1091
  }
968
1092
 
1093
+ function handleMessageRightClick(event: MouseEvent, message: MessageInfo) {
1094
+ event.preventDefault()
1095
+ event.stopPropagation()
1096
+
1097
+ // 检查是否是第二次右键点击同一个目标
1098
+ const isSecondClick = contextMenu.value.show &&
1099
+ contextMenu.value.type === 'message' &&
1100
+ contextMenu.value.message?.id === message.id
1101
+
1102
+ if (isSecondClick) {
1103
+ // 第二次右键,隐藏自定义菜单,让浏览器显示原生菜单
1104
+ hideContextMenu()
1105
+ return
1106
+ }
1107
+ showMessageContextMenu(event, message)
1108
+ }
1109
+
969
1110
  function showContextMenu(event: MouseEvent, type: 'bot' | 'channel', targetId: string) {
970
1111
  // 确保菜单不会超出屏幕边界
971
1112
  const menuWidth = 180
@@ -994,6 +1135,35 @@ export function useChatLogic() {
994
1135
  document.addEventListener('keydown', handleKeyDown)
995
1136
  }
996
1137
 
1138
+ function showMessageContextMenu(event: MouseEvent, message: MessageInfo) {
1139
+ // 确保菜单不会超出屏幕边界
1140
+ const menuWidth = 180
1141
+ const menuHeight = 120
1142
+ let x = event.clientX
1143
+ let y = event.clientY
1144
+
1145
+ if (x + menuWidth > window.innerWidth) {
1146
+ x = window.innerWidth - menuWidth - 10
1147
+ }
1148
+ if (y + menuHeight > window.innerHeight) {
1149
+ y = window.innerHeight - menuHeight - 10
1150
+ }
1151
+
1152
+ contextMenu.value = {
1153
+ show: true,
1154
+ x,
1155
+ y,
1156
+ type: 'message',
1157
+ targetId: message.id,
1158
+ isSecondClick: false,
1159
+ message: message
1160
+ }
1161
+
1162
+ // 添加全局事件监听器来隐藏菜单
1163
+ document.addEventListener('click', hideContextMenu, { once: true })
1164
+ document.addEventListener('keydown', handleKeyDown)
1165
+ }
1166
+
997
1167
  function hideContextMenu() {
998
1168
  contextMenu.value.show = false
999
1169
  document.removeEventListener('click', hideContextMenu)
@@ -1147,6 +1317,9 @@ export function useChatLogic() {
1147
1317
  const messageContent = inputMessage.value.trim()
1148
1318
  if (!messageContent && uploadedImages.value.length === 0) return
1149
1319
 
1320
+ // 保存当前频道信息,用于发送后重新加载
1321
+ const currentChannelId = selectedChannel.value
1322
+
1150
1323
  // 设置发送状态
1151
1324
  isSending.value = true
1152
1325
 
@@ -1176,6 +1349,15 @@ export function useChatLogic() {
1176
1349
  uploadedImages.value = []
1177
1350
  showActionMenu.value = false
1178
1351
 
1352
+ // 消息发送成功后,重新选择当前频道以完全重新渲染
1353
+ if (currentChannelId) {
1354
+ // 临时清空选择
1355
+ selectedChannel.value = ''
1356
+ await nextTick()
1357
+ // 重新选择当前频道,触发完整的重新渲染流程
1358
+ selectChannel(currentChannelId)
1359
+ }
1360
+
1179
1361
  // 消息发送成功后,主动通知后端清理临时文件
1180
1362
  if (result.tempImageIds && result.tempImageIds.length > 0) {
1181
1363
  try {
@@ -1212,7 +1394,6 @@ export function useChatLogic() {
1212
1394
  }
1213
1395
  showActionMenu.value = false
1214
1396
  }
1215
-
1216
1397
  async function handleFileSelect(event: Event) {
1217
1398
  const target = event.target as HTMLInputElement
1218
1399
  const files = target.files
@@ -1344,6 +1525,155 @@ export function useChatLogic() {
1344
1525
  })
1345
1526
  }
1346
1527
 
1528
+ // 消息右键菜单处理函数
1529
+ async function handlePlusOne(message: MessageInfo | undefined) {
1530
+ if (!message) return;
1531
+ hideContextMenu()
1532
+
1533
+ try {
1534
+ // 构造要发送的内容,保持原始类型
1535
+ let content = ''
1536
+ const elements = message.elements || []
1537
+
1538
+ // 如果有elements,处理各种元素类型
1539
+ if (elements.length > 0) {
1540
+ for (const element of elements) {
1541
+ switch (element.type) {
1542
+ case 'img':
1543
+ case 'image':
1544
+ // 对于图片类型,转换为img标签
1545
+ const imageUrl = element.attrs.src || element.attrs.url || element.attrs.file
1546
+ if (imageUrl) {
1547
+ content += `<img src="${imageUrl}"/>`
1548
+ }
1549
+ break
1550
+ case 'mface':
1551
+ // 对于mface,转换为img标签
1552
+ const mfaceUrl = element.attrs.src || element.attrs.url || element.attrs.file
1553
+ if (mfaceUrl) {
1554
+ content += `<img src="${mfaceUrl}"/>`
1555
+ }
1556
+ break
1557
+ case 'face':
1558
+ if (element.children && element.children[0]?.attrs?.src) {
1559
+ const faceUrl = element.children[0].attrs.src
1560
+ content += `<img src="${faceUrl}"/>`
1561
+ } else {
1562
+ content += `[${element.attrs.name || element.attrs.id}]`
1563
+ }
1564
+ break
1565
+ case 'at':
1566
+ // 对于@消息,保持原始格式
1567
+ content += `<at id="${element.attrs.id}" name="${element.attrs.name}"/>`
1568
+ break
1569
+ case 'text':
1570
+ // 对于文本元素,直接添加文本内容
1571
+ content += element.attrs.content || ''
1572
+ break
1573
+ default:
1574
+ // 其他类型保持原始内容
1575
+ content += message.content
1576
+ break
1577
+ }
1578
+ }
1579
+ } else {
1580
+ // 如果没有elements,使用原始内容
1581
+ content = message.content
1582
+ }
1583
+
1584
+ // 设置输入框内容
1585
+ inputMessage.value = content
1586
+
1587
+ // 触发发送
1588
+ await sendMessage()
1589
+ } catch (error: any) {
1590
+ console.error('+1操作失败:', error)
1591
+ showNotification('操作失败: ' + (error?.message || String(error)), 'error')
1592
+ }
1593
+ }
1594
+
1595
+ async function handleCopyMessage(message: MessageInfo | undefined) {
1596
+ if (!message) return;
1597
+ hideContextMenu()
1598
+
1599
+ try {
1600
+ // 复制消息内容到剪贴板
1601
+ let content = ''
1602
+ const elements = message.elements || []
1603
+
1604
+ // 如果有elements,处理各种元素类型
1605
+ if (elements.length > 0) {
1606
+ for (const element of elements) {
1607
+ switch (element.type) {
1608
+ case 'img':
1609
+ case 'image':
1610
+ const imageUrl = element.attrs.src || element.attrs.url || element.attrs.file
1611
+ if (imageUrl) {
1612
+ content += `<img src="${imageUrl}"/>`
1613
+ }
1614
+ break
1615
+ case 'mface':
1616
+ const mfaceUrl = element.attrs.src || element.attrs.url || element.attrs.file
1617
+ if (mfaceUrl) {
1618
+ content += `<img src="${mfaceUrl}"/>`
1619
+ }
1620
+ break
1621
+ case 'face':
1622
+ if (element.children && element.children[0]?.attrs?.src) {
1623
+ const faceUrl = element.children[0].attrs.src
1624
+ content += `<img src="${faceUrl}"/>`
1625
+ } else {
1626
+ content += `[${element.attrs.name || element.attrs.id}]`
1627
+ }
1628
+ break
1629
+ case 'at':
1630
+ // 对于@消息,保持原始格式
1631
+ content += `<at id="${element.attrs.id}" name="${element.attrs.name}"/>`
1632
+ break
1633
+ case 'text':
1634
+ // 对于文本元素,直接添加文本内容
1635
+ content += element.attrs.content || ''
1636
+ break
1637
+ default:
1638
+ // 其他类型保持原始内容
1639
+ content += message.content
1640
+ break
1641
+ }
1642
+ }
1643
+ } else {
1644
+ // 如果没有elements,使用原始内容
1645
+ content = message.content
1646
+ }
1647
+
1648
+ await navigator.clipboard.writeText(content)
1649
+ showNotification('已复制到剪贴板', 'success')
1650
+ } catch (error: any) {
1651
+ console.error('复制失败:', error)
1652
+ showNotification('复制失败: ' + (error?.message || String(error)), 'error')
1653
+ }
1654
+ }
1655
+
1656
+ function handleReplyMessage(message: MessageInfo | undefined) {
1657
+ if (!message) return;
1658
+ hideContextMenu()
1659
+
1660
+ try {
1661
+ // 在输入框中添加引用
1662
+ const quote = `<quote id="${(message.id).replace("bot-msg-", "")}"/>`
1663
+ inputMessage.value = quote + inputMessage.value
1664
+
1665
+ // 聚焦输入框
1666
+ nextTick(() => {
1667
+ if (messageInput.value) {
1668
+ messageInput.value.focus()
1669
+ }
1670
+ })
1671
+ } catch (error: any) {
1672
+ console.error('回复操作失败:', error)
1673
+ showNotification('操作失败: ' + (error?.message || String(error)), 'error')
1674
+ }
1675
+ }
1676
+
1347
1677
  function getChannelTypeText(type: number | string): string {
1348
1678
  if (typeof type === 'number') {
1349
1679
  switch (type) {
@@ -1380,7 +1710,7 @@ export function useChatLogic() {
1380
1710
  // 滚动到底部时,重置滚动状态
1381
1711
  isUserScrolling.value = false
1382
1712
  }
1383
-
1713
+
1384
1714
  // 检查是否滚动到顶部,如果是则加载更多消息
1385
1715
  if (scrollTop <= 10 && selectedBot.value && selectedChannel.value) {
1386
1716
  loadMoreMessages()
@@ -1391,13 +1721,13 @@ export function useChatLogic() {
1391
1721
  // 加载更多消息的函数
1392
1722
  async function loadMoreMessages() {
1393
1723
  if (!selectedBot.value || !selectedChannel.value) return
1394
-
1724
+
1395
1725
  const channelKey = `${selectedBot.value}:${selectedChannel.value}`
1396
-
1726
+
1397
1727
  // 检查是否正在加载或没有更多消息
1398
1728
  const pagination = channelPagination.value[channelKey]
1399
1729
  if (isLoadingMore.value || (pagination && !pagination.hasMore)) return
1400
-
1730
+
1401
1731
  // 更新加载状态
1402
1732
  if (!channelPagination.value[channelKey]) {
1403
1733
  channelPagination.value[channelKey] = {
@@ -1406,18 +1736,18 @@ export function useChatLogic() {
1406
1736
  loading: false
1407
1737
  }
1408
1738
  }
1409
-
1739
+
1410
1740
  // 设置为加载中状态
1411
1741
  isLoadingMore.value = true
1412
1742
  channelPagination.value[channelKey].loading = true
1413
-
1743
+
1414
1744
  try {
1415
1745
  // 获取当前offset
1416
1746
  const currentOffset = pagination?.offset || 0
1417
-
1747
+
1418
1748
  // 加载下一批消息(50条)
1419
1749
  const result = await loadHistoryMessages(selectedBot.value, selectedChannel.value, 50, currentOffset)
1420
-
1750
+
1421
1751
  if (result) {
1422
1752
  // 更新分页状态已在loadHistoryMessages中处理
1423
1753
  } else {
@@ -2669,64 +2999,83 @@ export function useChatLogic() {
2669
2999
  chatData.value.messages[channelKey] = []
2670
3000
  }
2671
3001
 
2672
- // 检查消息是否已存在
2673
- const exists = chatData.value.messages[channelKey].find(m => m.id === sentEvent.messageId)
2674
- if (!exists) {
2675
- const botMessage: MessageInfo = {
2676
- id: sentEvent.messageId,
2677
- content: sentEvent.content,
2678
- userId: sentEvent.selfId,
2679
- username: sentEvent.botUsername,
2680
- avatar: sentEvent.botAvatar,
2681
- timestamp: sentEvent.timestamp,
2682
- channelId: sentEvent.channelId,
2683
- selfId: sentEvent.selfId,
2684
- elements: sentEvent.elements,
2685
- isBot: true, // 标记为机器人发送的消息
2686
- quote: sentEvent.quote
2687
- }
3002
+ const messages = chatData.value.messages[channelKey];
3003
+
3004
+ // 查找是否有使用临时ID的消息(以bot-msg-开头)
3005
+ const tempMessageIndex = messages.findIndex(m => m.id.startsWith('bot-msg-') && Math.abs(m.timestamp - sentEvent.timestamp) < 5000);
3006
+
3007
+ if (tempMessageIndex !== -1) {
3008
+ // 找到了临时消息,更新它的ID和其他信息
3009
+ const tempMessage = messages[tempMessageIndex];
3010
+ tempMessage.id = sentEvent.messageId; // 更新为真实ID
3011
+ tempMessage.content = sentEvent.content;
3012
+ tempMessage.userId = sentEvent.selfId;
3013
+ tempMessage.username = sentEvent.botUsername;
3014
+ tempMessage.avatar = sentEvent.botAvatar;
3015
+ tempMessage.channelId = sentEvent.channelId;
3016
+ tempMessage.selfId = sentEvent.selfId;
3017
+ tempMessage.elements = sentEvent.elements;
3018
+ tempMessage.isBot = true;
3019
+ tempMessage.quote = sentEvent.quote;
3020
+ } else {
3021
+ // 没有找到临时消息,检查是否已存在真实ID的消息
3022
+ const exists = messages.find(m => m.id === sentEvent.messageId);
3023
+ if (!exists) {
3024
+ const botMessage: MessageInfo = {
3025
+ id: sentEvent.messageId,
3026
+ content: sentEvent.content,
3027
+ userId: sentEvent.selfId,
3028
+ username: sentEvent.botUsername,
3029
+ avatar: sentEvent.botAvatar,
3030
+ timestamp: sentEvent.timestamp,
3031
+ channelId: sentEvent.channelId,
3032
+ selfId: sentEvent.selfId,
3033
+ elements: sentEvent.elements,
3034
+ isBot: true, // 标记为机器人发送的消息
3035
+ quote: sentEvent.quote
3036
+ };
2688
3037
 
2689
- // 按时间戳排序插入消息
2690
- const messages = chatData.value.messages[channelKey]
2691
- let insertIndex = messages.length
3038
+ // 按时间戳排序插入消息
3039
+ let insertIndex = messages.length;
2692
3040
 
2693
- // 找到正确的插入位置(按时间戳排序)
2694
- for (let i = messages.length - 1; i >= 0; i--) {
2695
- if (messages[i].timestamp <= sentEvent.timestamp) {
2696
- insertIndex = i + 1
2697
- break
2698
- }
2699
- if (i === 0) {
2700
- insertIndex = 0
3041
+ // 找到正确的插入位置(按时间戳排序)
3042
+ for (let i = messages.length - 1; i >= 0; i--) {
3043
+ if (messages[i].timestamp <= sentEvent.timestamp) {
3044
+ insertIndex = i + 1;
3045
+ break;
3046
+ }
3047
+ if (i === 0) {
3048
+ insertIndex = 0;
3049
+ }
2701
3050
  }
2702
- }
2703
3051
 
2704
- messages.splice(insertIndex, 0, botMessage)
3052
+ messages.splice(insertIndex, 0, botMessage);
2705
3053
 
2706
- // 保持消息数量限制
2707
- if (messages.length > 100) {
2708
- chatData.value.messages[channelKey] = messages.slice(-100)
2709
- }
3054
+ // 保持消息数量限制
3055
+ if (messages.length > 100) {
3056
+ chatData.value.messages[channelKey] = messages.slice(-100);
3057
+ }
2710
3058
 
2711
- // 更新频道消息数量缓存
2712
- channelMessageCounts.value[channelKey] = messages.length
3059
+ // 更新频道消息数量缓存
3060
+ channelMessageCounts.value[channelKey] = messages.length;
2713
3061
 
2714
- // 在添加新消息前检查是否在底部附近
2715
- const wasNearBottom = isNearBottom()
3062
+ // 在添加新消息前检查是否在底部附近
3063
+ const wasNearBottom = isNearBottom();
2716
3064
 
2717
- // 智能滚动:基于添加消息前的位置状态来决定是否滚动
2718
- nextTick(() => {
2719
- // 再次等待,确保新消息的DOM已经渲染
2720
- setTimeout(() => {
2721
- if (wasNearBottom) {
2722
- scrollToBottom()
2723
- }
2724
- }, 10)
2725
- })
3065
+ // 智能滚动:基于添加消息前的位置状态来决定是否滚动
3066
+ nextTick(() => {
3067
+ // 再次等待,确保新消息的DOM已经渲染
3068
+ setTimeout(() => {
3069
+ if (wasNearBottom) {
3070
+ scrollToBottom();
3071
+ }
3072
+ }, 10);
3073
+ });
3074
+ }
2726
3075
  }
2727
3076
 
2728
3077
  // 触发响应式更新
2729
- chatData.value = { ...chatData.value }
3078
+ chatData.value = { ...chatData.value };
2730
3079
  }
2731
3080
 
2732
3081
  // 处理机器人消息事件
@@ -2957,7 +3306,7 @@ export function useChatLogic() {
2957
3306
  selfId: botId,
2958
3307
  channelId: channelId
2959
3308
  }
2960
-
3309
+
2961
3310
  // 如果提供了分页参数,则添加到请求中
2962
3311
  if (limit !== undefined) {
2963
3312
  requestData.limit = limit
@@ -2991,7 +3340,7 @@ export function useChatLogic() {
2991
3340
  if (limit !== undefined) {
2992
3341
  // 按时间戳排序
2993
3342
  messages.sort((a, b) => a.timestamp - b.timestamp)
2994
-
3343
+
2995
3344
  // 如果是第一页(offset为0),则替换现有消息
2996
3345
  if (offset === 0) {
2997
3346
  chatData.value.messages[channelKey] = messages
@@ -3016,7 +3365,7 @@ export function useChatLogic() {
3016
3365
  } else {
3017
3366
  // 按时间戳排序
3018
3367
  messages.sort((a, b) => a.timestamp - b.timestamp)
3019
-
3368
+
3020
3369
  // 设置历史消息
3021
3370
  chatData.value.messages[channelKey] = messages
3022
3371
  // 初始化分页状态
@@ -3199,6 +3548,20 @@ export function useChatLogic() {
3199
3548
  // 添加点击外部关闭菜单的监听器
3200
3549
  document.addEventListener('click', handleClickOutside)
3201
3550
 
3551
+ // 首先加载插件配置
3552
+ await loadPluginConfig()
3553
+
3554
+ // 检查是否需要清空 IndexedDB
3555
+ if (pluginConfig.value.clearIndexedDBOnStart) {
3556
+ console.log('启动时清空 IndexedDB 缓存...')
3557
+ const clearResult = await clearAllIndexedDBData()
3558
+ if (clearResult) {
3559
+ console.log('IndexedDB 缓存已清空')
3560
+ } else {
3561
+ console.warn('清空 IndexedDB 缓存失败')
3562
+ }
3563
+ }
3564
+
3202
3565
  // 初始化IndexedDB
3203
3566
  const dbInitialized = await initImageDB()
3204
3567
  if (!dbInitialized) {
@@ -3217,9 +3580,6 @@ export function useChatLogic() {
3217
3580
  }, 5 * 60 * 1000)
3218
3581
  }
3219
3582
 
3220
- // 首先加载插件配置
3221
- await loadPluginConfig()
3222
-
3223
3583
  // 然后加载历史数据
3224
3584
  await loadChatData()
3225
3585
 
@@ -3307,7 +3667,7 @@ export function useChatLogic() {
3307
3667
  // 响应式数据
3308
3668
  chatData,
3309
3669
  channelMessageCounts,
3310
- channelPagination,
3670
+ channelPagination,
3311
3671
  pluginConfig,
3312
3672
  selectedBot,
3313
3673
  selectedChannel,
@@ -3357,6 +3717,7 @@ export function useChatLogic() {
3357
3717
  selectChannel,
3358
3718
  handleBotRightClick,
3359
3719
  handleChannelRightClick,
3720
+ handleMessageRightClick,
3360
3721
  showContextMenu,
3361
3722
  hideContextMenu,
3362
3723
  handleKeyDown,
@@ -3394,11 +3755,22 @@ export function useChatLogic() {
3394
3755
  handleTouchEnd,
3395
3756
  handleInputFocus,
3396
3757
  loadMoreMessages,
3758
+ handlePlusOne,
3759
+ handleCopyMessage,
3760
+ handleReplyMessage,
3761
+ parseInlineQuote,
3762
+ getInlineQuoteMessage,
3763
+ getQuoteUser,
3764
+ getQuoteTimestamp,
3765
+ getQuoteContent,
3766
+ getQuoteElements,
3767
+ getMessageContentWithoutQuote,
3397
3768
 
3398
3769
  // 图片缓存相关
3399
3770
  getCachedImageUrl,
3400
3771
  cacheImage,
3401
3772
  clearChannelImageCache,
3773
+ clearAllIndexedDBData,
3402
3774
  getMemoryStats,
3403
3775
  getCacheStats,
3404
3776
 
@@ -3406,6 +3778,7 @@ export function useChatLogic() {
3406
3778
  isFileUrl,
3407
3779
  loadHistoryMessages,
3408
3780
  handleMessageEvent,
3781
+ handleBotMessageSentEvent,
3409
3782
  saveSelectionState,
3410
3783
  restoreSelectionState
3411
3784
  }