mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-22 19:53:48 +08:00
feat(core): add candidates popover in ai chat-panel (#11178)
Close [BS-2853](https://linear.app/affine-design/issue/BS-2853).
This commit is contained in:
@@ -28,7 +28,8 @@ export interface AINetworkSearchConfig {
|
|||||||
|
|
||||||
export interface DocDisplayConfig {
|
export interface DocDisplayConfig {
|
||||||
getIcon: (docId: string) => any;
|
getIcon: (docId: string) => any;
|
||||||
getTitle: (docId: string) => {
|
getTitle: (docId: string) => string;
|
||||||
|
getTitleSignal: (docId: string) => {
|
||||||
signal: Signal<string>;
|
signal: Signal<string>;
|
||||||
cleanup: () => void;
|
cleanup: () => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import {
|
|||||||
} from '@blocksuite/affine/block-std';
|
} from '@blocksuite/affine/block-std';
|
||||||
import { createLitPortal } from '@blocksuite/affine/components/portal';
|
import { createLitPortal } from '@blocksuite/affine/components/portal';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import { PlusIcon } from '@blocksuite/icons/lit';
|
import { MoreVerticalIcon, PlusIcon } from '@blocksuite/icons/lit';
|
||||||
import { flip, offset } from '@floating-ui/dom';
|
import { flip, offset } from '@floating-ui/dom';
|
||||||
import { type Signal, signal } from '@preact/signals-core';
|
import { computed, type Signal, signal } from '@preact/signals-core';
|
||||||
import { css, html, nothing, type PropertyValues } from 'lit';
|
import { css, html, nothing, type PropertyValues } from 'lit';
|
||||||
import { property, query, state } from 'lit/decorators.js';
|
import { property, query, state } from 'lit/decorators.js';
|
||||||
import { repeat } from 'lit/directives/repeat.js';
|
import { repeat } from 'lit/directives/repeat.js';
|
||||||
@@ -36,6 +36,8 @@ import {
|
|||||||
// 100k tokens limit for the docs context
|
// 100k tokens limit for the docs context
|
||||||
const MAX_TOKEN_COUNT = 100000;
|
const MAX_TOKEN_COUNT = 100000;
|
||||||
|
|
||||||
|
const MAX_CANDIDATES = 3;
|
||||||
|
|
||||||
export class ChatPanelChips extends SignalWatcher(
|
export class ChatPanelChips extends SignalWatcher(
|
||||||
WithDisposable(ShadowlessElement)
|
WithDisposable(ShadowlessElement)
|
||||||
) {
|
) {
|
||||||
@@ -45,13 +47,14 @@ export class ChatPanelChips extends SignalWatcher(
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.add-button,
|
.add-button,
|
||||||
.collapse-button {
|
.collapse-button,
|
||||||
|
.more-candidate-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border: 1px solid var(--affine-border-color);
|
border: 0.5px solid var(--affine-border-color);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -62,6 +65,15 @@ export class ChatPanelChips extends SignalWatcher(
|
|||||||
.collapse-button:hover {
|
.collapse-button:hover {
|
||||||
background-color: var(--affine-hover-color);
|
background-color: var(--affine-hover-color);
|
||||||
}
|
}
|
||||||
|
.more-candidate-button {
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: dashed;
|
||||||
|
background: var(--affine-background-secondary-color);
|
||||||
|
color: var(--affine-icon-secondary);
|
||||||
|
}
|
||||||
|
.more-candidate-button svg {
|
||||||
|
color: var(--affine-icon-secondary);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
private _abortController: AbortController | null = null;
|
private _abortController: AbortController | null = null;
|
||||||
@@ -90,6 +102,9 @@ export class ChatPanelChips extends SignalWatcher(
|
|||||||
@query('.add-button')
|
@query('.add-button')
|
||||||
accessor addButton!: HTMLDivElement;
|
accessor addButton!: HTMLDivElement;
|
||||||
|
|
||||||
|
@query('.more-candidate-button')
|
||||||
|
accessor moreCandidateButton!: HTMLDivElement;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
accessor isCollapsed = false;
|
accessor isCollapsed = false;
|
||||||
|
|
||||||
@@ -114,11 +129,14 @@ export class ChatPanelChips extends SignalWatcher(
|
|||||||
docId: doc.docId,
|
docId: doc.docId,
|
||||||
state: 'candidate',
|
state: 'candidate',
|
||||||
}));
|
}));
|
||||||
const allChips = this.chatContextValue.chips.concat(candidates);
|
const moreCandidates = candidates.length > MAX_CANDIDATES;
|
||||||
|
const allChips = this.chatContextValue.chips.concat(
|
||||||
|
candidates.slice(0, MAX_CANDIDATES)
|
||||||
|
);
|
||||||
const isCollapsed = this.isCollapsed && allChips.length > 1;
|
const isCollapsed = this.isCollapsed && allChips.length > 1;
|
||||||
const chips = isCollapsed ? allChips.slice(0, 1) : allChips;
|
const chips = isCollapsed ? allChips.slice(0, 1) : allChips;
|
||||||
|
|
||||||
return html` <div class="chips-wrapper">
|
return html`<div class="chips-wrapper">
|
||||||
<div class="add-button" @click=${this._toggleAddDocMenu}>
|
<div class="add-button" @click=${this._toggleAddDocMenu}>
|
||||||
${PlusIcon()}
|
${PlusIcon()}
|
||||||
</div>
|
</div>
|
||||||
@@ -170,6 +188,14 @@ export class ChatPanelChips extends SignalWatcher(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
${moreCandidates && !isCollapsed
|
||||||
|
? html`<div
|
||||||
|
class="more-candidate-button"
|
||||||
|
@click=${this._toggleMoreCandidatesMenu}
|
||||||
|
>
|
||||||
|
${MoreVerticalIcon()}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
${isCollapsed
|
${isCollapsed
|
||||||
? html`<div class="collapse-button" @click=${this._toggleCollapse}>
|
? html`<div class="collapse-button" @click=${this._toggleCollapse}>
|
||||||
+${allChips.length - 1}
|
+${allChips.length - 1}
|
||||||
@@ -247,6 +273,45 @@ export class ChatPanelChips extends SignalWatcher(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly _toggleMoreCandidatesMenu = () => {
|
||||||
|
if (this._abortController) {
|
||||||
|
this._abortController.abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._abortController = new AbortController();
|
||||||
|
this._abortController.signal.addEventListener('abort', () => {
|
||||||
|
this._abortController = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const referenceDocs = computed(() =>
|
||||||
|
this.referenceDocs.value.slice(MAX_CANDIDATES)
|
||||||
|
);
|
||||||
|
|
||||||
|
createLitPortal({
|
||||||
|
template: html`
|
||||||
|
<chat-panel-candidates-popover
|
||||||
|
.addChip=${this._addChip}
|
||||||
|
.referenceDocs=${referenceDocs}
|
||||||
|
.docDisplayConfig=${this.docDisplayConfig}
|
||||||
|
.abortController=${this._abortController}
|
||||||
|
></chat-panel-candidates-popover>
|
||||||
|
`,
|
||||||
|
portalStyles: {
|
||||||
|
zIndex: 'var(--affine-z-index-popover)',
|
||||||
|
},
|
||||||
|
container: document.body,
|
||||||
|
computePosition: {
|
||||||
|
referenceElement: this.moreCandidateButton,
|
||||||
|
placement: 'top-start',
|
||||||
|
middleware: [offset({ crossAxis: 0, mainAxis: 6 }), flip()],
|
||||||
|
autoUpdate: { animationFrame: true },
|
||||||
|
},
|
||||||
|
abortController: this._abortController,
|
||||||
|
closeOnClickAway: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private readonly _addChip = async (chip: ChatChip) => {
|
private readonly _addChip = async (chip: ChatChip) => {
|
||||||
this.isCollapsed = false;
|
this.isCollapsed = false;
|
||||||
// remove the chip if it already exists
|
// remove the chip if it already exists
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export type MenuItem = {
|
|||||||
name: string | TemplateResult<1>;
|
name: string | TemplateResult<1>;
|
||||||
icon: TemplateResult<1>;
|
icon: TemplateResult<1>;
|
||||||
action: MenuAction;
|
action: MenuAction;
|
||||||
suffix?: string | TemplateResult<1>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MenuAction = () => Promise<void> | void;
|
export type MenuAction = () => Promise<void> | void;
|
||||||
@@ -111,11 +110,6 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
.menu-items icon-button {
|
.menu-items icon-button {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
.item-suffix {
|
|
||||||
margin-left: auto;
|
|
||||||
font-size: var(--affine-font-xs);
|
|
||||||
color: var(--affine-text-secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
${scrollbarStyle('.add-popover')}
|
${scrollbarStyle('.add-popover')}
|
||||||
`;
|
`;
|
||||||
@@ -335,7 +329,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
${repeat(
|
${repeat(
|
||||||
items,
|
items,
|
||||||
item => item.key,
|
item => item.key,
|
||||||
({ key, name, icon, action, suffix }, idx) => {
|
({ key, name, icon, action }, idx) => {
|
||||||
const curIdx = startIndex + idx;
|
const curIdx = startIndex + idx;
|
||||||
return html`<icon-button
|
return html`<icon-button
|
||||||
width="280px"
|
width="280px"
|
||||||
@@ -348,7 +342,6 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
@mousemove=${() => (this._activatedIndex = curIdx)}
|
@mousemove=${() => (this._activatedIndex = curIdx)}
|
||||||
>
|
>
|
||||||
${icon}
|
${icon}
|
||||||
${suffix ? html`<div class="item-suffix">${suffix}</div>` : ''}
|
|
||||||
</icon-button>`;
|
</icon-button>`;
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import { ShadowlessElement } from '@blocksuite/affine/block-std';
|
||||||
|
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
|
import { scrollbarStyle } from '@blocksuite/affine/shared/styles';
|
||||||
|
import { PlusIcon } from '@blocksuite/icons/lit';
|
||||||
|
import { type Signal, signal } from '@preact/signals-core';
|
||||||
|
import { css, html } from 'lit';
|
||||||
|
import { property, state } from 'lit/decorators.js';
|
||||||
|
import { repeat } from 'lit/directives/repeat.js';
|
||||||
|
|
||||||
|
import type { DocDisplayConfig } from '../chat-config';
|
||||||
|
import type { DocChip } from '../chat-context';
|
||||||
|
|
||||||
|
export class ChatPanelCandidatesPopover extends SignalWatcher(
|
||||||
|
WithDisposable(ShadowlessElement)
|
||||||
|
) {
|
||||||
|
static override styles = css`
|
||||||
|
.candidates-popover {
|
||||||
|
width: 280px;
|
||||||
|
max-height: 450px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 0.5px solid var(--affine-border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--affine-background-primary-color);
|
||||||
|
box-shadow: var(--affine-shadow-2);
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.candidates-popover icon-button {
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.candidates-popover icon-button svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
color: var(--svg-icon-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scrollbarStyle('.candidates-popover')}
|
||||||
|
`;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor referenceDocs: Signal<
|
||||||
|
Array<{
|
||||||
|
docId: string;
|
||||||
|
title: string;
|
||||||
|
}>
|
||||||
|
> = signal([]);
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor abortController!: AbortController;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor addChip!: (chip: DocChip) => void;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor docDisplayConfig!: DocDisplayConfig;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private accessor _activatedIndex = 0;
|
||||||
|
|
||||||
|
override connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
document.addEventListener('keydown', this._handleKeyDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
override disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
document.removeEventListener('keydown', this._handleKeyDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
override render() {
|
||||||
|
return html`<div class="candidates-popover">
|
||||||
|
${repeat(
|
||||||
|
this.referenceDocs.value,
|
||||||
|
doc => doc.docId,
|
||||||
|
(doc, curIdx) => {
|
||||||
|
const { docId } = doc;
|
||||||
|
const title = this.docDisplayConfig.getTitle(docId);
|
||||||
|
const getIcon = this.docDisplayConfig.getIcon(docId);
|
||||||
|
const docIcon = typeof getIcon === 'function' ? getIcon() : getIcon;
|
||||||
|
|
||||||
|
return html`<div class="candidate-item">
|
||||||
|
<icon-button
|
||||||
|
width="280px"
|
||||||
|
height="30px"
|
||||||
|
data-id=${docId}
|
||||||
|
data-index=${curIdx}
|
||||||
|
.text=${title}
|
||||||
|
hover=${this._activatedIndex === curIdx}
|
||||||
|
@click=${() => this._addDocChip(docId)}
|
||||||
|
@mousemove=${() => (this._activatedIndex = curIdx)}
|
||||||
|
>
|
||||||
|
${docIcon}
|
||||||
|
<span slot="suffix">${PlusIcon()}</span>
|
||||||
|
</icon-button>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly _addDocChip = (docId: string) => {
|
||||||
|
this.addChip({
|
||||||
|
docId,
|
||||||
|
state: 'processing',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly _handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.isComposing) return;
|
||||||
|
|
||||||
|
const { key } = event;
|
||||||
|
if (key === 'ArrowDown' || key === 'ArrowUp') {
|
||||||
|
event.preventDefault();
|
||||||
|
const totalItems = this.referenceDocs.value.length;
|
||||||
|
if (totalItems === 0) return;
|
||||||
|
|
||||||
|
if (key === 'ArrowDown') {
|
||||||
|
this._activatedIndex = (this._activatedIndex + 1) % totalItems;
|
||||||
|
} else if (key === 'ArrowUp') {
|
||||||
|
this._activatedIndex =
|
||||||
|
(this._activatedIndex - 1 + totalItems) % totalItems;
|
||||||
|
}
|
||||||
|
this._scrollItemIntoView();
|
||||||
|
} else if (key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
if (this.referenceDocs.value.length > 0) {
|
||||||
|
const docId = this.referenceDocs.value[this._activatedIndex].docId;
|
||||||
|
this._addDocChip(docId);
|
||||||
|
}
|
||||||
|
} else if (key === 'Escape') {
|
||||||
|
event.preventDefault();
|
||||||
|
this.abortController.abort();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private _scrollItemIntoView() {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const element = this.renderRoot.querySelector(
|
||||||
|
`[data-index="${this._activatedIndex}"]`
|
||||||
|
);
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'nearest',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ export class ChatPanelChip extends SignalWatcher(
|
|||||||
.chip-card[data-state='candidate'] {
|
.chip-card[data-state='candidate'] {
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-style: dashed;
|
border-style: dashed;
|
||||||
background: var(--affine-tag-white);
|
background: var(--affine-background-secondary-color);
|
||||||
color: var(--affine-icon-secondary);
|
color: var(--affine-icon-secondary);
|
||||||
}
|
}
|
||||||
.chip-card[data-state='candidate'] svg {
|
.chip-card[data-state='candidate'] svg {
|
||||||
@@ -58,7 +58,7 @@ export class ChatPanelChip extends SignalWatcher(
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.chip-card[data-state='candidate'] .chip-card-title {
|
.chip-card[data-state='candidate'] {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.chip-card-close {
|
.chip-card-close {
|
||||||
@@ -101,10 +101,11 @@ export class ChatPanelChip extends SignalWatcher(
|
|||||||
class="chip-card"
|
class="chip-card"
|
||||||
data-testid="chat-panel-chip"
|
data-testid="chat-panel-chip"
|
||||||
data-state=${this.state}
|
data-state=${this.state}
|
||||||
|
@click=${this.onChipClick}
|
||||||
>
|
>
|
||||||
<div class="chip-card-content">
|
<div class="chip-card-content">
|
||||||
${this.icon}
|
${this.icon}
|
||||||
<span class="chip-card-title" @click=${this.onChipClick}>
|
<span class="chip-card-title">
|
||||||
<span data-testid="chat-panel-chip-title">${this.name}</span>
|
<span data-testid="chat-panel-chip-title">${this.name}</span>
|
||||||
</span>
|
</span>
|
||||||
<affine-tooltip>${this.tooltip}</affine-tooltip>
|
<affine-tooltip>${this.tooltip}</affine-tooltip>
|
||||||
|
|||||||
@@ -47,7 +47,9 @@ export class ChatPanelDocChip extends SignalWatcher(
|
|||||||
override connectedCallback() {
|
override connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
||||||
const { signal, cleanup } = this.docDisplayConfig.getTitle(this.chip.docId);
|
const { signal, cleanup } = this.docDisplayConfig.getTitleSignal(
|
||||||
|
this.chip.docId
|
||||||
|
);
|
||||||
this.chipName = signal;
|
this.chipName = signal;
|
||||||
this.disposables.add(cleanup);
|
this.disposables.add(cleanup);
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { ChatPanelChips } from './chat-panel/chat-panel-chips';
|
|||||||
import { ChatPanelInput } from './chat-panel/chat-panel-input';
|
import { ChatPanelInput } from './chat-panel/chat-panel-input';
|
||||||
import { ChatPanelMessages } from './chat-panel/chat-panel-messages';
|
import { ChatPanelMessages } from './chat-panel/chat-panel-messages';
|
||||||
import { ChatPanelAddPopover } from './chat-panel/components/add-popover';
|
import { ChatPanelAddPopover } from './chat-panel/components/add-popover';
|
||||||
|
import { ChatPanelCandidatesPopover } from './chat-panel/components/candidates-popover';
|
||||||
import { ChatPanelChip } from './chat-panel/components/chip';
|
import { ChatPanelChip } from './chat-panel/components/chip';
|
||||||
import { ChatPanelCollectionChip } from './chat-panel/components/collection-chip';
|
import { ChatPanelCollectionChip } from './chat-panel/components/collection-chip';
|
||||||
import { ChatPanelDocChip } from './chat-panel/components/doc-chip';
|
import { ChatPanelDocChip } from './chat-panel/components/doc-chip';
|
||||||
@@ -100,6 +101,10 @@ export function registerAIEffects() {
|
|||||||
customElements.define('chat-panel', ChatPanel);
|
customElements.define('chat-panel', ChatPanel);
|
||||||
customElements.define('chat-panel-chips', ChatPanelChips);
|
customElements.define('chat-panel-chips', ChatPanelChips);
|
||||||
customElements.define('chat-panel-add-popover', ChatPanelAddPopover);
|
customElements.define('chat-panel-add-popover', ChatPanelAddPopover);
|
||||||
|
customElements.define(
|
||||||
|
'chat-panel-candidates-popover',
|
||||||
|
ChatPanelCandidatesPopover
|
||||||
|
);
|
||||||
customElements.define('chat-panel-doc-chip', ChatPanelDocChip);
|
customElements.define('chat-panel-doc-chip', ChatPanelDocChip);
|
||||||
customElements.define('chat-panel-file-chip', ChatPanelFileChip);
|
customElements.define('chat-panel-file-chip', ChatPanelFileChip);
|
||||||
customElements.define('chat-panel-tag-chip', ChatPanelTagChip);
|
customElements.define('chat-panel-tag-chip', ChatPanelTagChip);
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
|
|||||||
return docDisplayMetaService.icon$(docId, { type: 'lit' }).value;
|
return docDisplayMetaService.icon$(docId, { type: 'lit' }).value;
|
||||||
},
|
},
|
||||||
getTitle: (docId: string) => {
|
getTitle: (docId: string) => {
|
||||||
|
return docDisplayMetaService.title$(docId).value;
|
||||||
|
},
|
||||||
|
getTitleSignal: (docId: string) => {
|
||||||
const title$ = docDisplayMetaService.title$(docId);
|
const title$ = docDisplayMetaService.title$(docId);
|
||||||
return createSignalFromObservable(title$, '');
|
return createSignalFromObservable(title$, '');
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ test.describe('chat panel', () => {
|
|||||||
await page.waitForTimeout(200);
|
await page.waitForTimeout(200);
|
||||||
await createLocalWorkspace({ name: 'test' }, page);
|
await createLocalWorkspace({ name: 'test' }, page);
|
||||||
await clickNewPageButton(page);
|
await clickNewPageButton(page);
|
||||||
await makeChat(page, 'hello');
|
await makeChat(page, 'What is AFFiNE?');
|
||||||
const content = (await collectChat(page))[1].content;
|
const content = (await collectChat(page))[1].content;
|
||||||
await page.getByTestId('action-copy-button').click();
|
await page.getByTestId('action-copy-button').click();
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
|
|||||||
Reference in New Issue
Block a user