mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-27 02:42:25 +08:00
feat(core): completely remove the dependence on EditorHost (#13110)
Close [AI-260](https://linear.app/affine-design/issue/AI-260) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added theme support to AI chat and message components, enabling dynamic theming based on the current app theme. * Introduced a reactive theme signal to the theme service for improved theme handling. * Integrated notification and theme services across various AI chat, playground, and message components for consistent user experience. * **Refactor** * Simplified component APIs by removing dependencies on editor host and related properties across AI chat, message, and tool components. * Centralized and streamlined clipboard and markdown conversion utilities, reducing external dependencies. * Standardized the interface for context file addition and improved type usage for better consistency. * Reworked notification service to a class-based implementation for improved encapsulation. * Updated AI chat components to use injected notification and theme services instead of host-based retrieval. * **Bug Fixes** * Improved reliability of copy and notification actions by decoupling them from editor host dependencies. * **Chores** * Updated and cleaned up internal imports and removed unused properties to enhance maintainability. * Added test IDs for sidebar close button to improve test reliability. * Updated test prompts in end-to-end tests for consistency. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -40,7 +40,6 @@ export interface NotificationService {
|
|||||||
}[];
|
}[];
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}): void;
|
}): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify with undo action, it is a helper function to notify with undo action.
|
* Notify with undo action, it is a helper function to notify with undo action.
|
||||||
* And the notification card will be closed when undo action is triggered by shortcut key or other ways.
|
* And the notification card will be closed when undo action is triggered by shortcut key or other ways.
|
||||||
@@ -55,13 +54,16 @@ export const NotificationProvider = createIdentifier<NotificationService>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export function NotificationExtension(
|
export function NotificationExtension(
|
||||||
notificationService: Omit<NotificationService, 'notifyWithUndoAction'>
|
notificationService: NotificationService
|
||||||
): ExtensionType {
|
): ExtensionType {
|
||||||
return {
|
return {
|
||||||
setup: di => {
|
setup: di => {
|
||||||
di.addImpl(NotificationProvider, provider => {
|
di.addImpl(NotificationProvider, provider => {
|
||||||
return {
|
return {
|
||||||
...notificationService,
|
notify: notificationService.notify,
|
||||||
|
toast: notificationService.toast,
|
||||||
|
confirm: notificationService.confirm,
|
||||||
|
prompt: notificationService.prompt,
|
||||||
notifyWithUndoAction: options => {
|
notifyWithUndoAction: options => {
|
||||||
notifyWithUndoActionImpl(
|
notifyWithUndoActionImpl(
|
||||||
provider,
|
provider,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export interface BlockStdOptions {
|
|||||||
extensions: ExtensionType[];
|
extensions: ExtensionType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const internalExtensions = [
|
export const internalExtensions = [
|
||||||
ServiceManager,
|
ServiceManager,
|
||||||
CommandManager,
|
CommandManager,
|
||||||
UIEventDispatcher,
|
UIEventDispatcher,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type {
|
import type {
|
||||||
|
AddContextFileInput,
|
||||||
ContextMatchedDocChunk,
|
ContextMatchedDocChunk,
|
||||||
ContextMatchedFileChunk,
|
ContextMatchedFileChunk,
|
||||||
ContextWorkspaceEmbeddingStatus,
|
ContextWorkspaceEmbeddingStatus,
|
||||||
@@ -295,10 +296,7 @@ declare global {
|
|||||||
}) => Promise<boolean>;
|
}) => Promise<boolean>;
|
||||||
addContextFile: (
|
addContextFile: (
|
||||||
file: File,
|
file: File,
|
||||||
options: {
|
options: AddContextFileInput
|
||||||
contextId: string;
|
|
||||||
blobId: string;
|
|
||||||
}
|
|
||||||
) => Promise<CopilotContextFile>;
|
) => Promise<CopilotContextFile>;
|
||||||
removeContextFile: (options: {
|
removeContextFile: (options: {
|
||||||
contextId: string;
|
contextId: string;
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import type { TextRendererOptions } from '@affine/core/blocksuite/ai/components/text-renderer';
|
import type { TextRendererOptions } from '@affine/core/blocksuite/ai/components/text-renderer';
|
||||||
import type { EditorHost } from '@blocksuite/affine/std';
|
import type { EditorHost } from '@blocksuite/affine/std';
|
||||||
|
import {
|
||||||
|
NotificationProvider,
|
||||||
|
ThemeProvider,
|
||||||
|
} from '@blocksuite/affine-shared/services';
|
||||||
import { css, html, LitElement } from 'lit';
|
import { css, html, LitElement } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
import { classMap } from 'lit/directives/class-map.js';
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
@@ -65,6 +69,7 @@ export class AIChatBlockMessage extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderStreamObjects(answer: StreamObject[]) {
|
private renderStreamObjects(answer: StreamObject[]) {
|
||||||
|
const notificationService = this.host.std.get(NotificationProvider);
|
||||||
return html`<chat-content-stream-objects
|
return html`<chat-content-stream-objects
|
||||||
.answer=${answer}
|
.answer=${answer}
|
||||||
.host=${this.host}
|
.host=${this.host}
|
||||||
@@ -72,17 +77,19 @@ export class AIChatBlockMessage extends LitElement {
|
|||||||
.extensions=${this.textRendererOptions.extensions}
|
.extensions=${this.textRendererOptions.extensions}
|
||||||
.affineFeatureFlagService=${this.textRendererOptions
|
.affineFeatureFlagService=${this.textRendererOptions
|
||||||
.affineFeatureFlagService}
|
.affineFeatureFlagService}
|
||||||
|
.notificationService=${notificationService}
|
||||||
|
.theme=${this.host.std.get(ThemeProvider).app$}
|
||||||
></chat-content-stream-objects>`;
|
></chat-content-stream-objects>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderRichText(text: string) {
|
private renderRichText(text: string) {
|
||||||
return html`<chat-content-rich-text
|
return html`<chat-content-rich-text
|
||||||
.host=${this.host}
|
|
||||||
.text=${text}
|
.text=${text}
|
||||||
.state=${this.state}
|
.state=${this.state}
|
||||||
.extensions=${this.textRendererOptions.extensions}
|
.extensions=${this.textRendererOptions.extensions}
|
||||||
.affineFeatureFlagService=${this.textRendererOptions
|
.affineFeatureFlagService=${this.textRendererOptions
|
||||||
.affineFeatureFlagService}
|
.affineFeatureFlagService}
|
||||||
|
.theme=${this.host.std.get(ThemeProvider).app$}
|
||||||
></chat-content-rich-text>`;
|
></chat-content-rich-text>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import type { EditorHost } from '@blocksuite/affine/std';
|
import type { EditorHost } from '@blocksuite/affine/std';
|
||||||
|
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||||
import {
|
import {
|
||||||
ArrowDownBigIcon as ArrowDownIcon,
|
ArrowDownBigIcon as ArrowDownIcon,
|
||||||
ArrowUpBigIcon as ArrowUpIcon,
|
ArrowUpBigIcon as ArrowUpIcon,
|
||||||
@@ -163,16 +164,18 @@ export class ActionWrapper extends WithDisposable(LitElement) {
|
|||||||
></chat-content-images>`
|
></chat-content-images>`
|
||||||
: nothing}
|
: nothing}
|
||||||
${answer
|
${answer
|
||||||
? createTextRenderer(this.host, {
|
? createTextRenderer({
|
||||||
customHeading: true,
|
customHeading: true,
|
||||||
testId: 'chat-message-action-answer',
|
testId: 'chat-message-action-answer',
|
||||||
|
theme: this.host.std.get(ThemeProvider).app$,
|
||||||
})(answer)
|
})(answer)
|
||||||
: nothing}
|
: nothing}
|
||||||
${originalText
|
${originalText
|
||||||
? html`<div class="subtitle prompt">Prompt</div>
|
? html`<div class="subtitle prompt">Prompt</div>
|
||||||
${createTextRenderer(this.host, {
|
${createTextRenderer({
|
||||||
customHeading: true,
|
customHeading: true,
|
||||||
testId: 'chat-message-action-prompt',
|
testId: 'chat-message-action-prompt',
|
||||||
|
theme: this.host.std.get(ThemeProvider).app$,
|
||||||
})(item.messages[0].content + originalText)}`
|
})(item.messages[0].content + originalText)}`
|
||||||
: nothing}
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import './action-wrapper';
|
|||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import { unsafeCSSVar } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVar } from '@blocksuite/affine/shared/theme';
|
||||||
import type { EditorHost } from '@blocksuite/affine/std';
|
import type { EditorHost } from '@blocksuite/affine/std';
|
||||||
|
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||||
import { css, html, LitElement } from 'lit';
|
import { css, html, LitElement } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
import { styleMap } from 'lit/directives/style-map.js';
|
import { styleMap } from 'lit/directives/style-map.js';
|
||||||
@@ -57,8 +58,9 @@ export class ActionText extends WithDisposable(LitElement) {
|
|||||||
class="original-text"
|
class="original-text"
|
||||||
data-testid="original-text"
|
data-testid="original-text"
|
||||||
>
|
>
|
||||||
${createTextRenderer(this.host, {
|
${createTextRenderer({
|
||||||
customHeading: true,
|
customHeading: true,
|
||||||
|
theme: this.host.std.get(ThemeProvider).app$,
|
||||||
})(originalText)}
|
})(originalText)}
|
||||||
</div>
|
</div>
|
||||||
</action-wrapper>`;
|
</action-wrapper>`;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
import type { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
|
import type { AppThemeService } from '@affine/core/modules/theme';
|
||||||
import type { WorkbenchService } from '@affine/core/modules/workbench';
|
import type { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
import type {
|
import type {
|
||||||
ContextEmbedStatus,
|
ContextEmbedStatus,
|
||||||
@@ -7,7 +8,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 { 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 type { EditorHost } from '@blocksuite/affine/std';
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
@@ -125,6 +126,12 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor affineWorkbenchService!: WorkbenchService;
|
accessor affineWorkbenchService!: WorkbenchService;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor affineThemeService!: AppThemeService;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
accessor session: CopilotChatHistoryFragment | null | undefined;
|
accessor session: CopilotChatHistoryFragment | null | undefined;
|
||||||
|
|
||||||
@@ -144,7 +151,6 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
private get chatTitle() {
|
private get chatTitle() {
|
||||||
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`
|
return html`
|
||||||
<div class="chat-panel-title-text">
|
<div class="chat-panel-title-text">
|
||||||
@@ -170,7 +176,7 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
.onOpenSession=${this.openSession}
|
.onOpenSession=${this.openSession}
|
||||||
.onOpenDoc=${this.openDoc}
|
.onOpenDoc=${this.openDoc}
|
||||||
.docDisplayConfig=${this.docDisplayConfig}
|
.docDisplayConfig=${this.docDisplayConfig}
|
||||||
.notification=${notification}
|
.notificationService=${this.notificationService}
|
||||||
></ai-chat-toolbar>
|
></ai-chat-toolbar>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -371,6 +377,8 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
.docDisplayConfig=${this.docDisplayConfig}
|
.docDisplayConfig=${this.docDisplayConfig}
|
||||||
.extensions=${this.extensions}
|
.extensions=${this.extensions}
|
||||||
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
||||||
|
.affineThemeService=${this.affineThemeService}
|
||||||
|
.notificationService=${this.notificationService}
|
||||||
></playground-content>
|
></playground-content>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -444,6 +452,8 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
.extensions=${this.extensions}
|
.extensions=${this.extensions}
|
||||||
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
||||||
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
|
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
|
||||||
|
.affineThemeService=${this.affineThemeService}
|
||||||
|
.notificationService=${this.notificationService}
|
||||||
.onEmbeddingProgressChange=${this.onEmbeddingProgressChange}
|
.onEmbeddingProgressChange=${this.onEmbeddingProgressChange}
|
||||||
.onContextChange=${this.onContextChange}
|
.onContextChange=${this.onContextChange}
|
||||||
.width=${this.sidebarWidth}
|
.width=${this.sidebarWidth}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
|
import type { AppThemeService } from '@affine/core/modules/theme';
|
||||||
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import { isInsidePageEditor } from '@blocksuite/affine/shared/utils';
|
import { isInsidePageEditor } from '@blocksuite/affine/shared/utils';
|
||||||
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';
|
||||||
import type { ExtensionType } from '@blocksuite/affine/store';
|
import type { ExtensionType } from '@blocksuite/affine/store';
|
||||||
|
import type { NotificationService } from '@blocksuite/affine-shared/services';
|
||||||
import type { Signal } from '@preact/signals-core';
|
import type { Signal } from '@preact/signals-core';
|
||||||
import { css, html, nothing } from 'lit';
|
import { css, html, nothing } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
@@ -35,9 +37,6 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor host: EditorHost | null | undefined;
|
accessor host: EditorHost | null | undefined;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor docId: string | undefined;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor item!: ChatMessage;
|
accessor item!: ChatMessage;
|
||||||
|
|
||||||
@@ -56,6 +55,9 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor affineFeatureFlagService!: FeatureFlagService;
|
accessor affineFeatureFlagService!: FeatureFlagService;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor affineThemeService!: AppThemeService;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor session!: CopilotChatHistoryFragment | null | undefined;
|
accessor session!: CopilotChatHistoryFragment | null | undefined;
|
||||||
|
|
||||||
@@ -68,6 +70,9 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor width: Signal<number | undefined> | undefined;
|
accessor width: Signal<number | undefined> | undefined;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
get state() {
|
get state() {
|
||||||
const { isLast, status } = this;
|
const { isLast, status } = this;
|
||||||
return isLast
|
return isLast
|
||||||
@@ -118,27 +123,29 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
|||||||
|
|
||||||
private renderStreamObjects(answer: StreamObject[]) {
|
private renderStreamObjects(answer: StreamObject[]) {
|
||||||
return html`<chat-content-stream-objects
|
return html`<chat-content-stream-objects
|
||||||
.answer=${answer}
|
|
||||||
.host=${this.host}
|
.host=${this.host}
|
||||||
|
.answer=${answer}
|
||||||
.state=${this.state}
|
.state=${this.state}
|
||||||
.width=${this.width}
|
.width=${this.width}
|
||||||
.extensions=${this.extensions}
|
.extensions=${this.extensions}
|
||||||
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
||||||
|
.notificationService=${this.notificationService}
|
||||||
|
.theme=${this.affineThemeService.appTheme.themeSignal}
|
||||||
></chat-content-stream-objects>`;
|
></chat-content-stream-objects>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderRichText(text: string) {
|
private renderRichText(text: string) {
|
||||||
return html`<chat-content-rich-text
|
return html`<chat-content-rich-text
|
||||||
.host=${this.host}
|
|
||||||
.text=${text}
|
.text=${text}
|
||||||
.state=${this.state}
|
.state=${this.state}
|
||||||
.extensions=${this.extensions}
|
.extensions=${this.extensions}
|
||||||
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
||||||
|
.theme=${this.affineThemeService.appTheme.themeSignal}
|
||||||
></chat-content-rich-text>`;
|
></chat-content-rich-text>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderEditorActions() {
|
private renderEditorActions() {
|
||||||
const { item, isLast, status, host, session, docId } = this;
|
const { item, isLast, status, host, session } = this;
|
||||||
|
|
||||||
if (!isChatMessage(item) || item.role !== 'assistant') return nothing;
|
if (!isChatMessage(item) || item.role !== 'assistant') return nothing;
|
||||||
|
|
||||||
@@ -161,7 +168,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
|||||||
: EdgelessEditorActions
|
: EdgelessEditorActions
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const showActions = host && docId && !!markdown;
|
const showActions = host && !!markdown;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<chat-copy-more
|
<chat-copy-more
|
||||||
@@ -173,6 +180,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
|||||||
.messageId=${messageId}
|
.messageId=${messageId}
|
||||||
.withMargin=${true}
|
.withMargin=${true}
|
||||||
.retry=${() => this.retry()}
|
.retry=${() => this.retry()}
|
||||||
|
.notificationService=${this.notificationService}
|
||||||
></chat-copy-more>
|
></chat-copy-more>
|
||||||
${isLast && showActions
|
${isLast && showActions
|
||||||
? html`<chat-action-list
|
? html`<chat-action-list
|
||||||
@@ -182,6 +190,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
|||||||
.content=${markdown}
|
.content=${markdown}
|
||||||
.messageId=${messageId ?? undefined}
|
.messageId=${messageId ?? undefined}
|
||||||
.withMargin=${true}
|
.withMargin=${true}
|
||||||
|
.notificationService=${this.notificationService}
|
||||||
></chat-action-list>`
|
></chat-action-list>`
|
||||||
: nothing}
|
: nothing}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { TagMeta } from '@affine/core/components/page-list';
|
|||||||
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 { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
import { MoreVerticalIcon, 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 { computed, type Signal, signal } from '@preact/signals-core';
|
import { computed, type Signal, signal } from '@preact/signals-core';
|
||||||
@@ -82,9 +82,6 @@ export class ChatPanelChips extends SignalWatcher(
|
|||||||
|
|
||||||
private _abortController: AbortController | null = null;
|
private _abortController: AbortController | null = null;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor host: EditorHost | null | undefined;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor chips!: ChatChip[];
|
accessor chips!: ChatChip[];
|
||||||
|
|
||||||
@@ -167,7 +164,6 @@ export class ChatPanelChips extends SignalWatcher(
|
|||||||
.removeChip=${this._removeChip}
|
.removeChip=${this._removeChip}
|
||||||
.checkTokenLimit=${this._checkTokenLimit}
|
.checkTokenLimit=${this._checkTokenLimit}
|
||||||
.docDisplayConfig=${this.docDisplayConfig}
|
.docDisplayConfig=${this.docDisplayConfig}
|
||||||
.host=${this.host}
|
|
||||||
></chat-panel-doc-chip>`;
|
></chat-panel-doc-chip>`;
|
||||||
}
|
}
|
||||||
if (isFileChip(chip)) {
|
if (isFileChip(chip)) {
|
||||||
@@ -407,13 +403,8 @@ export class ChatPanelChips extends SignalWatcher(
|
|||||||
if (!contextId || !AIProvider.context) {
|
if (!contextId || !AIProvider.context) {
|
||||||
throw new Error('Context not found');
|
throw new Error('Context not found');
|
||||||
}
|
}
|
||||||
if (!this.host) {
|
|
||||||
throw new Error('Host not found');
|
|
||||||
}
|
|
||||||
const blobId = await this.host.store.blobSync.set(chip.file);
|
|
||||||
const contextFile = await AIProvider.context.addContextFile(chip.file, {
|
const contextFile = await AIProvider.context.addContextFile(chip.file, {
|
||||||
contextId,
|
contextId,
|
||||||
blobId,
|
|
||||||
});
|
});
|
||||||
this._updateChip(chip, {
|
this._updateChip(chip, {
|
||||||
state: contextFile.status,
|
state: contextFile.status,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import track from '@affine/track';
|
import track from '@affine/track';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
import { Signal } from '@preact/signals-core';
|
import { Signal } from '@preact/signals-core';
|
||||||
import { html, type PropertyValues } from 'lit';
|
import { html, type PropertyValues } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
@@ -36,9 +36,6 @@ export class ChatPanelDocChip extends SignalWatcher(
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor docDisplayConfig!: DocDisplayConfig;
|
accessor docDisplayConfig!: DocDisplayConfig;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor host: EditorHost | null | undefined;
|
|
||||||
|
|
||||||
private chipName = new Signal<string>('');
|
private chipName = new Signal<string>('');
|
||||||
|
|
||||||
override connectedCallback() {
|
override connectedCallback() {
|
||||||
@@ -103,9 +100,6 @@ export class ChatPanelDocChip extends SignalWatcher(
|
|||||||
};
|
};
|
||||||
|
|
||||||
private readonly processDocChip = async () => {
|
private readonly processDocChip = async () => {
|
||||||
if (!this.host) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const doc = this.docDisplayConfig.getDoc(this.chip.docId);
|
const doc = this.docDisplayConfig.getDoc(this.chip.docId);
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
@@ -114,10 +108,7 @@ export class ChatPanelDocChip extends SignalWatcher(
|
|||||||
if (!doc.ready) {
|
if (!doc.ready) {
|
||||||
doc.load();
|
doc.load();
|
||||||
}
|
}
|
||||||
const value = await extractMarkdownFromDoc(
|
const value = await extractMarkdownFromDoc(doc);
|
||||||
doc,
|
|
||||||
this.host.std.store.provider
|
|
||||||
);
|
|
||||||
const tokenCount = estimateTokenCount(value);
|
const tokenCount = estimateTokenCount(value);
|
||||||
if (this.checkTokenLimit(this.chip, tokenCount)) {
|
if (this.checkTokenLimit(this.chip, tokenCount)) {
|
||||||
const markdown = this.chip.markdown ?? new Signal<string>('');
|
const markdown = this.chip.markdown ?? new Signal<string>('');
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ export class AIChatComposer extends SignalWatcher(
|
|||||||
override render() {
|
override render() {
|
||||||
return html`
|
return html`
|
||||||
<chat-panel-chips
|
<chat-panel-chips
|
||||||
.host=${this.host}
|
|
||||||
.chips=${this.chips}
|
.chips=${this.chips}
|
||||||
.createContextId=${this._createContextId}
|
.createContextId=${this._createContextId}
|
||||||
.updateChips=${this.updateChips}
|
.updateChips=${this.updateChips}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
import type { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
|
import type { AppThemeService } from '@affine/core/modules/theme';
|
||||||
import type {
|
import type {
|
||||||
ContextEmbedStatus,
|
ContextEmbedStatus,
|
||||||
CopilotChatHistoryFragment,
|
CopilotChatHistoryFragment,
|
||||||
@@ -8,6 +9,7 @@ import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
|||||||
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';
|
||||||
import type { ExtensionType } from '@blocksuite/affine/store';
|
import type { ExtensionType } from '@blocksuite/affine/store';
|
||||||
|
import type { NotificationService } from '@blocksuite/affine-shared/services';
|
||||||
import { type Signal } from '@preact/signals-core';
|
import { type Signal } from '@preact/signals-core';
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@@ -160,6 +162,12 @@ export class AIChatContent extends SignalWatcher(
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor affineWorkspaceDialogService!: WorkspaceDialogService;
|
accessor affineWorkspaceDialogService!: WorkspaceDialogService;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor affineThemeService!: AppThemeService;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor onEmbeddingProgressChange!: (
|
accessor onEmbeddingProgressChange!: (
|
||||||
count: Record<ContextEmbedStatus, number>
|
count: Record<ContextEmbedStatus, number>
|
||||||
@@ -401,6 +409,8 @@ export class AIChatContent extends SignalWatcher(
|
|||||||
.isHistoryLoading=${this.isHistoryLoading}
|
.isHistoryLoading=${this.isHistoryLoading}
|
||||||
.extensions=${this.extensions}
|
.extensions=${this.extensions}
|
||||||
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
||||||
|
.affineThemeService=${this.affineThemeService}
|
||||||
|
.notificationService=${this.notificationService}
|
||||||
.networkSearchConfig=${this.networkSearchConfig}
|
.networkSearchConfig=${this.networkSearchConfig}
|
||||||
.reasoningConfig=${this.reasoningConfig}
|
.reasoningConfig=${this.reasoningConfig}
|
||||||
.width=${this.width}
|
.width=${this.width}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import type { AppThemeService } from '@affine/core/modules/theme';
|
||||||
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import {
|
import {
|
||||||
DocModeProvider,
|
DocModeProvider,
|
||||||
FeatureFlagService,
|
type FeatureFlagService,
|
||||||
|
type NotificationService,
|
||||||
} from '@blocksuite/affine/shared/services';
|
} from '@blocksuite/affine/shared/services';
|
||||||
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';
|
||||||
@@ -187,6 +189,12 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor affineFeatureFlagService!: FeatureFlagService;
|
accessor affineFeatureFlagService!: FeatureFlagService;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor affineThemeService!: AppThemeService;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor networkSearchConfig!: AINetworkSearchConfig;
|
accessor networkSearchConfig!: AINetworkSearchConfig;
|
||||||
|
|
||||||
@@ -222,8 +230,7 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _renderAIOnboarding() {
|
private _renderAIOnboarding() {
|
||||||
return this.isHistoryLoading ||
|
return this.isHistoryLoading
|
||||||
!this.host?.store.get(FeatureFlagService).getFlag('enable_ai_onboarding')
|
|
||||||
? nothing
|
? nothing
|
||||||
: html`<div class="onboarding-wrapper" data-testid="ai-onboarding">
|
: html`<div class="onboarding-wrapper" data-testid="ai-onboarding">
|
||||||
${repeat(
|
${repeat(
|
||||||
@@ -311,7 +318,6 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
} else if (isChatMessage(item) && item.role === 'assistant') {
|
} else if (isChatMessage(item) && item.role === 'assistant') {
|
||||||
return html`<chat-message-assistant
|
return html`<chat-message-assistant
|
||||||
.host=${this.host}
|
.host=${this.host}
|
||||||
.docId=${this.docId}
|
|
||||||
.session=${this.session}
|
.session=${this.session}
|
||||||
.item=${item}
|
.item=${item}
|
||||||
.isLast=${isLast}
|
.isLast=${isLast}
|
||||||
@@ -319,6 +325,8 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
.error=${isLast ? error : null}
|
.error=${isLast ? error : null}
|
||||||
.extensions=${this.extensions}
|
.extensions=${this.extensions}
|
||||||
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
||||||
|
.affineThemeService=${this.affineThemeService}
|
||||||
|
.notificationService=${this.notificationService}
|
||||||
.retry=${() => this.retry()}
|
.retry=${() => this.retry()}
|
||||||
.width=${this.width}
|
.width=${this.width}
|
||||||
></chat-message-assistant>`;
|
></chat-message-assistant>`;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
|
|||||||
accessor docDisplayConfig!: DocDisplayConfig;
|
accessor docDisplayConfig!: DocDisplayConfig;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor notification: NotificationService | null | undefined;
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
@query('.history-button')
|
@query('.history-button')
|
||||||
accessor historyButton!: HTMLDivElement;
|
accessor historyButton!: HTMLDivElement;
|
||||||
@@ -104,21 +104,19 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
|
|||||||
private readonly unpinConfirm = async () => {
|
private readonly unpinConfirm = async () => {
|
||||||
if (this.session && this.session.pinned) {
|
if (this.session && this.session.pinned) {
|
||||||
try {
|
try {
|
||||||
const confirm = this.notification
|
const confirm = await this.notificationService.confirm({
|
||||||
? await this.notification.confirm({
|
title: 'Switch Chat? Current chat is pinned',
|
||||||
title: 'Switch Chat? Current chat is pinned',
|
message:
|
||||||
message:
|
'Switching will unpinned the current chat. This will change the active chat panel, allowing you to navigate between different conversation histories.',
|
||||||
'Switching will unpinned the current chat. This will change the active chat panel, allowing you to navigate between different conversation histories.',
|
confirmText: 'Switch Chat',
|
||||||
confirmText: 'Switch Chat',
|
cancelText: 'Cancel',
|
||||||
cancelText: 'Cancel',
|
});
|
||||||
})
|
|
||||||
: true;
|
|
||||||
if (!confirm) {
|
if (!confirm) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
await this.onTogglePin();
|
await this.onTogglePin();
|
||||||
} catch {
|
} catch {
|
||||||
this.notification?.toast('Failed to unpin the chat');
|
this.notificationService.toast('Failed to unpin the chat');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -133,7 +131,7 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
|
|||||||
|
|
||||||
private readonly onSessionClick = async (sessionId: string) => {
|
private readonly onSessionClick = async (sessionId: string) => {
|
||||||
if (this.session?.sessionId === sessionId) {
|
if (this.session?.sessionId === sessionId) {
|
||||||
this.notification?.toast('You are already in this chat');
|
this.notificationService.toast('You are already in this chat');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const confirm = await this.unpinConfirm();
|
const confirm = await this.unpinConfirm();
|
||||||
@@ -144,7 +142,7 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
|
|||||||
|
|
||||||
private readonly onDocClick = async (docId: string, sessionId: string) => {
|
private readonly onDocClick = async (docId: string, sessionId: string) => {
|
||||||
if (this.docId === docId && this.session?.sessionId === sessionId) {
|
if (this.docId === docId && this.session?.sessionId === sessionId) {
|
||||||
this.notification?.toast('You are already in this chat');
|
this.notificationService.toast('You are already in this chat');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.onOpenDoc(docId, sessionId);
|
this.onOpenDoc(docId, sessionId);
|
||||||
@@ -169,7 +167,6 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
|
|||||||
.docDisplayConfig=${this.docDisplayConfig}
|
.docDisplayConfig=${this.docDisplayConfig}
|
||||||
.onSessionClick=${this.onSessionClick}
|
.onSessionClick=${this.onSessionClick}
|
||||||
.onDocClick=${this.onDocClick}
|
.onDocClick=${this.onDocClick}
|
||||||
.notification=${this.notification}
|
|
||||||
></ai-session-history>
|
></ai-session-history>
|
||||||
`,
|
`,
|
||||||
portalStyles: {
|
portalStyles: {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
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 type { NotificationService } from '@blocksuite/affine/shared/services';
|
|
||||||
import { scrollbarStyle } from '@blocksuite/affine/shared/styles';
|
import { scrollbarStyle } from '@blocksuite/affine/shared/styles';
|
||||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
@@ -134,9 +133,6 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor onDocClick!: (docId: string, sessionId: string) => void;
|
accessor onDocClick!: (docId: string, sessionId: string) => void;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor notification: NotificationService | null | undefined;
|
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private accessor sessions: BlockSuitePresets.AIRecentSession[] = [];
|
private accessor sessions: BlockSuitePresets.AIRecentSession[] = [];
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export class AIHistoryClear extends WithDisposable(ShadowlessElement) {
|
|||||||
accessor session!: CopilotChatHistoryFragment | null | undefined;
|
accessor session!: CopilotChatHistoryFragment | null | undefined;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor notification: NotificationService | null | undefined;
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor doc!: Store;
|
accessor doc!: Store;
|
||||||
@@ -52,15 +52,13 @@ export class AIHistoryClear extends WithDisposable(ShadowlessElement) {
|
|||||||
}
|
}
|
||||||
const sessionId = this.session.sessionId;
|
const sessionId = this.session.sessionId;
|
||||||
try {
|
try {
|
||||||
const confirm = this.notification
|
const confirm = await this.notificationService.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.',
|
confirmText: 'Confirm',
|
||||||
confirmText: 'Confirm',
|
cancelText: 'Cancel',
|
||||||
cancelText: 'Cancel',
|
});
|
||||||
})
|
|
||||||
: true;
|
|
||||||
|
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
const actionIds = this.chatContextValue.messages
|
const actionIds = this.chatContextValue.messages
|
||||||
@@ -71,11 +69,11 @@ export class AIHistoryClear extends WithDisposable(ShadowlessElement) {
|
|||||||
this.doc.id,
|
this.doc.id,
|
||||||
[...(sessionId ? [sessionId] : []), ...(actionIds || [])]
|
[...(sessionId ? [sessionId] : []), ...(actionIds || [])]
|
||||||
);
|
);
|
||||||
this.notification?.toast('History cleared');
|
this.notificationService.toast('History cleared');
|
||||||
this.onHistoryCleared?.();
|
this.onHistoryCleared?.();
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
this.notification?.toast('Failed to clear history');
|
this.notificationService.toast('Failed to clear history');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import type { EditorHost } from '@blocksuite/affine/std';
|
import type { ColorScheme } from '@blocksuite/affine/model';
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
import type { ExtensionType } from '@blocksuite/affine/store';
|
import type { ExtensionType } from '@blocksuite/affine/store';
|
||||||
|
import type { Signal } from '@preact/signals-core';
|
||||||
import { html } from 'lit';
|
import { html } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
|
|
||||||
import { createTextRenderer } from '../../components/text-renderer';
|
import { createTextRenderer } from '../../components/text-renderer';
|
||||||
|
|
||||||
export class ChatContentRichText extends WithDisposable(ShadowlessElement) {
|
export class ChatContentRichText extends WithDisposable(ShadowlessElement) {
|
||||||
@property({ attribute: false })
|
|
||||||
accessor host: EditorHost | null | undefined;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor text!: string;
|
accessor text!: string;
|
||||||
|
|
||||||
@@ -24,12 +22,16 @@ export class ChatContentRichText extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor affineFeatureFlagService!: FeatureFlagService;
|
accessor affineFeatureFlagService!: FeatureFlagService;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor theme!: Signal<ColorScheme>;
|
||||||
|
|
||||||
protected override render() {
|
protected override render() {
|
||||||
const { text, host } = this;
|
const { text } = this;
|
||||||
return html`${createTextRenderer(host, {
|
return html`${createTextRenderer({
|
||||||
customHeading: true,
|
customHeading: true,
|
||||||
extensions: this.extensions,
|
extensions: this.extensions,
|
||||||
affineFeatureFlagService: this.affineFeatureFlagService,
|
affineFeatureFlagService: this.affineFeatureFlagService,
|
||||||
|
theme: this.theme,
|
||||||
})(text, this.state)}`;
|
})(text, this.state)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import { ImageProxyService } from '@blocksuite/affine/shared/adapters';
|
import type { ColorScheme } from '@blocksuite/affine/model';
|
||||||
import type { EditorHost } from '@blocksuite/affine/std';
|
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
|
||||||
import type { ExtensionType } from '@blocksuite/affine/store';
|
import type { ExtensionType } from '@blocksuite/affine/store';
|
||||||
|
import type { NotificationService } from '@blocksuite/affine-shared/services';
|
||||||
import type { Signal } from '@preact/signals-core';
|
import type { Signal } from '@preact/signals-core';
|
||||||
import { css, html, nothing } from 'lit';
|
import { css, html, nothing } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
|
|
||||||
import { BlockDiffProvider } from '../../services/block-diff';
|
|
||||||
import type { AffineAIPanelState } from '../../widgets/ai-panel/type';
|
import type { AffineAIPanelState } from '../../widgets/ai-panel/type';
|
||||||
import type { StreamObject } from '../ai-chat-messages';
|
import type { StreamObject } from '../ai-chat-messages';
|
||||||
|
|
||||||
@@ -42,12 +41,16 @@ export class ChatContentStreamObjects extends WithDisposable(
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor affineFeatureFlagService!: FeatureFlagService;
|
accessor affineFeatureFlagService!: FeatureFlagService;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor theme!: Signal<ColorScheme>;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
private renderToolCall(streamObject: StreamObject) {
|
private renderToolCall(streamObject: StreamObject) {
|
||||||
if (streamObject.type !== 'tool-call') {
|
if (streamObject.type !== 'tool-call') {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
const imageProxyService = this.host?.store.get(ImageProxyService);
|
|
||||||
const blockDiffService = this.host?.view.std.getOptional(BlockDiffProvider);
|
|
||||||
|
|
||||||
switch (streamObject.toolName) {
|
switch (streamObject.toolName) {
|
||||||
case 'web_crawl_exa':
|
case 'web_crawl_exa':
|
||||||
@@ -55,7 +58,6 @@ export class ChatContentStreamObjects extends WithDisposable(
|
|||||||
<web-crawl-tool
|
<web-crawl-tool
|
||||||
.data=${streamObject}
|
.data=${streamObject}
|
||||||
.width=${this.width}
|
.width=${this.width}
|
||||||
.imageProxyService=${imageProxyService}
|
|
||||||
></web-crawl-tool>
|
></web-crawl-tool>
|
||||||
`;
|
`;
|
||||||
case 'web_search_exa':
|
case 'web_search_exa':
|
||||||
@@ -63,7 +65,6 @@ export class ChatContentStreamObjects extends WithDisposable(
|
|||||||
<web-search-tool
|
<web-search-tool
|
||||||
.data=${streamObject}
|
.data=${streamObject}
|
||||||
.width=${this.width}
|
.width=${this.width}
|
||||||
.imageProxyService=${imageProxyService}
|
|
||||||
></web-search-tool>
|
></web-search-tool>
|
||||||
`;
|
`;
|
||||||
case 'doc_compose':
|
case 'doc_compose':
|
||||||
@@ -72,7 +73,8 @@ export class ChatContentStreamObjects extends WithDisposable(
|
|||||||
.std=${this.host?.std}
|
.std=${this.host?.std}
|
||||||
.data=${streamObject}
|
.data=${streamObject}
|
||||||
.width=${this.width}
|
.width=${this.width}
|
||||||
.imageProxyService=${imageProxyService}
|
.theme=${this.theme}
|
||||||
|
.notificationService=${this.notificationService}
|
||||||
></doc-compose-tool>
|
></doc-compose-tool>
|
||||||
`;
|
`;
|
||||||
case 'code_artifact':
|
case 'code_artifact':
|
||||||
@@ -81,7 +83,6 @@ export class ChatContentStreamObjects extends WithDisposable(
|
|||||||
.std=${this.host?.std}
|
.std=${this.host?.std}
|
||||||
.data=${streamObject}
|
.data=${streamObject}
|
||||||
.width=${this.width}
|
.width=${this.width}
|
||||||
.imageProxyService=${imageProxyService}
|
|
||||||
></code-artifact-tool>
|
></code-artifact-tool>
|
||||||
`;
|
`;
|
||||||
case 'doc_edit':
|
case 'doc_edit':
|
||||||
@@ -89,7 +90,7 @@ export class ChatContentStreamObjects extends WithDisposable(
|
|||||||
<doc-edit-tool
|
<doc-edit-tool
|
||||||
.data=${streamObject}
|
.data=${streamObject}
|
||||||
.doc=${this.host?.store}
|
.doc=${this.host?.store}
|
||||||
.blockDiffService=${blockDiffService}
|
.notificationService=${this.notificationService}
|
||||||
></doc-edit-tool>
|
></doc-edit-tool>
|
||||||
`;
|
`;
|
||||||
default: {
|
default: {
|
||||||
@@ -105,8 +106,6 @@ export class ChatContentStreamObjects extends WithDisposable(
|
|||||||
if (streamObject.type !== 'tool-result') {
|
if (streamObject.type !== 'tool-result') {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
const imageProxyService = this.host?.store.get(ImageProxyService);
|
|
||||||
const blockDiffService = this.host?.view.std.getOptional(BlockDiffProvider);
|
|
||||||
|
|
||||||
switch (streamObject.toolName) {
|
switch (streamObject.toolName) {
|
||||||
case 'web_crawl_exa':
|
case 'web_crawl_exa':
|
||||||
@@ -114,7 +113,6 @@ export class ChatContentStreamObjects extends WithDisposable(
|
|||||||
<web-crawl-tool
|
<web-crawl-tool
|
||||||
.data=${streamObject}
|
.data=${streamObject}
|
||||||
.width=${this.width}
|
.width=${this.width}
|
||||||
.imageProxyService=${imageProxyService}
|
|
||||||
></web-crawl-tool>
|
></web-crawl-tool>
|
||||||
`;
|
`;
|
||||||
case 'web_search_exa':
|
case 'web_search_exa':
|
||||||
@@ -122,7 +120,6 @@ export class ChatContentStreamObjects extends WithDisposable(
|
|||||||
<web-search-tool
|
<web-search-tool
|
||||||
.data=${streamObject}
|
.data=${streamObject}
|
||||||
.width=${this.width}
|
.width=${this.width}
|
||||||
.imageProxyService=${imageProxyService}
|
|
||||||
></web-search-tool>
|
></web-search-tool>
|
||||||
`;
|
`;
|
||||||
case 'doc_compose':
|
case 'doc_compose':
|
||||||
@@ -131,7 +128,8 @@ export class ChatContentStreamObjects extends WithDisposable(
|
|||||||
.std=${this.host?.std}
|
.std=${this.host?.std}
|
||||||
.data=${streamObject}
|
.data=${streamObject}
|
||||||
.width=${this.width}
|
.width=${this.width}
|
||||||
.imageProxyService=${imageProxyService}
|
.theme=${this.theme}
|
||||||
|
.notificationService=${this.notificationService}
|
||||||
></doc-compose-tool>
|
></doc-compose-tool>
|
||||||
`;
|
`;
|
||||||
case 'code_artifact':
|
case 'code_artifact':
|
||||||
@@ -140,7 +138,6 @@ export class ChatContentStreamObjects extends WithDisposable(
|
|||||||
.std=${this.host?.std}
|
.std=${this.host?.std}
|
||||||
.data=${streamObject}
|
.data=${streamObject}
|
||||||
.width=${this.width}
|
.width=${this.width}
|
||||||
.imageProxyService=${imageProxyService}
|
|
||||||
></code-artifact-tool>
|
></code-artifact-tool>
|
||||||
`;
|
`;
|
||||||
case 'doc_edit':
|
case 'doc_edit':
|
||||||
@@ -148,8 +145,8 @@ export class ChatContentStreamObjects extends WithDisposable(
|
|||||||
<doc-edit-tool
|
<doc-edit-tool
|
||||||
.data=${streamObject}
|
.data=${streamObject}
|
||||||
.host=${this.host}
|
.host=${this.host}
|
||||||
.blockDiffService=${blockDiffService}
|
|
||||||
.renderRichText=${this.renderRichText.bind(this)}
|
.renderRichText=${this.renderRichText.bind(this)}
|
||||||
|
.notificationService=${this.notificationService}
|
||||||
></doc-edit-tool>
|
></doc-edit-tool>
|
||||||
`;
|
`;
|
||||||
default: {
|
default: {
|
||||||
@@ -158,7 +155,6 @@ export class ChatContentStreamObjects extends WithDisposable(
|
|||||||
<tool-result-card
|
<tool-result-card
|
||||||
.name=${name}
|
.name=${name}
|
||||||
.width=${this.width}
|
.width=${this.width}
|
||||||
.imageProxyService=${imageProxyService}
|
|
||||||
></tool-result-card>
|
></tool-result-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -167,11 +163,11 @@ export class ChatContentStreamObjects extends WithDisposable(
|
|||||||
|
|
||||||
private renderRichText(text: string) {
|
private renderRichText(text: string) {
|
||||||
return html`<chat-content-rich-text
|
return html`<chat-content-rich-text
|
||||||
.host=${this.host}
|
|
||||||
.text=${text}
|
.text=${text}
|
||||||
.state=${this.state}
|
.state=${this.state}
|
||||||
.extensions=${this.extensions}
|
.extensions=${this.extensions}
|
||||||
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
||||||
|
.theme=${this.theme}
|
||||||
></chat-content-rich-text>`;
|
></chat-content-rich-text>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export class AIScrollableTextRenderer extends WithDisposable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
const { host, answer, state, textRendererOptions } = this;
|
const { answer, state, textRendererOptions } = this;
|
||||||
|
|
||||||
return html` <style>
|
return html` <style>
|
||||||
.ai-scrollable-text-renderer {
|
.ai-scrollable-text-renderer {
|
||||||
@@ -71,7 +71,6 @@ export class AIScrollableTextRenderer extends WithDisposable(
|
|||||||
</style>
|
</style>
|
||||||
<div class="ai-scrollable-text-renderer" @wheel=${this._onWheel}>
|
<div class="ai-scrollable-text-renderer" @wheel=${this._onWheel}>
|
||||||
<text-renderer
|
<text-renderer
|
||||||
.host=${host}
|
|
||||||
.answer=${answer}
|
.answer=${answer}
|
||||||
.state=${state}
|
.state=${state}
|
||||||
.options=${textRendererOptions}
|
.options=${textRendererOptions}
|
||||||
@@ -83,7 +82,7 @@ export class AIScrollableTextRenderer extends WithDisposable(
|
|||||||
accessor answer!: string;
|
accessor answer!: string;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor host: EditorHost | null | undefined;
|
accessor host!: EditorHost;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor state: AffineAIPanelState | undefined;
|
accessor state: AffineAIPanelState | undefined;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { CodeBlockHighlighter } from '@blocksuite/affine/blocks/code';
|
import { CodeBlockHighlighter } from '@blocksuite/affine/blocks/code';
|
||||||
import { toast } from '@blocksuite/affine/components/toast';
|
import { toast } from '@blocksuite/affine/components/toast';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import type { ImageProxyService } from '@blocksuite/affine/shared/adapters';
|
|
||||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import { type BlockStdScope, ShadowlessElement } from '@blocksuite/affine/std';
|
import { type BlockStdScope, ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
import { CopyIcon, PageIcon, ToolIcon } from '@blocksuite/icons/lit';
|
import { CopyIcon, PageIcon, ToolIcon } from '@blocksuite/icons/lit';
|
||||||
@@ -296,9 +295,6 @@ export class CodeArtifactTool extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor width: Signal<number | undefined> | undefined;
|
accessor width: Signal<number | undefined> | undefined;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor imageProxyService: ImageProxyService | null | undefined;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor std: BlockStdScope | undefined;
|
accessor std: BlockStdScope | undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,11 @@ import { LoadingIcon } from '@blocksuite/affine/components/icons';
|
|||||||
import { toast } from '@blocksuite/affine/components/toast';
|
import { toast } from '@blocksuite/affine/components/toast';
|
||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
|
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
|
||||||
import type { ImageProxyService } from '@blocksuite/affine/shared/adapters';
|
import type { ColorScheme } from '@blocksuite/affine/model';
|
||||||
import {
|
|
||||||
NotificationProvider,
|
|
||||||
ThemeProvider,
|
|
||||||
} from '@blocksuite/affine/shared/services';
|
|
||||||
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import { type BlockStdScope, ShadowlessElement } from '@blocksuite/affine/std';
|
import { type BlockStdScope, ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
import { MarkdownTransformer } from '@blocksuite/affine/widgets/linked-doc';
|
import { MarkdownTransformer } from '@blocksuite/affine/widgets/linked-doc';
|
||||||
|
import type { NotificationService } from '@blocksuite/affine-shared/services';
|
||||||
import { CopyIcon, PageIcon, ToolIcon } from '@blocksuite/icons/lit';
|
import { CopyIcon, PageIcon, ToolIcon } from '@blocksuite/icons/lit';
|
||||||
import { type Signal } from '@preact/signals-core';
|
import { type Signal } from '@preact/signals-core';
|
||||||
import { css, html, nothing, type PropertyValues } from 'lit';
|
import { css, html, nothing, type PropertyValues } from 'lit';
|
||||||
@@ -110,10 +107,13 @@ export class DocComposeTool extends WithDisposable(ShadowlessElement) {
|
|||||||
accessor width: Signal<number | undefined> | undefined;
|
accessor width: Signal<number | undefined> | undefined;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor imageProxyService: ImageProxyService | null | undefined;
|
accessor std: BlockStdScope | undefined;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor std: BlockStdScope | undefined;
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor theme!: Signal<ColorScheme>;
|
||||||
|
|
||||||
override updated(changedProperties: PropertyValues) {
|
override updated(changedProperties: PropertyValues) {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
@@ -147,7 +147,6 @@ export class DocComposeTool extends WithDisposable(ShadowlessElement) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const workspace = std.store.workspace;
|
const workspace = std.store.workspace;
|
||||||
const notificationService = std.get(NotificationProvider);
|
|
||||||
const refNodeSlots = std.getOptional(RefNodeSlotsProvider);
|
const refNodeSlots = std.getOptional(RefNodeSlotsProvider);
|
||||||
const docId = await MarkdownTransformer.importMarkdownToDoc({
|
const docId = await MarkdownTransformer.importMarkdownToDoc({
|
||||||
collection: workspace,
|
collection: workspace,
|
||||||
@@ -157,7 +156,7 @@ export class DocComposeTool extends WithDisposable(ShadowlessElement) {
|
|||||||
extensions: getStoreManager().config.init().value.get('store'),
|
extensions: getStoreManager().config.init().value.get('store'),
|
||||||
});
|
});
|
||||||
if (docId) {
|
if (docId) {
|
||||||
const open = await notificationService.confirm({
|
const open = await this.notificationService.confirm({
|
||||||
title: 'Open the doc you just created',
|
title: 'Open the doc you just created',
|
||||||
message: 'Doc saved successfully! Would you like to open it now?',
|
message: 'Doc saved successfully! Would you like to open it now?',
|
||||||
cancelText: 'Cancel',
|
cancelText: 'Cancel',
|
||||||
@@ -200,8 +199,6 @@ export class DocComposeTool extends WithDisposable(ShadowlessElement) {
|
|||||||
${successResult
|
${successResult
|
||||||
? html`<text-renderer
|
? html`<text-renderer
|
||||||
.answer=${successResult.markdown}
|
.answer=${successResult.markdown}
|
||||||
.host=${std.host}
|
|
||||||
.schema=${std.store.schema}
|
|
||||||
.options=${{
|
.options=${{
|
||||||
customHeading: true,
|
customHeading: true,
|
||||||
extensions: getCustomPageEditorBlockSpecs(),
|
extensions: getCustomPageEditorBlockSpecs(),
|
||||||
@@ -237,10 +234,8 @@ export class DocComposeTool extends WithDisposable(ShadowlessElement) {
|
|||||||
></tool-call-failed>`;
|
></tool-call-failed>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const theme = this.std.get(ThemeProvider).theme;
|
|
||||||
|
|
||||||
const { LinkedDocEmptyBanner } = getEmbedLinkedDocIcons(
|
const { LinkedDocEmptyBanner } = getEmbedLinkedDocIcons(
|
||||||
theme,
|
this.theme.value,
|
||||||
'page',
|
'page',
|
||||||
'horizontal'
|
'horizontal'
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import { NotificationProvider } from '@blocksuite/affine/shared/services';
|
|
||||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
|
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
|
import type { NotificationService } from '@blocksuite/affine-shared/services';
|
||||||
import {
|
import {
|
||||||
CloseIcon,
|
CloseIcon,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
import { css, html, nothing } from 'lit';
|
import { css, html, nothing } from 'lit';
|
||||||
import { property, state } from 'lit/decorators.js';
|
import { property, state } from 'lit/decorators.js';
|
||||||
|
|
||||||
import type { BlockDiffService } from '../../services/block-diff';
|
import { BlockDiffProvider } from '../../services/block-diff';
|
||||||
import { diffMarkdown } from '../../utils/apply-model/markdown-diff';
|
import { diffMarkdown } from '../../utils/apply-model/markdown-diff';
|
||||||
import { copyText } from '../../utils/editor-actions';
|
import { copyText } from '../../utils/editor-actions';
|
||||||
import type { ToolError } from './type';
|
import type { ToolError } from './type';
|
||||||
@@ -190,14 +190,18 @@ export class DocEditTool extends WithDisposable(ShadowlessElement) {
|
|||||||
accessor data!: DocEditToolCall | DocEditToolResult;
|
accessor data!: DocEditToolCall | DocEditToolResult;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor blockDiffService: BlockDiffService | undefined;
|
accessor renderRichText!: (text: string) => string;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor renderRichText!: (text: string) => string;
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
accessor isCollapsed = false;
|
accessor isCollapsed = false;
|
||||||
|
|
||||||
|
get blockDiffService() {
|
||||||
|
return this.host?.std.getOptional(BlockDiffProvider);
|
||||||
|
}
|
||||||
|
|
||||||
private async _handleApply(markdown: string) {
|
private async _handleApply(markdown: string) {
|
||||||
if (!this.host) {
|
if (!this.host) {
|
||||||
return;
|
return;
|
||||||
@@ -229,14 +233,9 @@ export class DocEditTool extends WithDisposable(ShadowlessElement) {
|
|||||||
if (!this.host) {
|
if (!this.host) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const success = await copyText(
|
const success = await copyText(removeMarkdownComments(changedMarkdown));
|
||||||
this.host,
|
|
||||||
removeMarkdownComments(changedMarkdown)
|
|
||||||
);
|
|
||||||
if (success) {
|
if (success) {
|
||||||
const notificationService =
|
this.notificationService.notify({
|
||||||
this.host?.std.getOptional(NotificationProvider);
|
|
||||||
notificationService?.notify({
|
|
||||||
title: 'Copied to clipboard',
|
title: 'Copied to clipboard',
|
||||||
accent: 'success',
|
accent: 'success',
|
||||||
onClose: function (): void {},
|
onClose: function (): void {},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import { type ImageProxyService } from '@blocksuite/affine/shared/adapters';
|
|
||||||
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 { DEFAULT_IMAGE_PROXY_ENDPOINT } from '@blocksuite/affine-shared/consts';
|
||||||
import { ToggleDownIcon, ToolIcon } from '@blocksuite/icons/lit';
|
import { ToggleDownIcon, ToolIcon } from '@blocksuite/icons/lit';
|
||||||
import { type Signal } from '@preact/signals-core';
|
import { type Signal } from '@preact/signals-core';
|
||||||
import { css, html, nothing, type TemplateResult } from 'lit';
|
import { css, html, nothing, type TemplateResult } from 'lit';
|
||||||
@@ -205,12 +205,11 @@ export class ToolResultCard extends SignalWatcher(
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor width: Signal<number | undefined> | undefined;
|
accessor width: Signal<number | undefined> | undefined;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor imageProxyService: ImageProxyService | null | undefined;
|
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private accessor isCollapsed = true;
|
private accessor isCollapsed = true;
|
||||||
|
|
||||||
|
private readonly imageProxyURL = DEFAULT_IMAGE_PROXY_ENDPOINT;
|
||||||
|
|
||||||
protected override render() {
|
protected override render() {
|
||||||
return html`
|
return html`
|
||||||
<div class="ai-tool-result-wrapper">
|
<div class="ai-tool-result-wrapper">
|
||||||
@@ -272,15 +271,20 @@ export class ToolResultCard extends SignalWatcher(
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildUrl(imageUrl: string) {
|
||||||
|
if (imageUrl.startsWith(this.imageProxyURL)) {
|
||||||
|
return imageUrl;
|
||||||
|
}
|
||||||
|
return `${this.imageProxyURL}?url=${encodeURIComponent(imageUrl)}`;
|
||||||
|
}
|
||||||
|
|
||||||
private renderIcon(icon: string | TemplateResult<1> | undefined) {
|
private renderIcon(icon: string | TemplateResult<1> | undefined) {
|
||||||
if (!icon) {
|
if (!icon) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof icon === 'string') {
|
if (typeof icon === 'string') {
|
||||||
if (this.imageProxyService) {
|
return html`<img src=${this.buildUrl(icon)} />`;
|
||||||
return html`<img src=${this.imageProxyService.buildUrl(icon)} />`;
|
|
||||||
}
|
|
||||||
return html`<img src=${icon} />`;
|
|
||||||
}
|
}
|
||||||
return html`${icon}`;
|
return html`${icon}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import type { ImageProxyService } from '@blocksuite/affine/shared/adapters';
|
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
import { WebIcon } from '@blocksuite/icons/lit';
|
import { WebIcon } from '@blocksuite/icons/lit';
|
||||||
import type { Signal } from '@preact/signals-core';
|
import type { Signal } from '@preact/signals-core';
|
||||||
@@ -40,9 +39,6 @@ export class WebCrawlTool extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor width: Signal<number | undefined> | undefined;
|
accessor width: Signal<number | undefined> | undefined;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor imageProxyService: ImageProxyService | null | undefined;
|
|
||||||
|
|
||||||
renderToolCall() {
|
renderToolCall() {
|
||||||
return html`
|
return html`
|
||||||
<tool-call-card
|
<tool-call-card
|
||||||
@@ -73,7 +69,6 @@ export class WebCrawlTool extends WithDisposable(ShadowlessElement) {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
.width=${this.width}
|
.width=${this.width}
|
||||||
.imageProxyService=${this.imageProxyService}
|
|
||||||
></tool-result-card>
|
></tool-result-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import type { ImageProxyService } from '@blocksuite/affine/shared/adapters';
|
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
import { WebIcon } from '@blocksuite/icons/lit';
|
import { WebIcon } from '@blocksuite/icons/lit';
|
||||||
import type { Signal } from '@preact/signals-core';
|
import type { Signal } from '@preact/signals-core';
|
||||||
@@ -40,9 +39,6 @@ export class WebSearchTool extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor width: Signal<number | undefined> | undefined;
|
accessor width: Signal<number | undefined> | undefined;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor imageProxyService: ImageProxyService | null | undefined;
|
|
||||||
|
|
||||||
renderToolCall() {
|
renderToolCall() {
|
||||||
return html`
|
return html`
|
||||||
<tool-call-card
|
<tool-call-card
|
||||||
@@ -75,7 +71,6 @@ export class WebSearchTool extends WithDisposable(ShadowlessElement) {
|
|||||||
.footerIcons=${footerIcons}
|
.footerIcons=${footerIcons}
|
||||||
.results=${results}
|
.results=${results}
|
||||||
.width=${this.width}
|
.width=${this.width}
|
||||||
.imageProxyService=${this.imageProxyService}
|
|
||||||
></tool-result-card>
|
></tool-result-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
||||||
import type { ImageSelection } from '@blocksuite/affine/shared/selection';
|
import type { ImageSelection } from '@blocksuite/affine/shared/selection';
|
||||||
import { NotificationProvider } from '@blocksuite/affine/shared/services';
|
|
||||||
import type {
|
import type {
|
||||||
BlockSelection,
|
BlockSelection,
|
||||||
EditorHost,
|
EditorHost,
|
||||||
TextSelection,
|
TextSelection,
|
||||||
} from '@blocksuite/affine/std';
|
} from '@blocksuite/affine/std';
|
||||||
|
import type { NotificationService } from '@blocksuite/affine-shared/services';
|
||||||
import { css, html, LitElement, nothing } from 'lit';
|
import { css, html, LitElement, nothing } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
import { classMap } from 'lit/directives/class-map.js';
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
@@ -96,6 +96,9 @@ export class ChatActionList extends LitElement {
|
|||||||
@property({ attribute: 'data-testid', reflect: true })
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
accessor testId = 'chat-action-list';
|
accessor testId = 'chat-action-list';
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
const { actions } = this;
|
const { actions } = this;
|
||||||
if (!actions.length) {
|
if (!actions.length) {
|
||||||
@@ -148,7 +151,7 @@ export class ChatActionList extends LitElement {
|
|||||||
messageId
|
messageId
|
||||||
);
|
);
|
||||||
if (success) {
|
if (success) {
|
||||||
this.host.std.getOptional(NotificationProvider)?.notify({
|
this.notificationService.notify({
|
||||||
title: action.toast,
|
title: action.toast,
|
||||||
accent: 'success',
|
accent: 'success',
|
||||||
onClose: function (): void {},
|
onClose: function (): void {},
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
|||||||
import { Tooltip } from '@blocksuite/affine/components/toolbar';
|
import { Tooltip } from '@blocksuite/affine/components/toolbar';
|
||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import { noop } from '@blocksuite/affine/global/utils';
|
import { noop } from '@blocksuite/affine/global/utils';
|
||||||
import { NotificationProvider } from '@blocksuite/affine/shared/services';
|
|
||||||
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import { createButtonPopper } from '@blocksuite/affine/shared/utils';
|
import { createButtonPopper } from '@blocksuite/affine/shared/utils';
|
||||||
import type {
|
import type {
|
||||||
@@ -10,6 +9,7 @@ import type {
|
|||||||
EditorHost,
|
EditorHost,
|
||||||
TextSelection,
|
TextSelection,
|
||||||
} from '@blocksuite/affine/std';
|
} from '@blocksuite/affine/std';
|
||||||
|
import type { NotificationService } from '@blocksuite/affine-shared/services';
|
||||||
import { CopyIcon, MoreHorizontalIcon, ResetIcon } from '@blocksuite/icons/lit';
|
import { CopyIcon, MoreHorizontalIcon, ResetIcon } from '@blocksuite/icons/lit';
|
||||||
import { css, html, LitElement, nothing, type PropertyValues } from 'lit';
|
import { css, html, LitElement, nothing, type PropertyValues } from 'lit';
|
||||||
import { property, query, state } from 'lit/decorators.js';
|
import { property, query, state } from 'lit/decorators.js';
|
||||||
@@ -131,14 +131,15 @@ export class ChatCopyMore extends WithDisposable(LitElement) {
|
|||||||
@property({ attribute: 'data-testid', reflect: true })
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
accessor testId = 'chat-actions';
|
accessor testId = 'chat-actions';
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
private _toggle() {
|
private _toggle() {
|
||||||
this._morePopper?.toggle();
|
this._morePopper?.toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly _notifySuccess = (title: string) => {
|
private readonly _notifySuccess = (title: string) => {
|
||||||
const notificationService =
|
this.notificationService.notify({
|
||||||
this.host?.std.getOptional(NotificationProvider);
|
|
||||||
notificationService?.notify({
|
|
||||||
title: title,
|
title: title,
|
||||||
accent: 'success',
|
accent: 'success',
|
||||||
onClose: function (): void {},
|
onClose: function (): void {},
|
||||||
@@ -165,7 +166,7 @@ export class ChatCopyMore extends WithDisposable(LitElement) {
|
|||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
const { host, content, isLast, messageId, actions } = this;
|
const { host, content, isLast, messageId, actions } = this;
|
||||||
const showMoreIcon = !isLast && host && actions.length > 0;
|
const showMoreIcon = !isLast && actions.length > 0;
|
||||||
return html`<style>
|
return html`<style>
|
||||||
.copy-more {
|
.copy-more {
|
||||||
margin-top: ${this.withMargin ? '8px' : '0px'};
|
margin-top: ${this.withMargin ? '8px' : '0px'};
|
||||||
@@ -176,11 +177,11 @@ export class ChatCopyMore extends WithDisposable(LitElement) {
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="copy-more">
|
<div class="copy-more">
|
||||||
${content && host
|
${content
|
||||||
? html`<div
|
? html`<div
|
||||||
class="button copy"
|
class="button copy"
|
||||||
@click=${async () => {
|
@click=${async () => {
|
||||||
const success = await copyText(host, content);
|
const success = await copyText(content);
|
||||||
if (success) {
|
if (success) {
|
||||||
this._notifySuccess('Copied to clipboard');
|
this._notifySuccess('Copied to clipboard');
|
||||||
}
|
}
|
||||||
@@ -201,7 +202,7 @@ export class ChatCopyMore extends WithDisposable(LitElement) {
|
|||||||
<affine-tooltip .autoShift=${true}>Retry</affine-tooltip>
|
<affine-tooltip .autoShift=${true}>Retry</affine-tooltip>
|
||||||
</div>`
|
</div>`
|
||||||
: nothing}
|
: nothing}
|
||||||
${showMoreIcon
|
${showMoreIcon && host
|
||||||
? html`<div
|
? html`<div
|
||||||
class="button more"
|
class="button more"
|
||||||
data-testid="action-more-button"
|
data-testid="action-more-button"
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
|
import type { AppThemeService } from '@affine/core/modules/theme';
|
||||||
import type {
|
import type {
|
||||||
ContextEmbedStatus,
|
ContextEmbedStatus,
|
||||||
CopilotChatHistoryFragment,
|
CopilotChatHistoryFragment,
|
||||||
} 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 { 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 type { EditorHost } from '@blocksuite/affine/std';
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
@@ -16,6 +17,7 @@ import { createRef, type Ref, ref } from 'lit/directives/ref.js';
|
|||||||
import { throttle } from 'lodash-es';
|
import { throttle } from 'lodash-es';
|
||||||
|
|
||||||
import type { AppSidebarConfig } from '../../chat-panel/chat-config';
|
import type { AppSidebarConfig } from '../../chat-panel/chat-config';
|
||||||
|
import { HISTORY_IMAGE_ACTIONS } from '../../chat-panel/const';
|
||||||
import { AIProvider } from '../../provider';
|
import { AIProvider } from '../../provider';
|
||||||
import type { DocDisplayConfig, SearchMenuConfig } from '../ai-chat-chips';
|
import type { DocDisplayConfig, SearchMenuConfig } from '../ai-chat-chips';
|
||||||
import type { ChatContextValue } from '../ai-chat-content';
|
import type { ChatContextValue } from '../ai-chat-content';
|
||||||
@@ -29,6 +31,7 @@ import {
|
|||||||
type ChatAction,
|
type ChatAction,
|
||||||
type ChatMessage,
|
type ChatMessage,
|
||||||
type HistoryMessage,
|
type HistoryMessage,
|
||||||
|
isChatMessage,
|
||||||
} from '../ai-chat-messages';
|
} from '../ai-chat-messages';
|
||||||
|
|
||||||
const DEFAULT_CHAT_CONTEXT_VALUE: ChatContextValue = {
|
const DEFAULT_CHAT_CONTEXT_VALUE: ChatContextValue = {
|
||||||
@@ -159,6 +162,12 @@ export class PlaygroundChat extends SignalWatcher(
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor affineFeatureFlagService!: FeatureFlagService;
|
accessor affineFeatureFlagService!: FeatureFlagService;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor affineThemeService!: AppThemeService;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor addChat!: () => Promise<void>;
|
accessor addChat!: () => Promise<void>;
|
||||||
|
|
||||||
@@ -177,6 +186,17 @@ export class PlaygroundChat extends SignalWatcher(
|
|||||||
// request counter to track the latest request
|
// request counter to track the latest request
|
||||||
private _updateHistoryCounter = 0;
|
private _updateHistoryCounter = 0;
|
||||||
|
|
||||||
|
get messages() {
|
||||||
|
return this.chatContextValue.messages.filter(item => {
|
||||||
|
return (
|
||||||
|
isChatMessage(item) ||
|
||||||
|
item.messages?.length === 3 ||
|
||||||
|
(HISTORY_IMAGE_ACTIONS.includes(item.action) &&
|
||||||
|
item.messages?.length === 2)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private readonly _initPanel = async () => {
|
private readonly _initPanel = async () => {
|
||||||
const userId = (await AIProvider.userInfo)?.id;
|
const userId = (await AIProvider.userInfo)?.id;
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
@@ -276,7 +296,6 @@ 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">
|
||||||
@@ -294,7 +313,7 @@ export class PlaygroundChat extends SignalWatcher(
|
|||||||
<ai-history-clear
|
<ai-history-clear
|
||||||
.doc=${this.doc}
|
.doc=${this.doc}
|
||||||
.session=${this.session}
|
.session=${this.session}
|
||||||
.notification=${notification}
|
.notificationService=${this.notificationService}
|
||||||
.onHistoryCleared=${this._updateHistory}
|
.onHistoryCleared=${this._updateHistory}
|
||||||
.chatContextValue=${this.chatContextValue}
|
.chatContextValue=${this.chatContextValue}
|
||||||
></ai-history-clear>
|
></ai-history-clear>
|
||||||
@@ -312,8 +331,11 @@ export class PlaygroundChat extends SignalWatcher(
|
|||||||
.updateContext=${this.updateContext}
|
.updateContext=${this.updateContext}
|
||||||
.extensions=${this.extensions}
|
.extensions=${this.extensions}
|
||||||
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
||||||
|
.affineThemeService=${this.affineThemeService}
|
||||||
|
.notificationService=${this.notificationService}
|
||||||
.networkSearchConfig=${this.networkSearchConfig}
|
.networkSearchConfig=${this.networkSearchConfig}
|
||||||
.reasoningConfig=${this.reasoningConfig}
|
.reasoningConfig=${this.reasoningConfig}
|
||||||
|
.messages=${this.messages}
|
||||||
></ai-chat-messages>
|
></ai-chat-messages>
|
||||||
<ai-chat-composer
|
<ai-chat-composer
|
||||||
.host=${this.host}
|
.host=${this.host}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
|
import type { AppThemeService } from '@affine/core/modules/theme';
|
||||||
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
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';
|
||||||
import type { ExtensionType, Store } from '@blocksuite/affine/store';
|
import type { ExtensionType, Store } from '@blocksuite/affine/store';
|
||||||
|
import type { NotificationService } from '@blocksuite/affine-shared/services';
|
||||||
import { css, html } from 'lit';
|
import { css, html } from 'lit';
|
||||||
import { property, state } from 'lit/decorators.js';
|
import { property, state } from 'lit/decorators.js';
|
||||||
import { repeat } from 'lit/directives/repeat.js';
|
import { repeat } from 'lit/directives/repeat.js';
|
||||||
@@ -83,6 +85,12 @@ export class PlaygroundContent extends SignalWatcher(
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor affineFeatureFlagService!: FeatureFlagService;
|
accessor affineFeatureFlagService!: FeatureFlagService;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor affineThemeService!: AppThemeService;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
accessor sessions: CopilotChatHistoryFragment[] = [];
|
accessor sessions: CopilotChatHistoryFragment[] = [];
|
||||||
|
|
||||||
@@ -336,6 +344,8 @@ export class PlaygroundContent extends SignalWatcher(
|
|||||||
.docDisplayConfig=${this.docDisplayConfig}
|
.docDisplayConfig=${this.docDisplayConfig}
|
||||||
.extensions=${this.extensions}
|
.extensions=${this.extensions}
|
||||||
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
||||||
|
.affineThemeService=${this.affineThemeService}
|
||||||
|
.notificationService=${this.notificationService}
|
||||||
.addChat=${this.addChat}
|
.addChat=${this.addChat}
|
||||||
></playground-chat>
|
></playground-chat>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import { createReactComponentFromLit } from '@affine/component';
|
import { createReactComponentFromLit } from '@affine/component';
|
||||||
import { getStoreManager } from '@affine/core/blocksuite/manager/store';
|
|
||||||
import { getViewManager } from '@affine/core/blocksuite/manager/view';
|
import { getViewManager } from '@affine/core/blocksuite/manager/view';
|
||||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
import { PeekViewProvider } from '@blocksuite/affine/components/peek';
|
import { PeekViewProvider } from '@blocksuite/affine/components/peek';
|
||||||
import { Container, type ServiceProvider } from '@blocksuite/affine/global/di';
|
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
|
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
|
||||||
|
import type { ColorScheme } from '@blocksuite/affine/model';
|
||||||
import {
|
import {
|
||||||
codeBlockWrapMiddleware,
|
codeBlockWrapMiddleware,
|
||||||
defaultImageProxyMiddleware,
|
defaultImageProxyMiddleware,
|
||||||
ImageProxyService,
|
ImageProxyService,
|
||||||
} from '@blocksuite/affine/shared/adapters';
|
} from '@blocksuite/affine/shared/adapters';
|
||||||
import { ThemeProvider } from '@blocksuite/affine/shared/services';
|
|
||||||
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import {
|
import {
|
||||||
BlockStdScope,
|
BlockStdScope,
|
||||||
@@ -22,10 +20,10 @@ import {
|
|||||||
import type {
|
import type {
|
||||||
ExtensionType,
|
ExtensionType,
|
||||||
Query,
|
Query,
|
||||||
Schema,
|
|
||||||
Store,
|
Store,
|
||||||
TransformerMiddleware,
|
TransformerMiddleware,
|
||||||
} from '@blocksuite/affine/store';
|
} from '@blocksuite/affine/store';
|
||||||
|
import type { Signal } from '@preact/signals-core';
|
||||||
import {
|
import {
|
||||||
darkCssVariablesV2,
|
darkCssVariablesV2,
|
||||||
lightCssVariablesV2,
|
lightCssVariablesV2,
|
||||||
@@ -103,6 +101,7 @@ export type TextRendererOptions = {
|
|||||||
additionalMiddlewares?: TransformerMiddleware[];
|
additionalMiddlewares?: TransformerMiddleware[];
|
||||||
testId?: string;
|
testId?: string;
|
||||||
affineFeatureFlagService?: FeatureFlagService;
|
affineFeatureFlagService?: FeatureFlagService;
|
||||||
|
theme?: Signal<ColorScheme>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// todo: refactor it for more general purpose usage instead of AI only?
|
// todo: refactor it for more general purpose usage instead of AI only?
|
||||||
@@ -221,6 +220,8 @@ export class TextRenderer extends SignalWatcher(
|
|||||||
|
|
||||||
private _doc: Store | null = null;
|
private _doc: Store | null = null;
|
||||||
|
|
||||||
|
private _host: EditorHost | null = null;
|
||||||
|
|
||||||
private readonly _query: Query = {
|
private readonly _query: Query = {
|
||||||
mode: 'strict',
|
mode: 'strict',
|
||||||
match: [
|
match: [
|
||||||
@@ -243,7 +244,7 @@ export class TextRenderer extends SignalWatcher(
|
|||||||
private _timer?: ReturnType<typeof setInterval> | null = null;
|
private _timer?: ReturnType<typeof setInterval> | null = null;
|
||||||
|
|
||||||
private readonly _subscribeDocLinkClicked = () => {
|
private readonly _subscribeDocLinkClicked = () => {
|
||||||
const refNodeSlots = this.host?.std.getOptional(RefNodeSlotsProvider);
|
const refNodeSlots = this._host?.std.getOptional(RefNodeSlotsProvider);
|
||||||
if (!refNodeSlots) return;
|
if (!refNodeSlots) return;
|
||||||
this.disposables.add(
|
this.disposables.add(
|
||||||
refNodeSlots.docLinkClicked
|
refNodeSlots.docLinkClicked
|
||||||
@@ -254,7 +255,7 @@ export class TextRenderer extends SignalWatcher(
|
|||||||
)
|
)
|
||||||
.subscribe(options => {
|
.subscribe(options => {
|
||||||
// Open the doc in center peek
|
// Open the doc in center peek
|
||||||
this.host?.std
|
this._host?.std
|
||||||
.getOptional(PeekViewProvider)
|
.getOptional(PeekViewProvider)
|
||||||
?.peek({
|
?.peek({
|
||||||
docId: options.pageId,
|
docId: options.pageId,
|
||||||
@@ -268,40 +269,27 @@ export class TextRenderer extends SignalWatcher(
|
|||||||
if (this._answers.length > 0) {
|
if (this._answers.length > 0) {
|
||||||
const latestAnswer = this._answers.pop();
|
const latestAnswer = this._answers.pop();
|
||||||
this._answers = [];
|
this._answers = [];
|
||||||
const schema = this.schema ?? this.host?.std.store.schema;
|
if (latestAnswer) {
|
||||||
let provider: ServiceProvider;
|
|
||||||
if (this.host) {
|
|
||||||
provider = this.host.std.store.provider;
|
|
||||||
} else {
|
|
||||||
const container = new Container();
|
|
||||||
getStoreManager()
|
|
||||||
.config.init()
|
|
||||||
.value.get('store')
|
|
||||||
.forEach(ext => {
|
|
||||||
ext.setup(container);
|
|
||||||
});
|
|
||||||
|
|
||||||
provider = container.provider();
|
|
||||||
}
|
|
||||||
if (latestAnswer && schema) {
|
|
||||||
const middlewares = [
|
const middlewares = [
|
||||||
defaultImageProxyMiddleware,
|
defaultImageProxyMiddleware,
|
||||||
codeBlockWrapMiddleware(true),
|
codeBlockWrapMiddleware(true),
|
||||||
...(this.options.additionalMiddlewares ?? []),
|
...(this.options.additionalMiddlewares ?? []),
|
||||||
];
|
];
|
||||||
const affineFeatureFlagService = this.options.affineFeatureFlagService;
|
|
||||||
markDownToDoc(
|
markDownToDoc(
|
||||||
provider,
|
|
||||||
schema,
|
|
||||||
latestAnswer,
|
latestAnswer,
|
||||||
middlewares,
|
middlewares,
|
||||||
affineFeatureFlagService
|
this.options.affineFeatureFlagService
|
||||||
)
|
)
|
||||||
.then(doc => {
|
.then(doc => {
|
||||||
this.disposeDoc();
|
this.disposeDoc();
|
||||||
this._doc = doc.doc.getStore({
|
this._doc = doc.doc.getStore({
|
||||||
query: this._query,
|
query: this._query,
|
||||||
});
|
});
|
||||||
|
this._host = new BlockStdScope({
|
||||||
|
store: this._doc,
|
||||||
|
extensions:
|
||||||
|
this.options.extensions ?? getCustomPageEditorBlockSpecs(),
|
||||||
|
}).render();
|
||||||
this.disposables.add(() => {
|
this.disposables.add(() => {
|
||||||
doc.doc.removeStore({ query: this._query });
|
doc.doc.removeStore({ query: this._query });
|
||||||
});
|
});
|
||||||
@@ -309,14 +297,10 @@ export class TextRenderer extends SignalWatcher(
|
|||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
if (this.state !== 'generating') {
|
if (this.state !== 'generating') {
|
||||||
this._doc.load();
|
this._doc.load();
|
||||||
// LinkPreviewService & ImageProxyService config should read from host settings
|
const imageProxyService = this._host.std.get(ImageProxyService);
|
||||||
const imageProxyService =
|
imageProxyService.setImageProxyURL(
|
||||||
this.host?.std.store.get(ImageProxyService);
|
imageProxyService.imageProxyURL
|
||||||
if (imageProxyService) {
|
);
|
||||||
this._doc
|
|
||||||
?.get(ImageProxyService)
|
|
||||||
.setImageProxyURL(imageProxyService.imageProxyURL);
|
|
||||||
}
|
|
||||||
this._clearTimer();
|
this._clearTimer();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -341,7 +325,6 @@ export class TextRenderer extends SignalWatcher(
|
|||||||
|
|
||||||
private disposeDoc() {
|
private disposeDoc() {
|
||||||
this._doc?.dispose();
|
this._doc?.dispose();
|
||||||
this._doc?.workspace.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override disconnectedCallback() {
|
override disconnectedCallback() {
|
||||||
@@ -355,22 +338,22 @@ export class TextRenderer extends SignalWatcher(
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { customHeading, testId } = this.options;
|
const { customHeading, testId = 'ai-text-renderer' } = this.options;
|
||||||
const classes = classMap({
|
const classes = classMap({
|
||||||
'text-renderer-container': true,
|
'text-renderer-container': true,
|
||||||
'custom-heading': !!customHeading,
|
'custom-heading': !!customHeading,
|
||||||
});
|
});
|
||||||
const theme = this.host?.std.get(ThemeProvider).app$.value;
|
const theme = this.options.theme?.value;
|
||||||
return html`
|
return html`
|
||||||
<div class=${classes} data-testid=${testId} data-app-theme=${theme}>
|
<div
|
||||||
|
class=${classes}
|
||||||
|
data-testid=${testId}
|
||||||
|
data-app-theme=${theme ?? 'light'}
|
||||||
|
>
|
||||||
${keyed(
|
${keyed(
|
||||||
this._doc,
|
this._doc,
|
||||||
html`<div class="ai-answer-text-editor affine-page-viewport">
|
html`<div class="ai-answer-text-editor affine-page-viewport">
|
||||||
${new BlockStdScope({
|
${this._host}
|
||||||
store: this._doc,
|
|
||||||
extensions:
|
|
||||||
this.options.extensions ?? getCustomPageEditorBlockSpecs(),
|
|
||||||
}).render()}
|
|
||||||
</div>`
|
</div>`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -416,12 +399,6 @@ export class TextRenderer extends SignalWatcher(
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor answer!: string;
|
accessor answer!: string;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor host: EditorHost | null | undefined;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor schema: Schema | null = null;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor options!: TextRendererOptions;
|
accessor options!: TextRendererOptions;
|
||||||
|
|
||||||
@@ -429,14 +406,10 @@ export class TextRenderer extends SignalWatcher(
|
|||||||
accessor state: AffineAIPanelState | undefined = undefined;
|
accessor state: AffineAIPanelState | undefined = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createTextRenderer = (
|
export const createTextRenderer = (options: TextRendererOptions) => {
|
||||||
host: EditorHost | null | undefined,
|
|
||||||
options: TextRendererOptions
|
|
||||||
) => {
|
|
||||||
return (answer: string, state?: AffineAIPanelState) => {
|
return (answer: string, state?: AffineAIPanelState) => {
|
||||||
return html`<text-renderer
|
return html`<text-renderer
|
||||||
contenteditable="false"
|
contenteditable="false"
|
||||||
.host=${host}
|
|
||||||
.answer=${answer}
|
.answer=${answer}
|
||||||
.state=${state}
|
.state=${state}
|
||||||
.options=${options}
|
.options=${options}
|
||||||
|
|||||||
@@ -468,6 +468,8 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
return html`<ai-loading></ai-loading>`;
|
return html`<ai-loading></ai-loading>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const notificationService = this.host.std.get(NotificationProvider);
|
||||||
|
|
||||||
return html`<div class=${messageClasses}>
|
return html`<div class=${messageClasses}>
|
||||||
<ai-chat-block-message
|
<ai-chat-block-message
|
||||||
.host=${host}
|
.host=${host}
|
||||||
@@ -485,6 +487,7 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
.isLast=${isLastReply}
|
.isLast=${isLastReply}
|
||||||
.messageId=${message.id ?? undefined}
|
.messageId=${message.id ?? undefined}
|
||||||
.retry=${() => this.retry()}
|
.retry=${() => this.retry()}
|
||||||
|
.notificationService=${notificationService}
|
||||||
></chat-copy-more>`
|
></chat-copy-more>`
|
||||||
: nothing}
|
: nothing}
|
||||||
${shouldRenderActions
|
${shouldRenderActions
|
||||||
@@ -495,6 +498,7 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
.content=${markdown}
|
.content=${markdown}
|
||||||
.messageId=${message.id ?? undefined}
|
.messageId=${message.id ?? undefined}
|
||||||
.layoutDirection=${'horizontal'}
|
.layoutDirection=${'horizontal'}
|
||||||
|
.notificationService=${notificationService}
|
||||||
></chat-action-list>`
|
></chat-action-list>`
|
||||||
: nothing}
|
: nothing}
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -569,7 +573,7 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
} = this;
|
} = this;
|
||||||
|
|
||||||
const { messages: currentChatMessages } = chatContext;
|
const { messages: currentChatMessages } = chatContext;
|
||||||
const notification = this.host.std.getOptional(NotificationProvider);
|
const notificationService = this.host.std.get(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">
|
||||||
@@ -578,7 +582,7 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
.session=${this.forkSession}
|
.session=${this.forkSession}
|
||||||
.onHistoryCleared=${this._onHistoryCleared}
|
.onHistoryCleared=${this._onHistoryCleared}
|
||||||
.chatContextValue=${chatContext}
|
.chatContextValue=${chatContext}
|
||||||
.notification=${notification}
|
.notificationService=${notificationService}
|
||||||
></ai-history-clear>
|
></ai-history-clear>
|
||||||
</div>
|
</div>
|
||||||
<div class="ai-chat-messages-container">
|
<div class="ai-chat-messages-container">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { toggleGeneralAIOnboarding } from '@affine/core/components/affine/ai-onb
|
|||||||
import type { AuthAccountInfo, AuthService } from '@affine/core/modules/cloud';
|
import type { AuthAccountInfo, AuthService } from '@affine/core/modules/cloud';
|
||||||
import type { GlobalDialogService } from '@affine/core/modules/dialogs';
|
import type { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||||
import {
|
import {
|
||||||
|
type AddContextFileInput,
|
||||||
ContextCategories,
|
ContextCategories,
|
||||||
type ContextWorkspaceEmbeddingStatus,
|
type ContextWorkspaceEmbeddingStatus,
|
||||||
type getCopilotHistoriesQuery,
|
type getCopilotHistoriesQuery,
|
||||||
@@ -609,10 +610,7 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
removeContextDoc: async (options: { contextId: string; docId: string }) => {
|
removeContextDoc: async (options: { contextId: string; docId: string }) => {
|
||||||
return client.removeContextDoc(options);
|
return client.removeContextDoc(options);
|
||||||
},
|
},
|
||||||
addContextFile: async (
|
addContextFile: async (file: File, options: AddContextFileInput) => {
|
||||||
file: File,
|
|
||||||
options: { contextId: string; blobId: string }
|
|
||||||
) => {
|
|
||||||
return client.addContextFile(file, options);
|
return client.addContextFile(file, options);
|
||||||
},
|
},
|
||||||
removeContextFile: async (options: {
|
removeContextFile: async (options: {
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { WorkspaceImpl } from '@affine/core/modules/workspace/impls/workspace';
|
import { WorkspaceImpl } from '@affine/core/modules/workspace/impls/workspace';
|
||||||
|
import { clipboardConfigs } from '@blocksuite/affine/foundation/clipboard';
|
||||||
import { defaultImageProxyMiddleware } from '@blocksuite/affine/shared/adapters';
|
import { defaultImageProxyMiddleware } from '@blocksuite/affine/shared/adapters';
|
||||||
import { replaceSelectedTextWithBlocksCommand } from '@blocksuite/affine/shared/commands';
|
import { replaceSelectedTextWithBlocksCommand } from '@blocksuite/affine/shared/commands';
|
||||||
import { isInsideEdgelessEditor } from '@blocksuite/affine/shared/utils';
|
import { isInsideEdgelessEditor } from '@blocksuite/affine/shared/utils';
|
||||||
import {
|
import {
|
||||||
type BlockComponent,
|
type BlockComponent,
|
||||||
BlockSelection,
|
BlockSelection,
|
||||||
|
BlockStdScope,
|
||||||
|
Clipboard,
|
||||||
type EditorHost,
|
type EditorHost,
|
||||||
SurfaceSelection,
|
SurfaceSelection,
|
||||||
type TextSelection,
|
type TextSelection,
|
||||||
@@ -185,27 +188,25 @@ export const replace = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const copyTextAnswer = async (panel: AffineAIPanelWidget) => {
|
export const copyTextAnswer = async (panel: AffineAIPanelWidget) => {
|
||||||
const host = panel.host;
|
|
||||||
if (!panel.answer) {
|
if (!panel.answer) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return copyText(host, panel.answer);
|
return copyText(panel.answer);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const copyText = async (host: EditorHost, text: string) => {
|
export const copyText = async (text: string) => {
|
||||||
const previewDoc = await markDownToDoc(
|
const previewDoc = await markDownToDoc(text, [defaultImageProxyMiddleware]);
|
||||||
host.std.store.provider,
|
|
||||||
host.std.store.schema,
|
|
||||||
text,
|
|
||||||
[defaultImageProxyMiddleware]
|
|
||||||
);
|
|
||||||
const models = previewDoc
|
const models = previewDoc
|
||||||
.getBlocksByFlavour('affine:note')
|
.getBlocksByFlavour('affine:note')
|
||||||
.map(b => b.model)
|
.map(b => b.model)
|
||||||
.flatMap(model => model.children);
|
.flatMap(model => model.children);
|
||||||
const slice = Slice.fromModels(previewDoc, models);
|
const slice = Slice.fromModels(previewDoc, models);
|
||||||
await host.std.clipboard.copySlice(slice);
|
const std = new BlockStdScope({
|
||||||
|
store: previewDoc,
|
||||||
|
extensions: [...clipboardConfigs],
|
||||||
|
});
|
||||||
|
const clipboard = std.provider.get(Clipboard);
|
||||||
|
await clipboard.copySlice(slice);
|
||||||
previewDoc.dispose();
|
previewDoc.dispose();
|
||||||
previewDoc.workspace.dispose();
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { ServiceProvider } from '@blocksuite/affine/global/di';
|
|
||||||
import {
|
import {
|
||||||
DatabaseBlockModel,
|
DatabaseBlockModel,
|
||||||
ImageBlockModel,
|
ImageBlockModel,
|
||||||
@@ -19,10 +18,11 @@ import {
|
|||||||
isInsideEdgelessEditor,
|
isInsideEdgelessEditor,
|
||||||
matchModels,
|
matchModels,
|
||||||
} from '@blocksuite/affine/shared/utils';
|
} from '@blocksuite/affine/shared/utils';
|
||||||
import type { EditorHost } from '@blocksuite/affine/std';
|
import { BlockStdScope, type EditorHost } from '@blocksuite/affine/std';
|
||||||
import type { BlockModel, Store } from '@blocksuite/affine/store';
|
import type { BlockModel, Store } from '@blocksuite/affine/store';
|
||||||
import { Slice, toDraftModel } from '@blocksuite/affine/store';
|
import { Slice, toDraftModel } from '@blocksuite/affine/store';
|
||||||
|
|
||||||
|
import { getStoreManager } from '../../manager/store';
|
||||||
import type { ChatContextValue } from '../components/ai-chat-content';
|
import type { ChatContextValue } from '../components/ai-chat-content';
|
||||||
import {
|
import {
|
||||||
getSelectedImagesAsBlobs,
|
getSelectedImagesAsBlobs,
|
||||||
@@ -96,12 +96,13 @@ async function extractPageSelected(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function extractMarkdownFromDoc(
|
export async function extractMarkdownFromDoc(doc: Store): Promise<string> {
|
||||||
doc: Store,
|
const std = new BlockStdScope({
|
||||||
provider: ServiceProvider
|
store: doc,
|
||||||
): Promise<string> {
|
extensions: getStoreManager().config.init().value.get('store'),
|
||||||
|
});
|
||||||
const transformer = await getTransformer(doc);
|
const transformer = await getTransformer(doc);
|
||||||
const adapter = new MarkdownAdapter(transformer, provider);
|
const adapter = new MarkdownAdapter(transformer, std.provider);
|
||||||
const blockModels = getNoteBlockModels(doc);
|
const blockModels = getNoteBlockModels(doc);
|
||||||
const textModels = blockModels.filter(
|
const textModels = blockModels.filter(
|
||||||
model => !matchModels(model, [ImageBlockModel, DatabaseBlockModel])
|
model => !matchModels(model, [ImageBlockModel, DatabaseBlockModel])
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { WidgetComponent, WidgetViewExtension } from '@blocksuite/affine/std';
|
import { WidgetComponent, WidgetViewExtension } from '@blocksuite/affine/std';
|
||||||
|
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||||
import { css, html, nothing, type TemplateResult } from 'lit';
|
import { css, html, nothing, type TemplateResult } from 'lit';
|
||||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||||
@@ -98,9 +99,9 @@ export class AffineBlockDiffWidgetForBlock extends WidgetComponent {
|
|||||||
: html`<div class="ai-block-diff insert" data-diff-id=${diffId}>
|
: html`<div class="ai-block-diff insert" data-diff-id=${diffId}>
|
||||||
<chat-content-rich-text
|
<chat-content-rich-text
|
||||||
.text=${block.content}
|
.text=${block.content}
|
||||||
.host=${this.host}
|
|
||||||
.state="finished"
|
.state="finished"
|
||||||
.extensions=${this.userExtensions}
|
.extensions=${this.userExtensions}
|
||||||
|
.theme=${this.host.std.get(ThemeProvider).app$}
|
||||||
></chat-content-rich-text>
|
></chat-content-rich-text>
|
||||||
<ai-block-diff-options
|
<ai-block-diff-options
|
||||||
class="diff-options"
|
class="diff-options"
|
||||||
@@ -132,9 +133,9 @@ export class AffineBlockDiffWidgetForBlock extends WidgetComponent {
|
|||||||
<div class="ai-block-diff update" data-diff-id=${diffId}>
|
<div class="ai-block-diff update" data-diff-id=${diffId}>
|
||||||
<chat-content-rich-text
|
<chat-content-rich-text
|
||||||
.text=${content}
|
.text=${content}
|
||||||
.host=${this.host}
|
|
||||||
.state="finished"
|
.state="finished"
|
||||||
.extensions=${this.userExtensions}
|
.extensions=${this.userExtensions}
|
||||||
|
.theme=${this.host.std.get(ThemeProvider).app$}
|
||||||
></chat-content-rich-text>
|
></chat-content-rich-text>
|
||||||
<ai-block-diff-options
|
<ai-block-diff-options
|
||||||
class="diff-options"
|
class="diff-options"
|
||||||
|
|||||||
@@ -15,10 +15,7 @@ import {
|
|||||||
import { toDocSearchParams } from '@affine/core/modules/navigation/utils';
|
import { toDocSearchParams } from '@affine/core/modules/navigation/utils';
|
||||||
import { GlobalSessionStateService } from '@affine/core/modules/storage';
|
import { GlobalSessionStateService } from '@affine/core/modules/storage';
|
||||||
import { WorkbenchLink } from '@affine/core/modules/workbench';
|
import { WorkbenchLink } from '@affine/core/modules/workbench';
|
||||||
import {
|
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||||
getAFFiNEWorkspaceSchema,
|
|
||||||
WorkspaceService,
|
|
||||||
} from '@affine/core/modules/workspace';
|
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import track from '@affine/track';
|
import track from '@affine/track';
|
||||||
import type {
|
import type {
|
||||||
@@ -338,7 +335,6 @@ export const LinkPreview = ({
|
|||||||
<LitTextRenderer
|
<LitTextRenderer
|
||||||
className={styles.linkPreviewRenderer}
|
className={styles.linkPreviewRenderer}
|
||||||
answer={link.markdownPreview}
|
answer={link.markdownPreview}
|
||||||
schema={getAFFiNEWorkspaceSchema()}
|
|
||||||
options={textRendererOptions}
|
options={textRendererOptions}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
import { WorkspaceImpl } from '@affine/core/modules/workspace/impls/workspace';
|
import { WorkspaceImpl } from '@affine/core/modules/workspace/impls/workspace';
|
||||||
import type { ServiceProvider } from '@blocksuite/affine/global/di';
|
|
||||||
import {
|
import {
|
||||||
defaultImageProxyMiddleware,
|
defaultImageProxyMiddleware,
|
||||||
embedSyncedDocMiddleware,
|
embedSyncedDocMiddleware,
|
||||||
@@ -11,6 +10,7 @@ import {
|
|||||||
titleMiddleware,
|
titleMiddleware,
|
||||||
} from '@blocksuite/affine/shared/adapters';
|
} from '@blocksuite/affine/shared/adapters';
|
||||||
import {
|
import {
|
||||||
|
BlockStdScope,
|
||||||
type EditorHost,
|
type EditorHost,
|
||||||
type TextRangePoint,
|
type TextRangePoint,
|
||||||
TextSelection,
|
TextSelection,
|
||||||
@@ -19,7 +19,6 @@ import type {
|
|||||||
BlockModel,
|
BlockModel,
|
||||||
BlockSnapshot,
|
BlockSnapshot,
|
||||||
DraftModel,
|
DraftModel,
|
||||||
Schema,
|
|
||||||
Slice,
|
Slice,
|
||||||
SliceSnapshot,
|
SliceSnapshot,
|
||||||
Store,
|
Store,
|
||||||
@@ -27,6 +26,43 @@ import type {
|
|||||||
} from '@blocksuite/affine/store';
|
} from '@blocksuite/affine/store';
|
||||||
import { toDraftModel, Transformer } from '@blocksuite/affine/store';
|
import { toDraftModel, Transformer } from '@blocksuite/affine/store';
|
||||||
import { Doc as YDoc } from 'yjs';
|
import { Doc as YDoc } from 'yjs';
|
||||||
|
|
||||||
|
import { getStoreManager } from '../manager/store';
|
||||||
|
|
||||||
|
interface MarkdownWorkspace {
|
||||||
|
collection: WorkspaceImpl;
|
||||||
|
std: BlockStdScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
let markdownWorkspace: MarkdownWorkspace | null = null;
|
||||||
|
|
||||||
|
const getMarkdownWorkspace = (
|
||||||
|
featureFlagService?: FeatureFlagService
|
||||||
|
): MarkdownWorkspace => {
|
||||||
|
if (markdownWorkspace) {
|
||||||
|
return markdownWorkspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
const collection = new WorkspaceImpl({
|
||||||
|
rootDoc: new YDoc({ guid: 'markdownToDoc' }),
|
||||||
|
featureFlagService: featureFlagService,
|
||||||
|
});
|
||||||
|
collection.meta.initialize();
|
||||||
|
|
||||||
|
const mockDoc = collection.createDoc('mock-id');
|
||||||
|
const std = new BlockStdScope({
|
||||||
|
store: mockDoc.getStore(),
|
||||||
|
extensions: getStoreManager().config.init().value.get('store'),
|
||||||
|
});
|
||||||
|
|
||||||
|
markdownWorkspace = {
|
||||||
|
collection,
|
||||||
|
std,
|
||||||
|
};
|
||||||
|
|
||||||
|
return markdownWorkspace;
|
||||||
|
};
|
||||||
|
|
||||||
const updateSnapshotText = (
|
const updateSnapshotText = (
|
||||||
point: TextRangePoint,
|
point: TextRangePoint,
|
||||||
snapshot: BlockSnapshot,
|
snapshot: BlockSnapshot,
|
||||||
@@ -184,20 +220,14 @@ export async function replaceFromMarkdown(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function markDownToDoc(
|
export async function markDownToDoc(
|
||||||
provider: ServiceProvider,
|
|
||||||
schema: Schema,
|
|
||||||
answer: string,
|
answer: string,
|
||||||
middlewares?: TransformerMiddleware[],
|
middlewares?: TransformerMiddleware[],
|
||||||
affineFeatureFlagService?: FeatureFlagService
|
affineFeatureFlagService?: FeatureFlagService
|
||||||
) {
|
) {
|
||||||
// Should not create a new doc in the original collection
|
const { collection, std } = getMarkdownWorkspace(affineFeatureFlagService);
|
||||||
const collection = new WorkspaceImpl({
|
|
||||||
rootDoc: new YDoc({ guid: 'markdownToDoc' }),
|
|
||||||
featureFlagService: affineFeatureFlagService,
|
|
||||||
});
|
|
||||||
collection.meta.initialize();
|
|
||||||
const transformer = new Transformer({
|
const transformer = new Transformer({
|
||||||
schema,
|
schema: std.store.schema,
|
||||||
blobCRUD: collection.blobSync,
|
blobCRUD: collection.blobSync,
|
||||||
docCRUD: {
|
docCRUD: {
|
||||||
create: (id: string) => collection.createDoc(id).getStore({ id }),
|
create: (id: string) => collection.createDoc(id).getStore({ id }),
|
||||||
@@ -206,7 +236,7 @@ export async function markDownToDoc(
|
|||||||
},
|
},
|
||||||
middlewares,
|
middlewares,
|
||||||
});
|
});
|
||||||
const mdAdapter = new MarkdownAdapter(transformer, provider);
|
const mdAdapter = new MarkdownAdapter(transformer, std.store.provider);
|
||||||
const doc = await mdAdapter.toDoc({
|
const doc = await mdAdapter.toDoc({
|
||||||
file: answer,
|
file: answer,
|
||||||
assets: transformer.assetsManager,
|
assets: transformer.assetsManager,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
type ConfirmModalProps,
|
||||||
Input,
|
Input,
|
||||||
type Notification,
|
type Notification,
|
||||||
notify,
|
notify,
|
||||||
@@ -7,127 +8,156 @@ import {
|
|||||||
toReactNode,
|
toReactNode,
|
||||||
type useConfirmModal,
|
type useConfirmModal,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { NotificationExtension } from '@blocksuite/affine/shared/services';
|
import {
|
||||||
|
NotificationExtension,
|
||||||
|
type NotificationService,
|
||||||
|
} from '@blocksuite/affine/shared/services';
|
||||||
|
|
||||||
|
export class NotificationServiceImpl implements NotificationService {
|
||||||
|
constructor(
|
||||||
|
private readonly closeConfirmModal: () => void,
|
||||||
|
private readonly openConfirmModal: (props: ConfirmModalProps) => void
|
||||||
|
) {}
|
||||||
|
|
||||||
|
confirm = async ({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
confirmText,
|
||||||
|
cancelText,
|
||||||
|
abort,
|
||||||
|
}: Parameters<NotificationService['confirm']>[0]) => {
|
||||||
|
return new Promise<boolean>(resolve => {
|
||||||
|
this.openConfirmModal({
|
||||||
|
title: toReactNode(title),
|
||||||
|
description: toReactNode(message),
|
||||||
|
confirmText,
|
||||||
|
confirmButtonOptions: {
|
||||||
|
variant: 'primary',
|
||||||
|
},
|
||||||
|
cancelText,
|
||||||
|
onConfirm: () => {
|
||||||
|
resolve(true);
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
resolve(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
abort?.addEventListener('abort', () => {
|
||||||
|
resolve(false);
|
||||||
|
this.closeConfirmModal();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
prompt = async ({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
confirmText,
|
||||||
|
placeholder,
|
||||||
|
cancelText,
|
||||||
|
autofill,
|
||||||
|
abort,
|
||||||
|
}: Parameters<NotificationService['prompt']>[0]) => {
|
||||||
|
return new Promise<string | null>(resolve => {
|
||||||
|
let value = autofill || '';
|
||||||
|
const description = (
|
||||||
|
<div>
|
||||||
|
<span style={{ marginBottom: 12 }}>{toReactNode(message)}</span>
|
||||||
|
<Input
|
||||||
|
autoSelect={true}
|
||||||
|
placeholder={placeholder}
|
||||||
|
defaultValue={value}
|
||||||
|
onChange={e => (value = e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
this.openConfirmModal({
|
||||||
|
title: toReactNode(title),
|
||||||
|
description: description,
|
||||||
|
confirmText: confirmText ?? 'Confirm',
|
||||||
|
confirmButtonOptions: {
|
||||||
|
variant: 'primary',
|
||||||
|
},
|
||||||
|
cancelText: cancelText ?? 'Cancel',
|
||||||
|
onConfirm: () => {
|
||||||
|
resolve(value);
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
resolve(null);
|
||||||
|
},
|
||||||
|
autoFocusConfirm: false,
|
||||||
|
});
|
||||||
|
abort?.addEventListener('abort', () => {
|
||||||
|
resolve(null);
|
||||||
|
this.closeConfirmModal();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
toast = (message: string, options: ToastOptions) => {
|
||||||
|
return toast(message, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
notify = (notification: Parameters<NotificationService['notify']>[0]) => {
|
||||||
|
const accentToNotify = {
|
||||||
|
error: notify.error,
|
||||||
|
success: notify.success,
|
||||||
|
warning: notify.warning,
|
||||||
|
info: notify,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fn = accentToNotify[notification.accent || 'info'];
|
||||||
|
if (!fn) {
|
||||||
|
throw new Error('Invalid notification accent');
|
||||||
|
}
|
||||||
|
|
||||||
|
const toAffineNotificationActions = (
|
||||||
|
actions: (typeof notification)['actions']
|
||||||
|
): Notification['actions'] => {
|
||||||
|
if (!actions) return undefined;
|
||||||
|
|
||||||
|
return actions.map(({ label, onClick, key }) => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
label: toReactNode(label),
|
||||||
|
onClick,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toastId = fn(
|
||||||
|
{
|
||||||
|
title: toReactNode(notification.title),
|
||||||
|
message: toReactNode(notification.message),
|
||||||
|
actions: toAffineNotificationActions(notification.actions),
|
||||||
|
onDismiss: notification.onClose,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
duration: notification.duration || 0,
|
||||||
|
onDismiss: notification.onClose,
|
||||||
|
onAutoClose: notification.onClose,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
notification.abort?.addEventListener('abort', () => {
|
||||||
|
notify.dismiss(toastId);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
notifyWithUndoAction = (
|
||||||
|
options: Parameters<NotificationService['notifyWithUndoAction']>[0]
|
||||||
|
) => {
|
||||||
|
this.notify(options);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function patchNotificationService({
|
export function patchNotificationService({
|
||||||
closeConfirmModal,
|
closeConfirmModal,
|
||||||
openConfirmModal,
|
openConfirmModal,
|
||||||
}: ReturnType<typeof useConfirmModal>) {
|
}: ReturnType<typeof useConfirmModal>) {
|
||||||
return NotificationExtension({
|
const notificationService = new NotificationServiceImpl(
|
||||||
confirm: async ({ title, message, confirmText, cancelText, abort }) => {
|
closeConfirmModal,
|
||||||
return new Promise<boolean>(resolve => {
|
openConfirmModal
|
||||||
openConfirmModal({
|
);
|
||||||
title: toReactNode(title),
|
return NotificationExtension(notificationService);
|
||||||
description: toReactNode(message),
|
|
||||||
confirmText,
|
|
||||||
confirmButtonOptions: {
|
|
||||||
variant: 'primary',
|
|
||||||
},
|
|
||||||
cancelText,
|
|
||||||
onConfirm: () => {
|
|
||||||
resolve(true);
|
|
||||||
},
|
|
||||||
onCancel: () => {
|
|
||||||
resolve(false);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
abort?.addEventListener('abort', () => {
|
|
||||||
resolve(false);
|
|
||||||
closeConfirmModal();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
prompt: async ({
|
|
||||||
title,
|
|
||||||
message,
|
|
||||||
confirmText,
|
|
||||||
placeholder,
|
|
||||||
cancelText,
|
|
||||||
autofill,
|
|
||||||
abort,
|
|
||||||
}) => {
|
|
||||||
return new Promise<string | null>(resolve => {
|
|
||||||
let value = autofill || '';
|
|
||||||
const description = (
|
|
||||||
<div>
|
|
||||||
<span style={{ marginBottom: 12 }}>{toReactNode(message)}</span>
|
|
||||||
<Input
|
|
||||||
autoSelect={true}
|
|
||||||
placeholder={placeholder}
|
|
||||||
defaultValue={value}
|
|
||||||
onChange={e => (value = e)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
openConfirmModal({
|
|
||||||
title: toReactNode(title),
|
|
||||||
description: description,
|
|
||||||
confirmText: confirmText ?? 'Confirm',
|
|
||||||
confirmButtonOptions: {
|
|
||||||
variant: 'primary',
|
|
||||||
},
|
|
||||||
cancelText: cancelText ?? 'Cancel',
|
|
||||||
onConfirm: () => {
|
|
||||||
resolve(value);
|
|
||||||
},
|
|
||||||
onCancel: () => {
|
|
||||||
resolve(null);
|
|
||||||
},
|
|
||||||
autoFocusConfirm: false,
|
|
||||||
});
|
|
||||||
abort?.addEventListener('abort', () => {
|
|
||||||
resolve(null);
|
|
||||||
closeConfirmModal();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
toast: (message: string, options: ToastOptions) => {
|
|
||||||
return toast(message, options);
|
|
||||||
},
|
|
||||||
notify: notification => {
|
|
||||||
const accentToNotify = {
|
|
||||||
error: notify.error,
|
|
||||||
success: notify.success,
|
|
||||||
warning: notify.warning,
|
|
||||||
info: notify,
|
|
||||||
};
|
|
||||||
|
|
||||||
const fn = accentToNotify[notification.accent || 'info'];
|
|
||||||
if (!fn) {
|
|
||||||
throw new Error('Invalid notification accent');
|
|
||||||
}
|
|
||||||
|
|
||||||
const toAffineNotificationActions = (
|
|
||||||
actions: (typeof notification)['actions']
|
|
||||||
): Notification['actions'] => {
|
|
||||||
if (!actions) return undefined;
|
|
||||||
|
|
||||||
return actions.map(({ label, onClick, key }) => {
|
|
||||||
return {
|
|
||||||
key,
|
|
||||||
label: toReactNode(label),
|
|
||||||
onClick,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const toastId = fn(
|
|
||||||
{
|
|
||||||
title: toReactNode(notification.title),
|
|
||||||
message: toReactNode(notification.message),
|
|
||||||
actions: toAffineNotificationActions(notification.actions),
|
|
||||||
onDismiss: notification.onClose,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
duration: notification.duration || 0,
|
|
||||||
onDismiss: notification.onClose,
|
|
||||||
onAutoClose: notification.onClose,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
notification.abort?.addEventListener('abort', () => {
|
|
||||||
notify.dismiss(toastId);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { observeResize } from '@affine/component';
|
import { observeResize, useConfirmModal } from '@affine/component';
|
||||||
import { CopilotClient } from '@affine/core/blocksuite/ai';
|
import { CopilotClient } from '@affine/core/blocksuite/ai';
|
||||||
import { AIChatContent } from '@affine/core/blocksuite/ai/components/ai-chat-content';
|
import { AIChatContent } from '@affine/core/blocksuite/ai/components/ai-chat-content';
|
||||||
import { AIChatToolbar } from '@affine/core/blocksuite/ai/components/ai-chat-toolbar';
|
import { AIChatToolbar } from '@affine/core/blocksuite/ai/components/ai-chat-toolbar';
|
||||||
import { getCustomPageEditorBlockSpecs } from '@affine/core/blocksuite/ai/components/text-renderer';
|
|
||||||
import type { PromptKey } from '@affine/core/blocksuite/ai/provider/prompt';
|
import type { PromptKey } from '@affine/core/blocksuite/ai/provider/prompt';
|
||||||
|
import { NotificationServiceImpl } from '@affine/core/blocksuite/view-extensions/editor-view/notification-service';
|
||||||
import { useAIChatConfig } from '@affine/core/components/hooks/affine/use-ai-chat-config';
|
import { useAIChatConfig } from '@affine/core/components/hooks/affine/use-ai-chat-config';
|
||||||
import { getCollection } from '@affine/core/desktop/dialogs/setting/general-setting/editor/edgeless/docs';
|
|
||||||
import {
|
import {
|
||||||
EventSourceService,
|
EventSourceService,
|
||||||
FetchService,
|
FetchService,
|
||||||
@@ -13,6 +12,7 @@ import {
|
|||||||
} from '@affine/core/modules/cloud';
|
} from '@affine/core/modules/cloud';
|
||||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
|
import { AppThemeService } from '@affine/core/modules/theme';
|
||||||
import {
|
import {
|
||||||
ViewBody,
|
ViewBody,
|
||||||
ViewHeader,
|
ViewHeader,
|
||||||
@@ -21,8 +21,6 @@ import {
|
|||||||
} from '@affine/core/modules/workbench';
|
} from '@affine/core/modules/workbench';
|
||||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import type { Doc, Store } from '@blocksuite/affine/store';
|
|
||||||
import { BlockStdScope, type EditorHost } from '@blocksuite/std';
|
|
||||||
import { type Signal, signal } from '@preact/signals-core';
|
import { type Signal, signal } from '@preact/signals-core';
|
||||||
import { useFramework, useService } from '@toeverything/infra';
|
import { useFramework, useService } from '@toeverything/infra';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
@@ -52,7 +50,6 @@ export const Component = () => {
|
|||||||
const framework = useFramework();
|
const framework = useFramework();
|
||||||
const [isBodyProvided, setIsBodyProvided] = useState(false);
|
const [isBodyProvided, setIsBodyProvided] = useState(false);
|
||||||
const [isHeaderProvided, setIsHeaderProvided] = useState(false);
|
const [isHeaderProvided, setIsHeaderProvided] = useState(false);
|
||||||
const [host, setHost] = useState<EditorHost | null>(null);
|
|
||||||
const [chatContent, setChatContent] = useState<AIChatContent | null>(null);
|
const [chatContent, setChatContent] = useState<AIChatContent | null>(null);
|
||||||
const [chatTool, setChatTool] = useState<AIChatToolbar | null>(null);
|
const [chatTool, setChatTool] = useState<AIChatToolbar | null>(null);
|
||||||
const [currentSession, setCurrentSession] = useState<CopilotSession | null>(
|
const [currentSession, setCurrentSession] = useState<CopilotSession | null>(
|
||||||
@@ -132,28 +129,11 @@ export const Component = () => {
|
|||||||
[chatContent, chatTool, client, isOpeningSession, workspaceId]
|
[chatContent, chatTool, client, isOpeningSession, workspaceId]
|
||||||
);
|
);
|
||||||
|
|
||||||
// create a temp doc/host for ai-chat-content
|
const confirmModal = useConfirmModal();
|
||||||
useEffect(() => {
|
|
||||||
let tempDoc: Doc | null = null;
|
|
||||||
const collection = getCollection();
|
|
||||||
const doc = collection.createDoc();
|
|
||||||
tempDoc = doc;
|
|
||||||
doc.load(() => {
|
|
||||||
const host = new BlockStdScope({
|
|
||||||
store: tempDoc?.getStore() as Store,
|
|
||||||
extensions: getCustomPageEditorBlockSpecs(),
|
|
||||||
}).render();
|
|
||||||
setHost(host);
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
tempDoc?.dispose();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// init or update ai-chat-content
|
// init or update ai-chat-content
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isBodyProvided || !host) {
|
if (!isBodyProvided) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +144,6 @@ export const Component = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
content.session = currentSession;
|
content.session = currentSession;
|
||||||
content.host = host;
|
|
||||||
content.workspaceId = workspaceId;
|
content.workspaceId = workspaceId;
|
||||||
content.docDisplayConfig = docDisplayConfig;
|
content.docDisplayConfig = docDisplayConfig;
|
||||||
content.searchMenuConfig = searchMenuConfig;
|
content.searchMenuConfig = searchMenuConfig;
|
||||||
@@ -174,6 +153,11 @@ export const Component = () => {
|
|||||||
content.affineWorkspaceDialogService = framework.get(
|
content.affineWorkspaceDialogService = framework.get(
|
||||||
WorkspaceDialogService
|
WorkspaceDialogService
|
||||||
);
|
);
|
||||||
|
content.affineThemeService = framework.get(AppThemeService);
|
||||||
|
content.notificationService = new NotificationServiceImpl(
|
||||||
|
confirmModal.closeConfirmModal,
|
||||||
|
confirmModal.openConfirmModal
|
||||||
|
);
|
||||||
|
|
||||||
if (!chatContent) {
|
if (!chatContent) {
|
||||||
// initial values that won't change
|
// initial values that won't change
|
||||||
@@ -190,12 +174,12 @@ export const Component = () => {
|
|||||||
currentSession,
|
currentSession,
|
||||||
docDisplayConfig,
|
docDisplayConfig,
|
||||||
framework,
|
framework,
|
||||||
host,
|
|
||||||
isBodyProvided,
|
isBodyProvided,
|
||||||
networkSearchConfig,
|
networkSearchConfig,
|
||||||
reasoningConfig,
|
reasoningConfig,
|
||||||
searchMenuConfig,
|
searchMenuConfig,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
confirmModal,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// init or update header ai-chat-toolbar
|
// init or update header ai-chat-toolbar
|
||||||
@@ -213,6 +197,10 @@ export const Component = () => {
|
|||||||
tool.workspaceId = workspaceId;
|
tool.workspaceId = workspaceId;
|
||||||
tool.docDisplayConfig = docDisplayConfig;
|
tool.docDisplayConfig = docDisplayConfig;
|
||||||
tool.onOpenSession = onOpenSession;
|
tool.onOpenSession = onOpenSession;
|
||||||
|
tool.notificationService = new NotificationServiceImpl(
|
||||||
|
confirmModal.closeConfirmModal,
|
||||||
|
confirmModal.openConfirmModal
|
||||||
|
);
|
||||||
|
|
||||||
tool.onNewSession = () => {
|
tool.onNewSession = () => {
|
||||||
if (!currentSession) return;
|
if (!currentSession) return;
|
||||||
@@ -239,6 +227,7 @@ export const Component = () => {
|
|||||||
onOpenSession,
|
onOpenSession,
|
||||||
togglePin,
|
togglePin,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
confirmModal,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const onChatContainerRef = useCallback((node: HTMLDivElement) => {
|
const onChatContainerRef = useCallback((node: HTMLDivElement) => {
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { useConfirmModal } from '@affine/component';
|
||||||
import { AIProvider, ChatPanel } from '@affine/core/blocksuite/ai';
|
import { AIProvider, ChatPanel } from '@affine/core/blocksuite/ai';
|
||||||
import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor';
|
import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor';
|
||||||
|
import { NotificationServiceImpl } from '@affine/core/blocksuite/view-extensions/editor-view/notification-service';
|
||||||
import { useAIChatConfig } from '@affine/core/components/hooks/affine/use-ai-chat-config';
|
import { useAIChatConfig } from '@affine/core/components/hooks/affine/use-ai-chat-config';
|
||||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
|
import { AppThemeService } from '@affine/core/modules/theme';
|
||||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
import { ViewExtensionManagerIdentifier } from '@blocksuite/affine/ext-loader';
|
import { ViewExtensionManagerIdentifier } from '@blocksuite/affine/ext-loader';
|
||||||
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
|
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
|
||||||
@@ -51,6 +54,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
|
|||||||
reasoningConfig,
|
reasoningConfig,
|
||||||
playgroundConfig,
|
playgroundConfig,
|
||||||
} = useAIChatConfig();
|
} = useAIChatConfig();
|
||||||
|
const confirmModal = useConfirmModal();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editor || !editor.host) return;
|
if (!editor || !editor.host) return;
|
||||||
@@ -87,6 +91,11 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
|
|||||||
);
|
);
|
||||||
chatPanelRef.current.affineWorkbenchService =
|
chatPanelRef.current.affineWorkbenchService =
|
||||||
framework.get(WorkbenchService);
|
framework.get(WorkbenchService);
|
||||||
|
chatPanelRef.current.affineThemeService = framework.get(AppThemeService);
|
||||||
|
chatPanelRef.current.notificationService = new NotificationServiceImpl(
|
||||||
|
confirmModal.closeConfirmModal,
|
||||||
|
confirmModal.openConfirmModal
|
||||||
|
);
|
||||||
|
|
||||||
containerRef.current?.append(chatPanelRef.current);
|
containerRef.current?.append(chatPanelRef.current);
|
||||||
} else {
|
} else {
|
||||||
@@ -117,6 +126,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
|
|||||||
searchMenuConfig,
|
searchMenuConfig,
|
||||||
reasoningConfig,
|
reasoningConfig,
|
||||||
playgroundConfig,
|
playgroundConfig,
|
||||||
|
confirmModal,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [autoResized, setAutoResized] = useState(false);
|
const [autoResized, setAutoResized] = useState(false);
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
|
import { ColorScheme } from '@blocksuite/affine/model';
|
||||||
|
import { createSignalFromObservable } from '@blocksuite/affine-shared/utils';
|
||||||
|
import type { Signal } from '@preact/signals-core';
|
||||||
import { Entity, LiveData } from '@toeverything/infra';
|
import { Entity, LiveData } from '@toeverything/infra';
|
||||||
|
|
||||||
export class AppTheme extends Entity {
|
export class AppTheme extends Entity {
|
||||||
theme$ = new LiveData<string | undefined>(undefined);
|
theme$ = new LiveData<string | undefined>(undefined);
|
||||||
|
|
||||||
|
themeSignal: Signal<ColorScheme>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
const { signal, cleanup } = createSignalFromObservable<ColorScheme>(
|
||||||
|
this.theme$.map(theme =>
|
||||||
|
theme === 'dark' ? ColorScheme.Dark : ColorScheme.Light
|
||||||
|
),
|
||||||
|
ColorScheme.Light
|
||||||
|
);
|
||||||
|
this.themeSignal = signal;
|
||||||
|
this.disposables.push(cleanup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function Container({
|
|||||||
|
|
||||||
const ToggleButton = ({ onToggle }: { onToggle?: () => void }) => {
|
const ToggleButton = ({ onToggle }: { onToggle?: () => void }) => {
|
||||||
return (
|
return (
|
||||||
<IconButton size="24" onClick={onToggle}>
|
<IconButton size="24" onClick={onToggle} data-testid="right-sidebar-close">
|
||||||
<RightSidebarIcon />
|
<RightSidebarIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ test.describe('AIBasic/Authority', () => {
|
|||||||
page,
|
page,
|
||||||
utils,
|
utils,
|
||||||
}) => {
|
}) => {
|
||||||
await utils.chatPanel.makeChat(page, 'Hello');
|
await utils.chatPanel.makeChat(page, 'Hello. Answer in 50 words.');
|
||||||
|
|
||||||
await expect(page.getByTestId('ai-error')).toBeVisible();
|
await expect(page.getByTestId('ai-error')).toBeVisible();
|
||||||
await expect(page.getByTestId('ai-error-action-button')).toBeVisible();
|
await expect(page.getByTestId('ai-error-action-button')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should support login in error state', async ({ page, utils }) => {
|
test('should support login in error state', async ({ page, utils }) => {
|
||||||
await utils.chatPanel.makeChat(page, 'Hello');
|
await utils.chatPanel.makeChat(page, 'Hello. Answer in 50 words.');
|
||||||
const loginButton = page.getByTestId('ai-error-action-button');
|
const loginButton = page.getByTestId('ai-error-action-button');
|
||||||
await loginButton.click();
|
await loginButton.click();
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ test.describe('AIInsertion/AddToEdgelessAsNote', () => {
|
|||||||
utils,
|
utils,
|
||||||
}) => {
|
}) => {
|
||||||
await utils.editor.focusToEditor(page);
|
await utils.editor.focusToEditor(page);
|
||||||
await utils.chatPanel.makeChat(page, 'Hello');
|
await utils.chatPanel.makeChat(page, 'Hello. Answer in 50 words.');
|
||||||
|
|
||||||
await utils.chatPanel.waitForHistory(page, [
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: 'Hello',
|
content: 'Hello. Answer in 50 words.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
@@ -47,12 +47,12 @@ test.describe('AIInsertion/AddToEdgelessAsNote', () => {
|
|||||||
await page.keyboard.press('Delete');
|
await page.keyboard.press('Delete');
|
||||||
|
|
||||||
await utils.chatPanel.openChatPanel(page);
|
await utils.chatPanel.openChatPanel(page);
|
||||||
await utils.chatPanel.makeChat(page, 'Hello');
|
await utils.chatPanel.makeChat(page, 'Hello. Answer in 50 words.');
|
||||||
|
|
||||||
await utils.chatPanel.waitForHistory(page, [
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: 'Hello',
|
content: 'Hello. Answer in 50 words.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ test.describe('AIInsertion/Insert', () => {
|
|||||||
await page.keyboard.insertText('World Block');
|
await page.keyboard.insertText('World Block');
|
||||||
|
|
||||||
await utils.chatPanel.openChatPanel(page);
|
await utils.chatPanel.openChatPanel(page);
|
||||||
await utils.chatPanel.makeChat(page, 'Hello');
|
await utils.chatPanel.makeChat(page, 'Hello. Answer in 50 words.');
|
||||||
|
|
||||||
await utils.chatPanel.waitForHistory(page, [
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: 'Hello',
|
content: 'Hello. Answer in 50 words.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
@@ -60,12 +60,12 @@ test.describe('AIInsertion/Insert', () => {
|
|||||||
await page.keyboard.insertText('World Block');
|
await page.keyboard.insertText('World Block');
|
||||||
|
|
||||||
await utils.chatPanel.openChatPanel(page);
|
await utils.chatPanel.openChatPanel(page);
|
||||||
await utils.chatPanel.makeChat(page, 'Hello');
|
await utils.chatPanel.makeChat(page, 'Hello. Answer in 50 words.');
|
||||||
|
|
||||||
await utils.chatPanel.waitForHistory(page, [
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: 'Hello',
|
content: 'Hello. Answer in 50 words.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
@@ -101,12 +101,12 @@ test.describe('AIInsertion/Insert', () => {
|
|||||||
await page.keyboard.insertText('World Block');
|
await page.keyboard.insertText('World Block');
|
||||||
|
|
||||||
await utils.chatPanel.openChatPanel(page);
|
await utils.chatPanel.openChatPanel(page);
|
||||||
await utils.chatPanel.makeChat(page, 'Hello');
|
await utils.chatPanel.makeChat(page, 'Hello. Answer in 50 words.');
|
||||||
|
|
||||||
await utils.chatPanel.waitForHistory(page, [
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: 'Hello',
|
content: 'Hello. Answer in 50 words.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
@@ -139,12 +139,12 @@ test.describe('AIInsertion/Insert', () => {
|
|||||||
await page.keyboard.insertText('World Block');
|
await page.keyboard.insertText('World Block');
|
||||||
|
|
||||||
await utils.chatPanel.openChatPanel(page);
|
await utils.chatPanel.openChatPanel(page);
|
||||||
await utils.chatPanel.makeChat(page, 'Hello');
|
await utils.chatPanel.makeChat(page, 'Hello. Answer in 50 words.');
|
||||||
|
|
||||||
await utils.chatPanel.waitForHistory(page, [
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: 'Hello',
|
content: 'Hello. Answer in 50 words.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
@@ -173,12 +173,12 @@ test.describe('AIInsertion/Insert', () => {
|
|||||||
await page.keyboard.press('Delete');
|
await page.keyboard.press('Delete');
|
||||||
|
|
||||||
await utils.chatPanel.openChatPanel(page);
|
await utils.chatPanel.openChatPanel(page);
|
||||||
await utils.chatPanel.makeChat(page, 'Hello');
|
await utils.chatPanel.makeChat(page, 'Hello. Answer in 50 words.');
|
||||||
|
|
||||||
await utils.chatPanel.waitForHistory(page, [
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: 'Hello',
|
content: 'Hello. Answer in 50 words.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ test.describe('AIInsertion/SaveAsBlock', () => {
|
|||||||
utils,
|
utils,
|
||||||
}) => {
|
}) => {
|
||||||
await utils.chatPanel.openChatPanel(page);
|
await utils.chatPanel.openChatPanel(page);
|
||||||
await utils.chatPanel.makeChat(page, 'Hello');
|
await utils.chatPanel.makeChat(page, 'Hello. Answer in 50 words.');
|
||||||
|
|
||||||
await utils.chatPanel.waitForHistory(page, [
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: 'Hello',
|
content: 'Hello. Answer in 50 words.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
@@ -45,12 +45,12 @@ test.describe('AIInsertion/SaveAsBlock', () => {
|
|||||||
await utils.editor.switchToEdgelessMode(page);
|
await utils.editor.switchToEdgelessMode(page);
|
||||||
|
|
||||||
await utils.chatPanel.openChatPanel(page);
|
await utils.chatPanel.openChatPanel(page);
|
||||||
await utils.chatPanel.makeChat(page, 'Hello');
|
await utils.chatPanel.makeChat(page, 'Hello. Answer in 50 words.');
|
||||||
|
|
||||||
await utils.chatPanel.waitForHistory(page, [
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: 'Hello',
|
content: 'Hello. Answer in 50 words.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ test.describe('AIInsertion/SaveAsDoc', () => {
|
|||||||
utils,
|
utils,
|
||||||
}) => {
|
}) => {
|
||||||
await utils.chatPanel.openChatPanel(page);
|
await utils.chatPanel.openChatPanel(page);
|
||||||
await utils.chatPanel.makeChat(page, 'Hello');
|
await utils.chatPanel.makeChat(page, 'Hello. Answer in 50 words.');
|
||||||
|
|
||||||
await utils.chatPanel.waitForHistory(page, [
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: 'Hello',
|
content: 'Hello. Answer in 50 words.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
@@ -45,12 +45,12 @@ test.describe('AIInsertion/SaveAsDoc', () => {
|
|||||||
await utils.editor.switchToEdgelessMode(page);
|
await utils.editor.switchToEdgelessMode(page);
|
||||||
|
|
||||||
await utils.chatPanel.openChatPanel(page);
|
await utils.chatPanel.openChatPanel(page);
|
||||||
await utils.chatPanel.makeChat(page, 'Hello');
|
await utils.chatPanel.makeChat(page, 'Hello. Answer in 50 words.');
|
||||||
|
|
||||||
await utils.chatPanel.waitForHistory(page, [
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: 'Hello',
|
content: 'Hello. Answer in 50 words.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export class ChatPanelUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static async closeChatPanel(page: Page) {
|
public static async closeChatPanel(page: Page) {
|
||||||
await page.getByTestId('right-sidebar-toggle').click({
|
await page.getByTestId('right-sidebar-close').click({
|
||||||
delay: 200,
|
delay: 200,
|
||||||
});
|
});
|
||||||
await expect(page.getByTestId('sidebar-tab-content-chat')).toBeHidden();
|
await expect(page.getByTestId('sidebar-tab-content-chat')).toBeHidden();
|
||||||
|
|||||||
Reference in New Issue
Block a user