mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-21 20:11:43 +08:00
feat(server): add compatibility for ios client (#13263)
fix AI-355 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for uploading a single file as an attachment when creating chat messages, in addition to existing multiple file uploads. * **Tests** * Expanded test coverage to verify message creation with both single and multiple file attachments. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -461,6 +461,29 @@ test('should create message correctly', async t => {
|
|||||||
sessionId,
|
sessionId,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
|
new File([new Uint8Array(pngData)], '1.png', { type: 'image/png' })
|
||||||
|
);
|
||||||
|
t.truthy(messageId, 'should be able to create message with blob');
|
||||||
|
}
|
||||||
|
|
||||||
|
// with attachments
|
||||||
|
{
|
||||||
|
const { id } = await createWorkspace(app);
|
||||||
|
const sessionId = await createCopilotSession(
|
||||||
|
app,
|
||||||
|
id,
|
||||||
|
randomUUID(),
|
||||||
|
textPromptName
|
||||||
|
);
|
||||||
|
const smallestPng =
|
||||||
|
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII';
|
||||||
|
const pngData = await fetch(smallestPng).then(res => res.arrayBuffer());
|
||||||
|
const messageId = await createCopilotMessage(
|
||||||
|
app,
|
||||||
|
sessionId,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
[new File([new Uint8Array(pngData)], '1.png', { type: 'image/png' })]
|
[new File([new Uint8Array(pngData)], '1.png', { type: 'image/png' })]
|
||||||
);
|
);
|
||||||
t.truthy(messageId, 'should be able to create message with blobs');
|
t.truthy(messageId, 'should be able to create message with blobs');
|
||||||
|
|||||||
@@ -554,52 +554,73 @@ export async function createCopilotMessage(
|
|||||||
sessionId: string,
|
sessionId: string,
|
||||||
content?: string,
|
content?: string,
|
||||||
attachments?: string[],
|
attachments?: string[],
|
||||||
|
blob?: File,
|
||||||
blobs?: File[],
|
blobs?: File[],
|
||||||
params?: Record<string, string>
|
params?: Record<string, string>
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
let resp = app
|
const gql = {
|
||||||
.POST('/graphql')
|
query: `
|
||||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
|
||||||
.field(
|
|
||||||
'operations',
|
|
||||||
JSON.stringify({
|
|
||||||
query: `
|
|
||||||
mutation createCopilotMessage($options: CreateChatMessageInput!) {
|
mutation createCopilotMessage($options: CreateChatMessageInput!) {
|
||||||
createCopilotMessage(options: $options)
|
createCopilotMessage(options: $options)
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
options: { sessionId, content, attachments, blobs: [], params },
|
options: {
|
||||||
},
|
sessionId,
|
||||||
})
|
content,
|
||||||
)
|
attachments,
|
||||||
.field(
|
blob: null,
|
||||||
'map',
|
blobs: [],
|
||||||
JSON.stringify(
|
params,
|
||||||
Array.from<any>({ length: blobs?.length ?? 0 }).reduce(
|
},
|
||||||
(acc, _, idx) => {
|
},
|
||||||
acc[idx.toString()] = [`variables.options.blobs.${idx}`];
|
};
|
||||||
return acc;
|
|
||||||
},
|
let resp = app
|
||||||
{}
|
.POST('/graphql')
|
||||||
)
|
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' });
|
||||||
)
|
if (blob || blobs) {
|
||||||
);
|
resp = resp.field('operations', JSON.stringify(gql));
|
||||||
if (blobs && blobs.length) {
|
|
||||||
for (const [idx, file] of blobs.entries()) {
|
if (blob) {
|
||||||
resp = resp.attach(
|
resp = resp.field(
|
||||||
idx.toString(),
|
'map',
|
||||||
Buffer.from(await file.arrayBuffer()),
|
JSON.stringify({ '0': ['variables.options.blob'] })
|
||||||
{
|
|
||||||
filename: file.name || `file${idx}`,
|
|
||||||
contentType: file.type || 'application/octet-stream',
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
resp = resp.attach('0', Buffer.from(await blob.arrayBuffer()), {
|
||||||
|
filename: blob.name || 'file',
|
||||||
|
contentType: blob.type || 'application/octet-stream',
|
||||||
|
});
|
||||||
|
} else if (blobs && blobs.length) {
|
||||||
|
resp = resp.field(
|
||||||
|
'map',
|
||||||
|
JSON.stringify(
|
||||||
|
Array.from<any>({ length: blobs?.length ?? 0 }).reduce(
|
||||||
|
(acc, _, idx) => {
|
||||||
|
acc[idx.toString()] = [`variables.options.blobs.${idx}`];
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
for (const [idx, file] of blobs.entries()) {
|
||||||
|
resp = resp.attach(
|
||||||
|
idx.toString(),
|
||||||
|
Buffer.from(await file.arrayBuffer()),
|
||||||
|
{
|
||||||
|
filename: file.name || `file${idx}`,
|
||||||
|
contentType: file.type || 'application/octet-stream',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
resp = resp.send(gql);
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await resp.expect(200);
|
const res = await resp.expect(200);
|
||||||
|
console.log('createCopilotMessage', res.body);
|
||||||
return res.body.data.createCopilotMessage;
|
return res.body.data.createCopilotMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,9 @@ class CreateChatMessageInput implements Omit<SubmittedMessage, 'content'> {
|
|||||||
@Field(() => [String], { nullable: true, deprecationReason: 'use blobs' })
|
@Field(() => [String], { nullable: true, deprecationReason: 'use blobs' })
|
||||||
attachments!: string[] | undefined;
|
attachments!: string[] | undefined;
|
||||||
|
|
||||||
|
@Field(() => GraphQLUpload, { nullable: true })
|
||||||
|
blob!: Promise<FileUpload> | undefined;
|
||||||
|
|
||||||
@Field(() => [GraphQLUpload], { nullable: true })
|
@Field(() => [GraphQLUpload], { nullable: true })
|
||||||
blobs!: Promise<FileUpload>[] | undefined;
|
blobs!: Promise<FileUpload>[] | undefined;
|
||||||
|
|
||||||
@@ -703,10 +706,13 @@ export class CopilotResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const attachments: PromptMessage['attachments'] = options.attachments || [];
|
const attachments: PromptMessage['attachments'] = options.attachments || [];
|
||||||
if (options.blobs) {
|
if (options.blob || options.blobs) {
|
||||||
const { workspaceId } = session.config;
|
const { workspaceId } = session.config;
|
||||||
|
|
||||||
const blobs = await Promise.all(options.blobs);
|
const blobs = await Promise.all(
|
||||||
|
options.blob ? [options.blob] : options.blobs || []
|
||||||
|
);
|
||||||
|
delete options.blob;
|
||||||
delete options.blobs;
|
delete options.blobs;
|
||||||
|
|
||||||
for (const blob of blobs) {
|
for (const blob of blobs) {
|
||||||
|
|||||||
@@ -457,6 +457,7 @@ type CopilotWorkspaceIgnoredDocTypeEdge {
|
|||||||
|
|
||||||
input CreateChatMessageInput {
|
input CreateChatMessageInput {
|
||||||
attachments: [String!]
|
attachments: [String!]
|
||||||
|
blob: Upload
|
||||||
blobs: [Upload!]
|
blobs: [Upload!]
|
||||||
content: String
|
content: String
|
||||||
params: JSON
|
params: JSON
|
||||||
|
|||||||
@@ -569,6 +569,7 @@ export interface CopilotWorkspaceIgnoredDocTypeEdge {
|
|||||||
|
|
||||||
export interface CreateChatMessageInput {
|
export interface CreateChatMessageInput {
|
||||||
attachments?: InputMaybe<Array<Scalars['String']['input']>>;
|
attachments?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||||
|
blob?: InputMaybe<Scalars['Upload']['input']>;
|
||||||
blobs?: InputMaybe<Array<Scalars['Upload']['input']>>;
|
blobs?: InputMaybe<Array<Scalars['Upload']['input']>>;
|
||||||
content?: InputMaybe<Scalars['String']['input']>;
|
content?: InputMaybe<Scalars['String']['input']>;
|
||||||
params?: InputMaybe<Scalars['JSON']['input']>;
|
params?: InputMaybe<Scalars['JSON']['input']>;
|
||||||
|
|||||||
Reference in New Issue
Block a user