feat: adopt createMessage upload api (#6596)
This commit is contained in:
@@ -64,7 +64,7 @@ class CreateChatMessageInput implements Omit<SubmittedMessage, 'content'> {
|
|||||||
attachments!: string[] | undefined;
|
attachments!: string[] | undefined;
|
||||||
|
|
||||||
@Field(() => [GraphQLUpload], { nullable: true })
|
@Field(() => [GraphQLUpload], { nullable: true })
|
||||||
blobs!: FileUpload[] | undefined;
|
blobs!: Promise<FileUpload>[] | undefined;
|
||||||
|
|
||||||
@Field(() => GraphQLJSON, { nullable: true })
|
@Field(() => GraphQLJSON, { nullable: true })
|
||||||
params!: Record<string, string> | undefined;
|
params!: Record<string, string> | undefined;
|
||||||
@@ -277,7 +277,7 @@ export class CopilotResolver {
|
|||||||
options.attachments = options.attachments || [];
|
options.attachments = options.attachments || [];
|
||||||
const { workspaceId } = session.config;
|
const { workspaceId } = session.config;
|
||||||
|
|
||||||
for (const blob of options.blobs) {
|
for (const blob of await Promise.all(options.blobs)) {
|
||||||
const uploaded = await this.storage.handleUpload(user.id, blob);
|
const uploaded = await this.storage.handleUpload(user.id, blob);
|
||||||
const link = await this.storage.put(
|
const link = await this.storage.put(
|
||||||
user.id,
|
user.id,
|
||||||
@@ -286,6 +286,7 @@ export class CopilotResolver {
|
|||||||
uploaded.buffer
|
uploaded.buffer
|
||||||
);
|
);
|
||||||
options.attachments.push(link);
|
options.attachments.push(link);
|
||||||
|
delete options.blobs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ export function setupAIProvider() {
|
|||||||
return toImage({
|
return toImage({
|
||||||
...options,
|
...options,
|
||||||
promptName,
|
promptName,
|
||||||
forceToImage: true,
|
forceCreate: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { toTextStream } from '@blocksuite/presets';
|
import { toTextStream } from '@blocksuite/presets';
|
||||||
|
import { partition } from 'lodash-es';
|
||||||
|
|
||||||
import { CopilotClient } from './copilot-client';
|
import { CopilotClient } from './copilot-client';
|
||||||
import type { PromptKey } from './prompt';
|
import type { PromptKey } from './prompt';
|
||||||
@@ -7,19 +8,14 @@ const TIMEOUT = 50000;
|
|||||||
|
|
||||||
const client = new CopilotClient();
|
const client = new CopilotClient();
|
||||||
|
|
||||||
function readBlobAsURL(blob: Blob | File) {
|
async function calculateBlobHash(blob: Blob) {
|
||||||
return new Promise<string>((resolve, reject) => {
|
const buffer = await blob.arrayBuffer();
|
||||||
const reader = new FileReader();
|
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
|
||||||
reader.onload = e => {
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||||
if (typeof e.target?.result === 'string') {
|
return hashArray
|
||||||
resolve(e.target.result);
|
.map(byte => byte.toString(16).padStart(2, '0'))
|
||||||
} else {
|
.join('')
|
||||||
reject();
|
.slice(0, 32);
|
||||||
}
|
|
||||||
};
|
|
||||||
reader.onerror = reject;
|
|
||||||
reader.readAsDataURL(blob);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TextToTextOptions = {
|
export type TextToTextOptions = {
|
||||||
@@ -32,7 +28,7 @@ export type TextToTextOptions = {
|
|||||||
params?: Record<string, string>;
|
params?: Record<string, string>;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
stream?: boolean;
|
stream?: boolean;
|
||||||
forceToImage?: boolean; // force to image
|
forceCreate?: boolean; // force to create a message
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createChatSession({
|
export function createChatSession({
|
||||||
@@ -57,7 +53,7 @@ async function createSessionMessage({
|
|||||||
sessionId: providedSessionId,
|
sessionId: providedSessionId,
|
||||||
attachments,
|
attachments,
|
||||||
params,
|
params,
|
||||||
forceToImage,
|
forceCreate,
|
||||||
}: TextToTextOptions) {
|
}: TextToTextOptions) {
|
||||||
if (!promptName && !providedSessionId) {
|
if (!promptName && !providedSessionId) {
|
||||||
throw new Error('promptName or sessionId is required');
|
throw new Error('promptName or sessionId is required');
|
||||||
@@ -70,28 +66,27 @@ async function createSessionMessage({
|
|||||||
promptName: promptName as string,
|
promptName: promptName as string,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (forceToImage || hasAttachments) {
|
if (forceCreate || hasAttachments) {
|
||||||
const options = {
|
const options: Parameters<CopilotClient['createMessage']>[0] = {
|
||||||
sessionId,
|
sessionId,
|
||||||
content,
|
content,
|
||||||
params,
|
params,
|
||||||
} as {
|
|
||||||
sessionId: string;
|
|
||||||
content?: string;
|
|
||||||
params?: Record<string, string>;
|
|
||||||
attachments?: string[];
|
|
||||||
};
|
};
|
||||||
if (hasAttachments) {
|
if (hasAttachments) {
|
||||||
const normalizedAttachments = await Promise.all(
|
const [stringAttachments, blobs] = partition(
|
||||||
attachments.map(async attachment => {
|
attachments,
|
||||||
if (typeof attachment === 'string') {
|
attachment => typeof attachment === 'string'
|
||||||
return attachment;
|
) as [string[], (Blob | File)[]];
|
||||||
|
options.attachments = stringAttachments;
|
||||||
|
options.blobs = await Promise.all(
|
||||||
|
blobs.map(async blob => {
|
||||||
|
if (blob instanceof File) {
|
||||||
|
return blob;
|
||||||
|
} else {
|
||||||
|
return new File([blob], await calculateBlobHash(blob));
|
||||||
}
|
}
|
||||||
const url = await readBlobAsURL(attachment);
|
|
||||||
return url;
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
options.attachments = normalizedAttachments;
|
|
||||||
}
|
}
|
||||||
const messageId = await client.createMessage(options);
|
const messageId = await client.createMessage(options);
|
||||||
return {
|
return {
|
||||||
@@ -180,7 +175,7 @@ export function toImage({
|
|||||||
content,
|
content,
|
||||||
attachments,
|
attachments,
|
||||||
params,
|
params,
|
||||||
forceToImage,
|
forceCreate,
|
||||||
timeout = TIMEOUT,
|
timeout = TIMEOUT,
|
||||||
}: TextToTextOptions) {
|
}: TextToTextOptions) {
|
||||||
return {
|
return {
|
||||||
@@ -192,7 +187,7 @@ export function toImage({
|
|||||||
content,
|
content,
|
||||||
attachments,
|
attachments,
|
||||||
params,
|
params,
|
||||||
forceToImage,
|
forceCreate,
|
||||||
});
|
});
|
||||||
|
|
||||||
const eventSource = client.imagesStream(
|
const eventSource = client.imagesStream(
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ function getExportedName(def) {
|
|||||||
* @type {import('@graphql-codegen/plugin-helpers').CodegenPlugin}
|
* @type {import('@graphql-codegen/plugin-helpers').CodegenPlugin}
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugin: (_schema, documents, { output }) => {
|
plugin: (schema, documents, { output }) => {
|
||||||
const nameLocationMap = new Map();
|
const nameLocationMap = new Map();
|
||||||
const locationSourceMap = new Map(
|
const locationSourceMap = new Map(
|
||||||
documents
|
documents
|
||||||
@@ -133,12 +133,24 @@ module.exports = {
|
|||||||
const { variableDefinitions } = def;
|
const { variableDefinitions } = def;
|
||||||
if (variableDefinitions) {
|
if (variableDefinitions) {
|
||||||
return variableDefinitions.some(variableDefinition => {
|
return variableDefinitions.some(variableDefinition => {
|
||||||
if (
|
const varType = variableDefinition?.type?.type?.name?.value;
|
||||||
variableDefinition?.type?.type?.name?.value === 'Upload'
|
const checkContainFile = type => {
|
||||||
) {
|
if (schema.getType(type)?.name === 'Upload') return true;
|
||||||
|
const typeDef = schema.getType(type);
|
||||||
|
const fields = typeDef.getFields?.();
|
||||||
|
if (!fields || !fields) return false;
|
||||||
|
for (let field of Object.values(fields)) {
|
||||||
|
let type = field.type;
|
||||||
|
while (type.ofType) {
|
||||||
|
type = type.ofType;
|
||||||
|
}
|
||||||
|
if (type.name === 'Upload') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
|
};
|
||||||
|
return varType ? checkContainFile(varType) : false;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -115,17 +115,26 @@ export function transformToForm(body: RequestBody) {
|
|||||||
if (body.operationName) {
|
if (body.operationName) {
|
||||||
gqlBody.name = body.operationName;
|
gqlBody.name = body.operationName;
|
||||||
}
|
}
|
||||||
const map: Record<string, [string]> = {};
|
const map: Record<string, string[]> = {};
|
||||||
const files: File[] = [];
|
const files: File[] = [];
|
||||||
if (body.variables) {
|
if (body.variables) {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
Object.entries(body.variables).forEach(([key, value]) => {
|
const buildMap = (key: string, value: any) => {
|
||||||
if (value instanceof File) {
|
if (value instanceof File) {
|
||||||
map['0'] = [`variables.${key}`];
|
map['' + i] = [key];
|
||||||
files[i] = value;
|
files[i] = value;
|
||||||
i++;
|
i++;
|
||||||
}
|
} else if (Array.isArray(value)) {
|
||||||
|
value.forEach((v, index) => {
|
||||||
|
buildMap(`${key}.${index}`, v);
|
||||||
});
|
});
|
||||||
|
} else if (isObject(value)) {
|
||||||
|
Object.entries(value).forEach(([k, v]) => {
|
||||||
|
buildMap(`${key}.${k}`, v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
buildMap('variables', body.variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
form.set('operations', JSON.stringify(gqlBody));
|
form.set('operations', JSON.stringify(gqlBody));
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export const createCopilotMessageMutation = {
|
|||||||
id: 'createCopilotMessageMutation' as const,
|
id: 'createCopilotMessageMutation' as const,
|
||||||
operationName: 'createCopilotMessage',
|
operationName: 'createCopilotMessage',
|
||||||
definitionName: 'createCopilotMessage',
|
definitionName: 'createCopilotMessage',
|
||||||
containsFile: false,
|
containsFile: true,
|
||||||
query: `
|
query: `
|
||||||
mutation createCopilotMessage($options: CreateChatMessageInput!) {
|
mutation createCopilotMessage($options: CreateChatMessageInput!) {
|
||||||
createCopilotMessage(options: $options)
|
createCopilotMessage(options: $options)
|
||||||
|
|||||||
Reference in New Issue
Block a user