feat: implement session deletion protection and enhance chat history management
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
require("electron");
|
require("electron");
|
||||||
require("./main-DfEXs_ww.js");
|
require("./main-CDQKuYIF.js");
|
||||||
require("electron-squirrel-startup");
|
require("electron-squirrel-startup");
|
||||||
require("electron-log");
|
require("electron-log");
|
||||||
require("bytenode");
|
require("bytenode");
|
||||||
|
|||||||
@@ -798,6 +798,10 @@ class GatewayManager {
|
|||||||
}
|
}
|
||||||
case 'session.delete': {
|
case 'session.delete': {
|
||||||
const request = params as GatewayRpcParams['session.delete'];
|
const request = params as GatewayRpcParams['session.delete'];
|
||||||
|
if (normalizeAgentSessionKey(request.sessionKey).endsWith(':main')) {
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
await this.rpcGateway('sessions.delete', {
|
await this.rpcGateway('sessions.delete', {
|
||||||
key: normalizeAgentSessionKey(request.sessionKey),
|
key: normalizeAgentSessionKey(request.sessionKey),
|
||||||
deleteTranscript: true,
|
deleteTranscript: true,
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ function ChatHistoryPanel({
|
|||||||
{bucket.sessions.map((session) => {
|
{bucket.sessions.map((session) => {
|
||||||
const isActive = session.conversationId === selectedConversationId;
|
const isActive = session.conversationId === selectedConversationId;
|
||||||
const isMenuOpen = menuState?.conversationId === session.conversationId;
|
const isMenuOpen = menuState?.conversationId === session.conversationId;
|
||||||
|
const canDelete = Boolean(onDeleteConversation) && session.canDelete !== false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={session.conversationId}>
|
<li key={session.conversationId}>
|
||||||
@@ -273,19 +274,20 @@ function ChatHistoryPanel({
|
|||||||
<PencilLine className="h-4 w-4 text-[#94a3b8]" />
|
<PencilLine className="h-4 w-4 text-[#94a3b8]" />
|
||||||
{t('conversation.historyPanel.rename')}
|
{t('conversation.historyPanel.rename')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
{canDelete ? (
|
||||||
type="button"
|
<button
|
||||||
className="flex w-full items-center gap-2 border-t border-[#eef3f9] px-3 py-2.5 text-left text-sm text-[#ef4444] transition-colors hover:bg-[#fff5f5] disabled:cursor-not-allowed disabled:text-[#f5a2a2] dark:border-[#2f3136] dark:hover:bg-[#2a2a2d]"
|
type="button"
|
||||||
disabled={!onDeleteConversation}
|
className="flex w-full items-center gap-2 border-t border-[#eef3f9] px-3 py-2.5 text-left text-sm text-[#ef4444] transition-colors hover:bg-[#fff5f5] disabled:cursor-not-allowed disabled:text-[#f5a2a2] dark:border-[#2f3136] dark:hover:bg-[#2a2a2d]"
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setMenuState(null);
|
setMenuState(null);
|
||||||
onDeleteConversation?.(session.conversationId);
|
onDeleteConversation?.(session.conversationId);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
{t('conversation.historyPanel.delete')}
|
{t('conversation.historyPanel.delete')}
|
||||||
</button>
|
</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -101,8 +101,8 @@ function ChatMessageList({ messages, loading, showWelcomeState }: ChatMessageLis
|
|||||||
}, [loading, messages]);
|
}, [loading, messages]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-0 flex-1 flex-col overflow-hidden px-4 py-6 dark:bg-[#161618] sm:px-6">
|
<div className="flex min-h-0 flex-1 flex-col overflow-hidden p-4 dark:bg-[#161618] sm:px-6">
|
||||||
<div ref={containerRef} className="mx-auto flex min-h-0 w-full flex-1 flex-col gap-5 overflow-y-auto pr-1">
|
<div ref={containerRef} className="mx-auto flex min-h-0 w-full flex-1 flex-col gap-5 overflow-y-auto pr-2">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="w-full rounded-[18px] border border-dashed border-[#DDD3C3] bg-[#EFE7D8] px-4 py-3 text-sm text-[#645C50] dark:border-white/10 dark:bg-white/5 dark:text-gray-400">
|
<div className="w-full rounded-[18px] border border-dashed border-[#DDD3C3] bg-[#EFE7D8] px-4 py-3 text-sm text-[#645C50] dark:border-white/10 dark:bg-white/5 dark:text-gray-400">
|
||||||
{t('conversation.messageList.loading')}
|
{t('conversation.messageList.loading')}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export type ChatHistoryBucket = {
|
|||||||
conversationId: string;
|
conversationId: string;
|
||||||
title: string;
|
title: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
canDelete?: boolean;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,10 @@ function getTaskRoomTypeLabel(task: { roomType: string; roomList: Array<{ id?: s
|
|||||||
return matchedRoom?.pmsName || task.roomType;
|
return matchedRoom?.pmsName || task.roomType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isProtectedMainSession(sessionKey: string): boolean {
|
||||||
|
return sessionKey.endsWith(':main');
|
||||||
|
}
|
||||||
|
|
||||||
function mapMessages(
|
function mapMessages(
|
||||||
messages: RawMessage[],
|
messages: RawMessage[],
|
||||||
streamingMessage: RawMessage | null,
|
streamingMessage: RawMessage | null,
|
||||||
@@ -281,6 +285,7 @@ export default function HomePage() {
|
|||||||
conversationId: session.key,
|
conversationId: session.key,
|
||||||
title: chatSessionLabels[session.key] || session.displayName || session.key,
|
title: chatSessionLabels[session.key] || session.displayName || session.key,
|
||||||
updatedAt: formatHistoryTime(locale, t, activityMs, currentMs),
|
updatedAt: formatHistoryTime(locale, t, activityMs, currentMs),
|
||||||
|
canDelete: !isProtectedMainSession(session.key),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,6 +426,10 @@ export default function HomePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleDeleteConversation(conversationId: string): void {
|
function handleDeleteConversation(conversationId: string): void {
|
||||||
|
if (isProtectedMainSession(conversationId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const nextTitle = chatSessionLabels[conversationId]
|
const nextTitle = chatSessionLabels[conversationId]
|
||||||
|| chatSessions.find((session) => session.key === conversationId)?.displayName
|
|| chatSessions.find((session) => session.key === conversationId)?.displayName
|
||||||
|| t('conversation.untitledConversation');
|
|| t('conversation.untitledConversation');
|
||||||
@@ -433,6 +442,10 @@ export default function HomePage() {
|
|||||||
|
|
||||||
async function handleConfirmDeleteConversation(): Promise<void> {
|
async function handleConfirmDeleteConversation(): Promise<void> {
|
||||||
if (!deleteConversationTarget) return;
|
if (!deleteConversationTarget) return;
|
||||||
|
if (isProtectedMainSession(deleteConversationTarget.conversationId)) {
|
||||||
|
setDeleteConversationTarget(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setDeletingConversation(true);
|
setDeletingConversation(true);
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -197,6 +197,10 @@ function buildNewSessionKey(agentId: string | null | undefined): string {
|
|||||||
return buildAgentSessionKey(agentId || getDefaultAgentId(), `session-${Date.now()}`);
|
return buildAgentSessionKey(agentId || getDefaultAgentId(), `session-${Date.now()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isProtectedMainSession(sessionKey: string): boolean {
|
||||||
|
return normalizeAgentSessionKey(sessionKey).endsWith(':main');
|
||||||
|
}
|
||||||
|
|
||||||
function clearSessionEntryFromMap<T extends Record<string, unknown>>(entries: T, sessionKey: string): T {
|
function clearSessionEntryFromMap<T extends Record<string, unknown>>(entries: T, sessionKey: string): T {
|
||||||
return Object.fromEntries(Object.entries(entries).filter(([key]) => key !== sessionKey)) as T;
|
return Object.fromEntries(Object.entries(entries).filter(([key]) => key !== sessionKey)) as T;
|
||||||
}
|
}
|
||||||
@@ -671,6 +675,10 @@ function selectAgent(agentId: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSession(sessionKey: string): Promise<void> {
|
async function deleteSession(sessionKey: string): Promise<void> {
|
||||||
|
if (isProtectedMainSession(sessionKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const currentState = state;
|
const currentState = state;
|
||||||
const isDeletingCurrentSession = sessionKey === currentState.currentSessionKey;
|
const isDeletingCurrentSession = sessionKey === currentState.currentSessionKey;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user