feat(core): support ai chat delete action (#13655)

<img width="411" height="205" alt="截屏2025-09-26 10 58 39"
src="https://github.com/user-attachments/assets/c3bce144-7847-4794-b766-5a3777cbc00d"
/>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- New Features
- Delete icon added to AI session history with tooltip and confirmation
prompt; deleting current session opens a new session.
- Session deletion wired end-to-end (toolbar → provider → backend) and
shows notifications.

- Improvements
- Cleanup now supports deleting sessions with or without a document ID
(document-specific or workspace-wide).
- UI tweaks for cleaner session item layout and safer click handling
(delete won’t trigger item click).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Wu Yue
2025-09-27 19:58:58 +08:00
committed by GitHub
parent 8d6f7047c2
commit 9f94d5c216
10 changed files with 108 additions and 12 deletions

View File

@@ -441,7 +441,7 @@ declare global {
) => Promise<AIHistory[] | undefined>;
cleanup: (
workspaceId: string,
docId: string,
docId: string | undefined,
sessionIds: string[]
) => Promise<void>;
ids: (

View File

@@ -130,6 +130,9 @@ export class AIChatPanelTitle extends SignalWatcher(
@property({ attribute: false })
accessor openDoc!: (docId: string, sessionId: string) => void;
@property({ attribute: false })
accessor deleteSession!: (session: BlockSuitePresets.AIRecentSession) => void;
private readonly openPlayground = () => {
const playgroundContent = html`
<playground-content
@@ -182,6 +185,7 @@ export class AIChatPanelTitle extends SignalWatcher(
.onTogglePin=${this.togglePin}
.onOpenSession=${this.openSession}
.onOpenDoc=${this.openDoc}
.onSessionDelete=${this.deleteSession}
.docDisplayConfig=${this.docDisplayConfig}
.notificationService=${this.notificationService}
></ai-chat-toolbar>

View File

@@ -237,6 +237,31 @@ export class ChatPanel extends SignalWatcher(
return this.session;
};
private readonly deleteSession = async (
session: BlockSuitePresets.AIRecentSession
) => {
if (!AIProvider.histories) {
return;
}
const confirm = await this.notificationService.confirm({
title: 'Delete this history?',
message:
'Do you want to delete this AI conversation history? Once deleted, it cannot be recovered.',
confirmText: 'Delete',
cancelText: 'Cancel',
});
if (confirm) {
await AIProvider.histories.cleanup(
session.workspaceId,
session.docId || undefined,
[session.sessionId]
);
if (session.sessionId === this.session?.sessionId) {
this.newSession();
}
}
};
private readonly updateSession = async (options: UpdateChatSessionInput) => {
await AIProvider.session?.updateSession(options);
const session = await AIProvider.session?.getSession(
@@ -413,6 +438,7 @@ export class ChatPanel extends SignalWatcher(
.togglePin=${this.togglePin}
.openSession=${this.openSession}
.openDoc=${this.openDoc}
.deleteSession=${this.deleteSession}
></ai-chat-panel-title>
${keyed(
this.hasPinned ? this.session?.sessionId : this.doc.id,

View File

@@ -42,6 +42,11 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor onOpenDoc!: (docId: string, sessionId: string) => void;
@property({ attribute: false })
accessor onSessionDelete!: (
session: BlockSuitePresets.AIRecentSession
) => void;
@property({ attribute: false })
accessor docDisplayConfig!: DocDisplayConfig;
@@ -198,7 +203,9 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
.workspaceId=${this.workspaceId}
.docDisplayConfig=${this.docDisplayConfig}
.onSessionClick=${this.onSessionClick}
.onSessionDelete=${this.onSessionDelete}
.onDocClick=${this.onDocClick}
.notificationService=${this.notificationService}
></ai-session-history>
`,
portalStyles: {

View File

@@ -3,6 +3,7 @@ import { WithDisposable } from '@blocksuite/affine/global/lit';
import { scrollbarStyle } from '@blocksuite/affine/shared/styles';
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import { ShadowlessElement } from '@blocksuite/affine/std';
import { DeleteIcon } from '@blocksuite/icons/lit';
import { css, html, nothing, type PropertyValues } from 'lit';
import { property, query, state } from 'lit/decorators.js';
@@ -62,7 +63,6 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
position: relative;
display: flex;
height: 24px;
padding: 2px 4px;
justify-content: space-between;
align-items: center;
border-radius: 4px;
@@ -85,6 +85,7 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
font-size: 12px;
font-weight: 400;
line-height: 20px;
padding: 2px 4px;
color: ${unsafeCSSVarV2('text/primary')};
overflow: hidden;
text-overflow: ellipsis;
@@ -94,7 +95,7 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
.ai-session-doc {
display: flex;
width: 120px;
padding: 0px 4px;
padding: 2px;
align-items: center;
gap: 4px;
flex-shrink: 0;
@@ -117,6 +118,36 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
white-space: nowrap;
}
}
.ai-session-item-delete {
position: absolute;
right: 2px;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
justify-content: center;
background: ${unsafeCSSVarV2('layer/background/primary')};
border-radius: 2px;
padding: 2px;
cursor: pointer;
opacity: 0;
visibility: hidden;
transition:
opacity 0.2s ease,
visibility 0.2s ease;
svg {
width: 16px;
height: 16px;
color: ${unsafeCSSVarV2('icon/primary')};
}
}
.ai-session-item:hover .ai-session-item-delete {
opacity: 1;
visibility: visible;
}
}
${scrollbarStyle('.ai-session-history')}
@@ -134,6 +165,11 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor onSessionClick!: (sessionId: string) => void;
@property({ attribute: false })
accessor onSessionDelete!: (
session: BlockSuitePresets.AIRecentSession
) => void;
@property({ attribute: false })
accessor onDocClick!: (docId: string, sessionId: string) => void;
@@ -272,6 +308,16 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
${session.docId
? this.renderSessionDoc(session.docId, session.sessionId)
: nothing}
<div
class="ai-session-item-delete"
@click=${(e: MouseEvent) => {
e.stopPropagation();
this.onSessionDelete(session);
}}
>
${DeleteIcon()}
<affine-tooltip>Delete</affine-tooltip>
</div>
</div>
`;
})}

View File

@@ -261,7 +261,7 @@ export class CopilotClient {
async cleanupSessions(input: {
workspaceId: string;
docId: string;
docId: string | undefined;
sessionIds: string[];
}) {
try {

View File

@@ -794,7 +794,7 @@ Could you make a new website based on these notes and send back just the html fi
},
cleanup: async (
workspaceId: string,
docId: string,
docId: string | undefined,
sessionIds: string[]
) => {
await client.cleanupSessions({ workspaceId, docId, sessionIds });