mirror of
https://github.com/EthanMarti/infio-copilot.git
synced 2026-01-17 08:35:10 +00:00
添加清理过时对话的功能,更新聊天管理器以支持删除旧版本,优化查找最新对话的逻辑,并在聊天历史视图中添加清理按钮。
This commit is contained in:
parent
923d98cae9
commit
cd65d6b3de
@ -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 { Notice } from 'obsidian'
|
||||||
import React, { useMemo, useRef, useState } from 'react'
|
import React, { useMemo, useRef, useState } from 'react'
|
||||||
|
|
||||||
@ -23,6 +23,7 @@ const ChatHistoryView = ({
|
|||||||
deleteConversation,
|
deleteConversation,
|
||||||
updateConversationTitle,
|
updateConversationTitle,
|
||||||
chatList,
|
chatList,
|
||||||
|
cleanupOutdatedChats,
|
||||||
} = useChatHistory()
|
} = useChatHistory()
|
||||||
|
|
||||||
// search term
|
// search term
|
||||||
@ -37,6 +38,25 @@ const ChatHistoryView = ({
|
|||||||
|
|
||||||
const titleInputRefs = useRef<Map<string, HTMLInputElement>>(new Map())
|
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
|
// handle search
|
||||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setSearchTerm(e.target.value)
|
setSearchTerm(e.target.value)
|
||||||
@ -192,6 +212,14 @@ const ChatHistoryView = ({
|
|||||||
<h2>{t('chat.history.title')}</h2>
|
<h2>{t('chat.history.title')}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="infio-chat-history-header-actions">
|
<div className="infio-chat-history-header-actions">
|
||||||
|
<button
|
||||||
|
onClick={handleCleanup}
|
||||||
|
className="infio-chat-history-cleanup-btn"
|
||||||
|
title={'清理历史版本'}
|
||||||
|
>
|
||||||
|
<Sparkles size={16} />
|
||||||
|
清理
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={toggleSelectionMode}
|
onClick={toggleSelectionMode}
|
||||||
className={`infio-chat-history-selection-btn ${selectionMode ? 'active' : ''}`}
|
className={`infio-chat-history-selection-btn ${selectionMode ? 'active' : ''}`}
|
||||||
@ -398,6 +426,7 @@ const ChatHistoryView = ({
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.infio-chat-history-cleanup-btn,
|
||||||
.infio-chat-history-selection-btn {
|
.infio-chat-history-selection-btn {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -415,6 +444,7 @@ const ChatHistoryView = ({
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.infio-chat-history-cleanup-btn:hover,
|
||||||
.infio-chat-history-selection-btn:hover {
|
.infio-chat-history-selection-btn:hover {
|
||||||
background-color: var(--background-modifier-hover, #f5f5f5);
|
background-color: var(--background-modifier-hover, #f5f5f5);
|
||||||
border-color: var(--background-modifier-border-hover, #d0d0d0);
|
border-color: var(--background-modifier-border-hover, #d0d0d0);
|
||||||
|
|||||||
@ -71,11 +71,15 @@ export class ChatManager extends AbstractJsonRepository<
|
|||||||
|
|
||||||
public async findById(id: string): Promise<ChatConversation | null> {
|
public async findById(id: string): Promise<ChatConversation | null> {
|
||||||
const allMetadata = await this.listMetadata()
|
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(
|
public async updateChat(
|
||||||
@ -103,16 +107,65 @@ export class ChatManager extends AbstractJsonRepository<
|
|||||||
|
|
||||||
public async deleteChat(id: string): Promise<boolean> {
|
public async deleteChat(id: string): Promise<boolean> {
|
||||||
const allMetadata = await this.listMetadata()
|
const allMetadata = await this.listMetadata()
|
||||||
const targetMetadata = allMetadata.find((meta) => meta.id === id)
|
const targetsToDelete = allMetadata.filter((meta) => meta.id === id)
|
||||||
if (!targetMetadata) return false
|
|
||||||
|
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
|
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[]> {
|
public async listChats(): Promise<ChatConversationMeta[]> {
|
||||||
const metadata = await this.listMetadata()
|
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
|
return sorted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ type UseChatHistory = {
|
|||||||
getChatMessagesById: (id: string) => Promise<ChatMessage[] | null>
|
getChatMessagesById: (id: string) => Promise<ChatMessage[] | null>
|
||||||
updateConversationTitle: (id: string, title: string) => Promise<void>
|
updateConversationTitle: (id: string, title: string) => Promise<void>
|
||||||
chatList: ChatConversationMeta[]
|
chatList: ChatConversationMeta[]
|
||||||
|
cleanupOutdatedChats: () => Promise<number>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useChatHistory(): UseChatHistory {
|
export function useChatHistory(): UseChatHistory {
|
||||||
@ -111,11 +112,18 @@ export function useChatHistory(): UseChatHistory {
|
|||||||
[chatManager, fetchChatList],
|
[chatManager, fetchChatList],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const cleanupOutdatedChats = useCallback(async (): Promise<number> => {
|
||||||
|
const count = await chatManager.cleanupOutdatedChats()
|
||||||
|
await fetchChatList()
|
||||||
|
return count
|
||||||
|
}, [chatManager, fetchChatList])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createOrUpdateConversation,
|
createOrUpdateConversation,
|
||||||
deleteConversation,
|
deleteConversation,
|
||||||
getChatMessagesById,
|
getChatMessagesById,
|
||||||
updateConversationTitle,
|
updateConversationTitle,
|
||||||
chatList,
|
chatList,
|
||||||
|
cleanupOutdatedChats,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user