feat(core): add ai history loading placeholder (#13092)

Close [AI-324](https://linear.app/affine-design/issue/AI-324)

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

* **New Features**
* Added a loading state indicator to the chat panel, displaying a styled
message and icon while history is loading.
* Enhanced the session history component with clear loading and empty
state messages for improved user feedback.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Wu Yue
2025-07-08 17:39:53 +08:00
committed by GitHub
parent d2f016c628
commit d5c959a83f
2 changed files with 70 additions and 4 deletions

View File

@@ -18,6 +18,7 @@ import { css, html, nothing, type PropertyValues } from 'lit';
import { property, state } from 'lit/decorators.js';
import { keyed } from 'lit/directives/keyed.js';
import { AffineIcon } from '../_common/icons';
import type {
DocDisplayConfig,
SearchMenuConfig,
@@ -50,6 +51,28 @@ export class ChatPanel extends SignalWatcher(
color: var(--affine-text-secondary-color);
}
.chat-loading-container {
position: relative;
padding: 44px 0 166px 0;
height: 100%;
display: flex;
align-items: center;
}
.chat-loading {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.chat-loading-title {
font-weight: 600;
font-size: var(--affine-font-sm);
color: var(--affine-text-secondary-color);
}
.chat-panel-playground {
cursor: pointer;
padding: 2px;
@@ -394,7 +417,14 @@ export class ChatPanel extends SignalWatcher(
override render() {
if (!this.isInitialized) {
return nothing;
return html`<div class="chat-loading-container">
<div class="chat-loading">
${AffineIcon('var(--affine-icon-secondary)')}
<div class="chat-loading-title">
<span> AFFiNE AI is loading history... </span>
</div>
</div>
</div>`;
}
return html`<div class="chat-panel-container">

View File

@@ -30,6 +30,21 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
background: ${unsafeCSSVarV2('layer/background/overlayPanel')};
box-shadow: ${unsafeCSSVar('overlayPanelShadow')};
.loading-container,
.empty-container {
display: flex;
align-items: center;
justify-content: center;
height: 344px;
}
.loading-title,
.empty-title {
font-weight: 600;
font-size: var(--affine-font-sm);
color: var(--affine-text-secondary-color);
}
.ai-session-group {
display: flex;
flex-direction: column;
@@ -125,6 +140,9 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
@state()
private accessor sessions: BlockSuitePresets.AIRecentSession[] = [];
@state()
private accessor loading = true;
private groupSessionsByTime(
sessions: BlockSuitePresets.AIRecentSession[]
): GroupedSessions {
@@ -174,6 +192,7 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
}
private async getRecentSessions() {
this.loading = true;
const limit = 50;
const sessions = await AIProvider.session?.getRecentSessions(
this.workspaceId,
@@ -182,6 +201,7 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
if (sessions) {
this.sessions = sessions;
}
this.loading = false;
}
override connectedCallback() {
@@ -241,11 +261,27 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
}
override render() {
if (this.sessions.length === 0) {
return nothing;
if (this.loading) {
return html`
<div class="ai-session-history">
<div class="loading-container">
<div class="loading-title">Loading history...</div>
</div>
</div>
`;
}
const groupedSessions = this.groupSessionsByTime(this.sessions);
if (this.sessions.length === 0) {
return html`
<div class="ai-session-history">
<div class="empty-container">
<div class="empty-title">Empty history</div>
</div>
</div>
`;
}
const groupedSessions = this.groupSessionsByTime(this.sessions);
return html`
<div class="ai-session-history">
${this.renderSessionGroup('Today', groupedSessions.today)}