From ec1c7c1493b7921d5ec6a9cbad2d2da790ad3f47 Mon Sep 17 00:00:00 2001 From: Finley Ge Date: Mon, 8 Dec 2025 17:48:35 +0800 Subject: [PATCH] feat: wecom multi-model message support --- packages/global/common/file/tools.ts | 4 +-- packages/global/common/file/utils.ts | 22 +++++++++++++++ packages/global/support/outLink/type.d.ts | 10 +++++-- .../service/common/s3/sources/chat/index.ts | 28 ++++++++++++++++++- .../service/common/s3/sources/chat/type.ts | 12 ++++++++ 5 files changed, 70 insertions(+), 6 deletions(-) diff --git a/packages/global/common/file/tools.ts b/packages/global/common/file/tools.ts index fbaa9a8ef..245fb8535 100644 --- a/packages/global/common/file/tools.ts +++ b/packages/global/common/file/tools.ts @@ -75,9 +75,9 @@ export const parseUrlToFileType = (url: string): UserChatItemFileItemType | unde }; } - // Default to image type for non-document files + // Default to file type for non-extension files return { - type: ChatFileTypeEnum.image, + type: ChatFileTypeEnum.file, name: filename || 'null', url }; diff --git a/packages/global/common/file/utils.ts b/packages/global/common/file/utils.ts index a8118bf2b..fe3ad7a27 100644 --- a/packages/global/common/file/utils.ts +++ b/packages/global/common/file/utils.ts @@ -4,3 +4,25 @@ export const isCSVFile = (filename: string) => { const extension = path.extname(filename).toLowerCase(); return extension === '.csv'; }; + +export function detectImageContentType(buffer: Buffer): string { + if (!buffer || buffer.length < 12) return 'text/plain'; + + // JPEG + if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) return 'image/jpeg'; + + // PNG + const pngSig = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]; + if (pngSig.every((v, i) => buffer.readUInt8(i) === v)) return 'image/png'; + + // GIF + const gifSig = buffer.subarray(0, 6).toString('ascii'); + if (gifSig === 'GIF87a' || gifSig === 'GIF89a') return 'image/gif'; + + // WEBP + const riff = buffer.subarray(0, 4).toString('ascii'); + const webp = buffer.subarray(8, 12).toString('ascii'); + if (riff === 'RIFF' && webp === 'WEBP') return 'image/webp'; + + return 'text/plain'; +} diff --git a/packages/global/support/outLink/type.d.ts b/packages/global/support/outLink/type.d.ts index 2c3f54037..d2bd692e4 100644 --- a/packages/global/support/outLink/type.d.ts +++ b/packages/global/support/outLink/type.d.ts @@ -20,11 +20,15 @@ export interface DingtalkAppType { } export interface WecomAppType { - AgentId: string; - CorpId: string; - SuiteSecret: string; CallbackToken: string; CallbackEncodingAesKey: string; + + /** @deprecated */ + // AgentId: string; + /** @deprecated */ + // CorpId: string; + /** @deprecated */ + // SuiteSecret: string; } // TODO: unused diff --git a/packages/service/common/s3/sources/chat/index.ts b/packages/service/common/s3/sources/chat/index.ts index 5baf71e46..772548a62 100644 --- a/packages/service/common/s3/sources/chat/index.ts +++ b/packages/service/common/s3/sources/chat/index.ts @@ -1,11 +1,13 @@ import { parseFileExtensionFromUrl } from '@fastgpt/global/common/string/tools'; import { S3PrivateBucket } from '../../buckets/private'; import { S3Sources } from '../../type'; +import type { UploadFileParams } from './type'; import { type CheckChatFileKeys, type DelChatFileByPrefixParams, ChatFileUploadSchema, - DelChatFileByPrefixSchema + DelChatFileByPrefixSchema, + UploadChatFileSchema } from './type'; import { differenceInHours } from 'date-fns'; import { S3Buckets } from '../../constants'; @@ -109,6 +111,30 @@ export class S3ChatSource { deleteChatFileByKey(key: string) { return this.bucket.addDeleteJob({ key }); } + + async uploadFile(params: UploadFileParams) { + const { appId, chatId, uId, filename, expiredTime, buffer, contentType } = + UploadChatFileSchema.parse(params); + const { fileKey } = getFileS3Key.chat({ + appId, + chatId, + uId, + filename + }); + + console.log('upload to s3, contentType:', contentType); + await this.bucket.putObject(fileKey, buffer, undefined, { + 'Content-Type': contentType || 'application/octet-stream' + }); + + return { + fileKey, + accessUrl: await this.bucket.createPreviewUrl({ + key: fileKey, + expiredHours: expiredTime ? differenceInHours(new Date(), expiredTime) : 24 + }) + }; + } } export function getS3ChatSource() { diff --git a/packages/service/common/s3/sources/chat/type.ts b/packages/service/common/s3/sources/chat/type.ts index bccd7942f..9141b96fb 100644 --- a/packages/service/common/s3/sources/chat/type.ts +++ b/packages/service/common/s3/sources/chat/type.ts @@ -16,3 +16,15 @@ export const DelChatFileByPrefixSchema = z.object({ uId: z.string().nonempty().optional() }); export type DelChatFileByPrefixParams = z.infer; + +export const UploadChatFileSchema = z.object({ + appId: ObjectIdSchema, + chatId: z.string().nonempty(), + uId: z.string().nonempty(), + filename: z.string().nonempty(), + expiredTime: z.date().optional(), + buffer: z.instanceof(Buffer), + contentType: z.string().optional() +}); + +export type UploadFileParams = z.infer;