添加清理过时对话的功能,更新聊天管理器以支持删除旧版本,优化查找最新对话的逻辑,并在聊天历史视图中添加清理按钮。

This commit is contained in:
duanfuxiang 2025-07-03 12:15:07 +08:00
parent 923d98cae9
commit cd65d6b3de
3 changed files with 99 additions and 8 deletions

View File

@ -1,4 +1,4 @@
import { CheckSquare, Clock, Edit3, MessageSquare, Pencil, Search, Square, Trash2, CopyPlus } from 'lucide-react'
import { CheckSquare, Clock, CopyPlus, MessageSquare, Pencil, Search, Sparkles, Square, Trash2 } from 'lucide-react'
import { Notice } from 'obsidian'
import React, { useMemo, useRef, useState } from 'react'
@ -23,6 +23,7 @@ const ChatHistoryView = ({
deleteConversation,
updateConversationTitle,
chatList,
cleanupOutdatedChats,
} = useChatHistory()
// search term
@ -37,6 +38,25 @@ const ChatHistoryView = ({
const titleInputRefs = useRef<Map<string, HTMLInputElement>>(new Map())
const handleCleanup = async () => {
const confirmed = confirm('此操作将永久删除所有对话的历史版本,只保留最新版。这有助于清理数据,但操作不可撤销。确定要继续吗?')
if (!confirmed) {
return
}
try {
const count = await cleanupOutdatedChats()
if (count > 0) {
new Notice(`成功清理了 ${count} 个过时的对话文件。`)
} else {
new Notice('没有需要清理的对话文件。')
}
} catch (error) {
new Notice('清理失败,请检查开发者控制台获取更多信息。')
console.error('Failed to cleanup outdated chats', error)
}
}
// handle search
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(e.target.value)
@ -192,6 +212,14 @@ const ChatHistoryView = ({
<h2>{t('chat.history.title')}</h2>
</div>
<div className="infio-chat-history-header-actions">
<button
onClick={handleCleanup}
className="infio-chat-history-cleanup-btn"
title={'清理历史版本'}
>
<Sparkles size={16} />
</button>
<button
onClick={toggleSelectionMode}
className={`infio-chat-history-selection-btn ${selectionMode ? 'active' : ''}`}
@ -398,6 +426,7 @@ const ChatHistoryView = ({
flex-shrink: 0;
}
.infio-chat-history-cleanup-btn,
.infio-chat-history-selection-btn {
display: flex !important;
align-items: center;
@ -415,6 +444,7 @@ const ChatHistoryView = ({
box-sizing: border-box;
}
.infio-chat-history-cleanup-btn:hover,
.infio-chat-history-selection-btn:hover {
background-color: var(--background-modifier-hover, #f5f5f5);
border-color: var(--background-modifier-border-hover, #d0d0d0);

View File

@ -71,11 +71,15 @@ export class ChatManager extends AbstractJsonRepository<
public async findById(id: string): Promise<ChatConversation | null> {
const allMetadata = await this.listMetadata()
const targetMetadata = allMetadata.find((meta) => meta.id === id)
const targetMetadatas = allMetadata.filter((meta) => meta.id === id)
if (!targetMetadata) return null
if (targetMetadatas.length === 0) return null
return this.read(targetMetadata.fileName)
// Sort by updatedAt descending to find the latest version
targetMetadatas.sort((a, b) => b.updatedAt - a.updatedAt)
const latestMetadata = targetMetadatas[0]
return this.read(latestMetadata.fileName)
}
public async updateChat(
@ -103,16 +107,65 @@ export class ChatManager extends AbstractJsonRepository<
public async deleteChat(id: string): Promise<boolean> {
const allMetadata = await this.listMetadata()
const targetMetadata = allMetadata.find((meta) => meta.id === id)
if (!targetMetadata) return false
const targetsToDelete = allMetadata.filter((meta) => meta.id === id)
if (targetsToDelete.length === 0) return false
// Delete all files associated with this ID
await Promise.all(targetsToDelete.map(meta => this.delete(meta.fileName)))
await this.delete(targetMetadata.fileName)
return true
}
public async cleanupOutdatedChats(): Promise<number> {
const allMetadata = await this.listMetadata()
const chatsById = new Map<string, (ChatConversationMeta & { fileName: string })[]>()
// Group chats by ID
for (const meta of allMetadata) {
if (!chatsById.has(meta.id)) {
chatsById.set(meta.id, [])
}
chatsById.get(meta.id)!.push(meta)
}
const filesToDelete: string[] = []
// Find outdated files for each ID
for (const chatGroup of chatsById.values()) {
if (chatGroup.length > 1) {
// Sort by date to find the newest
chatGroup.sort((a, b) => b.updatedAt - a.updatedAt)
// The first one is the latest, the rest are outdated
const outdatedFiles = chatGroup.slice(1)
for (const outdated of outdatedFiles) {
filesToDelete.push(outdated.fileName)
}
}
}
if (filesToDelete.length > 0) {
await Promise.all(filesToDelete.map(fileName => this.delete(fileName)))
}
return filesToDelete.length
}
public async listChats(): Promise<ChatConversationMeta[]> {
const metadata = await this.listMetadata()
const sorted = metadata.sort((a, b) => b.updatedAt - a.updatedAt)
// Use a Map to store the latest version of each chat by ID.
const latestChats = new Map<string, ChatConversationMeta>()
for (const meta of metadata) {
const existing = latestChats.get(meta.id)
if (!existing || meta.updatedAt > existing.updatedAt) {
latestChats.set(meta.id, meta)
}
}
const uniqueMetadata = Array.from(latestChats.values())
const sorted = uniqueMetadata.sort((a, b) => b.updatedAt - a.updatedAt)
return sorted
}
}

View File

@ -17,6 +17,7 @@ type UseChatHistory = {
getChatMessagesById: (id: string) => Promise<ChatMessage[] | null>
updateConversationTitle: (id: string, title: string) => Promise<void>
chatList: ChatConversationMeta[]
cleanupOutdatedChats: () => Promise<number>
}
export function useChatHistory(): UseChatHistory {
@ -111,11 +112,18 @@ export function useChatHistory(): UseChatHistory {
[chatManager, fetchChatList],
)
const cleanupOutdatedChats = useCallback(async (): Promise<number> => {
const count = await chatManager.cleanupOutdatedChats()
await fetchChatList()
return count
}, [chatManager, fetchChatList])
return {
createOrUpdateConversation,
deleteConversation,
getChatMessagesById,
updateConversationTitle,
chatList,
cleanupOutdatedChats,
}
}