mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 20:08:37 +00:00
refactor: move chat block to affine (#8368)
[BS-898](https://linear.app/affine-design/issue/BS-898/move-ai-chat-block-to-affine) Should be merged after https://github.com/toeverything/blocksuite/pull/8420 merged and bumped.
This commit is contained in:
2
packages/common/env/package.json
vendored
2
packages/common/env/package.json
vendored
@@ -3,7 +3,7 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@blocksuite/affine": "0.17.18",
|
||||
"@blocksuite/affine": "0.17.19",
|
||||
"vitest": "2.1.1"
|
||||
},
|
||||
"exports": {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"@affine/debug": "workspace:*",
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@blocksuite/affine": "0.17.18",
|
||||
"@blocksuite/affine": "0.17.19",
|
||||
"@datastructures-js/binary-search-tree": "^5.3.2",
|
||||
"foxact": "^0.2.33",
|
||||
"fractional-indexing": "^3.2.0",
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { GfxCompatible } from '@blocksuite/affine/block-std/gfx';
|
||||
import type { SerializedXYWH } from '@blocksuite/affine/global/utils';
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/affine/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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export const CHAT_BLOCK_WIDTH = 300;
|
||||
export const CHAT_BLOCK_HEIGHT = 320;
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './ai-chat-model';
|
||||
export * from './consts';
|
||||
export * from './types';
|
||||
@@ -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
packages/common/infra/src/blocksuite/blocks/index.ts
Normal file
1
packages/common/infra/src/blocksuite/blocks/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './ai-chat-block';
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './blocks';
|
||||
export {
|
||||
migratePages as forceUpgradePages,
|
||||
migrateGuidCompatibility,
|
||||
|
||||
@@ -30,7 +30,7 @@ export function initDocFromProps(doc: Doc, props?: DocProps) {
|
||||
'affine:page',
|
||||
props?.page || { title: new Text('') }
|
||||
);
|
||||
doc.addBlock('affine:surface', props?.surface || {}, pageBlockId);
|
||||
doc.addBlock('affine:surface' as never, props?.surface || {}, pageBlockId);
|
||||
const noteBlockId = doc.addBlock(
|
||||
'affine:note',
|
||||
{
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { Unreachable } from '@affine/env/constant';
|
||||
import { type DocMode } from '@blocksuite/affine/blocks';
|
||||
import {
|
||||
type AffineTextAttributes,
|
||||
type DocMode,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import type { DeltaInsert } from '@blocksuite/affine/inline';
|
||||
|
||||
import { Service } from '../../../framework';
|
||||
import { type DocProps, initDocFromProps } from '../../../initialization';
|
||||
@@ -77,7 +81,7 @@ export class DocsService extends Service {
|
||||
const { doc, release } = this.open(targetDocId);
|
||||
doc.setPriorityLoad(10);
|
||||
await doc.waitForSyncReady();
|
||||
const text = doc.blockSuiteDoc.Text.fromDelta([
|
||||
const text = new doc.blockSuiteDoc.Text([
|
||||
{
|
||||
insert: ' ',
|
||||
attributes: {
|
||||
@@ -87,7 +91,7 @@ export class DocsService extends Service {
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
] as DeltaInsert<AffineTextAttributes>[]);
|
||||
const [frame] = doc.blockSuiteDoc.getBlocksByFlavour('affine:note');
|
||||
frame &&
|
||||
doc.blockSuiteDoc.addBlock(
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { AffineSchemas } from '@blocksuite/affine/blocks/schemas';
|
||||
import { AIChatBlockSchema } from '@blocksuite/affine/presets';
|
||||
import { Schema } from '@blocksuite/affine/store';
|
||||
|
||||
import { AIChatBlockSchema } from '../../blocksuite/blocks/ai-chat-block/ai-chat-model';
|
||||
|
||||
let _schema: Schema | null = null;
|
||||
export function getAFFiNEWorkspaceSchema() {
|
||||
if (!_schema) {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"@affine/core": "workspace:*",
|
||||
"@affine/i18n": "workspace:*",
|
||||
"@affine/native": "workspace:*",
|
||||
"@blocksuite/affine": "0.17.18",
|
||||
"@blocksuite/affine": "0.17.19",
|
||||
"@electron-forge/cli": "^7.3.0",
|
||||
"@electron-forge/core": "^7.3.0",
|
||||
"@electron-forge/core-utils": "^7.3.0",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"@affine/component": "workspace:*",
|
||||
"@affine/core": "workspace:*",
|
||||
"@affine/i18n": "workspace:*",
|
||||
"@blocksuite/affine": "0.17.18",
|
||||
"@blocksuite/affine": "0.17.19",
|
||||
"@blocksuite/icons": "^2.1.67",
|
||||
"@sentry/react": "^8.0.0",
|
||||
"react": "^18.2.0",
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/affine": "0.17.18",
|
||||
"@blocksuite/affine": "0.17.19",
|
||||
"@blocksuite/icons": "2.1.68",
|
||||
"@chromatic-com/storybook": "^2.0.0",
|
||||
"@storybook/addon-essentials": "^8.2.9",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"@affine/i18n": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/track": "workspace:*",
|
||||
"@blocksuite/affine": "0.17.18",
|
||||
"@blocksuite/affine": "0.17.19",
|
||||
"@blocksuite/icons": "2.1.68",
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/modifiers": "^7.0.0",
|
||||
@@ -27,6 +27,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",
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { BlockStdScope, type EditorHost } from '@blocksuite/affine/block-std';
|
||||
import {
|
||||
type AffineAIPanelState,
|
||||
type AffineAIPanelWidgetConfig,
|
||||
import type {
|
||||
AffineAIPanelState,
|
||||
AffineAIPanelWidgetConfig,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import {
|
||||
CodeBlockComponent,
|
||||
DividerBlockComponent,
|
||||
ListBlockComponent,
|
||||
ParagraphBlockComponent,
|
||||
SpecProvider,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import { WithDisposable } from '@blocksuite/affine/global/utils';
|
||||
import { BlockViewType, type Doc, type Query } from '@blocksuite/affine/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`
|
||||
@@ -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;
|
||||
@@ -138,43 +138,14 @@ 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);
|
||||
@@ -182,6 +153,8 @@ export class AIAnswerText extends WithDisposable(LitElement) {
|
||||
}
|
||||
};
|
||||
|
||||
private _doc: Doc | null = null;
|
||||
|
||||
private readonly _query: Query = {
|
||||
mode: 'strict',
|
||||
match: [
|
||||
@@ -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();
|
||||
@@ -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() {
|
||||
@@ -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,
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>`;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './components/text-renderer';
|
||||
export * from './utils/markdown-utils';
|
||||
@@ -12,7 +12,8 @@ import {
|
||||
PlainTextAdapter,
|
||||
titleMiddleware,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import { assertExists } from '@blocksuite/affine/global/utils';
|
||||
import { DocCollection, Job } from '@blocksuite/affine/store';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type {
|
||||
BlockModel,
|
||||
BlockSnapshot,
|
||||
@@ -20,8 +21,7 @@ import type {
|
||||
DraftModel,
|
||||
Slice,
|
||||
SliceSnapshot,
|
||||
} from '@blocksuite/affine/store';
|
||||
import { DocCollection, Job } from '@blocksuite/affine/store';
|
||||
} from '@blocksuite/store';
|
||||
|
||||
const updateSnapshotText = (
|
||||
point: TextRangePoint,
|
||||
@@ -24,14 +24,14 @@ import {
|
||||
getElementsBound,
|
||||
type SerializedXYWH,
|
||||
} from '@blocksuite/affine/global/utils';
|
||||
import { type ChatMessage } from '@blocksuite/affine/presets';
|
||||
import type { Doc } from '@blocksuite/affine/store';
|
||||
import type { ChatMessage } from '@toeverything/infra/blocksuite';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
import { insertFromMarkdown } from '../../_common';
|
||||
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;
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
import { assertExists } from '@blocksuite/affine/global/utils';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
import { createTextRenderer } from '../../_common';
|
||||
import {
|
||||
buildCopyConfig,
|
||||
buildErrorConfig,
|
||||
@@ -14,7 +15,6 @@ import {
|
||||
buildGeneratingConfig,
|
||||
getAIPanel,
|
||||
} from '../ai-panel';
|
||||
import { createTextRenderer } from '../messages/text';
|
||||
import { AIProvider } from '../provider';
|
||||
import { reportResponse } from '../utils/action-reporter';
|
||||
import {
|
||||
|
||||
@@ -15,17 +15,17 @@ import {
|
||||
TextElementModel,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import { assertExists } from '@blocksuite/affine/global/utils';
|
||||
import { AIChatBlockModel } from '@blocksuite/affine/presets';
|
||||
import { Slice } from '@blocksuite/affine/store';
|
||||
import { AIChatBlockModel } from '@toeverything/infra';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
import { createTextRenderer, getContentFromSlice } from '../../_common';
|
||||
import { getAIPanel } from '../ai-panel';
|
||||
import {
|
||||
createMindmapExecuteRenderer,
|
||||
createMindmapRenderer,
|
||||
} from '../messages/mindmap';
|
||||
import { createSlidesRenderer } from '../messages/slides-renderer';
|
||||
import { createTextRenderer } from '../messages/text';
|
||||
import { createIframeRenderer, createImageRenderer } from '../messages/wrapper';
|
||||
import { AIProvider } from '../provider';
|
||||
import { reportResponse } from '../utils/action-reporter';
|
||||
@@ -35,7 +35,6 @@ import {
|
||||
isMindMapRoot,
|
||||
} from '../utils/edgeless';
|
||||
import { copyTextAnswer } from '../utils/editor-actions';
|
||||
import { getContentFromSlice } from '../utils/markdown-utils';
|
||||
import {
|
||||
getCopilotSelectedElems,
|
||||
getSelectedNoteAnchor,
|
||||
|
||||
@@ -28,6 +28,7 @@ import { assertExists, Bound } from '@blocksuite/affine/global/utils';
|
||||
import { html, type TemplateResult } from 'lit';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { insertFromMarkdown } from '../../_common';
|
||||
import { AIPenIcon, ChatWithAIIcon } from '../_common/icons';
|
||||
import { getAIPanel } from '../ai-panel';
|
||||
import { AIProvider } from '../provider';
|
||||
@@ -39,7 +40,6 @@ import {
|
||||
} from '../utils/edgeless';
|
||||
import { preprocessHtml } from '../utils/html';
|
||||
import { fetchImageToFile } from '../utils/image';
|
||||
import { insertFromMarkdown } from '../utils/markdown-utils';
|
||||
import {
|
||||
getCopilotSelectedElems,
|
||||
getEdgelessRootFromEditor,
|
||||
@@ -313,7 +313,7 @@ const imageHandler = (host: EditorHost) => {
|
||||
|
||||
host.doc.transact(() => {
|
||||
edgelessRoot
|
||||
.addImages([img], [x, y], true)
|
||||
.addImages([img], [x, y])
|
||||
.then(blockIds => {
|
||||
const imageBlockId = blockIds[0];
|
||||
const imageBlock = host.doc.getBlock(imageBlockId);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { assertExists, Bound } from '@blocksuite/affine/global/utils';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
import { createTextRenderer, insertFromMarkdown } from '../_common';
|
||||
import {
|
||||
AIPenIcon,
|
||||
AIStarIconWithAnimation,
|
||||
@@ -24,7 +25,6 @@ import {
|
||||
RetryIcon,
|
||||
} from './_common/icons';
|
||||
import { INSERT_ABOVE_ACTIONS } from './actions/consts';
|
||||
import { createTextRenderer } from './messages/text';
|
||||
import { AIProvider } from './provider';
|
||||
import { reportResponse } from './utils/action-reporter';
|
||||
import { findNoteBlockModel, getService } from './utils/edgeless';
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
insertBelow,
|
||||
replace,
|
||||
} from './utils/editor-actions';
|
||||
import { insertFromMarkdown } from './utils/markdown-utils';
|
||||
import { getSelections } from './utils/selection-utils';
|
||||
|
||||
function getSelection(host: EditorHost) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { WithDisposable } from '@blocksuite/affine/global/utils';
|
||||
import { css, html, LitElement, nothing, type TemplateResult } from 'lit';
|
||||
import { property, state } from 'lit/decorators.js';
|
||||
|
||||
import { createTextRenderer } from '../../../_common';
|
||||
import {
|
||||
ActionIcon,
|
||||
AIChangeToneIcon,
|
||||
@@ -22,7 +23,6 @@ import {
|
||||
ArrowDownIcon,
|
||||
ArrowUpIcon,
|
||||
} from '../../_common/icons';
|
||||
import { createTextRenderer } from '../../messages/text';
|
||||
import type { ChatAction } from '../chat-context';
|
||||
import { renderImages } from '../components/images';
|
||||
import { HISTORY_IMAGE_ACTIONS } from '../const';
|
||||
|
||||
@@ -6,7 +6,7 @@ import { WithDisposable } from '@blocksuite/affine/global/utils';
|
||||
import { html, nothing } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import { createTextRenderer } from '../../messages/text';
|
||||
import { createTextRenderer } from '../../../_common';
|
||||
import { renderImages } from '../components/images';
|
||||
export class ChatText extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
|
||||
@@ -6,7 +6,7 @@ import { css, html, LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { createTextRenderer } from '../../messages/text';
|
||||
import { createTextRenderer } from '../../../_common';
|
||||
import type { ChatAction } from '../chat-context';
|
||||
|
||||
export class ActionText extends WithDisposable(LitElement) {
|
||||
|
||||
@@ -1,18 +1,3 @@
|
||||
import '../messages/slides-renderer';
|
||||
import './ai-loading';
|
||||
import '../messages/text';
|
||||
import './actions/text';
|
||||
import './actions/action-wrapper';
|
||||
import './actions/make-real';
|
||||
import './actions/slides';
|
||||
import './actions/mindmap';
|
||||
import './actions/chat-text';
|
||||
import './actions/image-to-text';
|
||||
import './actions/image';
|
||||
import './chat-cards';
|
||||
import '../_common/components/chat-action-list';
|
||||
import '../_common/components/copy-more';
|
||||
|
||||
import type { BaseSelection, EditorHost } from '@blocksuite/affine/block-std';
|
||||
import { ShadowlessElement } from '@blocksuite/affine/block-std';
|
||||
import {
|
||||
|
||||
@@ -6,4 +6,3 @@ export * from './entries/index';
|
||||
export * from './messages/index';
|
||||
export { AIChatBlockPeekViewTemplate } from './peek-view/chat-block-peek-view';
|
||||
export * from './provider';
|
||||
export * from './setup';
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from './text';
|
||||
export * from './wrapper';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { EditorHost } from '@blocksuite/affine/block-std';
|
||||
import { type AIError, openFileOrFiles } from '@blocksuite/affine/blocks';
|
||||
import { type ChatMessage } from '@blocksuite/affine/presets';
|
||||
import type { ChatMessage } from '@toeverything/infra';
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
import './chat-block-input';
|
||||
import './date-time';
|
||||
import '../_common/components/chat-action-list';
|
||||
import '../_common/components/copy-more';
|
||||
|
||||
import { type EditorHost } from '@blocksuite/affine/block-std';
|
||||
import {
|
||||
type AIError,
|
||||
@@ -17,7 +12,7 @@ import {
|
||||
type AIChatBlockModel,
|
||||
type ChatMessage,
|
||||
ChatMessagesSchema,
|
||||
} from '@blocksuite/affine/presets';
|
||||
} from '@toeverything/infra/blocksuite';
|
||||
import { html, LitElement, nothing } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AIError } from '@blocksuite/affine/blocks';
|
||||
import { type ChatMessage } from '@blocksuite/affine/presets';
|
||||
import type { ChatMessage } from '@toeverything/infra/blocksuite';
|
||||
|
||||
export type ChatStatus =
|
||||
| 'success'
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
type AIChatBlockModel,
|
||||
CHAT_BLOCK_HEIGHT,
|
||||
CHAT_BLOCK_WIDTH,
|
||||
} from '@blocksuite/affine/presets';
|
||||
} from '@toeverything/infra';
|
||||
|
||||
/**
|
||||
* Calculates the bounding box for a child block
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { AskAIButton } from './_common/components/ask-ai-button';
|
||||
import { AskAIPanel } from './_common/components/ask-ai-panel';
|
||||
import { ChatActionList } from './_common/components/chat-action-list';
|
||||
import { ChatCopyMore } from './_common/components/copy-more';
|
||||
import { ChatPanel } from './chat-panel';
|
||||
import { ActionWrapper } from './chat-panel/actions/action-wrapper';
|
||||
import { ChatText } from './chat-panel/actions/chat-text';
|
||||
import { ActionImage } from './chat-panel/actions/image';
|
||||
import { ActionImageToText } from './chat-panel/actions/image-to-text';
|
||||
import { ActionMakeReal } from './chat-panel/actions/make-real';
|
||||
import { ActionMindmap } from './chat-panel/actions/mindmap';
|
||||
import { ActionSlides } from './chat-panel/actions/slides';
|
||||
import { ActionText } from './chat-panel/actions/text';
|
||||
import { AILoading } from './chat-panel/ai-loading';
|
||||
import { ChatCards } from './chat-panel/chat-cards';
|
||||
import { ChatPanelInput } from './chat-panel/chat-panel-input';
|
||||
import { ChatPanelMessages } from './chat-panel/chat-panel-messages';
|
||||
import { AIAnswerText, AIAnswerWrapper } from './messages';
|
||||
import { AIErrorWrapper } from './messages/error';
|
||||
import { AISlidesRenderer } from './messages/slides-renderer';
|
||||
import { ChatBlockInput } from './peek-view/chat-block-input';
|
||||
import { AIChatBlockPeekView } from './peek-view/chat-block-peek-view';
|
||||
import { DateTime } from './peek-view/date-time';
|
||||
|
||||
export function registerAICustomComponents() {
|
||||
customElements.define('ask-ai-button', AskAIButton);
|
||||
customElements.define('ask-ai-panel', AskAIPanel);
|
||||
customElements.define('chat-action-list', ChatActionList);
|
||||
customElements.define('chat-copy-more', ChatCopyMore);
|
||||
customElements.define('action-wrapper', ActionWrapper);
|
||||
customElements.define('chat-text', ChatText);
|
||||
customElements.define('action-image-to-text', ActionImageToText);
|
||||
customElements.define('action-image', ActionImage);
|
||||
customElements.define('action-make-real', ActionMakeReal);
|
||||
customElements.define('action-mindmap', ActionMindmap);
|
||||
customElements.define('action-slides', ActionSlides);
|
||||
customElements.define('action-text', ActionText);
|
||||
customElements.define('ai-loading', AILoading);
|
||||
customElements.define('chat-cards', ChatCards);
|
||||
customElements.define('chat-panel-input', ChatPanelInput);
|
||||
customElements.define('chat-panel-messages', ChatPanelMessages);
|
||||
customElements.define('chat-panel', ChatPanel);
|
||||
customElements.define('ai-error-wrapper', AIErrorWrapper);
|
||||
customElements.define('ai-slides-renderer', AISlidesRenderer);
|
||||
customElements.define('ai-answer-wrapper', AIAnswerWrapper);
|
||||
customElements.define('ai-answer-text', AIAnswerText);
|
||||
customElements.define('chat-block-input', ChatBlockInput);
|
||||
customElements.define('ai-chat-block-peek-view', AIChatBlockPeekView);
|
||||
customElements.define('date-time', DateTime);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import type { EditorHost } from '@blocksuite/affine/block-std';
|
||||
import type { EdgelessRootService } from '@blocksuite/affine/blocks';
|
||||
import type { BlockSnapshot } from '@blocksuite/affine/store';
|
||||
|
||||
import { markdownToSnapshot } from '../utils/markdown-utils';
|
||||
import { markdownToSnapshot } from '../../_common';
|
||||
import { getSurfaceElementFromEditor } from '../utils/selection-utils';
|
||||
import {
|
||||
basicTheme,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
insertFromMarkdown,
|
||||
markDownToDoc,
|
||||
markdownToSnapshot,
|
||||
} from './markdown-utils';
|
||||
} from '../../_common';
|
||||
|
||||
const getNoteId = (blockElement: BlockComponent) => {
|
||||
let element = blockElement;
|
||||
|
||||
@@ -14,8 +14,8 @@ import {
|
||||
toDraftModel,
|
||||
} from '@blocksuite/affine/store';
|
||||
|
||||
import { getContentFromSlice } from '../../_common';
|
||||
import { getEdgelessCopilotWidget, getService } from './edgeless';
|
||||
import { getContentFromSlice } from './markdown-utils';
|
||||
|
||||
export const getRootService = (host: EditorHost) => {
|
||||
return host.std.getService('affine:page');
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import { html } from 'lit';
|
||||
|
||||
export const ChatWithAIIcon = html`<svg
|
||||
width="21"
|
||||
height="21"
|
||||
viewBox="0 0 21 21"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M3.59593 7.38585C2.75058 6.62196 3.25841 5.16699 4.43763 5.16699H16.3017C17.2852 5.16699 17.8664 6.23248 17.3995 7.06491L11.5806 17.4378C11.0097 18.4556 9.51091 18.2189 9.25406 17.098L7.9249 11.2976L3.59593 7.38585ZM9.20223 11.2755L10.4725 16.8188C10.4742 16.8262 10.4759 16.8301 10.4767 16.8316C10.4777 16.8321 10.4796 16.8329 10.4827 16.8334C10.4839 16.8336 10.4849 16.8337 10.4857 16.8337C10.4869 16.8321 10.4885 16.8297 10.4904 16.8263L15.7266 7.492L9.20223 11.2755ZM15.0887 6.41699H4.43763C4.4362 6.41699 4.43499 6.41703 4.434 6.41709C4.43249 6.41912 4.43033 6.42258 4.42836 6.42784C4.42439 6.43845 4.42506 6.44624 4.42551 6.44838C4.42564 6.44898 4.42571 6.4491 4.42586 6.44937L4.42588 6.44939C4.42593 6.44949 4.42768 6.4527 4.434 6.45841L8.57091 10.1967L15.0887 6.41699Z"
|
||||
/>
|
||||
</svg>`;
|
||||
|
||||
export const AffineAIIcon = html`<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.2812 5.49104C11.2403 5.13024 10.9353 4.85751 10.5722 4.85714C10.2091 4.85677 9.90345 5.12887 9.86185 5.48959C9.59131 7.83515 8.89003 9.48448 7.75868 10.6158C6.62734 11.7472 4.97801 12.4485 2.63244 12.719C2.27173 12.7606 1.99963 13.0662 2 13.4293C2.00037 13.7924 2.2731 14.0975 2.63389 14.1383C4.94069 14.3996 6.62508 15.1006 7.78328 16.2379C8.93713 17.3709 9.65305 19.0198 9.85994 21.3489C9.89271 21.7178 10.2019 22.0004 10.5722 22C10.9425 21.9996 11.2511 21.7162 11.2831 21.3473C11.4813 19.0565 12.1966 17.3729 13.3562 16.2133C14.5157 15.0537 16.1994 14.3385 18.4902 14.1402C18.8591 14.1083 19.1424 13.7997 19.1429 13.4294C19.1433 13.0591 18.8606 12.7499 18.4918 12.7171C16.1627 12.5102 14.5137 11.7943 13.3807 10.6404C12.2435 9.48222 11.5425 7.79783 11.2812 5.49104Z"
|
||||
/>
|
||||
<path
|
||||
d="M18.9427 2.24651C18.9268 2.1062 18.8082 2.00014 18.667 2C18.5257 1.99986 18.4069 2.10567 18.3907 2.24595C18.2855 3.15811 18.0128 3.79952 17.5728 4.23949C17.1329 4.67946 16.4914 4.95218 15.5793 5.05739C15.439 5.07356 15.3332 5.19241 15.3333 5.33362C15.3335 5.47482 15.4395 5.59345 15.5798 5.60935C16.4769 5.71096 17.132 5.98357 17.5824 6.42584C18.0311 6.86644 18.3095 7.50771 18.39 8.41347C18.4027 8.55691 18.523 8.66683 18.667 8.66667C18.811 8.6665 18.931 8.55632 18.9434 8.41284C19.0205 7.52199 19.2987 6.86723 19.7496 6.41629C20.2006 5.96534 20.8553 5.68719 21.7462 5.61008C21.8896 5.59766 21.9998 5.47765 22 5.33365C22.0002 5.18964 21.8902 5.06939 21.7468 5.05664C20.841 4.97619 20.1998 4.69777 19.7592 4.24905C19.3169 3.79864 19.0443 3.1436 18.9427 2.24651Z"
|
||||
/>
|
||||
</svg> `;
|
||||
|
||||
export const ImageLoadingFailedIcon = html`<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M2.1665 3.99984C2.1665 2.98732 2.98732 2.1665 3.99984 2.1665H11.9998C13.0124 2.1665 13.8332 2.98732 13.8332 3.99984V7.33317C13.8332 7.60931 13.6093 7.83317 13.3332 7.83317C13.057 7.83317 12.8332 7.60931 12.8332 7.33317V3.99984C12.8332 3.5396 12.4601 3.1665 11.9998 3.1665H3.99984C3.5396 3.1665 3.1665 3.5396 3.1665 3.99984V9.4594L5.37014 7.25576C6.0861 6.5398 7.2469 6.5398 7.96287 7.25576L8.35339 7.64628C8.54865 7.84155 8.54865 8.15813 8.35339 8.35339C8.15813 8.54865 7.84155 8.54865 7.64628 8.35339L7.25576 7.96287C6.93032 7.63743 6.40268 7.63743 6.07725 7.96287L3.1665 10.8736V11.9998C3.1665 12.4601 3.5396 12.8332 3.99984 12.8332H7.33317C7.60931 12.8332 7.83317 13.057 7.83317 13.3332C7.83317 13.6093 7.60931 13.8332 7.33317 13.8332H3.99984C2.98732 13.8332 2.1665 13.0124 2.1665 11.9998V3.99984Z"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.6"
|
||||
/>
|
||||
<path
|
||||
d="M9.99984 5.33317C9.99984 5.70136 9.70136 5.99984 9.33317 5.99984C8.96498 5.99984 8.6665 5.70136 8.6665 5.33317C8.6665 4.96498 8.96498 4.6665 9.33317 4.6665C9.70136 4.6665 9.99984 4.96498 9.99984 5.33317Z"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M8.97962 8.97962C9.17488 8.78435 9.49146 8.78435 9.68672 8.97962L11.3332 10.6261L12.9796 8.97962C13.1749 8.78435 13.4915 8.78435 13.6867 8.97962C13.882 9.17488 13.882 9.49146 13.6867 9.68672L12.0403 11.3332L13.6867 12.9796C13.882 13.1749 13.882 13.4915 13.6867 13.6867C13.4915 13.882 13.1749 13.882 12.9796 13.6867L11.3332 12.0403L9.68672 13.6867C9.49146 13.882 9.17488 13.882 8.97962 13.6867C8.78435 13.4915 8.78435 13.1749 8.97962 12.9796L10.6261 11.3332L8.97962 9.68672C8.78435 9.49146 8.78435 9.17488 8.97962 8.97962Z"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.6"
|
||||
/>
|
||||
</svg>`;
|
||||
|
||||
export const LoadingIcon = html`<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<style xmlns="http://www.w3.org/2000/svg">
|
||||
.spinner {
|
||||
transform-origin: center;
|
||||
animation: spinner_animate 0.75s infinite linear;
|
||||
}
|
||||
@keyframes spinner_animate {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<path
|
||||
d="M14.6666 8.00004C14.6666 11.6819 11.6818 14.6667 7.99992 14.6667C4.31802 14.6667 1.33325 11.6819 1.33325 8.00004C1.33325 4.31814 4.31802 1.33337 7.99992 1.33337C11.6818 1.33337 14.6666 4.31814 14.6666 8.00004ZM3.30003 8.00004C3.30003 10.5957 5.40424 12.6999 7.99992 12.6999C10.5956 12.6999 12.6998 10.5957 12.6998 8.00004C12.6998 5.40436 10.5956 3.30015 7.99992 3.30015C5.40424 3.30015 3.30003 5.40436 3.30003 8.00004Z"
|
||||
fill="black"
|
||||
fill-opacity="0.1"
|
||||
/>
|
||||
<path
|
||||
d="M13.6833 8.00004C14.2263 8.00004 14.674 7.55745 14.5942 7.02026C14.5142 6.48183 14.3684 5.954 14.1591 5.44882C13.8241 4.63998 13.333 3.90505 12.714 3.286C12.0949 2.66694 11.36 2.17588 10.5511 1.84084C10.046 1.63159 9.51812 1.48576 8.9797 1.40576C8.44251 1.32595 7.99992 1.77363 7.99992 2.31671C7.99992 2.85979 8.44486 3.28974 8.9761 3.40253C9.25681 3.46214 9.53214 3.54746 9.79853 3.65781C10.3688 3.894 10.8869 4.2402 11.3233 4.67664C11.7598 5.11307 12.106 5.6312 12.3422 6.20143C12.4525 6.46782 12.5378 6.74315 12.5974 7.02386C12.7102 7.5551 13.1402 8.00004 13.6833 8.00004Z"
|
||||
fill="#1C9EE4"
|
||||
class="spinner"
|
||||
/>
|
||||
</svg>`;
|
||||
|
||||
export const SmallHintIcon = html`<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M8.00008 3.16699C5.33071 3.16699 3.16675 5.33095 3.16675 8.00033C3.16675 10.6697 5.33071 12.8337 8.00008 12.8337C10.6695 12.8337 12.8334 10.6697 12.8334 8.00033C12.8334 5.33095 10.6695 3.16699 8.00008 3.16699ZM2.16675 8.00033C2.16675 4.77866 4.77842 2.16699 8.00008 2.16699C11.2217 2.16699 13.8334 4.77866 13.8334 8.00033C13.8334 11.222 11.2217 13.8337 8.00008 13.8337C4.77842 13.8337 2.16675 11.222 2.16675 8.00033ZM8.00008 5.12996C8.27622 5.12996 8.50008 5.35381 8.50008 5.62996V8.00033C8.50008 8.27647 8.27622 8.50033 8.00008 8.50033C7.72394 8.50033 7.50008 8.27647 7.50008 8.00033V5.62996C7.50008 5.35381 7.72394 5.12996 8.00008 5.12996ZM7.50008 10.3707C7.50008 10.0946 7.72394 9.8707 8.00008 9.8707H8.00601C8.28215 9.8707 8.50601 10.0946 8.50601 10.3707C8.50601 10.6468 8.28215 10.8707 8.00601 10.8707H8.00008C7.72394 10.8707 7.50008 10.6468 7.50008 10.3707Z"
|
||||
fill-opacity="0.6"
|
||||
/>
|
||||
</svg> `;
|
||||
@@ -0,0 +1,60 @@
|
||||
import { BlockComponent } from '@blocksuite/affine/block-std';
|
||||
import { Peekable } from '@blocksuite/affine/blocks';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import {
|
||||
type AIChatBlockModel,
|
||||
ChatMessagesSchema,
|
||||
} from '@toeverything/infra/blocksuite';
|
||||
import { html } from 'lit';
|
||||
|
||||
import { ChatWithAIIcon } from '../_common/icon';
|
||||
import { AIChatBlockStyles } from './styles';
|
||||
|
||||
@Peekable({
|
||||
enableOn: ({ doc }: AIChatBlockComponent) => !doc.readonly,
|
||||
})
|
||||
export class AIChatBlockComponent extends BlockComponent<AIChatBlockModel> {
|
||||
static override styles = AIChatBlockStyles;
|
||||
|
||||
// Deserialize messages from JSON string and verify the type using zod
|
||||
private readonly _deserializeChatMessages = computed(() => {
|
||||
const messages = this.model.messages$.value;
|
||||
try {
|
||||
const result = ChatMessagesSchema.safeParse(JSON.parse(messages));
|
||||
if (result.success) {
|
||||
return result.data;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
override renderBlock() {
|
||||
const messages = this._deserializeChatMessages.value.slice(-2);
|
||||
const textRendererOptions = {
|
||||
customHeading: true,
|
||||
};
|
||||
|
||||
return html`<div class="affine-ai-chat-block-container">
|
||||
<div class="ai-chat-messages-container">
|
||||
<ai-chat-messages
|
||||
.host=${this.host}
|
||||
.messages=${messages}
|
||||
.textRendererOptions=${textRendererOptions}
|
||||
.withMask=${true}
|
||||
></ai-chat-messages>
|
||||
</div>
|
||||
<div class="ai-chat-block-button">
|
||||
${ChatWithAIIcon} <span>AI chat block</span>
|
||||
</div>
|
||||
</div> `;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-ai-chat': AIChatBlockComponent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { toGfxBlockComponent } from '@blocksuite/affine/block-std';
|
||||
import { Bound } from '@blocksuite/global/utils';
|
||||
import { html } from 'lit';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { AIChatBlockComponent } from './ai-chat-block';
|
||||
|
||||
export class EdgelessAIChatBlockComponent extends toGfxBlockComponent(
|
||||
AIChatBlockComponent
|
||||
) {
|
||||
override renderGfxBlock() {
|
||||
const bound = Bound.deserialize(this.model.xywh$.value);
|
||||
const scale = this.model.scale$.value;
|
||||
const width = bound.w / scale;
|
||||
const height = bound.h / scale;
|
||||
const style = {
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
borderRadius: '8px',
|
||||
transformOrigin: '0 0',
|
||||
boxShadow: 'var(--affine-shadow-1)',
|
||||
border: '1px solid var(--affine-border-color)',
|
||||
transform: `scale(${scale})`,
|
||||
};
|
||||
|
||||
return html`
|
||||
<div class="edgeless-ai-chat" style=${styleMap(style)}>
|
||||
${this.renderPageContent()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-edgeless-ai-chat': EdgelessAIChatBlockComponent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import {
|
||||
BlockViewExtension,
|
||||
type ExtensionType,
|
||||
} from '@blocksuite/affine/block-std';
|
||||
import { literal } from 'lit/static-html.js';
|
||||
|
||||
export const AIChatBlockSpec: ExtensionType[] = [
|
||||
BlockViewExtension('affine:embed-ai-chat', model => {
|
||||
const parent = model.doc.getParent(model.id);
|
||||
|
||||
if (parent?.flavour === 'affine:surface') {
|
||||
return literal`affine-edgeless-ai-chat`;
|
||||
}
|
||||
|
||||
return literal`affine-ai-chat`;
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,153 @@
|
||||
import type { EditorHost } from '@blocksuite/block-std';
|
||||
import type { AffineAIPanelState } from '@blocksuite/blocks';
|
||||
import type {
|
||||
ChatMessage,
|
||||
MessageRole,
|
||||
MessageUserInfo,
|
||||
} from '@toeverything/infra/blocksuite';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import type { TextRendererOptions } from '../../../_common/components/text-renderer';
|
||||
import { UserInfoTemplate } from './user-info';
|
||||
|
||||
export class AIChatMessage extends LitElement {
|
||||
static override styles = css`
|
||||
.ai-chat-message {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ai-chat-content {
|
||||
display: block;
|
||||
width: calc(100% - 34px);
|
||||
padding-left: 34px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.with-attachments {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
override render() {
|
||||
const {
|
||||
host,
|
||||
textRendererOptions,
|
||||
state,
|
||||
content,
|
||||
attachments,
|
||||
messageRole,
|
||||
userInfo,
|
||||
} = this;
|
||||
const withAttachments = !!attachments && attachments.length > 0;
|
||||
|
||||
const messageClasses = classMap({
|
||||
'with-attachments': withAttachments,
|
||||
});
|
||||
|
||||
return html`
|
||||
<div class="ai-chat-message">
|
||||
${UserInfoTemplate(userInfo, messageRole)}
|
||||
<div class="ai-chat-content">
|
||||
<chat-images .attachments=${attachments}></chat-images>
|
||||
<div class=${messageClasses}>
|
||||
<text-renderer
|
||||
.host=${host}
|
||||
.answer=${content}
|
||||
.options=${textRendererOptions}
|
||||
.state=${state}
|
||||
></text-renderer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor attachments: string[] | undefined = undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor content: string = '';
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor host!: EditorHost;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor messageRole: MessageRole | undefined = undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor state: AffineAIPanelState = 'finished';
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor textRendererOptions: TextRendererOptions = {};
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor userInfo: MessageUserInfo = {};
|
||||
}
|
||||
|
||||
export class AIChatMessages extends LitElement {
|
||||
static override styles = css`
|
||||
:host {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ai-chat-messages {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
override render() {
|
||||
return html`<div class="ai-chat-messages">
|
||||
${repeat(
|
||||
this.messages,
|
||||
message => message.id,
|
||||
message => {
|
||||
const { attachments, role, content } = message;
|
||||
const userInfo = {
|
||||
userId: message.userId,
|
||||
userName: message.userName,
|
||||
avatarUrl: message.avatarUrl,
|
||||
};
|
||||
return html`
|
||||
<ai-chat-message
|
||||
.host=${this.host}
|
||||
.textRendererOptions=${this.textRendererOptions}
|
||||
.content=${content}
|
||||
.attachments=${attachments}
|
||||
.messageRole=${role}
|
||||
.userInfo=${userInfo}
|
||||
></ai-chat-message>
|
||||
`;
|
||||
}
|
||||
)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor host!: EditorHost;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor messages: ChatMessage[] = [];
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor textRendererOptions: TextRendererOptions = {};
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'ai-chat-message': AIChatMessage;
|
||||
'ai-chat-messages': AIChatMessages;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { choose } from 'lit/directives/choose.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import { ImageLoadingFailedIcon, LoadingIcon } from '../../_common/icon';
|
||||
|
||||
export class ChatImage extends LitElement {
|
||||
static override styles = css`
|
||||
.image-container {
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 70%;
|
||||
max-width: 200px;
|
||||
max-height: 122px;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
override render() {
|
||||
return choose(this.status, [
|
||||
[
|
||||
'loading',
|
||||
() =>
|
||||
html`<image-placeholder
|
||||
.text=${'Loading image'}
|
||||
.icon=${LoadingIcon}
|
||||
></image-placeholder>`,
|
||||
],
|
||||
[
|
||||
'error',
|
||||
() =>
|
||||
html`<image-placeholder
|
||||
.text=${'Image Loading Failed'}
|
||||
.icon=${ImageLoadingFailedIcon}
|
||||
></image-placeholder>`,
|
||||
],
|
||||
[
|
||||
'success',
|
||||
() =>
|
||||
html`<div class="image-container">
|
||||
<img src=${this.imageUrl} />
|
||||
</div>`,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor imageUrl!: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor status!: 'loading' | 'error' | 'success';
|
||||
}
|
||||
|
||||
export class ChatImages extends LitElement {
|
||||
static override styles = css`
|
||||
.images-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
`;
|
||||
|
||||
override render() {
|
||||
if (!this.attachments || this.attachments.length === 0) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`<div class="images-container">
|
||||
${repeat(
|
||||
this.attachments,
|
||||
attachment => attachment,
|
||||
attachment =>
|
||||
html`<chat-image
|
||||
.imageUrl=${attachment}
|
||||
.status=${'success'}
|
||||
></chat-image>`
|
||||
)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor attachments: string[] | undefined;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'chat-image': ChatImage;
|
||||
'chat-images': ChatImages;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { css, html, LitElement, type TemplateResult, unsafeCSS } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
export class ImagePlaceholder extends LitElement {
|
||||
static override styles = css`
|
||||
.placeholder-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 122px;
|
||||
padding: 12px;
|
||||
align-items: flex-start;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--affine-background-tertiary-color);
|
||||
background: var(--affine-background-secondary-color);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
color: var(--affine-placeholder-color, #c0bfc1);
|
||||
text-align: justify;
|
||||
/* light/smBold */
|
||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
||||
font-size: var(--affine-font-sm);
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 22px; /* 157.143% */
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--affine-icon-color);
|
||||
}
|
||||
`;
|
||||
|
||||
override render() {
|
||||
return html`<div class="placeholder-container">
|
||||
<div class="placeholder-title">
|
||||
<span class="placeholder-icon">${this.icon}</span>
|
||||
<span>${this.text}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor icon!: TemplateResult<1>;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor text!: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'image-placeholder': ImagePlaceholder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import type { MessageRole, MessageUserInfo } from '@toeverything/infra';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { css, html, LitElement, type TemplateResult, unsafeCSS } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import { AffineAIIcon } from '../../_common/icon';
|
||||
|
||||
export class UserInfo extends LitElement {
|
||||
static override styles = css`
|
||||
.user-info-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
font-weight: 500;
|
||||
|
||||
.user-avatar-container {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: var(--affine-brand-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.default-avatar,
|
||||
.user-avatar-container img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-avatar-container img {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.default-avatar,
|
||||
.avatar-image {
|
||||
background-color: var(--affine-primary-color);
|
||||
}
|
||||
|
||||
.user-name {
|
||||
color: var(--affine-text-primary-color);
|
||||
text-align: justify;
|
||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
||||
font-size: var(--affine-font-sm);
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
private _handleAvatarLoadError(e: Event) {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.onerror = null;
|
||||
this.avatarLoadedFailed = true;
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`<div class="user-info-container">
|
||||
<div class="user-avatar-container">
|
||||
${this.avatarIcon
|
||||
? this.avatarIcon
|
||||
: this.avatarUrl && !this.avatarLoadedFailed
|
||||
? html`<img
|
||||
.src=${this.avatarUrl}
|
||||
@error=${this._handleAvatarLoadError}
|
||||
/>`
|
||||
: html`<span class="default-avatar"></span>`}
|
||||
</div>
|
||||
<span class="user-name">${this.userName}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor avatarIcon: TemplateResult<1> | undefined = undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor avatarLoadedFailed = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor avatarUrl: string | undefined = undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor userName!: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'user-info': UserInfo;
|
||||
}
|
||||
}
|
||||
|
||||
export function UserInfoTemplate(
|
||||
userInfo: MessageUserInfo,
|
||||
messageRole?: MessageRole
|
||||
) {
|
||||
const isUser = !!messageRole && messageRole === 'user';
|
||||
|
||||
const userInfoTemplate = isUser
|
||||
? html`<user-info
|
||||
.userName=${userInfo.userName ?? 'You'}
|
||||
.avatarUrl=${userInfo.avatarUrl}
|
||||
></user-info>`
|
||||
: html`<user-info
|
||||
.userName=${'AFFiNE AI'}
|
||||
.avatarIcon=${AffineAIIcon}
|
||||
></user-info>`;
|
||||
|
||||
return userInfoTemplate;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './ai-chat-block.js';
|
||||
export * from './ai-chat-edgeless-block.js';
|
||||
export * from './ai-chat-spec.js';
|
||||
@@ -0,0 +1,54 @@
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { css, unsafeCSS } from 'lit';
|
||||
|
||||
export const AIChatBlockStyles = css`
|
||||
.affine-ai-chat-block-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
background: var(--affine-white);
|
||||
color: var(--affine-text-primary-color);
|
||||
line-height: 22px;
|
||||
font-size: var(--affine-font-sm);
|
||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
||||
border-radius: 8px;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
|
||||
.ai-chat-messages-container {
|
||||
display: block;
|
||||
flex: 1 0 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(to top, transparent, var(--affine-white));
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to bottom,
|
||||
var(--affine-white) 25%,
|
||||
transparent
|
||||
);
|
||||
mask-image: linear-gradient(
|
||||
to bottom,
|
||||
var(--affine-white) 25%,
|
||||
transparent
|
||||
);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ai-chat-block-button {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
svg {
|
||||
color: var(--affine-icon-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { AIChatMessages } from './ai-chat-block/components/ai-chat-messages.js';
|
||||
export * from './ai-chat-block/index.js';
|
||||
74
packages/frontend/core/src/blocksuite/presets/effects.ts
Normal file
74
packages/frontend/core/src/blocksuite/presets/effects.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { TextRenderer } from './_common/components/text-renderer';
|
||||
import { AskAIButton } from './ai/_common/components/ask-ai-button';
|
||||
import { AskAIPanel } from './ai/_common/components/ask-ai-panel';
|
||||
import { ChatActionList } from './ai/_common/components/chat-action-list';
|
||||
import { ChatCopyMore } from './ai/_common/components/copy-more';
|
||||
import { ChatPanel } from './ai/chat-panel';
|
||||
import { ActionWrapper } from './ai/chat-panel/actions/action-wrapper';
|
||||
import { ChatText } from './ai/chat-panel/actions/chat-text';
|
||||
import { ActionImage } from './ai/chat-panel/actions/image';
|
||||
import { ActionImageToText } from './ai/chat-panel/actions/image-to-text';
|
||||
import { ActionMakeReal } from './ai/chat-panel/actions/make-real';
|
||||
import { ActionMindmap } from './ai/chat-panel/actions/mindmap';
|
||||
import { ActionSlides } from './ai/chat-panel/actions/slides';
|
||||
import { ActionText } from './ai/chat-panel/actions/text';
|
||||
import { AILoading } from './ai/chat-panel/ai-loading';
|
||||
import { ChatCards } from './ai/chat-panel/chat-cards';
|
||||
import { ChatPanelInput } from './ai/chat-panel/chat-panel-input';
|
||||
import { ChatPanelMessages } from './ai/chat-panel/chat-panel-messages';
|
||||
import { AIErrorWrapper } from './ai/messages/error';
|
||||
import { AISlidesRenderer } from './ai/messages/slides-renderer';
|
||||
import { AIAnswerWrapper } from './ai/messages/wrapper';
|
||||
import { ChatBlockInput } from './ai/peek-view/chat-block-input';
|
||||
import { AIChatBlockPeekView } from './ai/peek-view/chat-block-peek-view';
|
||||
import { DateTime } from './ai/peek-view/date-time';
|
||||
import { AIChatBlockComponent } from './blocks/ai-chat-block/ai-chat-block';
|
||||
import { EdgelessAIChatBlockComponent } from './blocks/ai-chat-block/ai-chat-edgeless-block';
|
||||
import {
|
||||
AIChatMessage,
|
||||
AIChatMessages,
|
||||
} from './blocks/ai-chat-block/components/ai-chat-messages';
|
||||
import {
|
||||
ChatImage,
|
||||
ChatImages,
|
||||
} from './blocks/ai-chat-block/components/chat-images';
|
||||
import { ImagePlaceholder } from './blocks/ai-chat-block/components/image-placeholder';
|
||||
import { UserInfo } from './blocks/ai-chat-block/components/user-info';
|
||||
|
||||
export function registerBlocksuitePresetsCustomComponents() {
|
||||
customElements.define('ask-ai-button', AskAIButton);
|
||||
customElements.define('ask-ai-panel', AskAIPanel);
|
||||
customElements.define('chat-action-list', ChatActionList);
|
||||
customElements.define('chat-copy-more', ChatCopyMore);
|
||||
customElements.define('action-wrapper', ActionWrapper);
|
||||
customElements.define('chat-text', ChatText);
|
||||
customElements.define('action-image-to-text', ActionImageToText);
|
||||
customElements.define('action-image', ActionImage);
|
||||
customElements.define('action-make-real', ActionMakeReal);
|
||||
customElements.define('action-mindmap', ActionMindmap);
|
||||
customElements.define('action-slides', ActionSlides);
|
||||
customElements.define('action-text', ActionText);
|
||||
customElements.define('ai-loading', AILoading);
|
||||
customElements.define('chat-cards', ChatCards);
|
||||
customElements.define('chat-panel-input', ChatPanelInput);
|
||||
customElements.define('chat-panel-messages', ChatPanelMessages);
|
||||
customElements.define('chat-panel', ChatPanel);
|
||||
customElements.define('ai-error-wrapper', AIErrorWrapper);
|
||||
customElements.define('ai-slides-renderer', AISlidesRenderer);
|
||||
customElements.define('ai-answer-wrapper', AIAnswerWrapper);
|
||||
customElements.define('chat-block-input', ChatBlockInput);
|
||||
customElements.define('ai-chat-block-peek-view', AIChatBlockPeekView);
|
||||
customElements.define('date-time', DateTime);
|
||||
customElements.define(
|
||||
'affine-edgeless-ai-chat',
|
||||
EdgelessAIChatBlockComponent
|
||||
);
|
||||
customElements.define('affine-ai-chat', AIChatBlockComponent);
|
||||
customElements.define('ai-chat-message', AIChatMessage);
|
||||
customElements.define('ai-chat-messages', AIChatMessages);
|
||||
customElements.define('image-placeholder', ImagePlaceholder);
|
||||
customElements.define('chat-image', ChatImage);
|
||||
customElements.define('chat-images', ChatImages);
|
||||
customElements.define('user-info', UserInfo);
|
||||
customElements.define('text-renderer', TextRenderer);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { registerAICustomComponents } from '@affine/core/blocksuite/presets/ai';
|
||||
import { registerBlocksuitePresetsCustomComponents } from '@affine/core/blocksuite/presets/effects';
|
||||
import { effects as bsEffects } from '@blocksuite/affine/effects';
|
||||
|
||||
import { setupAIProvider } from './ai/setup-provider';
|
||||
@@ -9,6 +9,6 @@ bsEffects();
|
||||
patchEffects();
|
||||
setupAIProvider();
|
||||
edgelessEffects();
|
||||
registerAICustomComponents();
|
||||
registerBlocksuitePresetsCustomComponents();
|
||||
|
||||
export * from './blocksuite-editor';
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
AIImageBlockSpec,
|
||||
AIParagraphBlockSpec,
|
||||
} from '@affine/core/blocksuite/presets/ai';
|
||||
import { AIChatBlockSpec } from '@affine/core/blocksuite/presets/blocks/ai-chat-block';
|
||||
import type { ExtensionType } from '@blocksuite/affine/block-std';
|
||||
import {
|
||||
BookmarkBlockSpec,
|
||||
@@ -25,7 +26,6 @@ import {
|
||||
RefNodeSlotsExtension,
|
||||
RichTextExtensions,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import { AIChatBlockSpec } from '@blocksuite/affine/presets';
|
||||
|
||||
import { CustomAttachmentBlockSpec } from './custom/attachment-block';
|
||||
|
||||
|
||||
@@ -11,24 +11,16 @@ import type {
|
||||
DatabaseBlockModel,
|
||||
MenuOptions,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import { menu } from '@blocksuite/affine-components/context-menu';
|
||||
import { LinkIcon } from '@blocksuite/icons/lit';
|
||||
import type { FrameworkProvider } from '@toeverything/infra';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
export function createDatabaseOptionsConfig(framework: FrameworkProvider) {
|
||||
return {
|
||||
configure: (model: DatabaseBlockModel, options: MenuOptions) => {
|
||||
const items = options.items;
|
||||
|
||||
const copyIndex = items.findIndex(
|
||||
item => item.type === 'action' && item.name === 'Copy'
|
||||
);
|
||||
|
||||
items.splice(
|
||||
copyIndex + 1,
|
||||
0,
|
||||
createCopyLinkToBlockMenuItem(framework, model)
|
||||
);
|
||||
items.splice(2, 0, createCopyLinkToBlockMenuItem(framework, model));
|
||||
|
||||
return options;
|
||||
},
|
||||
@@ -38,17 +30,10 @@ export function createDatabaseOptionsConfig(framework: FrameworkProvider) {
|
||||
function createCopyLinkToBlockMenuItem(
|
||||
framework: FrameworkProvider,
|
||||
model: DatabaseBlockModel
|
||||
): {
|
||||
type: 'action';
|
||||
name: string;
|
||||
icon?: TemplateResult<1>;
|
||||
hide?: () => boolean;
|
||||
select: () => void;
|
||||
} {
|
||||
return {
|
||||
type: 'action',
|
||||
) {
|
||||
return menu.action({
|
||||
name: 'Copy link to block',
|
||||
icon: LinkIcon({ width: '20', height: '20' }),
|
||||
prefix: LinkIcon({ width: '20', height: '20' }),
|
||||
hide: () => {
|
||||
const { editor } = framework.get(EditorService);
|
||||
const mode = editor.mode$.value;
|
||||
@@ -91,5 +76,5 @@ function createCopyLinkToBlockMenuItem(
|
||||
})
|
||||
.catch(console.error);
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -50,9 +50,9 @@ import {
|
||||
QuickSearchExtension,
|
||||
ReferenceNodeConfigExtension,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import { AIChatBlockSchema } from '@blocksuite/affine/presets';
|
||||
import { type BlockSnapshot, Text } from '@blocksuite/affine/store';
|
||||
import {
|
||||
AIChatBlockSchema,
|
||||
type DocProps,
|
||||
type DocService,
|
||||
DocsService,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AIChatBlockSpec } from '@affine/core/blocksuite/presets/blocks/ai-chat-block';
|
||||
import type { ExtensionType } from '@blocksuite/affine/block-std';
|
||||
import { SpecProvider } from '@blocksuite/affine/blocks';
|
||||
import { AIChatBlockSpec } from '@blocksuite/affine/presets';
|
||||
|
||||
import { getFontConfigExtension } from './font-extension';
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine/blocks';
|
||||
import type { DeltaInsert } from '@blocksuite/affine/inline';
|
||||
import type { DocCollection } from '@blocksuite/affine/store';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
@@ -8,7 +10,7 @@ export function useReferenceLinkHelper(docCollection: DocCollection) {
|
||||
if (!page) {
|
||||
return;
|
||||
}
|
||||
const text = page.Text.fromDelta([
|
||||
const text = new page.Text([
|
||||
{
|
||||
insert: ' ',
|
||||
attributes: {
|
||||
@@ -18,7 +20,7 @@ export function useReferenceLinkHelper(docCollection: DocCollection) {
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
] as DeltaInsert<AffineTextAttributes>[]);
|
||||
const [frame] = page.getBlockByFlavour('affine:note');
|
||||
|
||||
frame && page.addBlock('affine:paragraph', { text }, frame.id);
|
||||
|
||||
@@ -8,8 +8,8 @@ import type {
|
||||
SurfaceRefBlockModel,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import { AffineReference } from '@blocksuite/affine/blocks';
|
||||
import type { AIChatBlockModel } from '@blocksuite/affine/presets';
|
||||
import type { BlockModel } from '@blocksuite/affine/store';
|
||||
import type { AIChatBlockModel } from '@toeverything/infra';
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
import type { TemplateResult } from 'lit';
|
||||
import { firstValueFrom, map, race } from 'rxjs';
|
||||
|
||||
Reference in New Issue
Block a user