feat(core): support ai recent session history (#13025)
Close [AI-239](https://linear.app/affine-design/issue/AI-239) Close [AI-240](https://linear.app/affine-design/issue/AI-240) Close [AI-242](https://linear.app/affine-design/issue/AI-242) <img width="365" alt="截屏2025-07-04 13 49 25" src="https://github.com/user-attachments/assets/d7c830f0-cc16-4a26-baf1-480c7d42838f" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced a floating chat history menu, allowing users to view and switch between recent AI chat sessions grouped by recency. * Added a new component for displaying recent chat sessions with document icons and titles. * Enhanced chat toolbar with asynchronous confirmation dialogs before switching or creating sessions. * Added notification support for chat-related actions and history clearing. * Added ability to fetch and display recent AI chat sessions per workspace. * **Improvements** * Streamlined session management and event handling in the chat panel. * Improved embedding progress update and context change handling across chat components. * Refined UI for chat history, session switching, and notifications. * Updated chat components to use direct notification service injection for better user prompts and toasts. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -5,6 +5,7 @@ import type {
|
|||||||
CopilotContextCategory,
|
CopilotContextCategory,
|
||||||
CopilotContextDoc,
|
CopilotContextDoc,
|
||||||
CopilotContextFile,
|
CopilotContextFile,
|
||||||
|
CopilotHistories,
|
||||||
CopilotSessionType,
|
CopilotSessionType,
|
||||||
getCopilotHistoriesQuery,
|
getCopilotHistoriesQuery,
|
||||||
QueryChatSessionsInput,
|
QueryChatSessionsInput,
|
||||||
@@ -387,6 +388,8 @@ declare global {
|
|||||||
reuseLatestChat?: boolean;
|
reuseLatestChat?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AIRecentSession = Omit<CopilotHistories, 'messages'>;
|
||||||
|
|
||||||
interface AISessionService {
|
interface AISessionService {
|
||||||
createSession: (options: AICreateSessionOptions) => Promise<string>;
|
createSession: (options: AICreateSessionOptions) => Promise<string>;
|
||||||
getSessions: (
|
getSessions: (
|
||||||
@@ -394,6 +397,10 @@ declare global {
|
|||||||
docId?: string,
|
docId?: string,
|
||||||
options?: QueryChatSessionsInput
|
options?: QueryChatSessionsInput
|
||||||
) => Promise<CopilotSessionType[] | undefined>;
|
) => Promise<CopilotSessionType[] | undefined>;
|
||||||
|
getRecentSessions: (
|
||||||
|
workspaceId: string,
|
||||||
|
limit?: number
|
||||||
|
) => Promise<AIRecentSession[] | undefined>;
|
||||||
getSession: (
|
getSession: (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
sessionId: string
|
sessionId: string
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type {
|
|||||||
UpdateChatSessionInput,
|
UpdateChatSessionInput,
|
||||||
} from '@affine/graphql';
|
} from '@affine/graphql';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
|
import { NotificationProvider } from '@blocksuite/affine/shared/services';
|
||||||
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import type { EditorHost } from '@blocksuite/affine/std';
|
import type { EditorHost } from '@blocksuite/affine/std';
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
@@ -27,6 +28,7 @@ import type {
|
|||||||
DocDisplayConfig,
|
DocDisplayConfig,
|
||||||
SearchMenuConfig,
|
SearchMenuConfig,
|
||||||
} from '../components/ai-chat-chips';
|
} from '../components/ai-chat-chips';
|
||||||
|
import type { ChatContextValue } from '../components/ai-chat-content';
|
||||||
import type {
|
import type {
|
||||||
AINetworkSearchConfig,
|
AINetworkSearchConfig,
|
||||||
AIPlaygroundConfig,
|
AIPlaygroundConfig,
|
||||||
@@ -125,6 +127,38 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
return this.session !== undefined;
|
return this.session !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get chatTitle() {
|
||||||
|
const [done, total] = this.embeddingProgress;
|
||||||
|
const isEmbedding = total > 0 && done < total;
|
||||||
|
const notification = this.host.std.getOptional(NotificationProvider);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="chat-panel-title-text">
|
||||||
|
${isEmbedding
|
||||||
|
? html`<span data-testid="chat-panel-embedding-progress"
|
||||||
|
>Embedding ${done}/${total}</span
|
||||||
|
>`
|
||||||
|
: 'AFFiNE AI'}
|
||||||
|
</div>
|
||||||
|
${this.playgroundConfig.visible.value
|
||||||
|
? html`
|
||||||
|
<div class="chat-panel-playground" @click=${this.openPlayground}>
|
||||||
|
${CenterPeekIcon()}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
<ai-chat-toolbar
|
||||||
|
.session=${this.session}
|
||||||
|
.workspaceId=${this.doc.workspace.id}
|
||||||
|
.onNewSession=${this.newSession}
|
||||||
|
.onTogglePin=${this.togglePin}
|
||||||
|
.onOpenSession=${this.openSession}
|
||||||
|
.docDisplayConfig=${this.docDisplayConfig}
|
||||||
|
.notification=${notification}
|
||||||
|
></ai-chat-toolbar>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly initSession = async () => {
|
private readonly initSession = async () => {
|
||||||
if (!AIProvider.session) {
|
if (!AIProvider.session) {
|
||||||
return;
|
return;
|
||||||
@@ -132,7 +166,7 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
const pinSessions = await AIProvider.session.getSessions(
|
const pinSessions = await AIProvider.session.getSessions(
|
||||||
this.doc.workspace.id,
|
this.doc.workspace.id,
|
||||||
undefined,
|
undefined,
|
||||||
{ pinned: true }
|
{ pinned: true, limit: 1 }
|
||||||
);
|
);
|
||||||
if (Array.isArray(pinSessions) && pinSessions[0]) {
|
if (Array.isArray(pinSessions) && pinSessions[0]) {
|
||||||
this.session = pinSessions[0];
|
this.session = pinSessions[0];
|
||||||
@@ -140,7 +174,7 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
const docSessions = await AIProvider.session.getSessions(
|
const docSessions = await AIProvider.session.getSessions(
|
||||||
this.doc.workspace.id,
|
this.doc.workspace.id,
|
||||||
this.doc.id,
|
this.doc.id,
|
||||||
{ action: false, fork: false }
|
{ action: false, fork: false, limit: 1 }
|
||||||
);
|
);
|
||||||
// the first item is the latest session
|
// the first item is the latest session
|
||||||
this.session = docSessions?.[0] ?? null;
|
this.session = docSessions?.[0] ?? null;
|
||||||
@@ -186,6 +220,15 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly openSession = async (sessionId: string) => {
|
||||||
|
this.resetPanel();
|
||||||
|
const session = await AIProvider.session?.getSession(
|
||||||
|
this.doc.workspace.id,
|
||||||
|
sessionId
|
||||||
|
);
|
||||||
|
this.session = session ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
private readonly togglePin = async () => {
|
private readonly togglePin = async () => {
|
||||||
const pinned = !this.session?.pinned;
|
const pinned = !this.session?.pinned;
|
||||||
this.hasPinned = true;
|
this.hasPinned = true;
|
||||||
@@ -199,6 +242,18 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly rebindSession = async () => {
|
||||||
|
if (!this.session) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.session.docId !== this.doc.id) {
|
||||||
|
await this.updateSession({
|
||||||
|
sessionId: this.session.id,
|
||||||
|
docId: this.doc.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private readonly initPanel = async () => {
|
private readonly initPanel = async () => {
|
||||||
try {
|
try {
|
||||||
if (!this.isSidebarOpen.value) {
|
if (!this.isSidebarOpen.value) {
|
||||||
@@ -218,13 +273,21 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
this.hasPinned = false;
|
this.hasPinned = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly updateEmbeddingProgress = (
|
private readonly onEmbeddingProgressChange = (
|
||||||
count: Record<ContextEmbedStatus, number>
|
count: Record<ContextEmbedStatus, number>
|
||||||
) => {
|
) => {
|
||||||
const total = count.finished + count.processing + count.failed;
|
const total = count.finished + count.processing + count.failed;
|
||||||
this.embeddingProgress = [count.finished, total];
|
this.embeddingProgress = [count.finished, total];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly onContextChange = async (
|
||||||
|
context: Partial<ChatContextValue>
|
||||||
|
) => {
|
||||||
|
if (context.status === 'success') {
|
||||||
|
await this.rebindSession();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private readonly openPlayground = () => {
|
private readonly openPlayground = () => {
|
||||||
const playgroundContent = html`
|
const playgroundContent = html`
|
||||||
<playground-content
|
<playground-content
|
||||||
@@ -291,35 +354,12 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
const style = styleMap({
|
const style = styleMap({
|
||||||
padding: width > 540 ? '8px 24px 0 24px' : '8px 12px 0 12px',
|
padding: width > 540 ? '8px 24px 0 24px' : '8px 12px 0 12px',
|
||||||
});
|
});
|
||||||
const [done, total] = this.embeddingProgress;
|
|
||||||
const isEmbedding = total > 0 && done < total;
|
|
||||||
const title = html`
|
|
||||||
<div class="chat-panel-title-text">
|
|
||||||
${isEmbedding
|
|
||||||
? html`<span data-testid="chat-panel-embedding-progress"
|
|
||||||
>Embedding ${done}/${total}</span
|
|
||||||
>`
|
|
||||||
: 'AFFiNE AI'}
|
|
||||||
</div>
|
|
||||||
${this.playgroundConfig.visible.value
|
|
||||||
? html`
|
|
||||||
<div class="chat-panel-playground" @click=${this.openPlayground}>
|
|
||||||
${CenterPeekIcon()}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
<ai-chat-toolbar
|
|
||||||
.session=${this.session}
|
|
||||||
.onNewSession=${this.newSession}
|
|
||||||
.onTogglePin=${this.togglePin}
|
|
||||||
></ai-chat-toolbar>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const left = html`<div class="chat-panel-container" style=${style}>
|
const left = html`<div class="chat-panel-container" style=${style}>
|
||||||
${keyed(
|
${keyed(
|
||||||
this.hasPinned ? this.session?.id : this.doc.id,
|
this.hasPinned ? this.session?.id : this.doc.id,
|
||||||
html`<ai-chat-content
|
html`<ai-chat-content
|
||||||
.chatTitle=${title}
|
.chatTitle=${this.chatTitle}
|
||||||
.host=${this.host}
|
.host=${this.host}
|
||||||
.session=${this.session}
|
.session=${this.session}
|
||||||
.createSession=${this.createSession}
|
.createSession=${this.createSession}
|
||||||
@@ -332,7 +372,8 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
.extensions=${this.extensions}
|
.extensions=${this.extensions}
|
||||||
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
||||||
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
|
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
|
||||||
.updateEmbeddingProgress=${this.updateEmbeddingProgress}
|
.onEmbeddingProgressChange=${this.onEmbeddingProgressChange}
|
||||||
|
.onContextChange=${this.onContextChange}
|
||||||
.width=${this.sidebarWidth}
|
.width=${this.sidebarWidth}
|
||||||
></ai-chat-content>`
|
></ai-chat-content>`
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export class AIChatComposer extends SignalWatcher(
|
|||||||
accessor updateContext!: (context: Partial<AIChatInputContext>) => void;
|
accessor updateContext!: (context: Partial<AIChatInputContext>) => void;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor updateEmbeddingProgress!: (
|
accessor onEmbeddingProgressChange!: (
|
||||||
count: Record<ContextEmbedStatus, number>
|
count: Record<ContextEmbedStatus, number>
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
@@ -382,7 +382,7 @@ export class AIChatComposer extends SignalWatcher(
|
|||||||
return chip;
|
return chip;
|
||||||
});
|
});
|
||||||
this.updateChips(nextChips);
|
this.updateChips(nextChips);
|
||||||
this.updateEmbeddingProgress(count);
|
this.onEmbeddingProgressChange(count);
|
||||||
if (count.processing === 0) {
|
if (count.processing === 0) {
|
||||||
this._abortPoll();
|
this._abortPoll();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,10 +136,13 @@ export class AIChatContent extends SignalWatcher(
|
|||||||
accessor affineWorkspaceDialogService!: WorkspaceDialogService;
|
accessor affineWorkspaceDialogService!: WorkspaceDialogService;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor updateEmbeddingProgress!: (
|
accessor onEmbeddingProgressChange!: (
|
||||||
count: Record<ContextEmbedStatus, number>
|
count: Record<ContextEmbedStatus, number>
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor onContextChange!: (context: Partial<ChatContextValue>) => void;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor width: Signal<number | undefined> | undefined;
|
accessor width: Signal<number | undefined> | undefined;
|
||||||
|
|
||||||
@@ -177,14 +180,9 @@ export class AIChatContent extends SignalWatcher(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sessionId = this.session?.id;
|
const sessionId = this.session?.id;
|
||||||
const pinned = this.session?.pinned;
|
|
||||||
const [histories, actions] = await Promise.all([
|
const [histories, actions] = await Promise.all([
|
||||||
sessionId
|
sessionId
|
||||||
? AIProvider.histories.chats(
|
? AIProvider.histories.chats(this.workspaceId, sessionId)
|
||||||
this.workspaceId,
|
|
||||||
sessionId,
|
|
||||||
pinned ? undefined : this.docId
|
|
||||||
)
|
|
||||||
: Promise.resolve([]),
|
: Promise.resolve([]),
|
||||||
this.docId
|
this.docId
|
||||||
? AIProvider.histories.actions(this.workspaceId, this.docId)
|
? AIProvider.histories.actions(this.workspaceId, this.docId)
|
||||||
@@ -245,6 +243,7 @@ export class AIChatContent extends SignalWatcher(
|
|||||||
|
|
||||||
private readonly updateContext = (context: Partial<ChatContextValue>) => {
|
private readonly updateContext = (context: Partial<ChatContextValue>) => {
|
||||||
this.chatContextValue = { ...this.chatContextValue, ...context };
|
this.chatContextValue = { ...this.chatContextValue, ...context };
|
||||||
|
this.onContextChange?.(context);
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly scrollToEnd = () => {
|
private readonly scrollToEnd = () => {
|
||||||
@@ -389,7 +388,7 @@ export class AIChatContent extends SignalWatcher(
|
|||||||
.createSession=${this.createSession}
|
.createSession=${this.createSession}
|
||||||
.chatContextValue=${this.chatContextValue}
|
.chatContextValue=${this.chatContextValue}
|
||||||
.updateContext=${this.updateContext}
|
.updateContext=${this.updateContext}
|
||||||
.updateEmbeddingProgress=${this.updateEmbeddingProgress}
|
.onEmbeddingProgressChange=${this.onEmbeddingProgressChange}
|
||||||
.networkSearchConfig=${this.networkSearchConfig}
|
.networkSearchConfig=${this.networkSearchConfig}
|
||||||
.reasoningConfig=${this.reasoningConfig}
|
.reasoningConfig=${this.reasoningConfig}
|
||||||
.docDisplayConfig=${this.docDisplayConfig}
|
.docDisplayConfig=${this.docDisplayConfig}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { CopilotSessionType } from '@affine/graphql';
|
import type { CopilotSessionType } from '@affine/graphql';
|
||||||
|
import { createLitPortal } from '@blocksuite/affine/components/portal';
|
||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
|
import type { NotificationService } from '@blocksuite/affine/shared/services';
|
||||||
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
import {
|
import {
|
||||||
@@ -8,18 +10,38 @@ import {
|
|||||||
PinIcon,
|
PinIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
} from '@blocksuite/icons/lit';
|
} from '@blocksuite/icons/lit';
|
||||||
|
import { flip, offset } from '@floating-ui/dom';
|
||||||
import { css, html } from 'lit';
|
import { css, html } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property, query } from 'lit/decorators.js';
|
||||||
|
|
||||||
|
import type { DocDisplayConfig } from '../ai-chat-chips';
|
||||||
|
|
||||||
export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
|
export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor session!: CopilotSessionType | null | undefined;
|
accessor session!: CopilotSessionType | null | undefined;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor workspaceId!: string;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor onNewSession!: () => void;
|
accessor onNewSession!: () => void;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor onTogglePin!: () => void;
|
accessor onTogglePin!: () => Promise<void>;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor onOpenSession!: (sessionId: string) => void;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor docDisplayConfig!: DocDisplayConfig;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor notification: NotificationService | null | undefined;
|
||||||
|
|
||||||
|
@query('.history-button')
|
||||||
|
accessor historyButton!: HTMLDivElement;
|
||||||
|
|
||||||
|
private abortController: AbortController | null = null;
|
||||||
|
|
||||||
static override styles = css`
|
static override styles = css`
|
||||||
.ai-chat-toolbar {
|
.ai-chat-toolbar {
|
||||||
@@ -49,24 +71,100 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
const pined = this.session?.pinned;
|
const pinned = this.session?.pinned;
|
||||||
return html`
|
return html`
|
||||||
<div class="ai-chat-toolbar">
|
<div class="ai-chat-toolbar">
|
||||||
<div class="chat-toolbar-icon" @click=${this.onNewSession}>
|
<div class="chat-toolbar-icon" @click=${this.onPlusClick}>
|
||||||
${PlusIcon()}
|
${PlusIcon()}
|
||||||
<affine-tooltip>New Chat</affine-tooltip>
|
<affine-tooltip>New Chat</affine-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-toolbar-icon" @click=${this.onTogglePin}>
|
<div class="chat-toolbar-icon" @click=${this.onTogglePin}>
|
||||||
${pined ? PinedIcon() : PinIcon()}
|
${pinned ? PinedIcon() : PinIcon()}
|
||||||
<affine-tooltip
|
<affine-tooltip
|
||||||
>${pined ? 'Unpin this Chat' : 'Pin this Chat'}</affine-tooltip
|
>${pinned ? 'Unpin this Chat' : 'Pin this Chat'}</affine-tooltip
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-toolbar-icon">
|
<div
|
||||||
|
class="chat-toolbar-icon history-button"
|
||||||
|
@click=${this.toggleHistoryMenu}
|
||||||
|
>
|
||||||
${ArrowDownSmallIcon()}
|
${ArrowDownSmallIcon()}
|
||||||
<affine-tooltip>Chat History</affine-tooltip>
|
<affine-tooltip>Chat History</affine-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly unpinConfirm = async () => {
|
||||||
|
if (this.session && this.session.pinned) {
|
||||||
|
try {
|
||||||
|
const confirm = this.notification
|
||||||
|
? await this.notification.confirm({
|
||||||
|
title: 'Switch Chat? Current chat is pinned',
|
||||||
|
message:
|
||||||
|
'Switching will unpinned the current chat. This will change the active chat panel, allowing you to navigate between different conversation histories.',
|
||||||
|
confirmText: 'Switch Chat',
|
||||||
|
cancelText: 'Cancel',
|
||||||
|
})
|
||||||
|
: true;
|
||||||
|
if (!confirm) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
await this.onTogglePin();
|
||||||
|
} catch {
|
||||||
|
this.notification?.toast('Failed to unpin the chat');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly onPlusClick = async () => {
|
||||||
|
const confirm = await this.unpinConfirm();
|
||||||
|
if (confirm) {
|
||||||
|
this.onNewSession();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly onSessionClick = async (sessionId: string) => {
|
||||||
|
const confirm = await this.unpinConfirm();
|
||||||
|
if (confirm) {
|
||||||
|
this.onOpenSession(sessionId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly toggleHistoryMenu = () => {
|
||||||
|
if (this.abortController) {
|
||||||
|
this.abortController.abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.abortController = new AbortController();
|
||||||
|
this.abortController.signal.addEventListener('abort', () => {
|
||||||
|
this.abortController = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
createLitPortal({
|
||||||
|
template: html`
|
||||||
|
<ai-session-history
|
||||||
|
.session=${this.session}
|
||||||
|
.workspaceId=${this.workspaceId}
|
||||||
|
.docDisplayConfig=${this.docDisplayConfig}
|
||||||
|
.onSessionClick=${this.onSessionClick}
|
||||||
|
.notification=${this.notification}
|
||||||
|
></ai-session-history>
|
||||||
|
`,
|
||||||
|
portalStyles: {
|
||||||
|
zIndex: 'var(--affine-z-index-popover)',
|
||||||
|
},
|
||||||
|
container: document.body,
|
||||||
|
computePosition: {
|
||||||
|
referenceElement: this.historyButton,
|
||||||
|
placement: 'bottom-end',
|
||||||
|
middleware: [offset({ crossAxis: 0, mainAxis: 5 }), flip()],
|
||||||
|
autoUpdate: { animationFrame: true },
|
||||||
|
},
|
||||||
|
abortController: this.abortController,
|
||||||
|
closeOnClickAway: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,232 @@
|
|||||||
|
import type { CopilotSessionType } from '@affine/graphql';
|
||||||
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
|
import type { NotificationService } from '@blocksuite/affine/shared/services';
|
||||||
|
import { scrollbarStyle } from '@blocksuite/affine/shared/styles';
|
||||||
|
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
|
import { css, html, nothing } from 'lit';
|
||||||
|
import { property, state } from 'lit/decorators.js';
|
||||||
|
|
||||||
|
import { AIProvider } from '../../provider';
|
||||||
|
import type { DocDisplayConfig } from '../ai-chat-chips';
|
||||||
|
|
||||||
|
interface GroupedSessions {
|
||||||
|
today: BlockSuitePresets.AIRecentSession[];
|
||||||
|
last7Days: BlockSuitePresets.AIRecentSession[];
|
||||||
|
last30Days: BlockSuitePresets.AIRecentSession[];
|
||||||
|
older: BlockSuitePresets.AIRecentSession[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AISessionHistory extends WithDisposable(ShadowlessElement) {
|
||||||
|
static override styles = css`
|
||||||
|
.ai-session-history {
|
||||||
|
width: 316px;
|
||||||
|
max-height: 344px;
|
||||||
|
padding: 12px 8px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 0.5px solid ${unsafeCSSVarV2('layer/insideBorder/border')};
|
||||||
|
background: ${unsafeCSSVarV2('layer/background/primary')};
|
||||||
|
border-radius: 4px;
|
||||||
|
background: ${unsafeCSSVarV2('layer/background/overlayPanel')};
|
||||||
|
box-shadow: ${unsafeCSSVar('overlayPanelShadow')};
|
||||||
|
|
||||||
|
.ai-session-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-session-group-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
height: 20px;
|
||||||
|
color: ${unsafeCSSVarV2('text/secondary')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-session-item {
|
||||||
|
display: flex;
|
||||||
|
height: 24px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-session-item:hover {
|
||||||
|
background: ${unsafeCSSVarV2('layer/background/hoverOverlay')};
|
||||||
|
border-color: ${unsafeCSSVarV2('layer/insideBorder/border')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-session-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
color: ${unsafeCSSVarV2('text/primary')};
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-session-doc {
|
||||||
|
display: flex;
|
||||||
|
width: 120px;
|
||||||
|
padding: 0px 4px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
color: ${unsafeCSSVarV2('icon/primary')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
color: ${unsafeCSSVarV2('text/secondary')};
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${scrollbarStyle('.ai-session-history')}
|
||||||
|
`;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor session!: CopilotSessionType | null | undefined;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor workspaceId!: string;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor docDisplayConfig!: DocDisplayConfig;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor onSessionClick!: (sessionId: string) => void;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor notification: NotificationService | null | undefined;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private accessor sessions: BlockSuitePresets.AIRecentSession[] = [];
|
||||||
|
|
||||||
|
private groupSessionsByTime(
|
||||||
|
sessions: BlockSuitePresets.AIRecentSession[]
|
||||||
|
): GroupedSessions {
|
||||||
|
const now = new Date();
|
||||||
|
const todayStart = new Date(
|
||||||
|
now.getFullYear(),
|
||||||
|
now.getMonth(),
|
||||||
|
now.getDate()
|
||||||
|
);
|
||||||
|
const last7DaysStart = new Date(
|
||||||
|
todayStart.getTime() - 6 * 24 * 60 * 60 * 1000
|
||||||
|
);
|
||||||
|
const last30DaysStart = new Date(
|
||||||
|
todayStart.getTime() - 29 * 24 * 60 * 60 * 1000
|
||||||
|
);
|
||||||
|
|
||||||
|
const grouped: GroupedSessions = {
|
||||||
|
today: [],
|
||||||
|
last7Days: [],
|
||||||
|
last30Days: [],
|
||||||
|
older: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
sessions.forEach(session => {
|
||||||
|
const updatedAt = new Date(session.updatedAt);
|
||||||
|
|
||||||
|
if (updatedAt >= todayStart) {
|
||||||
|
grouped.today.push(session);
|
||||||
|
} else if (updatedAt >= last7DaysStart) {
|
||||||
|
grouped.last7Days.push(session);
|
||||||
|
} else if (updatedAt >= last30DaysStart) {
|
||||||
|
grouped.last30Days.push(session);
|
||||||
|
} else {
|
||||||
|
grouped.older.push(session);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort each group by updatedAt in descending order (newest first)
|
||||||
|
(Object.keys(grouped) as Array<keyof GroupedSessions>).forEach(key => {
|
||||||
|
grouped[key].sort(
|
||||||
|
(a, b) =>
|
||||||
|
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return grouped;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getRecentSessions() {
|
||||||
|
const limit = 50;
|
||||||
|
const sessions = await AIProvider.session?.getRecentSessions(
|
||||||
|
this.workspaceId,
|
||||||
|
limit
|
||||||
|
);
|
||||||
|
if (sessions) {
|
||||||
|
this.sessions = sessions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.getRecentSessions().catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderSessionGroup(
|
||||||
|
title: string,
|
||||||
|
sessions: BlockSuitePresets.AIRecentSession[]
|
||||||
|
) {
|
||||||
|
if (sessions.length === 0) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<div class="ai-session-group">
|
||||||
|
<div class="ai-session-group-title">${title}</div>
|
||||||
|
${sessions.map(session => {
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="ai-session-item"
|
||||||
|
@click=${() => this.onSessionClick(session.sessionId)}
|
||||||
|
>
|
||||||
|
<div class="ai-session-title">${session.sessionId}</div>
|
||||||
|
${session.docId ? this.renderSessionDoc(session.docId) : nothing}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderSessionDoc(docId: string) {
|
||||||
|
const getIcon = this.docDisplayConfig.getIcon(docId);
|
||||||
|
const docIcon = typeof getIcon === 'function' ? getIcon() : getIcon;
|
||||||
|
return html`<div class="ai-session-doc">
|
||||||
|
${docIcon}
|
||||||
|
<span class="doc-title">${this.docDisplayConfig.getTitle(docId)}</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
override render() {
|
||||||
|
if (this.sessions.length === 0) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
const groupedSessions = this.groupSessionsByTime(this.sessions);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="ai-session-history">
|
||||||
|
${this.renderSessionGroup('Today', groupedSessions.today)}
|
||||||
|
${this.renderSessionGroup('Last 7 days', groupedSessions.last7Days)}
|
||||||
|
${this.renderSessionGroup('Last 30 days', groupedSessions.last30Days)}
|
||||||
|
${this.renderSessionGroup('Older', groupedSessions.older)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
export * from './ai-chat-toolbar';
|
export * from './ai-chat-toolbar';
|
||||||
|
export * from './ai-session-history';
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import type { CopilotSessionType } from '@affine/graphql';
|
import type { CopilotSessionType } from '@affine/graphql';
|
||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import { NotificationProvider } from '@blocksuite/affine/shared/services';
|
import { type NotificationService } from '@blocksuite/affine/shared/services';
|
||||||
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import type { EditorHost } from '@blocksuite/affine/std';
|
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
import type { Store } from '@blocksuite/affine/store';
|
import type { Store } from '@blocksuite/affine/store';
|
||||||
import { css, html } from 'lit';
|
import { css, html } from 'lit';
|
||||||
@@ -19,7 +18,7 @@ export class AIHistoryClear extends WithDisposable(ShadowlessElement) {
|
|||||||
accessor session!: CopilotSessionType | null | undefined;
|
accessor session!: CopilotSessionType | null | undefined;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor host: EditorHost | null | undefined;
|
accessor notification: NotificationService | null | undefined;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor doc!: Store;
|
accessor doc!: Store;
|
||||||
@@ -52,10 +51,9 @@ export class AIHistoryClear extends WithDisposable(ShadowlessElement) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const sessionId = this.session.id;
|
const sessionId = this.session.id;
|
||||||
const notification = this.host?.std.getOptional(NotificationProvider);
|
|
||||||
try {
|
try {
|
||||||
const confirm = notification
|
const confirm = this.notification
|
||||||
? await notification.confirm({
|
? await this.notification.confirm({
|
||||||
title: 'Clear History',
|
title: 'Clear History',
|
||||||
message:
|
message:
|
||||||
'Are you sure you want to clear all history? This action will permanently delete all content, including all chat logs and data, and cannot be undone.',
|
'Are you sure you want to clear all history? This action will permanently delete all content, including all chat logs and data, and cannot be undone.',
|
||||||
@@ -73,11 +71,11 @@ export class AIHistoryClear extends WithDisposable(ShadowlessElement) {
|
|||||||
this.doc.id,
|
this.doc.id,
|
||||||
[...(sessionId ? [sessionId] : []), ...(actionIds || [])]
|
[...(sessionId ? [sessionId] : []), ...(actionIds || [])]
|
||||||
);
|
);
|
||||||
notification?.toast('History cleared');
|
this.notification?.toast('History cleared');
|
||||||
this.onHistoryCleared?.();
|
this.onHistoryCleared?.();
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
notification?.toast('Failed to clear history');
|
this.notification?.toast('Failed to clear history');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
import type { ContextEmbedStatus, CopilotSessionType } from '@affine/graphql';
|
import type { ContextEmbedStatus, CopilotSessionType } from '@affine/graphql';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
|
import { NotificationProvider } from '@blocksuite/affine/shared/services';
|
||||||
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import type { EditorHost } from '@blocksuite/affine/std';
|
import type { EditorHost } from '@blocksuite/affine/std';
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
@@ -229,7 +230,7 @@ export class PlaygroundChat extends SignalWatcher(
|
|||||||
this._scrollToEnd();
|
this._scrollToEnd();
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _updateEmbeddingProgress = (
|
private readonly onEmbeddingProgressChange = (
|
||||||
count: Record<ContextEmbedStatus, number>
|
count: Record<ContextEmbedStatus, number>
|
||||||
) => {
|
) => {
|
||||||
const total = count.finished + count.processing + count.failed;
|
const total = count.finished + count.processing + count.failed;
|
||||||
@@ -272,6 +273,7 @@ export class PlaygroundChat extends SignalWatcher(
|
|||||||
override render() {
|
override render() {
|
||||||
const [done, total] = this.embeddingProgress;
|
const [done, total] = this.embeddingProgress;
|
||||||
const isEmbedding = total > 0 && done < total;
|
const isEmbedding = total > 0 && done < total;
|
||||||
|
const notification = this.host.std.getOptional(NotificationProvider);
|
||||||
|
|
||||||
return html`<div class="chat-panel-container">
|
return html`<div class="chat-panel-container">
|
||||||
<div class="chat-panel-title">
|
<div class="chat-panel-title">
|
||||||
@@ -287,9 +289,9 @@ export class PlaygroundChat extends SignalWatcher(
|
|||||||
<affine-tooltip>Add chat</affine-tooltip>
|
<affine-tooltip>Add chat</affine-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<ai-history-clear
|
<ai-history-clear
|
||||||
.host=${this.host}
|
|
||||||
.doc=${this.doc}
|
.doc=${this.doc}
|
||||||
.session=${this.session}
|
.session=${this.session}
|
||||||
|
.notification=${notification}
|
||||||
.onHistoryCleared=${this._updateHistory}
|
.onHistoryCleared=${this._updateHistory}
|
||||||
.chatContextValue=${this.chatContextValue}
|
.chatContextValue=${this.chatContextValue}
|
||||||
></ai-history-clear>
|
></ai-history-clear>
|
||||||
@@ -318,7 +320,7 @@ export class PlaygroundChat extends SignalWatcher(
|
|||||||
.createSession=${this._createSession}
|
.createSession=${this._createSession}
|
||||||
.chatContextValue=${this.chatContextValue}
|
.chatContextValue=${this.chatContextValue}
|
||||||
.updateContext=${this.updateContext}
|
.updateContext=${this.updateContext}
|
||||||
.updateEmbeddingProgress=${this._updateEmbeddingProgress}
|
.onEmbeddingProgressChange=${this.onEmbeddingProgressChange}
|
||||||
.networkSearchConfig=${this.networkSearchConfig}
|
.networkSearchConfig=${this.networkSearchConfig}
|
||||||
.reasoningConfig=${this.reasoningConfig}
|
.reasoningConfig=${this.reasoningConfig}
|
||||||
.playgroundConfig=${this.playgroundConfig}
|
.playgroundConfig=${this.playgroundConfig}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import { AIChatInput } from './components/ai-chat-input';
|
|||||||
import { AIChatEmbeddingStatusTooltip } from './components/ai-chat-input/embedding-status-tooltip';
|
import { AIChatEmbeddingStatusTooltip } from './components/ai-chat-input/embedding-status-tooltip';
|
||||||
import { ChatInputPreference } from './components/ai-chat-input/preference-popup';
|
import { ChatInputPreference } from './components/ai-chat-input/preference-popup';
|
||||||
import { AIChatMessages } from './components/ai-chat-messages/ai-chat-messages';
|
import { AIChatMessages } from './components/ai-chat-messages/ai-chat-messages';
|
||||||
import { AIChatToolbar } from './components/ai-chat-toolbar';
|
import { AIChatToolbar, AISessionHistory } from './components/ai-chat-toolbar';
|
||||||
import { AIHistoryClear } from './components/ai-history-clear';
|
import { AIHistoryClear } from './components/ai-history-clear';
|
||||||
import { effects as componentAiItemEffects } from './components/ai-item';
|
import { effects as componentAiItemEffects } from './components/ai-item';
|
||||||
import { AssistantAvatar } from './components/ai-message-content/assistant-avatar';
|
import { AssistantAvatar } from './components/ai-message-content/assistant-avatar';
|
||||||
@@ -115,6 +115,7 @@ export function registerAIEffects() {
|
|||||||
customElements.define('ai-loading', AILoading);
|
customElements.define('ai-loading', AILoading);
|
||||||
customElements.define('ai-chat-content', AIChatContent);
|
customElements.define('ai-chat-content', AIChatContent);
|
||||||
customElements.define('ai-chat-toolbar', AIChatToolbar);
|
customElements.define('ai-chat-toolbar', AIChatToolbar);
|
||||||
|
customElements.define('ai-session-history', AISessionHistory);
|
||||||
customElements.define('ai-chat-messages', AIChatMessages);
|
customElements.define('ai-chat-messages', AIChatMessages);
|
||||||
customElements.define('chat-panel', ChatPanel);
|
customElements.define('chat-panel', ChatPanel);
|
||||||
customElements.define('ai-chat-input', AIChatInput);
|
customElements.define('ai-chat-input', AIChatInput);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { ViewExtensionManagerIdentifier } from '@blocksuite/affine/ext-loader';
|
|||||||
import { ConnectorMode } from '@blocksuite/affine/model';
|
import { ConnectorMode } from '@blocksuite/affine/model';
|
||||||
import {
|
import {
|
||||||
DocModeProvider,
|
DocModeProvider,
|
||||||
|
NotificationProvider,
|
||||||
TelemetryProvider,
|
TelemetryProvider,
|
||||||
} from '@blocksuite/affine/shared/services';
|
} from '@blocksuite/affine/shared/services';
|
||||||
import type { EditorHost } from '@blocksuite/affine/std';
|
import type { EditorHost } from '@blocksuite/affine/std';
|
||||||
@@ -311,7 +312,7 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
this.chatContext = { ...this.chatContext, ...context };
|
this.chatContext = { ...this.chatContext, ...context };
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _updateEmbeddingProgress = (
|
private readonly onEmbeddingProgressChange = (
|
||||||
count: Record<ContextEmbedStatus, number>
|
count: Record<ContextEmbedStatus, number>
|
||||||
) => {
|
) => {
|
||||||
const total = count.finished + count.processing + count.failed;
|
const total = count.finished + count.processing + count.failed;
|
||||||
@@ -565,15 +566,16 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
} = this;
|
} = this;
|
||||||
|
|
||||||
const { messages: currentChatMessages } = chatContext;
|
const { messages: currentChatMessages } = chatContext;
|
||||||
|
const notification = this.host.std.getOptional(NotificationProvider);
|
||||||
|
|
||||||
return html`<div class="ai-chat-block-peek-view-container">
|
return html`<div class="ai-chat-block-peek-view-container">
|
||||||
<div class="history-clear-container">
|
<div class="history-clear-container">
|
||||||
<ai-history-clear
|
<ai-history-clear
|
||||||
.host=${this.host}
|
|
||||||
.doc=${this.host.store}
|
.doc=${this.host.store}
|
||||||
.session=${this.forkSession}
|
.session=${this.forkSession}
|
||||||
.onHistoryCleared=${this._onHistoryCleared}
|
.onHistoryCleared=${this._onHistoryCleared}
|
||||||
.chatContextValue=${chatContext}
|
.chatContextValue=${chatContext}
|
||||||
|
.notification=${notification}
|
||||||
></ai-history-clear>
|
></ai-history-clear>
|
||||||
</div>
|
</div>
|
||||||
<div class="ai-chat-messages-container">
|
<div class="ai-chat-messages-container">
|
||||||
@@ -595,7 +597,7 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
.createSession=${this.createForkSession}
|
.createSession=${this.createForkSession}
|
||||||
.chatContextValue=${chatContext}
|
.chatContextValue=${chatContext}
|
||||||
.updateContext=${updateContext}
|
.updateContext=${updateContext}
|
||||||
.updateEmbeddingProgress=${this._updateEmbeddingProgress}
|
.onEmbeddingProgressChange=${this.onEmbeddingProgressChange}
|
||||||
.networkSearchConfig=${networkSearchConfig}
|
.networkSearchConfig=${networkSearchConfig}
|
||||||
.docDisplayConfig=${this.docDisplayConfig}
|
.docDisplayConfig=${this.docDisplayConfig}
|
||||||
.searchMenuConfig=${this.searchMenuConfig}
|
.searchMenuConfig=${this.searchMenuConfig}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
forkCopilotSessionMutation,
|
forkCopilotSessionMutation,
|
||||||
getCopilotHistoriesQuery,
|
getCopilotHistoriesQuery,
|
||||||
getCopilotHistoryIdsQuery,
|
getCopilotHistoryIdsQuery,
|
||||||
|
getCopilotRecentSessionsQuery,
|
||||||
getCopilotSessionQuery,
|
getCopilotSessionQuery,
|
||||||
getCopilotSessionsQuery,
|
getCopilotSessionsQuery,
|
||||||
getWorkspaceEmbeddingStatusQuery,
|
getWorkspaceEmbeddingStatusQuery,
|
||||||
@@ -179,6 +180,21 @@ export class CopilotClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getRecentSessions(workspaceId: string, limit?: number) {
|
||||||
|
try {
|
||||||
|
const res = await this.gql({
|
||||||
|
query: getCopilotRecentSessionsQuery,
|
||||||
|
variables: {
|
||||||
|
workspaceId,
|
||||||
|
limit,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return res.currentUser?.copilot?.histories;
|
||||||
|
} catch (err) {
|
||||||
|
throw resolveError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getHistories(
|
async getHistories(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
docId?: string,
|
docId?: string,
|
||||||
|
|||||||
@@ -588,6 +588,9 @@ 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) => {
|
||||||
|
return client.getRecentSessions(workspaceId, limit);
|
||||||
|
},
|
||||||
updateSession: async (options: UpdateChatSessionInput) => {
|
updateSession: async (options: UpdateChatSessionInput) => {
|
||||||
return client.updateSession(options);
|
return client.updateSession(options);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -195,8 +195,8 @@ export const Component = () => {
|
|||||||
setCurrentSession(null);
|
setCurrentSession(null);
|
||||||
chatContent?.reset();
|
chatContent?.reset();
|
||||||
};
|
};
|
||||||
tool.onTogglePin = () => {
|
tool.onTogglePin = async () => {
|
||||||
togglePin().catch(console.error);
|
await togglePin();
|
||||||
};
|
};
|
||||||
// mount
|
// mount
|
||||||
chatToolContainerRef.current.append(tool);
|
chatToolContainerRef.current.append(tool);
|
||||||
|
|||||||
Reference in New Issue
Block a user