mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-21 03:56:23 +08:00
feat(core): support lazy load for ai session history (#13221)
Close [AI-331](https://linear.app/affine-design/issue/AI-331) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added infinite scroll and incremental loading for AI session history, allowing users to load more sessions as they scroll. * **Refactor** * Improved session history component with better state management and modular rendering for loading, empty, and history states. * **Bug Fixes** * Enhanced handling of absent or uninitialized chat sessions, reducing potential errors when session data is missing. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -407,7 +407,8 @@ declare global {
|
|||||||
) => Promise<CopilotChatHistoryFragment[] | undefined>;
|
) => Promise<CopilotChatHistoryFragment[] | undefined>;
|
||||||
getRecentSessions: (
|
getRecentSessions: (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
limit?: number
|
limit?: number,
|
||||||
|
offset?: number
|
||||||
) => Promise<AIRecentSession[] | undefined>;
|
) => Promise<AIRecentSession[] | undefined>;
|
||||||
updateSession: (options: UpdateChatSessionInput) => Promise<string>;
|
updateSession: (options: UpdateChatSessionInput) => Promise<string>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export class AIChatPanelTitle extends SignalWatcher(
|
|||||||
accessor notificationService!: NotificationService;
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor session!: CopilotChatHistoryFragment;
|
accessor session!: CopilotChatHistoryFragment | null | undefined;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor status!: ChatStatus;
|
accessor status!: ChatStatus;
|
||||||
|
|||||||
+78
-37
@@ -3,8 +3,8 @@ import { WithDisposable } from '@blocksuite/affine/global/lit';
|
|||||||
import { scrollbarStyle } from '@blocksuite/affine/shared/styles';
|
import { scrollbarStyle } from '@blocksuite/affine/shared/styles';
|
||||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
import { css, html, nothing } from 'lit';
|
import { css, html, nothing, type PropertyValues } from 'lit';
|
||||||
import { property, state } from 'lit/decorators.js';
|
import { property, query, state } from 'lit/decorators.js';
|
||||||
|
|
||||||
import { AIProvider } from '../../provider';
|
import { AIProvider } from '../../provider';
|
||||||
import type { DocDisplayConfig } from '../ai-chat-chips';
|
import type { DocDisplayConfig } from '../ai-chat-chips';
|
||||||
@@ -133,11 +133,21 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor onDocClick!: (docId: string, sessionId: string) => void;
|
accessor onDocClick!: (docId: string, sessionId: string) => void;
|
||||||
|
|
||||||
@state()
|
@query('.ai-session-history')
|
||||||
private accessor sessions: BlockSuitePresets.AIRecentSession[] = [];
|
accessor scrollContainer!: HTMLElement;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private accessor loading = true;
|
private accessor sessions: BlockSuitePresets.AIRecentSession[] | undefined;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private accessor loadingMore = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private accessor hasMore = true;
|
||||||
|
|
||||||
|
private accessor currentOffset = 0;
|
||||||
|
|
||||||
|
private readonly pageSize = 10;
|
||||||
|
|
||||||
private groupSessionsByTime(
|
private groupSessionsByTime(
|
||||||
sessions: BlockSuitePresets.AIRecentSession[]
|
sessions: BlockSuitePresets.AIRecentSession[]
|
||||||
@@ -188,23 +198,46 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getRecentSessions() {
|
private async getRecentSessions() {
|
||||||
this.loading = true;
|
this.loadingMore = true;
|
||||||
const limit = 50;
|
|
||||||
const sessions = await AIProvider.session?.getRecentSessions(
|
const moreSessions =
|
||||||
this.workspaceId,
|
(await AIProvider.session?.getRecentSessions(
|
||||||
limit
|
this.workspaceId,
|
||||||
);
|
this.pageSize,
|
||||||
if (sessions) {
|
this.currentOffset
|
||||||
this.sessions = sessions;
|
)) || [];
|
||||||
}
|
this.sessions = [...(this.sessions || []), ...moreSessions];
|
||||||
this.loading = false;
|
|
||||||
|
this.currentOffset += moreSessions.length;
|
||||||
|
this.hasMore = moreSessions.length === this.pageSize;
|
||||||
|
this.loadingMore = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly onScroll = () => {
|
||||||
|
if (!this.hasMore || this.loadingMore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// load more when within 50px of bottom
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = this.scrollContainer;
|
||||||
|
const threshold = 50;
|
||||||
|
if (scrollTop + clientHeight >= scrollHeight - threshold) {
|
||||||
|
this.getRecentSessions().catch(console.error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
override connectedCallback() {
|
override connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.getRecentSessions().catch(console.error);
|
this.getRecentSessions().catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override firstUpdated(changedProperties: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProperties);
|
||||||
|
this.disposables.add(() => {
|
||||||
|
this.scrollContainer.removeEventListener('scroll', this.onScroll);
|
||||||
|
});
|
||||||
|
this.scrollContainer.addEventListener('scroll', this.onScroll);
|
||||||
|
}
|
||||||
|
|
||||||
private renderSessionGroup(
|
private renderSessionGroup(
|
||||||
title: string,
|
title: string,
|
||||||
sessions: BlockSuitePresets.AIRecentSession[]
|
sessions: BlockSuitePresets.AIRecentSession[]
|
||||||
@@ -256,35 +289,43 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
override render() {
|
private renderLoading() {
|
||||||
if (this.loading) {
|
return html`
|
||||||
return html`
|
<div class="loading-container">
|
||||||
<div class="ai-session-history">
|
<div class="loading-title">Loading history...</div>
|
||||||
<div class="loading-container">
|
</div>
|
||||||
<div class="loading-title">Loading history...</div>
|
`;
|
||||||
</div>
|
}
|
||||||
</div>
|
|
||||||
`;
|
private renderEmpty() {
|
||||||
|
return html`
|
||||||
|
<div class="empty-container">
|
||||||
|
<div class="empty-title">Empty history</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderHistory() {
|
||||||
|
if (!this.sessions) {
|
||||||
|
return this.renderLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.sessions.length === 0) {
|
if (this.sessions.length === 0) {
|
||||||
return html`
|
return this.renderEmpty();
|
||||||
<div class="ai-session-history">
|
|
||||||
<div class="empty-container">
|
|
||||||
<div class="empty-title">Empty history</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupedSessions = this.groupSessionsByTime(this.sessions);
|
const groupedSessions = this.groupSessionsByTime(this.sessions);
|
||||||
return html`
|
return html`
|
||||||
<div class="ai-session-history">
|
${this.renderSessionGroup('Today', groupedSessions.today)}
|
||||||
${this.renderSessionGroup('Today', groupedSessions.today)}
|
${this.renderSessionGroup('Last 7 days', groupedSessions.last7Days)}
|
||||||
${this.renderSessionGroup('Last 7 days', groupedSessions.last7Days)}
|
${this.renderSessionGroup('Last 30 days', groupedSessions.last30Days)}
|
||||||
${this.renderSessionGroup('Last 30 days', groupedSessions.last30Days)}
|
${this.renderSessionGroup('Older', groupedSessions.older)}
|
||||||
${this.renderSessionGroup('Older', groupedSessions.older)}
|
`;
|
||||||
</div>
|
}
|
||||||
|
|
||||||
|
override render() {
|
||||||
|
return html`
|
||||||
|
<div class="ai-session-history">${this.renderHistory()}</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,13 +186,18 @@ export class CopilotClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRecentSessions(workspaceId: string, limit?: number) {
|
async getRecentSessions(
|
||||||
|
workspaceId: string,
|
||||||
|
limit?: number,
|
||||||
|
offset?: number
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const res = await this.gql({
|
const res = await this.gql({
|
||||||
query: getCopilotRecentSessionsQuery,
|
query: getCopilotRecentSessionsQuery,
|
||||||
variables: {
|
variables: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
limit,
|
limit,
|
||||||
|
offset,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return res.currentUser?.copilot?.chats.edges.map(e => e.node);
|
return res.currentUser?.copilot?.chats.edges.map(e => e.node);
|
||||||
|
|||||||
@@ -589,8 +589,12 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
) => {
|
) => {
|
||||||
return client.getSessions(workspaceId, {}, docId, options);
|
return client.getSessions(workspaceId, {}, docId, options);
|
||||||
},
|
},
|
||||||
getRecentSessions: async (workspaceId: string, limit?: number) => {
|
getRecentSessions: async (
|
||||||
return client.getRecentSessions(workspaceId, limit);
|
workspaceId: string,
|
||||||
|
limit?: number,
|
||||||
|
offset?: number
|
||||||
|
) => {
|
||||||
|
return client.getRecentSessions(workspaceId, limit, offset);
|
||||||
},
|
},
|
||||||
updateSession: async (options: UpdateChatSessionInput) => {
|
updateSession: async (options: UpdateChatSessionInput) => {
|
||||||
return client.updateSession(options);
|
return client.updateSession(options);
|
||||||
|
|||||||
Reference in New Issue
Block a user