mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00: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,
|
||||
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' })]
|
||||
);
|
||||
t.truthy(messageId, 'should be able to create message with blobs');
|
||||
|
||||
@@ -554,52 +554,73 @@ export async function createCopilotMessage(
|
||||
sessionId: string,
|
||||
content?: string,
|
||||
attachments?: string[],
|
||||
blob?: File,
|
||||
blobs?: File[],
|
||||
params?: Record<string, string>
|
||||
): Promise<string> {
|
||||
let resp = app
|
||||
.POST('/graphql')
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.field(
|
||||
'operations',
|
||||
JSON.stringify({
|
||||
query: `
|
||||
const gql = {
|
||||
query: `
|
||||
mutation createCopilotMessage($options: CreateChatMessageInput!) {
|
||||
createCopilotMessage(options: $options)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
options: { sessionId, content, attachments, blobs: [], params },
|
||||
},
|
||||
})
|
||||
)
|
||||
.field(
|
||||
'map',
|
||||
JSON.stringify(
|
||||
Array.from<any>({ length: blobs?.length ?? 0 }).reduce(
|
||||
(acc, _, idx) => {
|
||||
acc[idx.toString()] = [`variables.options.blobs.${idx}`];
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
)
|
||||
)
|
||||
);
|
||||
if (blobs && blobs.length) {
|
||||
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',
|
||||
}
|
||||
variables: {
|
||||
options: {
|
||||
sessionId,
|
||||
content,
|
||||
attachments,
|
||||
blob: null,
|
||||
blobs: [],
|
||||
params,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
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 (blob) {
|
||||
resp = resp.field(
|
||||
'map',
|
||||
JSON.stringify({ '0': ['variables.options.blob'] })
|
||||
);
|
||||
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);
|
||||
|
||||
console.log('createCopilotMessage', res.body);
|
||||
return res.body.data.createCopilotMessage;
|
||||
}
|
||||
|
||||
|
||||
@@ -143,6 +143,9 @@ class CreateChatMessageInput implements Omit<SubmittedMessage, 'content'> {
|
||||
@Field(() => [String], { nullable: true, deprecationReason: 'use blobs' })
|
||||
attachments!: string[] | undefined;
|
||||
|
||||
@Field(() => GraphQLUpload, { nullable: true })
|
||||
blob!: Promise<FileUpload> | undefined;
|
||||
|
||||
@Field(() => [GraphQLUpload], { nullable: true })
|
||||
blobs!: Promise<FileUpload>[] | undefined;
|
||||
|
||||
@@ -703,10 +706,13 @@ export class CopilotResolver {
|
||||
}
|
||||
|
||||
const attachments: PromptMessage['attachments'] = options.attachments || [];
|
||||
if (options.blobs) {
|
||||
if (options.blob || options.blobs) {
|
||||
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;
|
||||
|
||||
for (const blob of blobs) {
|
||||
|
||||
@@ -457,6 +457,7 @@ type CopilotWorkspaceIgnoredDocTypeEdge {
|
||||
|
||||
input CreateChatMessageInput {
|
||||
attachments: [String!]
|
||||
blob: Upload
|
||||
blobs: [Upload!]
|
||||
content: String
|
||||
params: JSON
|
||||
|
||||
Reference in New Issue
Block a user