Skip to content

Commit

Permalink
feat(core): after the user clicks ask ai, the input pops up directly (t…
Browse files Browse the repository at this point in the history
  • Loading branch information
akumatus committed Dec 6, 2024
1 parent 3ef28ed commit 6b14e1c
Show file tree
Hide file tree
Showing 16 changed files with 388 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,23 @@ import './ask-ai-panel';
import { type EditorHost } from '@blocksuite/affine/block-std';
import {
type AIItemGroupConfig,
AIStarIcon,
EdgelessRootService,
} from '@blocksuite/affine/blocks';
import { createLitPortal, HoverController } from '@blocksuite/affine/blocks';
import { assertExists, WithDisposable } from '@blocksuite/affine/global/utils';
import { WithDisposable } from '@blocksuite/affine/global/utils';
import { flip, offset } from '@floating-ui/dom';
import { css, html, LitElement, nothing } from 'lit';
import { property, query } from 'lit/decorators.js';
import { ref } from 'lit/directives/ref.js';
import { styleMap } from 'lit/directives/style-map.js';

import { getRootService } from '../../utils/selection-utils';
import type { ButtonSize } from './ask-ai-icon';

type buttonSize = 'small' | 'middle' | 'large';
type toggleType = 'hover' | 'click';

const buttonWidthMap: Record<buttonSize, string> = {
small: '72px',
middle: '76px',
large: '82px',
};

const buttonHeightMap: Record<buttonSize, string> = {
small: '24px',
middle: '32px',
large: '32px',
};

export type AskAIButtonOptions = {
size: buttonSize;
size: ButtonSize;
backgroundColor?: string;
boxShadow?: string;
panelWidth?: number;
Expand All @@ -53,39 +40,6 @@ export class AskAIButton extends WithDisposable(LitElement) {
position: relative;
user-select: none;
}
.ask-ai-icon-button {
display: flex;
align-items: center;
justify-content: center;
color: var(--affine-brand-color);
font-size: var(--affine-font-sm);
font-weight: 500;
}
.ask-ai-icon-button.small {
font-size: var(--affine-font-xs);
svg {
scale: 0.8;
margin-right: 2px;
}
}
.ask-ai-icon-button.large {
font-size: var(--affine-font-md);
svg {
scale: 1.2;
}
}
.ask-ai-icon-button span {
line-height: 22px;
}
.ask-ai-icon-button svg {
margin-right: 4px;
color: var(--affine-brand-color);
}
`;

@query('.ask-ai-button')
Expand Down Expand Up @@ -127,7 +81,7 @@ export class AskAIButton extends WithDisposable(LitElement) {

@property({ attribute: false })
accessor options: AskAIButtonOptions = {
size: 'middle',
size: 'small',
backgroundColor: undefined,
boxShadow: undefined,
panelWidth: 330,
Expand All @@ -145,13 +99,16 @@ export class AskAIButton extends WithDisposable(LitElement) {
return;
}

if (!this._askAIButton) {
return;
}

if (this._abortController) {
this._clearAbortController();
return;
}

this._abortController = new AbortController();
assertExists(this._askAIButton);
const panelMinWidth = this.options.panelWidth || 330;
createLitPortal({
template: html`<ask-ai-panel
Expand All @@ -177,7 +134,7 @@ export class AskAIButton extends WithDisposable(LitElement) {
}

override render() {
const { size = 'small', backgroundColor, boxShadow } = this.options;
const { size, backgroundColor, boxShadow } = this.options;
const { toggleType } = this;
const buttonStyles = styleMap({
backgroundColor: backgroundColor || 'transparent',
Expand All @@ -189,13 +146,7 @@ export class AskAIButton extends WithDisposable(LitElement) {
${toggleType === 'hover' ? ref(this._whenHover.setReference) : nothing}
@click=${this._toggleAIPanel}
>
<icon-button
class="ask-ai-icon-button ${size}"
width=${buttonWidthMap[size]}
height=${buttonHeightMap[size]}
>
${AIStarIcon} <span>Ask AI</span></icon-button
>
<ask-ai-icon .size=${size}></ask-ai-icon>
</div>`;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { AIStarIcon } from '@blocksuite/affine/blocks';
import { WithDisposable } from '@blocksuite/affine/global/utils';
import { css, html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';

export type ButtonSize = 'small' | 'middle' | 'large';

const buttonWidthMap: Record<ButtonSize, string> = {
small: '72px',
middle: '76px',
large: '82px',
};

const buttonHeightMap: Record<ButtonSize, string> = {
small: '24px',
middle: '32px',
large: '32px',
};

export class AskAIIcon extends WithDisposable(LitElement) {
@property({ attribute: false })
accessor size!: ButtonSize;

static override styles = css`
.ask-ai-icon-button {
display: flex;
align-items: center;
justify-content: center;
color: var(--affine-brand-color);
font-size: var(--affine-font-sm);
font-weight: 500;
}
.ask-ai-icon-button.small {
font-size: var(--affine-font-xs);
svg {
scale: 0.8;
margin-right: 2px;
}
}
.ask-ai-icon-button.large {
font-size: var(--affine-font-md);
svg {
scale: 1.2;
}
}
.ask-ai-icon-button span {
line-height: 22px;
}
.ask-ai-icon-button svg {
margin-right: 4px;
color: var(--affine-brand-color);
}
`;

override render() {
return html`
<icon-button
class="ask-ai-icon-button ${this.size}"
width=${buttonWidthMap[this.size]}
height=${buttonHeightMap[this.size]}
>
${AIStarIcon}
<span>Ask AI</span>
</icon-button>
`;
}
}

declare global {
interface HTMLElementTagNameMap {
'ask-ai-icon': AskAIIcon;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ export class AskAIPanel extends WithDisposable(LitElement) {
@property({ attribute: false })
accessor abortController: AbortController | null = null;

@property({ attribute: false })
accessor onItemClick: (() => void) | undefined = undefined;

@property({ attribute: false })
accessor minWidth = 330;

Expand Down Expand Up @@ -81,6 +84,7 @@ export class AskAIPanel extends WithDisposable(LitElement) {
<ai-item-list
.host=${this.host}
.groups=${this._actionGroups}
.onClick=${this.onItemClick}
></ai-item-list>
</div>`;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import type { EditorHost } from '@blocksuite/affine/block-std';
import {
type AffineAIPanelWidgetConfig,
type AIItemGroupConfig,
createLitPortal,
} from '@blocksuite/affine/blocks';
import { WithDisposable } from '@blocksuite/affine/global/utils';
import { flip, offset } from '@floating-ui/dom';
import { css, html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';

import { getAIPanel } from '../../ai-panel';
import { AIProvider } from '../../provider';
import { extractContext } from '../../utils/extract';

export class AskAIToolbarButton extends WithDisposable(LitElement) {
static override styles = css`
.ask-ai-button {
border-radius: 4px;
position: relative;
user-select: none;
}
`;

private _abortController: AbortController | null = null;

private _panelRoot: HTMLDivElement | null = null;

@property({ attribute: false })
accessor host!: EditorHost;

@property({ attribute: false })
accessor actionGroups!: AIItemGroupConfig[];

private readonly _onItemClick = () => {
const aiPanel = getAIPanel(this.host);
aiPanel.restoreSelection();
this._clearAbortController();
};

private readonly _clearAbortController = () => {
this._abortController?.abort();
this._abortController = null;
};

private readonly _openAIPanel = () => {
this._clearAbortController();
const aiPanel = getAIPanel(this.host);
this._abortController = new AbortController();
this._panelRoot = createLitPortal({
template: html`
<ask-ai-panel
.host=${this.host}
.actionGroups=${this.actionGroups}
.onItemClick=${this._onItemClick}
></ask-ai-panel>
`,
computePosition: {
referenceElement: aiPanel,
placement: 'top-start',
middleware: [flip(), offset({ mainAxis: 3 })],
autoUpdate: true,
},
abortController: this._abortController,
closeOnClickAway: true,
});
};

private readonly _generateAnswer: AffineAIPanelWidgetConfig['generateAnswer'] =
({ finish, input }) => {
finish('success');
const aiPanel = getAIPanel(this.host);
aiPanel.discard();
AIProvider.slots.requestOpenWithChat.emit({ host: this.host });
extractContext(this.host)
.then(context => {
AIProvider.slots.requestSendWithChat.emit({ input, context });
})
.catch(console.error);
};

private readonly _onClick = () => {
const aiPanel = getAIPanel(this.host);
if (!aiPanel.config) return;
aiPanel.config.generateAnswer = this._generateAnswer;
aiPanel.config.inputCallback = text => {
if (!this._panelRoot) return;
this._panelRoot.style.visibility = text ? 'hidden' : 'visible';
};

const textSelection = this.host.selection.find('text');
const blockSelections = this.host.selection.filter('block');
let lastBlockId: string | undefined;
if (textSelection) {
lastBlockId = textSelection.to?.blockId ?? textSelection.blockId;
} else if (blockSelections.length) {
lastBlockId = blockSelections[blockSelections.length - 1].blockId;
}
if (!lastBlockId) return;
const block = this.host.view.getBlock(lastBlockId);
if (!block) return;
aiPanel.setState('input', block);

setTimeout(() => this._openAIPanel(), 0);
};

override render() {
return html`<div class="ask-ai-button" @click=${this._onClick}>
<ask-ai-icon .size=${'middle'}></ask-ai-icon>
</div>`;
}
}

declare global {
interface HTMLElementTagNameMap {
'ask-ai-toolbar-button': AskAIToolbarButton;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,11 @@ const OthersAIGroup: AIItemGroupConfig = {
icon: CommentIcon,
handler: host => {
const panel = getAIPanel(host);
AIProvider.slots.requestOpenWithChat.emit({ host, autoSelect: true });
AIProvider.slots.requestOpenWithChat.emit({
host,
autoSelect: true,
appendCard: true,
});
panel.hide();
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,9 @@ export class ChatCards extends WithDisposable(LitElement) {

this._disposables.add(
AIProvider.slots.requestOpenWithChat.on(async params => {
await this._appendCardWithParams(params);
if (params.appendCard) {
await this._appendCardWithParams(params);
}
})
);

Expand Down
Loading

0 comments on commit 6b14e1c

Please sign in to comment.