Skip to content

Commit

Permalink
chore: move ai chat block to affine
Browse files Browse the repository at this point in the history
  • Loading branch information
donteatfriedrice committed Sep 20, 2024
1 parent e3e15c6 commit b201e70
Show file tree
Hide file tree
Showing 47 changed files with 1,123 additions and 323 deletions.
19 changes: 18 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,23 @@
"which-typed-array": "npm:@nolyfill/which-typed-array@latest",
"@reforged/maker-appimage/@electron-forge/maker-base": "7.4.0",
"macos-alias": "npm:@napi-rs/macos-alias@0.0.4",
"fs-xattr": "npm:@napi-rs/xattr@latest"
"fs-xattr": "npm:@napi-rs/xattr@latest",
"@blocksuite/affine": "portal:/Users/chen/github/toeverything/blocksuite/packages/affine/all",
"@blocksuite/affine-block-embed": "portal:/Users/chen/github/toeverything/blocksuite/packages/affine/block-embed",
"@blocksuite/affine-block-list": "portal:/Users/chen/github/toeverything/blocksuite/packages/affine/block-list",
"@blocksuite/affine-block-paragraph": "portal:/Users/chen/github/toeverything/blocksuite/packages/affine/block-paragraph",
"@blocksuite/affine-block-surface": "portal:/Users/chen/github/toeverything/blocksuite/packages/affine/block-surface",
"@blocksuite/affine-components": "portal:/Users/chen/github/toeverything/blocksuite/packages/affine/components",
"@blocksuite/data-view": "portal:/Users/chen/github/toeverything/blocksuite/packages/affine/data-view",
"@blocksuite/affine-model": "portal:/Users/chen/github/toeverything/blocksuite/packages/affine/model",
"@blocksuite/affine-shared": "portal:/Users/chen/github/toeverything/blocksuite/packages/affine/shared",
"@blocksuite/affine-widget-scroll-anchoring": "portal:/Users/chen/github/toeverything/blocksuite/packages/affine/widget-scroll-anchoring",
"@blocksuite/blocks": "portal:/Users/chen/github/toeverything/blocksuite/packages/blocks",
"@blocksuite/block-std": "portal:/Users/chen/github/toeverything/blocksuite/packages/framework/block-std",
"@blocksuite/global": "portal:/Users/chen/github/toeverything/blocksuite/packages/framework/global",
"@blocksuite/inline": "portal:/Users/chen/github/toeverything/blocksuite/packages/framework/inline",
"@blocksuite/store": "portal:/Users/chen/github/toeverything/blocksuite/packages/framework/store",
"@blocksuite/sync": "portal:/Users/chen/github/toeverything/blocksuite/packages/framework/sync",
"@blocksuite/presets": "portal:/Users/chen/github/toeverything/blocksuite/packages/presets"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import type { SerializedXYWH } from '@blocksuite/global/utils';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';

type AIChatProps = {
xywh: SerializedXYWH;
index: string;
scale: number;
messages: string; // JSON string of ChatMessage[]
sessionId: string; // forked session id
rootWorkspaceId: string; // workspace id of root chat session
rootDocId: string; // doc id of root chat session
};

export const AIChatBlockSchema = defineBlockSchema({
flavour: 'affine:embed-ai-chat',
props: (): AIChatProps => ({
xywh: '[0,0,0,0]',
index: 'a0',
scale: 1,
messages: '',
sessionId: '',
rootWorkspaceId: '',
rootDocId: '',
}),
metadata: {
version: 1,
role: 'content',
children: [],
},
toModel: () => {
return new AIChatBlockModel();
},
});

export class AIChatBlockModel extends GfxCompatible<AIChatProps>(BlockModel) {}

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace BlockSuite {
interface EdgelessBlockModelMap {
'affine:embed-ai-chat': AIChatBlockModel;
}
interface BlockModels {
'affine:embed-ai-chat': AIChatBlockModel;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const CHAT_BLOCK_WIDTH = 300;
export const CHAT_BLOCK_HEIGHT = 320;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './ai-chat-model';
export * from './consts';
export * from './types';
25 changes: 25 additions & 0 deletions packages/common/infra/src/blocksuite/blocks/ai-chat-block/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { z } from 'zod';

// Define the Zod schema
const ChatMessageSchema = z.object({
id: z.string(),
content: z.string(),
role: z.union([z.literal('user'), z.literal('assistant')]),
createdAt: z.string(),
attachments: z.array(z.string()).optional(),
userId: z.string().optional(),
userName: z.string().optional(),
avatarUrl: z.string().optional(),
});

export const ChatMessagesSchema = z.array(ChatMessageSchema);

// Derive the TypeScript type from the Zod schema
export type ChatMessage = z.infer<typeof ChatMessageSchema>;

export type MessageRole = 'user' | 'assistant';
export type MessageUserInfo = {
userId?: string;
userName?: string;
avatarUrl?: string;
};
1 change: 1 addition & 0 deletions packages/common/infra/src/blocksuite/blocks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ai-chat-block';
1 change: 1 addition & 0 deletions packages/common/infra/src/blocksuite/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './blocks';
export {
migratePages as forceUpgradePages,
migrateGuidCompatibility,
Expand Down
3 changes: 2 additions & 1 deletion packages/common/infra/src/modules/workspace/global-schema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { AffineSchemas } from '@blocksuite/blocks/schemas';
import { AIChatBlockSchema } from '@blocksuite/presets';
import { Schema } from '@blocksuite/store';

import { AIChatBlockSchema } from '../../blocksuite/blocks/ai-chat-block/ai-chat-model';

let _schema: Schema | null = null;
export function getAFFiNEWorkspaceSchema() {
if (!_schema) {
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@floating-ui/dom": "^1.6.5",
"@juggle/resize-observer": "^3.4.0",
"@marsidev/react-turnstile": "^1.0.0",
"@preact/signals-core": "^1.8.0",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-popover": "^1.0.7",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { BlockStdScope, type EditorHost } from '@blocksuite/block-std';
import {
type AffineAIPanelState,
type AffineAIPanelWidgetConfig,
import type {
AffineAIPanelState,
AffineAIPanelWidgetConfig,
} from '@blocksuite/blocks';
import {
CodeBlockComponent,
DividerBlockComponent,
ListBlockComponent,
ParagraphBlockComponent,
SpecProvider,
} from '@blocksuite/blocks';
import { WithDisposable } from '@blocksuite/global/utils';
import { BlockViewType, type Doc, type Query } from '@blocksuite/store';
import { css, html, LitElement, type PropertyValues } from 'lit';
import { css, html, LitElement, nothing, type PropertyValues } from 'lit';
import { property, query } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { keyed } from 'lit/directives/keyed.js';

import { CustomPageEditorBlockSpecs } from '../utils/custom-specs';
import { markDownToDoc } from '../utils/markdown-utils';

const textBlockStyles = css`
Expand Down Expand Up @@ -67,12 +67,12 @@ const customHeadingStyles = css`
}
`;

type TextRendererOptions = {
export type TextRendererOptions = {
maxHeight?: number;
customHeading?: boolean;
};

export class AIAnswerText extends WithDisposable(LitElement) {
export class TextRenderer extends WithDisposable(LitElement) {
static override styles = css`
.ai-answer-text-editor.affine-page-viewport {
background: transparent;
Expand Down Expand Up @@ -138,50 +138,23 @@ export class AIAnswerText extends WithDisposable(LitElement) {
editor-host * {
box-sizing: border-box;
}
editor-host {
isolation: isolate;
}
}
${textBlockStyles}
${customHeadingStyles}
`;

@query('.ai-answer-text-container')
private accessor _container!: HTMLDivElement;

private _doc!: Doc;

private _answers: string[] = [];

private _timer?: ReturnType<typeof setInterval> | null = null;

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

@property({ attribute: false })
accessor answer!: string;

@property({ attribute: false })
accessor options!: TextRendererOptions;

@property({ attribute: false })
accessor state: AffineAIPanelState | undefined = undefined;

private _onWheel(e: MouseEvent) {
e.stopPropagation();
if (this.state === 'generating') {
e.preventDefault();
}
}

private readonly _clearTimer = () => {
if (this._timer) {
clearInterval(this._timer);
this._timer = null;
}
};

private _doc: Doc | null = null;

private readonly _query: Query = {
mode: 'strict',
match: [
Expand All @@ -195,6 +168,8 @@ export class AIAnswerText extends WithDisposable(LitElement) {
].map(flavour => ({ flavour, viewType: BlockViewType.Display })),
};

private _timer?: ReturnType<typeof setInterval> | null = null;

private readonly _updateDoc = () => {
if (this._answers.length > 0) {
const latestAnswer = this._answers.pop();
Expand Down Expand Up @@ -222,13 +197,11 @@ export class AIAnswerText extends WithDisposable(LitElement) {
}
};

override shouldUpdate(changedProperties: PropertyValues) {
if (changedProperties.has('answer')) {
this._answers.push(this.answer);
return false;
private _onWheel(e: MouseEvent) {
e.stopPropagation();
if (this.state === 'generating') {
e.preventDefault();
}

return true;
}

override connectedCallback() {
Expand All @@ -246,16 +219,13 @@ export class AIAnswerText extends WithDisposable(LitElement) {
this._clearTimer();
}

override updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
requestAnimationFrame(() => {
if (!this._container) return;
this._container.scrollTop = this._container.scrollHeight;
});
}

override render() {
if (!this._doc) {
return nothing;
}

const { maxHeight, customHeading } = this.options;
const previewSpec = SpecProvider.getInstance().getSpec('page:preview');
const classes = classMap({
'ai-answer-text-container': true,
'show-scrollbar': !!maxHeight,
Expand All @@ -273,18 +243,50 @@ export class AIAnswerText extends WithDisposable(LitElement) {
html`<div class="ai-answer-text-editor affine-page-viewport">
${new BlockStdScope({
doc: this._doc,
extensions: CustomPageEditorBlockSpecs,
extensions: previewSpec.value,
}).render()}
</div>`
)}
</div>
`;
}

override shouldUpdate(changedProperties: PropertyValues) {
if (changedProperties.has('answer')) {
this._answers.push(this.answer);
return false;
}

return true;
}

override updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
requestAnimationFrame(() => {
if (!this._container) return;
this._container.scrollTop = this._container.scrollHeight;
});
}

@query('.ai-answer-text-container')
private accessor _container!: HTMLDivElement;

@property({ attribute: false })
accessor answer!: string;

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

@property({ attribute: false })
accessor options!: TextRendererOptions;

@property({ attribute: false })
accessor state: AffineAIPanelState | undefined = undefined;
}

declare global {
interface HTMLElementTagNameMap {
'ai-answer-text': AIAnswerText;
'text-renderer': TextRenderer;
}
}

Expand All @@ -293,11 +295,11 @@ export const createTextRenderer: (
options: TextRendererOptions
) => AffineAIPanelWidgetConfig['answerRenderer'] = (host, options) => {
return (answer, state) => {
return html`<ai-answer-text
return html`<text-renderer
.host=${host}
.answer=${answer}
.state=${state}
.options=${options}
></ai-answer-text>`;
></text-renderer>`;
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ import { type ChatMessage } from '@blocksuite/presets';
import type { Doc } from '@blocksuite/store';
import type { TemplateResult } from 'lit';

import { insertFromMarkdown } from '../../_common/utils/markdown-utils';
import { AIProvider, type AIUserInfo } from '../provider';
import { reportResponse } from '../utils/action-reporter';
import { insertBelow, replace } from '../utils/editor-actions';
import { insertFromMarkdown } from '../utils/markdown-utils';
import { BlockIcon, CreateIcon, InsertBelowIcon, ReplaceIcon } from './icons';

const { matchFlavours } = BlocksUtils;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import type {
import { assertExists } from '@blocksuite/global/utils';
import type { TemplateResult } from 'lit';

import { createTextRenderer } from '../../_common/components/text-renderer';
import {
buildCopyConfig,
buildErrorConfig,
buildFinishConfig,
buildGeneratingConfig,
getAIPanel,
} from '../ai-panel';
import { createTextRenderer } from '../messages/text';
import { AIProvider } from '../provider';
import { reportResponse } from '../utils/action-reporter';
import {
Expand Down
Loading

0 comments on commit b201e70

Please sign in to comment.