diff --git a/bun.lock b/bun.lock
index a99575cd..e2f4277f 100644
--- a/bun.lock
+++ b/bun.lock
@@ -118,6 +118,18 @@
"typescript": "^5.0.0",
},
},
+ "modules/tool/packages/base64ToImage": {
+ "name": "@fastgpt-plugins/tool-base64to-image",
+ "dependencies": {
+ "zod": "^3.24.2",
+ },
+ "devDependencies": {
+ "@types/bun": "latest",
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0",
+ },
+ },
"modules/tool/packages/blackForestLab": {
"name": "fastgpt-tools-blackForestLab",
"dependencies": {
@@ -391,6 +403,18 @@
"typescript": "^5.0.0",
},
},
+ "modules/tool/packages/searchInfinity": {
+ "name": "@fastgpt-plugins/tool-search-infinity",
+ "dependencies": {
+ "zod": "^3.24.2",
+ },
+ "devDependencies": {
+ "@types/bun": "latest",
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0",
+ },
+ },
"modules/tool/packages/searchXNG": {
"name": "fastgpt-tools-searchXNG",
"dependencies": {
@@ -631,10 +655,14 @@
"@fastgpt-plugins/tool-ali-model-studio": ["@fastgpt-plugins/tool-ali-model-studio@workspace:modules/tool/packages/aliModelStudio"],
+ "@fastgpt-plugins/tool-base64to-image": ["@fastgpt-plugins/tool-base64to-image@workspace:modules/tool/packages/base64ToImage"],
+
"@fastgpt-plugins/tool-link-memo": ["@fastgpt-plugins/tool-link-memo@workspace:modules/tool/packages/linkMemo"],
"@fastgpt-plugins/tool-mineru": ["@fastgpt-plugins/tool-mineru@workspace:modules/tool/packages/mineru"],
+ "@fastgpt-plugins/tool-search-infinity": ["@fastgpt-plugins/tool-search-infinity@workspace:modules/tool/packages/searchInfinity"],
+
"@fastgpt-sdk/plugin": ["@fastgpt-sdk/plugin@workspace:sdk"],
"@fortaine/fetch-event-source": ["@fortaine/fetch-event-source@3.0.6", "", {}, "sha512-621GAuLMvKtyZQ3IA6nlDWhV1V/7PGOTNIGLUifxt0KzM+dZIweJ6F3XvQF3QnqeNfS1N7WQ0Kil1Di/lhChEw=="],
@@ -2047,10 +2075,14 @@
"@fast-csv/parse/@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="],
+ "@fastgpt-plugins/tool-base64to-image/@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
+
"@fastgpt-plugins/tool-link-memo/@types/bun": ["@types/bun@1.2.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="],
"@fastgpt-plugins/tool-mineru/@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],
+ "@fastgpt-plugins/tool-search-infinity/@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
+
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
"@inquirer/core/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
@@ -2209,10 +2241,14 @@
"zip-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
+ "@fastgpt-plugins/tool-base64to-image/@types/bun/bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
+
"@fastgpt-plugins/tool-link-memo/@types/bun/bun-types": ["bun-types@1.2.20", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-pxTnQYOrKvdOwyiyd/7sMt9yFOenN004Y6O4lCcCUoKVej48FS5cvTw9geRaEcB9TsDZaJKAxPTVvi8tFsVuXA=="],
"@fastgpt-plugins/tool-mineru/@types/bun/bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="],
+ "@fastgpt-plugins/tool-search-infinity/@types/bun/bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
+
"@inquirer/core/wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
diff --git a/modules/tool/packages/base64ToImage/config.ts b/modules/tool/packages/base64ToImage/config.ts
new file mode 100644
index 00000000..66755338
--- /dev/null
+++ b/modules/tool/packages/base64ToImage/config.ts
@@ -0,0 +1,50 @@
+import { defineTool } from '@tool/type';
+import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt';
+import { ToolTypeEnum } from '@tool/type/tool';
+
+export default defineTool({
+ name: {
+ 'zh-CN': 'Base64 转图片',
+ en: 'Base64 to Image'
+ },
+ type: ToolTypeEnum.tools,
+ description: {
+ 'zh-CN': '输入 Base64 编码的图片,输出图片可访问链接。',
+ en: 'Enter a Base64-encoded image and get a directly accessible image link.'
+ },
+ toolDescription: 'Enter a Base64-encoded image and get a directly accessible image link.',
+ versionList: [
+ {
+ value: '0.1.0',
+ description: 'Default version',
+ inputs: [
+ {
+ key: 'base64',
+ label: 'Base64 字符串',
+ renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference],
+ valueType: WorkflowIOValueTypeEnum.string
+ }
+ ],
+ outputs: [
+ {
+ valueType: WorkflowIOValueTypeEnum.string,
+ key: 'url',
+ label: '图片 URL',
+ description: '可访问的图片地址: http://example.com'
+ },
+ {
+ valueType: WorkflowIOValueTypeEnum.string,
+ key: 'type',
+ label: 'MIME 类型',
+ description: 'MIME 类型'
+ },
+ {
+ valueType: WorkflowIOValueTypeEnum.number,
+ key: 'size',
+ label: '图片大小(B)',
+ description: '图片大小(B)'
+ }
+ ]
+ }
+ ]
+});
diff --git a/modules/tool/packages/base64ToImage/index.ts b/modules/tool/packages/base64ToImage/index.ts
new file mode 100644
index 00000000..d698ed48
--- /dev/null
+++ b/modules/tool/packages/base64ToImage/index.ts
@@ -0,0 +1,10 @@
+import config from './config';
+import { InputType, OutputType, tool as toolCb } from './src';
+import { exportTool } from '@tool/utils/tool';
+
+export default exportTool({
+ toolCb,
+ InputType,
+ OutputType,
+ config
+});
diff --git a/modules/tool/packages/base64ToImage/logo.svg b/modules/tool/packages/base64ToImage/logo.svg
new file mode 100644
index 00000000..4725ae03
--- /dev/null
+++ b/modules/tool/packages/base64ToImage/logo.svg
@@ -0,0 +1,13 @@
+
diff --git a/modules/tool/packages/base64ToImage/package.json b/modules/tool/packages/base64ToImage/package.json
new file mode 100644
index 00000000..ea1cb5b0
--- /dev/null
+++ b/modules/tool/packages/base64ToImage/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "@fastgpt-plugins/tool-base64to-image",
+ "module": "index.ts",
+ "type": "module",
+ "scripts": {
+ "build": "bun ../../../../scripts/build.ts"
+ },
+ "devDependencies": {
+ "@types/bun": "latest"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0"
+ },
+ "dependencies": {
+ "zod": "^3.24.2"
+ }
+}
diff --git a/modules/tool/packages/base64ToImage/src/index.ts b/modules/tool/packages/base64ToImage/src/index.ts
new file mode 100644
index 00000000..cd5770bf
--- /dev/null
+++ b/modules/tool/packages/base64ToImage/src/index.ts
@@ -0,0 +1,123 @@
+import { z } from 'zod';
+import { uploadFile } from '@tool/utils/uploadFile';
+
+/**
+ * Detect image MIME type from base64 binary data by checking file signatures
+ * Supports JPEG, PNG, GIF, BMP, and WebP formats
+ */
+function detectImageMimeType(base64Data: string) {
+ try {
+ // Remove data URL prefix if exists and decode base64
+ const base64Content = base64Data.replace(/^data:[^;]+;base64,/, '');
+ const binaryString = atob(base64Content);
+ const bytes = new Uint8Array(binaryString.length);
+ for (let i = 0; i < binaryString.length; i++) {
+ bytes[i] = binaryString.charCodeAt(i);
+ }
+
+ // Check for common image file signatures
+ // JPEG: FF D8 FF
+ if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
+ return 'image/jpeg';
+ }
+
+ // PNG: 89 50 4E 47 0D 0A 1A 0A
+ if (
+ bytes.length >= 8 &&
+ bytes[0] === 0x89 &&
+ bytes[1] === 0x50 &&
+ bytes[2] === 0x4e &&
+ bytes[3] === 0x47 &&
+ bytes[4] === 0x0d &&
+ bytes[5] === 0x0a &&
+ bytes[6] === 0x1a &&
+ bytes[7] === 0x0a
+ ) {
+ return 'image/png';
+ }
+
+ // GIF: 47 49 46 38 (GIF8)
+ if (
+ bytes.length >= 4 &&
+ bytes[0] === 0x47 &&
+ bytes[1] === 0x49 &&
+ bytes[2] === 0x46 &&
+ bytes[3] === 0x38
+ ) {
+ return 'image/gif';
+ }
+
+ // BMP: 42 4D
+ if (bytes.length >= 2 && bytes[0] === 0x42 && bytes[1] === 0x4d) {
+ return 'image/bmp';
+ }
+
+ // WebP: RIFF + WEBP
+ if (
+ bytes.length >= 12 &&
+ bytes[0] === 0x52 &&
+ bytes[1] === 0x49 &&
+ bytes[2] === 0x46 &&
+ bytes[3] === 0x46 &&
+ bytes[8] === 0x57 &&
+ bytes[9] === 0x45 &&
+ bytes[10] === 0x42 &&
+ bytes[11] === 0x50
+ ) {
+ return 'image/webp';
+ }
+
+ // Default to PNG if no signature matches
+ return null;
+ } catch {
+ // If any error occurs during detection, default to PNG
+ return null;
+ }
+}
+
+export const InputType = z.object({
+ base64: z.string().nonempty()
+});
+
+export const OutputType = z.object({
+ url: z.string(),
+ type: z.string(),
+ size: z.number()
+});
+
+/**
+ * Convert base64 image data to a file and return its URL, type, and size
+ * Supports both data URL format (with MIME type) and raw base64 (auto-detected)
+ */
+export async function tool({
+ base64
+}: z.infer): Promise> {
+ // First try to get MIME type from data URL
+ const mime = (() => {
+ const match = base64.match(/^data:([^;]+);base64,/);
+ if (match?.[1]) {
+ return match[1];
+ }
+ const detectedType = detectImageMimeType(base64);
+ if (!detectedType) {
+ throw new Error('Image Type unknown');
+ }
+ return detectedType;
+ })();
+
+ const ext = (() => {
+ const m = mime.split('/')[1];
+ return m && m.length > 0 ? m : 'png';
+ })();
+
+ // Generate filename with appropriate extension
+ const filename = `image.${ext}`;
+
+ const meta = await uploadFile({ base64, defaultFilename: filename });
+
+ return {
+ url: meta.accessUrl,
+ type: meta.contentType,
+ size: meta.size
+ };
+}
diff --git a/modules/tool/packages/base64ToImage/test/index.test.ts b/modules/tool/packages/base64ToImage/test/index.test.ts
new file mode 100644
index 00000000..26bcb189
--- /dev/null
+++ b/modules/tool/packages/base64ToImage/test/index.test.ts
@@ -0,0 +1,17 @@
+import { expect, test } from 'vitest';
+import tool from '..';
+
+test(async () => {
+ expect(tool.name).toBeDefined();
+ expect(tool.description).toBeDefined();
+ expect(tool.cb).toBeDefined();
+
+ const v = tool.versionList?.[0];
+ expect(v).toBeDefined();
+ const inputKeys = (v?.inputs || []).map((i: any) => i.key);
+ const outputKeys = (v?.outputs || []).map((o: any) => o.key);
+ expect(inputKeys).toContain('base64');
+ expect(outputKeys).toContain('url');
+ expect(outputKeys).toContain('type');
+ expect(outputKeys).toContain('size');
+});