mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-31 17:19:56 +08:00
refactor(infra): refactor copilot client (#8813)
This commit is contained in:
@@ -142,10 +142,6 @@ export class AIProvider {
|
|||||||
...options: Parameters<BlockSuitePresets.AIActions[T]>
|
...options: Parameters<BlockSuitePresets.AIActions[T]>
|
||||||
) => ReturnType<BlockSuitePresets.AIActions[T]>
|
) => ReturnType<BlockSuitePresets.AIActions[T]>
|
||||||
): void {
|
): void {
|
||||||
if (this.actions[id]) {
|
|
||||||
console.warn(`AI action ${id} is already provided`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-expect-error TODO: maybe fix this
|
// @ts-expect-error TODO: maybe fix this
|
||||||
this.actions[id] = (
|
this.actions[id] = (
|
||||||
...args: Parameters<BlockSuitePresets.AIActions[T]>
|
...args: Parameters<BlockSuitePresets.AIActions[T]>
|
||||||
|
|||||||
+38
-56
@@ -7,10 +7,10 @@ import {
|
|||||||
getCopilotHistoriesQuery,
|
getCopilotHistoriesQuery,
|
||||||
getCopilotHistoryIdsQuery,
|
getCopilotHistoryIdsQuery,
|
||||||
getCopilotSessionsQuery,
|
getCopilotSessionsQuery,
|
||||||
gqlFetcherFactory,
|
|
||||||
GraphQLError,
|
GraphQLError,
|
||||||
type GraphQLQuery,
|
type GraphQLQuery,
|
||||||
type QueryOptions,
|
type QueryOptions,
|
||||||
|
type QueryResponse,
|
||||||
type RequestOptions,
|
type RequestOptions,
|
||||||
UserFriendlyError,
|
UserFriendlyError,
|
||||||
} from '@affine/graphql';
|
} from '@affine/graphql';
|
||||||
@@ -21,26 +21,6 @@ import {
|
|||||||
} from '@blocksuite/affine/blocks';
|
} from '@blocksuite/affine/blocks';
|
||||||
import { getCurrentStore } from '@toeverything/infra';
|
import { getCurrentStore } from '@toeverything/infra';
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated will be removed soon
|
|
||||||
*/
|
|
||||||
export function getBaseUrl(): string {
|
|
||||||
if (BUILD_CONFIG.isElectron || BUILD_CONFIG.isIOS || BUILD_CONFIG.isAndroid) {
|
|
||||||
return BUILD_CONFIG.serverUrlPrefix;
|
|
||||||
}
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
// is nodejs
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const { protocol, hostname, port } = window.location;
|
|
||||||
return `${protocol}//${hostname}${port ? `:${port}` : ''}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated will be removed soon
|
|
||||||
*/
|
|
||||||
const defaultFetcher = gqlFetcherFactory(getBaseUrl() + '/graphql');
|
|
||||||
|
|
||||||
type OptionsField<T extends GraphQLQuery> =
|
type OptionsField<T extends GraphQLQuery> =
|
||||||
RequestOptions<T>['variables'] extends { options: infer U } ? U : never;
|
RequestOptions<T>['variables'] extends { options: infer U } ? U : never;
|
||||||
|
|
||||||
@@ -76,23 +56,22 @@ export function handleError(src: any) {
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetcher = async <Query extends GraphQLQuery>(
|
|
||||||
options: QueryOptions<Query>
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
return await defaultFetcher<Query>(options);
|
|
||||||
} catch (err) {
|
|
||||||
throw handleError(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export class CopilotClient {
|
export class CopilotClient {
|
||||||
readonly backendUrl = getBaseUrl();
|
constructor(
|
||||||
|
readonly gql: <Query extends GraphQLQuery>(
|
||||||
|
options: QueryOptions<Query>
|
||||||
|
) => Promise<QueryResponse<Query>>,
|
||||||
|
readonly fetcher: (input: string, init?: RequestInit) => Promise<Response>,
|
||||||
|
readonly eventSource: (
|
||||||
|
url: string,
|
||||||
|
eventSourceInitDict?: EventSourceInit
|
||||||
|
) => EventSource
|
||||||
|
) {}
|
||||||
|
|
||||||
async createSession(
|
async createSession(
|
||||||
options: OptionsField<typeof createCopilotSessionMutation>
|
options: OptionsField<typeof createCopilotSessionMutation>
|
||||||
) {
|
) {
|
||||||
const res = await fetcher({
|
const res = await this.gql({
|
||||||
query: createCopilotSessionMutation,
|
query: createCopilotSessionMutation,
|
||||||
variables: {
|
variables: {
|
||||||
options,
|
options,
|
||||||
@@ -102,7 +81,7 @@ export class CopilotClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async forkSession(options: OptionsField<typeof forkCopilotSessionMutation>) {
|
async forkSession(options: OptionsField<typeof forkCopilotSessionMutation>) {
|
||||||
const res = await fetcher({
|
const res = await this.gql({
|
||||||
query: forkCopilotSessionMutation,
|
query: forkCopilotSessionMutation,
|
||||||
variables: {
|
variables: {
|
||||||
options,
|
options,
|
||||||
@@ -114,7 +93,7 @@ export class CopilotClient {
|
|||||||
async createMessage(
|
async createMessage(
|
||||||
options: OptionsField<typeof createCopilotMessageMutation>
|
options: OptionsField<typeof createCopilotMessageMutation>
|
||||||
) {
|
) {
|
||||||
const res = await fetcher({
|
const res = await this.gql({
|
||||||
query: createCopilotMessageMutation,
|
query: createCopilotMessageMutation,
|
||||||
variables: {
|
variables: {
|
||||||
options,
|
options,
|
||||||
@@ -124,7 +103,7 @@ export class CopilotClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getSessions(workspaceId: string) {
|
async getSessions(workspaceId: string) {
|
||||||
const res = await fetcher({
|
const res = await this.gql({
|
||||||
query: getCopilotSessionsQuery,
|
query: getCopilotSessionsQuery,
|
||||||
variables: {
|
variables: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -140,7 +119,7 @@ export class CopilotClient {
|
|||||||
typeof getCopilotHistoriesQuery
|
typeof getCopilotHistoriesQuery
|
||||||
>['variables']['options']
|
>['variables']['options']
|
||||||
) {
|
) {
|
||||||
const res = await fetcher({
|
const res = await this.gql({
|
||||||
query: getCopilotHistoriesQuery,
|
query: getCopilotHistoriesQuery,
|
||||||
variables: {
|
variables: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -159,7 +138,7 @@ export class CopilotClient {
|
|||||||
typeof getCopilotHistoriesQuery
|
typeof getCopilotHistoriesQuery
|
||||||
>['variables']['options']
|
>['variables']['options']
|
||||||
) {
|
) {
|
||||||
const res = await fetcher({
|
const res = await this.gql({
|
||||||
query: getCopilotHistoryIdsQuery,
|
query: getCopilotHistoryIdsQuery,
|
||||||
variables: {
|
variables: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -176,7 +155,7 @@ export class CopilotClient {
|
|||||||
docId: string;
|
docId: string;
|
||||||
sessionIds: string[];
|
sessionIds: string[];
|
||||||
}) {
|
}) {
|
||||||
const res = await fetcher({
|
const res = await this.gql({
|
||||||
query: cleanupCopilotSessionMutation,
|
query: cleanupCopilotSessionMutation,
|
||||||
variables: {
|
variables: {
|
||||||
input,
|
input,
|
||||||
@@ -194,11 +173,11 @@ export class CopilotClient {
|
|||||||
messageId?: string;
|
messageId?: string;
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
}) {
|
}) {
|
||||||
const url = new URL(`${this.backendUrl}/api/copilot/chat/${sessionId}`);
|
let url = `/api/copilot/chat/${sessionId}`;
|
||||||
if (messageId) {
|
if (messageId) {
|
||||||
url.searchParams.set('messageId', messageId);
|
url += `?messageId=${encodeURIComponent(messageId)}`;
|
||||||
}
|
}
|
||||||
const response = await fetch(url.toString(), { signal });
|
const response = await this.fetcher(url.toString(), { signal });
|
||||||
return response.text();
|
return response.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,11 +192,11 @@ export class CopilotClient {
|
|||||||
},
|
},
|
||||||
endpoint = 'stream'
|
endpoint = 'stream'
|
||||||
) {
|
) {
|
||||||
const url = new URL(
|
let url = `/api/copilot/chat/${sessionId}/${endpoint}`;
|
||||||
`${this.backendUrl}/api/copilot/chat/${sessionId}/${endpoint}`
|
if (messageId) {
|
||||||
);
|
url += `?messageId=${encodeURIComponent(messageId)}`;
|
||||||
if (messageId) url.searchParams.set('messageId', messageId);
|
}
|
||||||
return new EventSource(url.toString());
|
return this.eventSource(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text or image to images
|
// Text or image to images
|
||||||
@@ -227,15 +206,18 @@ export class CopilotClient {
|
|||||||
seed?: string,
|
seed?: string,
|
||||||
endpoint = 'images'
|
endpoint = 'images'
|
||||||
) {
|
) {
|
||||||
const url = new URL(
|
let url = `/api/copilot/chat/${sessionId}/${endpoint}`;
|
||||||
`${this.backendUrl}/api/copilot/chat/${sessionId}/${endpoint}`
|
|
||||||
);
|
if (messageId || seed) {
|
||||||
if (messageId) {
|
url += '?';
|
||||||
url.searchParams.set('messageId', messageId);
|
url += new URLSearchParams(
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries({ messageId, seed }).filter(
|
||||||
|
([_, v]) => v !== undefined
|
||||||
|
)
|
||||||
|
) as Record<string, string>
|
||||||
|
).toString();
|
||||||
}
|
}
|
||||||
if (seed) {
|
return this.eventSource(url);
|
||||||
url.searchParams.set('seed', seed);
|
|
||||||
}
|
|
||||||
return new EventSource(url);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,14 @@ import type { ForkChatSessionInput } from '@affine/graphql';
|
|||||||
import { assertExists } from '@blocksuite/affine/global/utils';
|
import { assertExists } from '@blocksuite/affine/global/utils';
|
||||||
import { partition } from 'lodash-es';
|
import { partition } from 'lodash-es';
|
||||||
|
|
||||||
import { CopilotClient } from './copilot-client';
|
import type { CopilotClient } from './copilot-client';
|
||||||
import { delay, toTextStream } from './event-source';
|
import { delay, toTextStream } from './event-source';
|
||||||
import type { PromptKey } from './prompt';
|
import type { PromptKey } from './prompt';
|
||||||
|
|
||||||
const TIMEOUT = 50000;
|
const TIMEOUT = 50000;
|
||||||
|
|
||||||
const client = new CopilotClient();
|
|
||||||
|
|
||||||
export type TextToTextOptions = {
|
export type TextToTextOptions = {
|
||||||
|
client: CopilotClient;
|
||||||
docId: string;
|
docId: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
promptName?: PromptKey;
|
promptName?: PromptKey;
|
||||||
@@ -33,9 +32,11 @@ export type ToImageOptions = TextToTextOptions & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function createChatSession({
|
export function createChatSession({
|
||||||
|
client,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
docId,
|
docId,
|
||||||
}: {
|
}: {
|
||||||
|
client: CopilotClient;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
docId: string;
|
docId: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -46,7 +47,10 @@ export function createChatSession({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function forkCopilotSession(forkChatSessionInput: ForkChatSessionInput) {
|
export function forkCopilotSession(
|
||||||
|
client: CopilotClient,
|
||||||
|
forkChatSessionInput: ForkChatSessionInput
|
||||||
|
) {
|
||||||
return client.forkSession(forkChatSessionInput);
|
return client.forkSession(forkChatSessionInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +87,7 @@ async function resizeImage(blob: Blob | File): Promise<Blob | null> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createSessionMessage({
|
async function createSessionMessage({
|
||||||
|
client,
|
||||||
docId,
|
docId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
promptName,
|
promptName,
|
||||||
@@ -140,6 +145,7 @@ async function createSessionMessage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function textToText({
|
export function textToText({
|
||||||
|
client,
|
||||||
docId,
|
docId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
promptName,
|
promptName,
|
||||||
@@ -169,6 +175,7 @@ export function textToText({
|
|||||||
_messageId = undefined;
|
_messageId = undefined;
|
||||||
} else {
|
} else {
|
||||||
const message = await createSessionMessage({
|
const message = await createSessionMessage({
|
||||||
|
client,
|
||||||
docId,
|
docId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
promptName,
|
promptName,
|
||||||
@@ -242,6 +249,7 @@ export function textToText({
|
|||||||
_messageId = undefined;
|
_messageId = undefined;
|
||||||
} else {
|
} else {
|
||||||
const message = await createSessionMessage({
|
const message = await createSessionMessage({
|
||||||
|
client,
|
||||||
docId,
|
docId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
promptName,
|
promptName,
|
||||||
@@ -268,10 +276,6 @@ export function textToText({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const listHistories = client.getHistories;
|
|
||||||
|
|
||||||
export const listHistoryIds = client.getHistoryIds;
|
|
||||||
|
|
||||||
// Only one image is currently being processed
|
// Only one image is currently being processed
|
||||||
export function toImage({
|
export function toImage({
|
||||||
docId,
|
docId,
|
||||||
@@ -286,6 +290,7 @@ export function toImage({
|
|||||||
timeout = TIMEOUT,
|
timeout = TIMEOUT,
|
||||||
retry = false,
|
retry = false,
|
||||||
workflow = false,
|
workflow = false,
|
||||||
|
client,
|
||||||
}: ToImageOptions) {
|
}: ToImageOptions) {
|
||||||
let _sessionId: string;
|
let _sessionId: string;
|
||||||
let _messageId: string | undefined;
|
let _messageId: string | undefined;
|
||||||
@@ -305,6 +310,7 @@ export function toImage({
|
|||||||
content,
|
content,
|
||||||
attachments,
|
attachments,
|
||||||
params,
|
params,
|
||||||
|
client,
|
||||||
});
|
});
|
||||||
_sessionId = sessionId;
|
_sessionId = sessionId;
|
||||||
_messageId = messageId;
|
_messageId = messageId;
|
||||||
@@ -334,10 +340,12 @@ export function cleanupSessions({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
docId,
|
docId,
|
||||||
sessionIds,
|
sessionIds,
|
||||||
|
client,
|
||||||
}: {
|
}: {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
docId: string;
|
docId: string;
|
||||||
sessionIds: string[];
|
sessionIds: string[];
|
||||||
|
client: CopilotClient;
|
||||||
}) {
|
}) {
|
||||||
return client.cleanupSessions({ workspaceId, docId, sessionIds });
|
return client.cleanupSessions({ workspaceId, docId, sessionIds });
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-15
@@ -10,13 +10,12 @@ import { assertExists } from '@blocksuite/affine/global/utils';
|
|||||||
import { getCurrentStore } from '@toeverything/infra';
|
import { getCurrentStore } from '@toeverything/infra';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { getBaseUrl } from './copilot-client';
|
import type { CopilotClient } from './copilot-client';
|
||||||
import type { PromptKey } from './prompt';
|
import type { PromptKey } from './prompt';
|
||||||
import {
|
import {
|
||||||
cleanupSessions,
|
cleanupSessions,
|
||||||
createChatSession,
|
createChatSession,
|
||||||
forkCopilotSession,
|
forkCopilotSession,
|
||||||
listHistories,
|
|
||||||
textToText,
|
textToText,
|
||||||
toImage,
|
toImage,
|
||||||
} from './request';
|
} from './request';
|
||||||
@@ -39,11 +38,11 @@ const processTypeToPromptName = new Map(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export function setupAIProvider() {
|
// a single workspace should have only a single chat session
|
||||||
// a single workspace should have only a single chat session
|
// user-id:workspace-id:doc-id -> chat session id
|
||||||
// user-id:workspace-id:doc-id -> chat session id
|
const chatSessions = new Map<string, Promise<string>>();
|
||||||
const chatSessions = new Map<string, Promise<string>>();
|
|
||||||
|
|
||||||
|
export function setupAIProvider(client: CopilotClient) {
|
||||||
async function getChatSessionId(workspaceId: string, docId: string) {
|
async function getChatSessionId(workspaceId: string, docId: string) {
|
||||||
const userId = (await AIProvider.userInfo)?.id;
|
const userId = (await AIProvider.userInfo)?.id;
|
||||||
|
|
||||||
@@ -56,6 +55,7 @@ export function setupAIProvider() {
|
|||||||
chatSessions.set(
|
chatSessions.set(
|
||||||
storeKey,
|
storeKey,
|
||||||
createChatSession({
|
createChatSession({
|
||||||
|
client,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
docId,
|
docId,
|
||||||
})
|
})
|
||||||
@@ -78,6 +78,7 @@ export function setupAIProvider() {
|
|||||||
options.sessionId ?? getChatSessionId(options.workspaceId, options.docId);
|
options.sessionId ?? getChatSessionId(options.workspaceId, options.docId);
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
sessionId,
|
sessionId,
|
||||||
});
|
});
|
||||||
@@ -86,6 +87,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('summary', options => {
|
AIProvider.provide('summary', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Summary',
|
promptName: 'Summary',
|
||||||
});
|
});
|
||||||
@@ -94,6 +96,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('translate', options => {
|
AIProvider.provide('translate', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
promptName: 'Translate to',
|
promptName: 'Translate to',
|
||||||
content: options.input,
|
content: options.input,
|
||||||
params: {
|
params: {
|
||||||
@@ -105,6 +108,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('changeTone', options => {
|
AIProvider.provide('changeTone', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
params: {
|
params: {
|
||||||
tone: options.tone.toLowerCase(),
|
tone: options.tone.toLowerCase(),
|
||||||
},
|
},
|
||||||
@@ -116,6 +120,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('improveWriting', options => {
|
AIProvider.provide('improveWriting', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Improve writing for it',
|
promptName: 'Improve writing for it',
|
||||||
});
|
});
|
||||||
@@ -124,6 +129,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('improveGrammar', options => {
|
AIProvider.provide('improveGrammar', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Improve grammar for it',
|
promptName: 'Improve grammar for it',
|
||||||
});
|
});
|
||||||
@@ -132,6 +138,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('fixSpelling', options => {
|
AIProvider.provide('fixSpelling', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Fix spelling for it',
|
promptName: 'Fix spelling for it',
|
||||||
});
|
});
|
||||||
@@ -140,6 +147,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('createHeadings', options => {
|
AIProvider.provide('createHeadings', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Create headings',
|
promptName: 'Create headings',
|
||||||
});
|
});
|
||||||
@@ -148,6 +156,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('makeLonger', options => {
|
AIProvider.provide('makeLonger', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Make it longer',
|
promptName: 'Make it longer',
|
||||||
});
|
});
|
||||||
@@ -156,6 +165,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('makeShorter', options => {
|
AIProvider.provide('makeShorter', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Make it shorter',
|
promptName: 'Make it shorter',
|
||||||
});
|
});
|
||||||
@@ -164,6 +174,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('checkCodeErrors', options => {
|
AIProvider.provide('checkCodeErrors', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Check code error',
|
promptName: 'Check code error',
|
||||||
});
|
});
|
||||||
@@ -172,6 +183,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('explainCode', options => {
|
AIProvider.provide('explainCode', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Explain this code',
|
promptName: 'Explain this code',
|
||||||
});
|
});
|
||||||
@@ -180,6 +192,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('writeArticle', options => {
|
AIProvider.provide('writeArticle', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Write an article about this',
|
promptName: 'Write an article about this',
|
||||||
});
|
});
|
||||||
@@ -188,6 +201,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('writeTwitterPost', options => {
|
AIProvider.provide('writeTwitterPost', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Write a twitter about this',
|
promptName: 'Write a twitter about this',
|
||||||
});
|
});
|
||||||
@@ -196,6 +210,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('writePoem', options => {
|
AIProvider.provide('writePoem', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Write a poem about this',
|
promptName: 'Write a poem about this',
|
||||||
});
|
});
|
||||||
@@ -204,6 +219,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('writeOutline', options => {
|
AIProvider.provide('writeOutline', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Write outline',
|
promptName: 'Write outline',
|
||||||
});
|
});
|
||||||
@@ -212,6 +228,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('writeBlogPost', options => {
|
AIProvider.provide('writeBlogPost', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Write a blog post about this',
|
promptName: 'Write a blog post about this',
|
||||||
});
|
});
|
||||||
@@ -220,6 +237,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('brainstorm', options => {
|
AIProvider.provide('brainstorm', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Brainstorm ideas about this',
|
promptName: 'Brainstorm ideas about this',
|
||||||
});
|
});
|
||||||
@@ -228,6 +246,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('findActions', options => {
|
AIProvider.provide('findActions', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Find action items from it',
|
promptName: 'Find action items from it',
|
||||||
});
|
});
|
||||||
@@ -236,6 +255,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('brainstormMindmap', options => {
|
AIProvider.provide('brainstormMindmap', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'workflow:brainstorm',
|
promptName: 'workflow:brainstorm',
|
||||||
workflow: true,
|
workflow: true,
|
||||||
@@ -246,6 +266,7 @@ export function setupAIProvider() {
|
|||||||
assertExists(options.input, 'expandMindmap action requires input');
|
assertExists(options.input, 'expandMindmap action requires input');
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
params: {
|
params: {
|
||||||
mindmap: options.mindmap,
|
mindmap: options.mindmap,
|
||||||
node: options.input,
|
node: options.input,
|
||||||
@@ -258,6 +279,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('explain', options => {
|
AIProvider.provide('explain', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Explain this',
|
promptName: 'Explain this',
|
||||||
});
|
});
|
||||||
@@ -266,6 +288,7 @@ export function setupAIProvider() {
|
|||||||
AIProvider.provide('explainImage', options => {
|
AIProvider.provide('explainImage', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Explain this image',
|
promptName: 'Explain this image',
|
||||||
});
|
});
|
||||||
@@ -288,6 +311,7 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
|
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content,
|
content,
|
||||||
promptName,
|
promptName,
|
||||||
});
|
});
|
||||||
@@ -332,6 +356,7 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
};
|
};
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'workflow:presentation',
|
promptName: 'workflow:presentation',
|
||||||
workflow: true,
|
workflow: true,
|
||||||
@@ -348,6 +373,7 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
}
|
}
|
||||||
return toImage({
|
return toImage({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
promptName,
|
promptName,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -357,6 +383,7 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
const promptName = filterStyleToPromptName.get(options.style as string);
|
const promptName = filterStyleToPromptName.get(options.style as string);
|
||||||
return toImage({
|
return toImage({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
timeout: 120000,
|
timeout: 120000,
|
||||||
promptName: promptName as PromptKey,
|
promptName: promptName as PromptKey,
|
||||||
workflow: !!promptName?.startsWith('workflow:'),
|
workflow: !!promptName?.startsWith('workflow:'),
|
||||||
@@ -370,6 +397,7 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
) as PromptKey;
|
) as PromptKey;
|
||||||
return toImage({
|
return toImage({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
timeout: 120000,
|
timeout: 120000,
|
||||||
promptName,
|
promptName,
|
||||||
});
|
});
|
||||||
@@ -378,6 +406,7 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
AIProvider.provide('generateCaption', options => {
|
AIProvider.provide('generateCaption', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Generate a caption',
|
promptName: 'Generate a caption',
|
||||||
});
|
});
|
||||||
@@ -386,6 +415,7 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
AIProvider.provide('continueWriting', options => {
|
AIProvider.provide('continueWriting', options => {
|
||||||
return textToText({
|
return textToText({
|
||||||
...options,
|
...options,
|
||||||
|
client,
|
||||||
content: options.input,
|
content: options.input,
|
||||||
promptName: 'Continue writing',
|
promptName: 'Continue writing',
|
||||||
});
|
});
|
||||||
@@ -399,7 +429,7 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
): Promise<BlockSuitePresets.AIHistory[]> => {
|
): Promise<BlockSuitePresets.AIHistory[]> => {
|
||||||
// @ts-expect-error - 'action' is missing in server impl
|
// @ts-expect-error - 'action' is missing in server impl
|
||||||
return (
|
return (
|
||||||
(await listHistories(workspaceId, docId, {
|
(await client.getHistories(workspaceId, docId, {
|
||||||
action: true,
|
action: true,
|
||||||
})) ?? []
|
})) ?? []
|
||||||
);
|
);
|
||||||
@@ -412,14 +442,14 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
>['variables']['options']
|
>['variables']['options']
|
||||||
): Promise<BlockSuitePresets.AIHistory[]> => {
|
): Promise<BlockSuitePresets.AIHistory[]> => {
|
||||||
// @ts-expect-error - 'action' is missing in server impl
|
// @ts-expect-error - 'action' is missing in server impl
|
||||||
return (await listHistories(workspaceId, docId, options)) ?? [];
|
return (await client.getHistories(workspaceId, docId, options)) ?? [];
|
||||||
},
|
},
|
||||||
cleanup: async (
|
cleanup: async (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
docId: string,
|
docId: string,
|
||||||
sessionIds: string[]
|
sessionIds: string[]
|
||||||
) => {
|
) => {
|
||||||
await cleanupSessions({ workspaceId, docId, sessionIds });
|
await cleanupSessions({ workspaceId, docId, sessionIds, client });
|
||||||
},
|
},
|
||||||
ids: async (
|
ids: async (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
@@ -429,21 +459,23 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
>['variables']['options']
|
>['variables']['options']
|
||||||
): Promise<BlockSuitePresets.AIHistoryIds[]> => {
|
): Promise<BlockSuitePresets.AIHistoryIds[]> => {
|
||||||
// @ts-expect-error - 'role' is missing type in server impl
|
// @ts-expect-error - 'role' is missing type in server impl
|
||||||
return await listHistories(workspaceId, docId, options);
|
return await client.getHistoryIds(workspaceId, docId, options);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
AIProvider.provide('photoEngine', {
|
AIProvider.provide('photoEngine', {
|
||||||
async searchImages(options): Promise<string[]> {
|
async searchImages(options): Promise<string[]> {
|
||||||
const url = new URL(getBaseUrl() + '/api/copilot/unsplash/photos');
|
let url = '/api/copilot/unsplash/photos';
|
||||||
url.searchParams.set('query', options.query);
|
if (options.query) {
|
||||||
|
url += `?query=${encodeURIComponent(options.query)}`;
|
||||||
|
}
|
||||||
const result: {
|
const result: {
|
||||||
results?: {
|
results?: {
|
||||||
urls: {
|
urls: {
|
||||||
regular: string;
|
regular: string;
|
||||||
};
|
};
|
||||||
}[];
|
}[];
|
||||||
} = await fetch(url.toString()).then(res => res.json());
|
} = await client.fetcher(url.toString()).then(res => res.json());
|
||||||
if (!result.results) return [];
|
if (!result.results) return [];
|
||||||
return result.results.map(r => {
|
return result.results.map(r => {
|
||||||
const url = new URL(r.urls.regular);
|
const url = new URL(r.urls.regular);
|
||||||
@@ -460,10 +492,10 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
AIProvider.provide('onboarding', toggleGeneralAIOnboarding);
|
AIProvider.provide('onboarding', toggleGeneralAIOnboarding);
|
||||||
|
|
||||||
AIProvider.provide('forkChat', options => {
|
AIProvider.provide('forkChat', options => {
|
||||||
return forkCopilotSession(options);
|
return forkCopilotSession(client, options);
|
||||||
});
|
});
|
||||||
|
|
||||||
AIProvider.slots.requestLogin.on(() => {
|
const disposeRequestLoginHandler = AIProvider.slots.requestLogin.on(() => {
|
||||||
getCurrentStore().set(authAtom, s => ({
|
getCurrentStore().set(authAtom, s => ({
|
||||||
...s,
|
...s,
|
||||||
openModal: true,
|
openModal: true,
|
||||||
@@ -471,4 +503,8 @@ Could you make a new website based on these notes and send back just the html fi
|
|||||||
});
|
});
|
||||||
|
|
||||||
setupTracker();
|
setupTracker();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
disposeRequestLoginHandler.dispose();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { registerBlocksuitePresetsCustomComponents } from '@affine/core/blocksuite/presets/effects';
|
import { registerBlocksuitePresetsCustomComponents } from '@affine/core/blocksuite/presets/effects';
|
||||||
import { effects as bsEffects } from '@blocksuite/affine/effects';
|
import { effects as bsEffects } from '@blocksuite/affine/effects';
|
||||||
|
|
||||||
import { setupAIProvider } from './ai/setup-provider';
|
|
||||||
import { effects as edgelessEffects } from './specs/edgeless';
|
import { effects as edgelessEffects } from './specs/edgeless';
|
||||||
import { effects as patchEffects } from './specs/preview';
|
import { effects as patchEffects } from './specs/preview';
|
||||||
|
|
||||||
bsEffects();
|
bsEffects();
|
||||||
patchEffects();
|
patchEffects();
|
||||||
setupAIProvider();
|
|
||||||
edgelessEffects();
|
edgelessEffects();
|
||||||
registerBlocksuitePresetsCustomComponents();
|
registerBlocksuitePresetsCustomComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ import { SyncAwareness } from '@affine/core/components/affine/awareness';
|
|||||||
import { useRegisterFindInPageCommands } from '@affine/core/components/hooks/affine/use-register-find-in-page-commands';
|
import { useRegisterFindInPageCommands } from '@affine/core/components/hooks/affine/use-register-find-in-page-commands';
|
||||||
import { useRegisterWorkspaceCommands } from '@affine/core/components/hooks/use-register-workspace-commands';
|
import { useRegisterWorkspaceCommands } from '@affine/core/components/hooks/use-register-workspace-commands';
|
||||||
import { OverCapacityNotification } from '@affine/core/components/over-capacity';
|
import { OverCapacityNotification } from '@affine/core/components/over-capacity';
|
||||||
|
import {
|
||||||
|
EventSourceService,
|
||||||
|
FetchService,
|
||||||
|
GraphQLService,
|
||||||
|
} from '@affine/core/modules/cloud';
|
||||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||||
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import { useRegisterNavigationCommands } from '@affine/core/modules/navigation/view/use-register-navigation-commands';
|
import { useRegisterNavigationCommands } from '@affine/core/modules/navigation/view/use-register-navigation-commands';
|
||||||
@@ -38,6 +43,9 @@ import {
|
|||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { Map as YMap } from 'yjs';
|
import { Map as YMap } from 'yjs';
|
||||||
|
|
||||||
|
import { CopilotClient } from '../blocksuite/block-suite-editor/ai/copilot-client';
|
||||||
|
import { setupAIProvider } from '../blocksuite/block-suite-editor/ai/setup-provider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated just for legacy code, will be removed in the future
|
* @deprecated just for legacy code, will be removed in the future
|
||||||
*/
|
*/
|
||||||
@@ -129,6 +137,23 @@ export const WorkspaceSideEffects = () => {
|
|||||||
};
|
};
|
||||||
}, [globalDialogService]);
|
}, [globalDialogService]);
|
||||||
|
|
||||||
|
const graphqlService = useService(GraphQLService);
|
||||||
|
const eventSourceService = useService(EventSourceService);
|
||||||
|
const fetchService = useService(FetchService);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const dispose = setupAIProvider(
|
||||||
|
new CopilotClient(
|
||||||
|
graphqlService.gql,
|
||||||
|
fetchService.fetch,
|
||||||
|
eventSourceService.eventSource
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return () => {
|
||||||
|
dispose();
|
||||||
|
};
|
||||||
|
}, [eventSourceService, fetchService, graphqlService]);
|
||||||
|
|
||||||
useRegisterWorkspaceCommands();
|
useRegisterWorkspaceCommands();
|
||||||
useRegisterNavigationCommands();
|
useRegisterNavigationCommands();
|
||||||
useRegisterFindInPageCommands();
|
useRegisterFindInPageCommands();
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export { WebSocketAuthProvider } from './provider/websocket-auth';
|
|||||||
export { AccountChanged, AuthService } from './services/auth';
|
export { AccountChanged, AuthService } from './services/auth';
|
||||||
export { CaptchaService } from './services/captcha';
|
export { CaptchaService } from './services/captcha';
|
||||||
export { DefaultServerService } from './services/default-server';
|
export { DefaultServerService } from './services/default-server';
|
||||||
|
export { EventSourceService } from './services/eventsource';
|
||||||
export { FetchService } from './services/fetch';
|
export { FetchService } from './services/fetch';
|
||||||
export { GraphQLService } from './services/graphql';
|
export { GraphQLService } from './services/graphql';
|
||||||
export { InvoicesService } from './services/invoices';
|
export { InvoicesService } from './services/invoices';
|
||||||
@@ -53,6 +54,7 @@ import { AuthService } from './services/auth';
|
|||||||
import { CaptchaService } from './services/captcha';
|
import { CaptchaService } from './services/captcha';
|
||||||
import { CloudDocMetaService } from './services/cloud-doc-meta';
|
import { CloudDocMetaService } from './services/cloud-doc-meta';
|
||||||
import { DefaultServerService } from './services/default-server';
|
import { DefaultServerService } from './services/default-server';
|
||||||
|
import { EventSourceService } from './services/eventsource';
|
||||||
import { FetchService } from './services/fetch';
|
import { FetchService } from './services/fetch';
|
||||||
import { GraphQLService } from './services/graphql';
|
import { GraphQLService } from './services/graphql';
|
||||||
import { InvoicesService } from './services/invoices';
|
import { InvoicesService } from './services/invoices';
|
||||||
@@ -84,6 +86,7 @@ export function configureCloudModule(framework: Framework) {
|
|||||||
.scope(ServerScope)
|
.scope(ServerScope)
|
||||||
.service(ServerService, [ServerScope])
|
.service(ServerService, [ServerScope])
|
||||||
.service(FetchService, [RawFetchProvider, ServerService])
|
.service(FetchService, [RawFetchProvider, ServerService])
|
||||||
|
.service(EventSourceService, [ServerService])
|
||||||
.service(GraphQLService, [FetchService])
|
.service(GraphQLService, [FetchService])
|
||||||
.service(
|
.service(
|
||||||
WebSocketService,
|
WebSocketService,
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Service } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import type { ServerService } from './server';
|
||||||
|
|
||||||
|
export class EventSourceService extends Service {
|
||||||
|
constructor(private readonly serverService: ServerService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
eventSource = (url: string, eventSourceInitDict?: EventSourceInit) => {
|
||||||
|
return new EventSource(
|
||||||
|
new URL(url, this.serverService.server.baseUrl),
|
||||||
|
eventSourceInitDict
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user