diff --git a/blocksuite/affine/components/src/toolbar/tooltip.ts b/blocksuite/affine/components/src/toolbar/tooltip.ts
index 99aad4dd5..fffe48f22 100644
--- a/blocksuite/affine/components/src/toolbar/tooltip.ts
+++ b/blocksuite/affine/components/src/toolbar/tooltip.ts
@@ -190,7 +190,10 @@ export class Tooltip extends LitElement {
middleware: [
this.autoFlip && flip({ padding: AUTO_FLIP_PADDING }),
this.autoShift && shift({ padding: AUTO_SHIFT_PADDING }),
- offset((this.arrow ? TRIANGLE_HEIGHT : 0) + this.offset),
+ offset({
+ mainAxis: (this.arrow ? TRIANGLE_HEIGHT : 0) + this.offsetY,
+ crossAxis: this.offsetX,
+ }),
arrow({
element: portalRoot.shadowRoot!.querySelector('.arrow')!,
}),
@@ -264,7 +267,7 @@ export class Tooltip extends LitElement {
* Show a triangle arrow pointing to the reference element.
*/
@property({ attribute: false })
- accessor arrow = true;
+ accessor arrow = false;
/**
* changes the placement of the floating element in order to keep it in view,
@@ -303,7 +306,10 @@ export class Tooltip extends LitElement {
* See https://floating-ui.com/docs/offset
*/
@property({ attribute: false })
- accessor offset = 4;
+ accessor offsetY = 6;
+
+ @property({ attribute: false })
+ accessor offsetX = 0;
@property({ attribute: 'tip-position' })
accessor placement: Placement = 'top';
diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts
index 7b1d4867e..5797aad91 100644
--- a/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts
+++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts
@@ -1,5 +1,6 @@
import type { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
+import type { WorkbenchService } from '@affine/core/modules/workbench';
import type {
ContextEmbedStatus,
CopilotSessionType,
@@ -98,6 +99,9 @@ export class ChatPanel extends SignalWatcher(
@property({ attribute: false })
accessor affineWorkspaceDialogService!: WorkspaceDialogService;
+ @property({ attribute: false })
+ accessor affineWorkbenchService!: WorkbenchService;
+
@state()
accessor session: CopilotSessionType | null | undefined;
@@ -137,34 +141,72 @@ export class ChatPanel extends SignalWatcher(
`;
}
+ private readonly getSessionIdFromUrl = () => {
+ if (this.affineWorkbenchService) {
+ const { workbench } = this.affineWorkbenchService;
+ const location = workbench.location$.value;
+ const searchParams = new URLSearchParams(location.search);
+ const sessionId = searchParams.get('sessionId');
+ if (sessionId) {
+ workbench.activeView$.value.updateQueryString(
+ { sessionId: undefined },
+ { replace: true }
+ );
+ }
+ return sessionId;
+ }
+ return undefined;
+ };
+
+ private readonly setSession = (
+ session: CopilotSessionType | null | undefined
+ ) => {
+ this.session = session ?? null;
+ };
+
private readonly initSession = async () => {
if (!AIProvider.session) {
return;
}
+ const sessionId = this.getSessionIdFromUrl();
const pinSessions = await AIProvider.session.getSessions(
this.doc.workspace.id,
undefined,
{ pinned: true, limit: 1 }
);
+
if (Array.isArray(pinSessions) && pinSessions[0]) {
+ // pinned session
this.session = pinSessions[0];
+ } else if (sessionId) {
+ // sessionId from url
+ const session = await AIProvider.session.getSession(
+ this.doc.workspace.id,
+ sessionId
+ );
+ this.setSession(session);
} else {
+ // latest doc session
const docSessions = await AIProvider.session.getSessions(
this.doc.workspace.id,
this.doc.id,
{ action: false, fork: false, limit: 1 }
);
+ // sessions is descending ordered by updatedAt
// the first item is the latest session
- this.session = docSessions?.[0] ?? null;
+ const session = docSessions?.[0];
+ this.setSession(session);
}
};
@@ -186,7 +228,7 @@ export class ChatPanel extends SignalWatcher(
this.doc.workspace.id,
sessionId
);
- this.session = session ?? null;
+ this.setSession(session);
}
return this.session;
};
@@ -197,7 +239,7 @@ export class ChatPanel extends SignalWatcher(
this.doc.workspace.id,
options.sessionId
);
- this.session = session ?? null;
+ this.setSession(session);
};
private readonly newSession = () => {
@@ -208,12 +250,31 @@ export class ChatPanel extends SignalWatcher(
};
private readonly openSession = async (sessionId: string) => {
+ if (this.session?.id === sessionId) {
+ return;
+ }
this.resetPanel();
const session = await AIProvider.session?.getSession(
this.doc.workspace.id,
sessionId
);
- this.session = session ?? null;
+ this.setSession(session);
+ };
+
+ private readonly openDoc = async (docId: string, sessionId: string) => {
+ if (this.doc.id === docId) {
+ if (this.session?.id === sessionId || this.session?.pinned) {
+ return;
+ }
+ await this.openSession(sessionId);
+ } else if (this.affineWorkbenchService) {
+ const { workbench } = this.affineWorkbenchService;
+ if (this.session?.pinned) {
+ workbench.open(`/${docId}`, { at: 'active' });
+ } else {
+ workbench.open(`/${docId}?sessionId=${sessionId}`, { at: 'active' });
+ }
+ }
};
private readonly togglePin = async () => {
diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-toolbar/ai-chat-toolbar.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-toolbar/ai-chat-toolbar.ts
index 64edff5fd..fed17e468 100644
--- a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-toolbar/ai-chat-toolbar.ts
+++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-toolbar/ai-chat-toolbar.ts
@@ -23,6 +23,9 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor workspaceId!: string;
+ @property({ attribute: false })
+ accessor docId: string | undefined;
+
@property({ attribute: false })
accessor onNewSession!: () => void;
@@ -32,6 +35,9 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor onOpenSession!: (sessionId: string) => void;
+ @property({ attribute: false })
+ accessor onOpenDoc!: (docId: string, sessionId: string) => void;
+
@property({ attribute: false })
accessor docDisplayConfig!: DocDisplayConfig;
@@ -80,9 +86,9 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
{
+ if (this.session?.id === sessionId) {
+ this.notification?.toast('You are already in this chat');
+ return;
+ }
const confirm = await this.unpinConfirm();
if (confirm) {
this.onOpenSession(sessionId);
}
};
+ private readonly onDocClick = async (docId: string, sessionId: string) => {
+ if (this.docId === docId && this.session?.id === sessionId) {
+ this.notification?.toast('You are already in this chat');
+ return;
+ }
+ this.onOpenDoc(docId, sessionId);
+ };
+
private readonly toggleHistoryMenu = () => {
if (this.abortController) {
this.abortController.abort();
@@ -150,6 +168,7 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
.workspaceId=${this.workspaceId}
.docDisplayConfig=${this.docDisplayConfig}
.onSessionClick=${this.onSessionClick}
+ .onDocClick=${this.onDocClick}
.notification=${this.notification}
>
`,
diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-toolbar/ai-session-history.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-toolbar/ai-session-history.ts
index 8b1fb890b..6b22b41ed 100644
--- a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-toolbar/ai-session-history.ts
+++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-toolbar/ai-session-history.ts
@@ -45,17 +45,22 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
}
.ai-session-item {
+ position: relative;
display: flex;
height: 24px;
padding: 2px 4px;
justify-content: space-between;
align-items: center;
+ border-radius: 4px;
cursor: pointer;
}
- .ai-session-item:hover {
+ .ai-session-item:hover:not(:has(.ai-session-doc:hover)) {
+ background: ${unsafeCSSVarV2('layer/background/hoverOverlay')};
+ }
+
+ .ai-session-doc:hover {
background: ${unsafeCSSVarV2('layer/background/hoverOverlay')};
- border-color: ${unsafeCSSVarV2('layer/insideBorder/border')};
}
.ai-session-title {
@@ -75,6 +80,7 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
align-items: center;
gap: 4px;
flex-shrink: 0;
+ border-radius: 2px;
cursor: pointer;
svg {
@@ -110,6 +116,9 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor onSessionClick!: (sessionId: string) => void;
+ @property({ attribute: false })
+ accessor onDocClick!: (docId: string, sessionId: string) => void;
+
@property({ attribute: false })
accessor notification: NotificationService | null | undefined;
@@ -194,10 +203,20 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
return html`
this.onSessionClick(session.sessionId)}
+ @click=${(e: MouseEvent) => {
+ e.stopPropagation();
+ this.onSessionClick(session.sessionId);
+ }}
>
-
${session.sessionId}
- ${session.docId ? this.renderSessionDoc(session.docId) : nothing}
+
+ ${session.sessionId}
+
+ Click to open this chat
+
+
+ ${session.docId
+ ? this.renderSessionDoc(session.docId, session.sessionId)
+ : nothing}
`;
})}
@@ -205,12 +224,19 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
`;
}
- private renderSessionDoc(docId: string) {
+ private renderSessionDoc(docId: string, sessionId: string) {
const getIcon = this.docDisplayConfig.getIcon(docId);
const docIcon = typeof getIcon === 'function' ? getIcon() : getIcon;
- return html`
+ return html`
{
+ e.stopPropagation();
+ this.onDocClick(docId, sessionId);
+ }}
+ >
${docIcon}
-
${this.docDisplayConfig.getTitle(docId)}
+
${this.docDisplayConfig.getTitle(docId)}
+
Open this doc
`;
}
diff --git a/packages/frontend/core/src/blocksuite/ai/messages/error.ts b/packages/frontend/core/src/blocksuite/ai/messages/error.ts
index 4b7190493..8e9c2fb91 100644
--- a/packages/frontend/core/src/blocksuite/ai/messages/error.ts
+++ b/packages/frontend/core/src/blocksuite/ai/messages/error.ts
@@ -155,9 +155,9 @@ export class AIErrorWrapper extends SignalWatcher(WithDisposable(LitElement)) {
>
${this.actionText}
${this.actionTooltip
- ? html`
${this.actionTooltip}`
+ ? html`
+ ${this.actionTooltip}
+ `
: nothing}
diff --git a/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/components/state/error.ts b/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/components/state/error.ts
index 60a9dc0de..84d0c2710 100644
--- a/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/components/state/error.ts
+++ b/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/components/state/error.ts
@@ -182,13 +182,12 @@ export class AIPanelError extends WithDisposable(LitElement) {
() => {
const tip = this.config.error?.message;
const error = tip
- ? html`
An error occurred${tip}`
+ ? html`
+ An error occurred
+
+ ${tip}
+
+ `
: 'An error occurred';
return html`