feat: 支持修改聊天记录名称

This commit is contained in:
digua
2025-12-02 00:07:47 +08:00
parent 3ef37f16be
commit d70549c9b1
7 changed files with 180 additions and 76 deletions
+24
View File
@@ -371,6 +371,30 @@ export function deleteSession(sessionId: string): boolean {
}
}
/**
* 重命名会话
*/
export function renameSession(sessionId: string, newName: string): boolean {
const dbPath = getDbPath(sessionId)
if (!fs.existsSync(dbPath)) {
return false
}
try {
const db = new Database(dbPath)
db.pragma('journal_mode = WAL')
const stmt = db.prepare('UPDATE meta SET name = ?')
stmt.run(newName)
db.close()
return true
} catch (error) {
console.error('[Database] Failed to rename session:', error)
return false
}
}
/**
* 获取数据库存储目录
*/
+15
View File
@@ -260,6 +260,21 @@ const mainIpcMain = (win: BrowserWindow) => {
}
})
/**
* 重命名会话
*/
ipcMain.handle('chat:renameSession', async (_, sessionId: string, newName: string) => {
try {
// 先关闭 Worker 中的数据库连接(确保没有其他进程占用)
await worker.closeDatabase(sessionId)
// 执行重命名
return databaseCore.renameSession(sessionId, newName)
} catch (error) {
console.error('重命名会话失败:', error)
return false
}
})
/**
* 获取可用年份列表
*/
+1
View File
@@ -36,6 +36,7 @@ interface ChatApi {
getSessions: () => Promise<AnalysisSession[]>
getSession: (sessionId: string) => Promise<AnalysisSession | null>
deleteSession: (sessionId: string) => Promise<boolean>
renameSession: (sessionId: string, newName: string) => Promise<boolean>
getAvailableYears: (sessionId: string) => Promise<number[]>
getMemberActivity: (sessionId: string, filter?: TimeFilter) => Promise<MemberActivity[]>
getMemberNameHistory: (sessionId: string, memberId: number) => Promise<MemberNameHistory[]>
+7
View File
@@ -90,6 +90,13 @@ const chatApi = {
return ipcRenderer.invoke('chat:deleteSession', sessionId)
},
/**
* 重命名会话
*/
renameSession: (sessionId: string, newName: string): Promise<boolean> => {
return ipcRenderer.invoke('chat:renameSession', sessionId, newName)
},
/**
* 获取可用年份列表
*/
+84 -48
View File
@@ -1,8 +1,9 @@
<script setup lang="ts">
import { useChatStore } from '@/stores/chat'
import { storeToRefs } from 'pinia'
import { ref, onMounted } from 'vue'
import { ref, onMounted, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import type { AnalysisSession } from '@/types/chat'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
@@ -17,7 +18,11 @@ const { toggleSidebar } = chatStore
const router = useRouter()
const route = useRoute()
const deleteConfirmId = ref<string | null>(null)
// 重命名相关状态
const showRenameModal = ref(false)
const renameTarget = ref<AnalysisSession | null>(null)
const newName = ref('')
const renameInputRef = ref<HTMLInputElement | null>(null)
// 加载会话列表
onMounted(() => {
@@ -33,18 +38,59 @@ function formatTime(timestamp: number): string {
return dayjs.unix(timestamp).fromNow()
}
function confirmDelete(id: string, event: Event) {
event.stopPropagation()
deleteConfirmId.value = id
// 打开重命名弹窗
function openRenameModal(session: AnalysisSession) {
renameTarget.value = session
newName.value = session.name
showRenameModal.value = true
// 等待 DOM 更新后聚焦输入框
nextTick(() => {
renameInputRef.value?.focus()
renameInputRef.value?.select()
})
}
async function handleDelete(id: string) {
await chatStore.deleteSession(id)
deleteConfirmId.value = null
// 执行重命名
async function handleRename() {
if (!renameTarget.value || !newName.value.trim()) return
const success = await chatStore.renameSession(renameTarget.value.id, newName.value.trim())
if (success) {
showRenameModal.value = false
renameTarget.value = null
newName.value = ''
}
}
function cancelDelete() {
deleteConfirmId.value = null
// 关闭重命名弹窗
function closeRenameModal() {
showRenameModal.value = false
renameTarget.value = null
newName.value = ''
}
// 删除会话
async function handleDelete(session: AnalysisSession) {
await chatStore.deleteSession(session.id)
}
// 生成右键菜单项
function getContextMenuItems(session: AnalysisSession) {
return [
[
{
label: '重命名',
icon: 'i-lucide-pencil',
onSelect: () => openRenameModal(session),
},
{
label: '删除',
icon: 'i-lucide-trash',
color: 'error' as const,
onSelect: () => handleDelete(session),
},
],
]
}
</script>
@@ -86,7 +132,7 @@ function cancelDelete() {
</UTooltip>
<!-- Tools Button -->
<UTooltip :text="isCollapsed ? '工具广场' : ''" :popper="{ placement: 'right' }">
<UTooltip :text="isCollapsed ? '实用工具' : ''" :popper="{ placement: 'right' }">
<UButton
:block="!isCollapsed"
class="transition-all rounded-full hover:bg-gray-200/60 dark:hover:bg-gray-800 h-12 cursor-pointer mt-2"
@@ -100,8 +146,8 @@ function cancelDelete() {
variant="ghost"
@click="router.push({ name: 'tools' })"
>
<UIcon name="i-heroicons-wrench-screwdriver" class="h-5 w-5 shrink-0" :class="[isCollapsed ? '' : 'mr-2']" />
<span v-if="!isCollapsed" class="truncate">工具广场</span>
<UIcon name="i-heroicons-squares-2x2" class="h-5 w-5 shrink-0" :class="[isCollapsed ? '' : 'mr-2']" />
<span v-if="!isCollapsed" class="truncate">实用工具</span>
</UButton>
</UTooltip>
</div>
@@ -111,8 +157,9 @@ function cancelDelete() {
<div v-if="sessions.length === 0 && !isCollapsed" class="py-8 text-center text-sm text-gray-500">暂无记录</div>
<div class="space-y-1">
<div v-if="!isCollapsed && sessions.length > 0" class="mb-2 px-2 text-xs font-medium text-gray-500">
聊天记录
<div v-if="!isCollapsed && sessions.length > 0" class="mb-2 px-2">
<div class="text-xs font-medium text-gray-500">聊天记录</div>
<div class="text-[10px] text-gray-400 font-normal mt-0.5">右键删除或重命名</div>
</div>
<UTooltip
@@ -121,6 +168,7 @@ function cancelDelete() {
:text="isCollapsed ? session.name : ''"
:popper="{ placement: 'right' }"
>
<UContextMenu :items="getContextMenuItems(session)">
<div
class="group relative flex w-full items-center rounded-full p-2 text-left transition-colors"
:class="[
@@ -153,44 +201,32 @@ function cancelDelete() {
{{ session.messageCount }} 条消息 · {{ formatTime(session.importedAt) }}
</p>
</div>
<!-- Delete Button -->
<div v-if="!isCollapsed" class="shrink-0 opacity-0 transition-opacity group-hover:opacity-100">
<UPopover v-if="deleteConfirmId === session.id" :open="true" @update:open="cancelDelete">
<template #default>
<UButton
icon="i-heroicons-trash"
color="red"
variant="ghost"
size="xs"
class="flex h-8 w-8 cursor-pointer items-center justify-center rounded-full"
@click="(e: Event) => confirmDelete(session.id, e)"
/>
</template>
<template #content>
<div class="p-3">
<p class="mb-3 text-sm">确定删除此记录</p>
<div class="flex justify-end gap-2">
<UButton size="xs" color="red" @click="handleDelete(session.id)">确定删除</UButton>
</div>
</div>
</template>
</UPopover>
<UButton
v-else
icon="i-heroicons-trash"
color="gray"
variant="ghost"
size="xs"
class="flex h-8 w-8 cursor-pointer items-center justify-center rounded-full"
@click="(e: Event) => confirmDelete(session.id, e)"
/>
</div>
</div>
</UContextMenu>
</UTooltip>
</div>
</div>
<!-- Rename Modal -->
<UModal v-model:open="showRenameModal">
<template #content>
<div class="p-4">
<h3 class="mb-3 font-semibold text-gray-900 dark:text-white">重命名</h3>
<UInput
ref="renameInputRef"
v-model="newName"
placeholder="请输入新名称"
class="mb-4"
@keydown.enter="handleRename"
/>
<div class="flex justify-end gap-2">
<UButton size="sm" color="gray" variant="soft" @click="closeRenameModal">取消</UButton>
<UButton size="sm" color="primary" :disabled="!newName.trim()" @click="handleRename">确定</UButton>
</div>
</div>
</template>
</UModal>
<!-- Footer -->
<div class="border-t border-gray-200 p-4 dark:border-gray-800">
<UTooltip :text="isCollapsed ? '设置和帮助' : ''" :popper="{ placement: 'right' }">
+1 -1
View File
@@ -12,7 +12,7 @@ const activeTab = ref('merge')
<div class="flex h-full flex-col bg-gray-50 dark:bg-gray-950">
<!-- Header -->
<div class="border-b border-gray-200 bg-white px-6 py-4 dark:border-gray-800 dark:bg-gray-900">
<h1 class="text-xl font-semibold text-gray-900 dark:text-white">工具广场</h1>
<h1 class="text-xl font-semibold text-gray-900 dark:text-white">实用工具</h1>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">提供聊天记录处理的实用工具</p>
</div>
+21
View File
@@ -190,6 +190,26 @@ export const useChatStore = defineStore(
}
}
/**
* 重命名会话
*/
async function renameSession(id: string, newName: string): Promise<boolean> {
try {
const success = await window.chatApi.renameSession(id, newName)
if (success) {
// 更新本地列表中的名称
const session = sessions.value.find((s) => s.id === id)
if (session) {
session.name = newName
}
}
return success
} catch (error) {
console.error('重命名会话失败:', error)
return false
}
}
/**
* 清除选中状态
*/
@@ -245,6 +265,7 @@ export const useChatStore = defineStore(
importFileFromPath,
selectSession,
deleteSession,
renameSession,
clearSelection,
toggleSidebar,
addCustomKeywordTemplate,