@nocobase/plugin-ai 2.2.0-alpha.7 → 2.2.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/ai/docs/nocobase/ai-employees/scenarios/company-background-research.md +125 -0
  2. package/dist/ai/docs/nocobase/building-tips/operations-dashboard.md +513 -0
  3. package/dist/ai/docs/nocobase/file-manager/stable-url.md +87 -0
  4. package/dist/ai/docs/nocobase/get-started/deployment/production.md +24 -2
  5. package/dist/ai/docs/nocobase/get-started/installation/docker-caddy.mdx +3 -0
  6. package/dist/ai/docs/nocobase/get-started/installation/docker-nginx.mdx +3 -0
  7. package/dist/ai/docs/nocobase/get-started/installation/docker.mdx +27 -3
  8. package/dist/ai/docs/nocobase/get-started/installation/env.md +33 -0
  9. package/dist/ai/docs/nocobase/index.md +1 -1
  10. package/dist/ai/docs/nocobase/interface-builder/index.md +7 -6
  11. package/dist/ai/docs/nocobase/interface-builder/ui-layout/desktop.md +97 -0
  12. package/dist/ai/docs/nocobase/interface-builder/ui-layout/index.md +50 -0
  13. package/dist/ai/docs/nocobase/interface-builder/ui-layout/mobile.md +133 -0
  14. package/dist/ai/docs/nocobase/multi-app/multi-app-vs-multi-portal-vs-multi-space.md +159 -0
  15. package/dist/ai/docs/nocobase/multi-app/multi-portal/index.md +195 -0
  16. package/dist/ai/docs/nocobase/nocobase-cli/production/index.md +10 -0
  17. package/dist/ai/docs/nocobase/nocobase-cli/production/reverse-proxy/caddy.md +15 -2
  18. package/dist/ai/docs/nocobase/nocobase-cli/production/reverse-proxy/index.md +1 -1
  19. package/dist/ai/docs/nocobase/nocobase-cli/production/reverse-proxy/nginx.md +16 -2
  20. package/dist/ai/docs/nocobase/runjs/context/ai.md +206 -0
  21. package/dist/ai/docs/nocobase/tutorials/index.md +20 -1
  22. package/dist/ai/tools/formFiller.js +4 -3
  23. package/dist/client/372.40eb52905e3f3049.js +10 -0
  24. package/dist/client/index.js +7 -7
  25. package/dist/client-v2/372.8cc3fde09c9bec77.js +10 -0
  26. package/dist/client-v2/ai-employees/chatbox/utils/normalizeTriggerTaskOptions.d.ts +19 -0
  27. package/dist/client-v2/ai-employees/chatbox/utils.d.ts +5 -0
  28. package/dist/client-v2/index.js +3 -3
  29. package/dist/client-v2/manager/ai-manager.d.ts +21 -0
  30. package/dist/client-v2/pages/EmployeesPage.d.ts +9 -0
  31. package/dist/client-v2/plugin.d.ts +1 -0
  32. package/dist/client-v2/runjs/registerAIEmployeeRunJSFacade.d.ts +20 -0
  33. package/dist/externalVersion.js +15 -15
  34. package/dist/locale/en-US.json +3 -0
  35. package/dist/locale/zh-CN.json +3 -0
  36. package/dist/node_modules/@langchain/mistralai/package.json +1 -1
  37. package/dist/node_modules/@langchain/xai/package.json +1 -1
  38. package/dist/node_modules/fs-extra/package.json +1 -1
  39. package/dist/node_modules/jsonrepair/package.json +1 -1
  40. package/dist/node_modules/just-bash/package.json +1 -1
  41. package/dist/node_modules/nodejs-snowflake/package.json +1 -1
  42. package/dist/node_modules/openai/package.json +1 -1
  43. package/dist/node_modules/zod/package.json +1 -1
  44. package/dist/server/ai-employees/ai-employee.d.ts +1 -0
  45. package/dist/server/ai-employees/ai-employee.js +30 -1
  46. package/dist/server/ai-employees/tool-call-sanitizer.d.ts +1 -0
  47. package/dist/server/ai-employees/tool-call-sanitizer.js +2 -1
  48. package/dist/server/ai-employees/utils.js +11 -5
  49. package/dist/server/attachments.d.ts +24 -0
  50. package/dist/server/attachments.js +204 -0
  51. package/dist/server/document-loader/cached.d.ts +1 -0
  52. package/dist/server/document-loader/cached.js +18 -9
  53. package/dist/server/document-loader/loader.d.ts +0 -1
  54. package/dist/server/document-loader/loader.js +25 -8
  55. package/dist/server/document-loader/types.d.ts +7 -0
  56. package/dist/server/llm-providers/anthropic.d.ts +2 -1
  57. package/dist/server/llm-providers/anthropic.js +1 -3
  58. package/dist/server/llm-providers/google-genai.d.ts +1 -1
  59. package/dist/server/llm-providers/google-genai.js +1 -5
  60. package/dist/server/llm-providers/provider.d.ts +3 -2
  61. package/dist/server/llm-providers/provider.js +26 -3
  62. package/dist/server/manager/ai-context-datasource-manager.js +34 -7
  63. package/dist/server/plugin.js +14 -0
  64. package/dist/server/resource/aiConversations.js +44 -1
  65. package/dist/server/utils.d.ts +5 -2
  66. package/dist/server/utils.js +11 -19
  67. package/dist/server/workflow/nodes/employee/files.d.ts +3 -1
  68. package/dist/server/workflow/nodes/employee/files.js +31 -5
  69. package/package.json +2 -2
  70. package/dist/client/372.da38fe350bf841f4.js +0 -10
  71. package/dist/client-v2/372.d76ea1ceed2be2a4.js +0 -10
@@ -0,0 +1,204 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var attachments_exports = {};
28
+ __export(attachments_exports, {
29
+ appendAIFileAttachmentSource: () => appendAIFileAttachmentSource,
30
+ findMessageAttachments: () => findMessageAttachments,
31
+ getAttachmentId: () => getAttachmentId,
32
+ getAttachmentSource: () => getAttachmentSource,
33
+ getMessageAttachmentLookupKey: () => getMessageAttachmentLookupKey,
34
+ shouldSkipAttachmentSourceLookup: () => shouldSkipAttachmentSourceLookup
35
+ });
36
+ module.exports = __toCommonJS(attachments_exports);
37
+ const AI_FILES_ATTACHMENT_SOURCE = {
38
+ dataSourceKey: "main",
39
+ collectionName: "aiFiles"
40
+ };
41
+ function isRecord(value) {
42
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
43
+ }
44
+ function appendSourceToAttachmentRecord(record) {
45
+ if (!isRecord(record)) {
46
+ return;
47
+ }
48
+ const meta = {
49
+ ...isRecord(record.meta) ? record.meta : {},
50
+ source: AI_FILES_ATTACHMENT_SOURCE
51
+ };
52
+ if (typeof record.set === "function") {
53
+ const model = record;
54
+ model.set("meta", meta);
55
+ model.dataValues.meta = meta;
56
+ return;
57
+ }
58
+ record.meta = meta;
59
+ }
60
+ function appendAIFileAttachmentSource(body) {
61
+ if (!isRecord(body)) {
62
+ return;
63
+ }
64
+ if (body.data) {
65
+ appendSourceToAttachmentRecord(body.data);
66
+ return;
67
+ }
68
+ appendSourceToAttachmentRecord(body);
69
+ }
70
+ function getAttachmentId(attachment) {
71
+ if (!isRecord(attachment)) {
72
+ return null;
73
+ }
74
+ const id = attachment.id ?? attachment.filterByTk;
75
+ if (typeof id === "string" || typeof id === "number") {
76
+ return id;
77
+ }
78
+ return null;
79
+ }
80
+ function shouldSkipAttachmentSourceLookup(source) {
81
+ return (source == null ? void 0 : source.trustworthy) === true;
82
+ }
83
+ function getAttachmentSource(attachment) {
84
+ if (!isRecord(attachment) || !isRecord(attachment.source)) {
85
+ return null;
86
+ }
87
+ const { dataSourceKey, collectionName, field, documentCache, trustworthy } = attachment.source;
88
+ const source = {
89
+ dataSourceKey: typeof dataSourceKey === "string" && dataSourceKey ? dataSourceKey : "main",
90
+ ...typeof collectionName === "string" && collectionName ? { collectionName } : {},
91
+ ...typeof field === "string" ? { field } : {},
92
+ ...typeof documentCache === "boolean" ? { documentCache } : {},
93
+ ...trustworthy === true ? { trustworthy } : {}
94
+ };
95
+ if (!source.trustworthy && !source.collectionName) {
96
+ return null;
97
+ }
98
+ return source;
99
+ }
100
+ function getSourceDatabase(ctx, dataSourceKey) {
101
+ var _a, _b, _c;
102
+ if (!dataSourceKey || dataSourceKey === "main") {
103
+ return ctx.db;
104
+ }
105
+ const dataSource = (_b = (_a = ctx.app.dataSourceManager) == null ? void 0 : _a.dataSources) == null ? void 0 : _b.get(dataSourceKey);
106
+ return ((_c = dataSource == null ? void 0 : dataSource.collectionManager) == null ? void 0 : _c.db) ?? null;
107
+ }
108
+ function getLookupKey(lookup) {
109
+ const dataSourceKey = lookup.source.dataSourceKey || "main";
110
+ const collectionName = lookup.source.collectionName;
111
+ return `${dataSourceKey}:${collectionName}:${lookup.id}`;
112
+ }
113
+ function isValidFileCollectionSource(db, lookup) {
114
+ var _a, _b, _c;
115
+ const collectionName = lookup.source.collectionName;
116
+ const collection = db.getCollection(collectionName);
117
+ if (((_a = collection == null ? void 0 : collection.options) == null ? void 0 : _a.template) !== "file") {
118
+ return false;
119
+ }
120
+ if (collectionName === "aiFiles") {
121
+ return true;
122
+ }
123
+ if (!lookup.source.field) {
124
+ return false;
125
+ }
126
+ return ((_c = (_b = db.getFieldByPath(lookup.source.field)) == null ? void 0 : _b.options) == null ? void 0 : _c.target) === collectionName;
127
+ }
128
+ async function findSourceAttachments(ctx, lookups) {
129
+ var _a;
130
+ const attachmentsByLookup = /* @__PURE__ */ new Map();
131
+ const groups = /* @__PURE__ */ new Map();
132
+ for (const lookup of lookups) {
133
+ const dataSourceKey = lookup.source.dataSourceKey || "main";
134
+ const collectionName = lookup.source.collectionName;
135
+ const groupKey = `${dataSourceKey}:${collectionName}`;
136
+ groups.set(groupKey, [...groups.get(groupKey) ?? [], lookup]);
137
+ }
138
+ for (const [groupKey, groupLookups] of groups.entries()) {
139
+ const [dataSourceKey, collectionName] = groupKey.split(":");
140
+ const db = getSourceDatabase(ctx, dataSourceKey);
141
+ if (!db) {
142
+ continue;
143
+ }
144
+ const validLookups = groupLookups.filter((lookup) => isValidFileCollectionSource(db, lookup));
145
+ if (!validLookups.length) {
146
+ continue;
147
+ }
148
+ const filter = {
149
+ id: {
150
+ $in: [...new Set(validLookups.map((lookup) => lookup.id))]
151
+ }
152
+ };
153
+ if (collectionName === "aiFiles") {
154
+ filter.createdById = (_a = ctx.auth) == null ? void 0 : _a.user.id;
155
+ }
156
+ const records = await db.getRepository(collectionName).find({ filter });
157
+ for (const record of records) {
158
+ const attachment = record.toJSON();
159
+ attachmentsByLookup.set(`${dataSourceKey}:${collectionName}:${attachment.id}`, attachment);
160
+ }
161
+ }
162
+ return attachmentsByLookup;
163
+ }
164
+ async function findMessageAttachments(ctx, attachments) {
165
+ const lookups = [];
166
+ for (const attachment of attachments) {
167
+ const source = getAttachmentSource(attachment);
168
+ if (!source || shouldSkipAttachmentSourceLookup(source)) {
169
+ continue;
170
+ }
171
+ const id = getAttachmentId(attachment);
172
+ if (id == null) {
173
+ continue;
174
+ }
175
+ lookups.push({
176
+ id,
177
+ source
178
+ });
179
+ }
180
+ return findSourceAttachments(ctx, lookups);
181
+ }
182
+ function getMessageAttachmentLookupKey(attachment) {
183
+ const source = getAttachmentSource(attachment);
184
+ if (!source || shouldSkipAttachmentSourceLookup(source)) {
185
+ return null;
186
+ }
187
+ const id = getAttachmentId(attachment);
188
+ if (id == null) {
189
+ return null;
190
+ }
191
+ return getLookupKey({
192
+ id,
193
+ source
194
+ });
195
+ }
196
+ // Annotate the CommonJS export names for ESM import in node:
197
+ 0 && (module.exports = {
198
+ appendAIFileAttachmentSource,
199
+ findMessageAttachments,
200
+ getAttachmentId,
201
+ getAttachmentSource,
202
+ getMessageAttachmentLookupKey,
203
+ shouldSkipAttachmentSourceLookup
204
+ });
@@ -16,6 +16,7 @@ export type CachedDocumentLoaderOptions = {
16
16
  parsedFileExtname: string;
17
17
  supports: (file: ParseableFile) => boolean;
18
18
  };
19
+ export declare function getDocumentCacheKey(sourceFile: ParseableFile): string | null;
19
20
  export declare class CachedDocumentLoader {
20
21
  private readonly plugin;
21
22
  private readonly options;
@@ -36,7 +36,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
36
36
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
37
37
  var cached_exports = {};
38
38
  __export(cached_exports, {
39
- CachedDocumentLoader: () => CachedDocumentLoader
39
+ CachedDocumentLoader: () => CachedDocumentLoader,
40
+ getDocumentCacheKey: () => getDocumentCacheKey
40
41
  });
41
42
  module.exports = __toCommonJS(cached_exports);
42
43
  var import_promises = __toESM(require("node:fs/promises"));
@@ -44,6 +45,19 @@ var import_node_os = __toESM(require("node:os"));
44
45
  var import_node_path = __toESM(require("node:path"));
45
46
  var import_documents = require("@langchain/core/documents");
46
47
  var import_utils = require("./utils");
48
+ function getDocumentCacheKey(sourceFile) {
49
+ var _a;
50
+ if (!sourceFile) {
51
+ return null;
52
+ }
53
+ if (((_a = sourceFile.source) == null ? void 0 : _a.documentCache) === false) {
54
+ return null;
55
+ }
56
+ if (!sourceFile.id || !sourceFile.storageId) {
57
+ return null;
58
+ }
59
+ return `${sourceFile.id}@${sourceFile.storageId}`;
60
+ }
47
61
  class CachedDocumentLoader {
48
62
  constructor(plugin, options) {
49
63
  this.plugin = plugin;
@@ -151,16 +165,11 @@ class CachedDocumentLoader {
151
165
  return this._cache;
152
166
  }
153
167
  getCacheKey(sourceFile) {
154
- if (!sourceFile) {
155
- return null;
156
- }
157
- if (!sourceFile.id || !sourceFile.storageId) {
158
- return null;
159
- }
160
- return `${sourceFile.id}@${sourceFile.storageId}`;
168
+ return getDocumentCacheKey(sourceFile);
161
169
  }
162
170
  }
163
171
  // Annotate the CommonJS export names for ESM import in node:
164
172
  0 && (module.exports = {
165
- CachedDocumentLoader
173
+ CachedDocumentLoader,
174
+ getDocumentCacheKey
166
175
  });
@@ -13,5 +13,4 @@ export declare class DocumentLoader {
13
13
  private readonly fileManager;
14
14
  constructor(fileManager: PluginFileManagerServer);
15
15
  load(file: ParseableFile, options?: any): Promise<Document[]>;
16
- private streamToBlob;
17
16
  }
@@ -7,9 +7,11 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
+ var __create = Object.create;
10
11
  var __defProp = Object.defineProperty;
11
12
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
13
  var __getOwnPropNames = Object.getOwnPropertyNames;
14
+ var __getProtoOf = Object.getPrototypeOf;
13
15
  var __hasOwnProp = Object.prototype.hasOwnProperty;
14
16
  var __export = (target, all) => {
15
17
  for (var name in all)
@@ -23,6 +25,14 @@ var __copyProps = (to, from, except, desc) => {
23
25
  }
24
26
  return to;
25
27
  };
28
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
29
+ // If the importer is in node compatibility mode or this is not an ESM
30
+ // file that has been converted to a CommonJS file using a Babel-
31
+ // compatible transform (i.e. "__esModule" has not been set), then set
32
+ // "default" to the CommonJS "module.exports" for node compatibility.
33
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
34
+ mod
35
+ ));
26
36
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
37
  var loader_exports = {};
28
38
  __export(loader_exports, {
@@ -32,6 +42,11 @@ module.exports = __toCommonJS(loader_exports);
32
42
  var import_constants = require("./constants");
33
43
  var import_utils = require("./utils");
34
44
  var import_ai = require("@nocobase/ai");
45
+ var import_node_fs = require("node:fs");
46
+ var import_promises = require("node:fs/promises");
47
+ var import_node_os = __toESM(require("node:os"));
48
+ var import_node_path = __toESM(require("node:path"));
49
+ var import_promises2 = require("node:stream/promises");
35
50
  class DocumentLoader {
36
51
  constructor(fileManager) {
37
52
  this.fileManager = fileManager;
@@ -42,15 +57,17 @@ class DocumentLoader {
42
57
  return [];
43
58
  }
44
59
  const { stream, contentType } = await this.fileManager.getFileStream(file, options);
45
- const blob = await this.streamToBlob(stream, contentType ?? file.mimetype);
46
- return await (0, import_ai.loadByWorker)(extname, blob);
47
- }
48
- async streamToBlob(stream, mimeType = "application/octet-stream") {
49
- const chunks = [];
50
- for await (const chunk of stream) {
51
- chunks.push(chunk);
60
+ const tempDir = await (0, import_promises.mkdtemp)(import_node_path.default.join(import_node_os.default.tmpdir(), "nocobase-document-loader-"));
61
+ const tempFilePath = import_node_path.default.join(tempDir, `source${extname}`);
62
+ try {
63
+ await (0, import_promises2.pipeline)(stream, (0, import_node_fs.createWriteStream)(tempFilePath));
64
+ return await (0, import_ai.loadByWorker)(extname, {
65
+ filePath: tempFilePath,
66
+ mimeType: contentType ?? file.mimetype
67
+ });
68
+ } finally {
69
+ await (0, import_promises.rm)(tempDir, { recursive: true, force: true });
52
70
  }
53
- return new Blob(chunks, { type: mimeType });
54
71
  }
55
72
  }
56
73
  // Annotate the CommonJS export names for ESM import in node:
@@ -31,6 +31,13 @@ export type ParseableFile = {
31
31
  storageId?: number;
32
32
  meta?: Record<string, any>;
33
33
  size?: number;
34
+ source?: {
35
+ dataSourceKey?: string;
36
+ collectionName?: string;
37
+ field?: string;
38
+ documentCache?: boolean;
39
+ trustworthy?: boolean;
40
+ };
34
41
  };
35
42
  export type DocumentLoaderLike = {
36
43
  load(file: ParseableFile, options?: any): Promise<Document[]>;
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { LLMProvider, ParsedAttachmentResult } from './provider';
10
10
  import { ChatAnthropic } from '@langchain/anthropic';
11
+ import type { AttachmentModel } from '@nocobase/plugin-file-manager';
11
12
  import { Model } from '@nocobase/database';
12
13
  import { LLMProviderMeta } from '../manager/ai-manager';
13
14
  import { Context } from '@nocobase/actions';
@@ -36,7 +37,7 @@ export declare class AnthropicProvider extends LLMProvider {
36
37
  type: string;
37
38
  query: string;
38
39
  }[];
39
- protected convertToContent(ctx: Context, attachment: any): Promise<ParsedAttachmentResult>;
40
+ protected convertToContent(ctx: Context, attachment: AttachmentModel): Promise<ParsedAttachmentResult>;
40
41
  private setMaxTokens;
41
42
  }
42
43
  export declare const anthropicProviderOptions: LLMProviderMeta;
@@ -186,9 +186,7 @@ class AnthropicProvider extends import_provider.LLMProvider {
186
186
  });
187
187
  }
188
188
  async convertToContent(ctx, attachment) {
189
- const fileManager = this.app.pm.get("file-manager");
190
- const url = await fileManager.getFileURL(attachment);
191
- const data = await (0, import_utils2.encodeFile)(ctx, decodeURIComponent(url));
189
+ const data = await this.encodeAttachment(ctx, attachment);
192
190
  if (attachment.mimetype.startsWith("image/")) {
193
191
  return {
194
192
  placement: "contentBlocks",
@@ -9,7 +9,7 @@
9
9
  import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
10
10
  import { EmbeddingProvider, LLMProvider, ParsedAttachmentResult } from './provider';
11
11
  import { Model } from '@nocobase/database';
12
- import { AttachmentModel } from '@nocobase/plugin-file-manager';
12
+ import type { AttachmentModel } from '@nocobase/plugin-file-manager';
13
13
  import { LLMProviderMeta } from '../manager/ai-manager';
14
14
  import { EmbeddingsInterface } from '@langchain/core/embeddings';
15
15
  import { Context } from '@nocobase/actions';
@@ -34,7 +34,6 @@ module.exports = __toCommonJS(google_genai_exports);
34
34
  var import_google_genai = require("@langchain/google-genai");
35
35
  var import_provider = require("./provider");
36
36
  var import_utils = require("@nocobase/utils");
37
- var import_utils2 = require("../utils");
38
37
  var import_ai_manager = require("../manager/ai-manager");
39
38
  const GOOGLE_GEN_AI_URL = "https://generativelanguage.googleapis.com";
40
39
  class GoogleGenAIProvider extends import_provider.LLMProvider {
@@ -120,10 +119,8 @@ class GoogleGenAIProvider extends import_provider.LLMProvider {
120
119
  }
121
120
  async convertToContent(ctx, attachment) {
122
121
  var _a;
123
- const fileManager = this.app.pm.get("file-manager");
124
- const url = await fileManager.getFileURL(attachment);
122
+ const data = await this.encodeAttachment(ctx, attachment);
125
123
  if ((_a = attachment.mimetype) == null ? void 0 : _a.startsWith("image/")) {
126
- const data = await (0, import_utils2.encodeFile)(ctx, decodeURIComponent(url));
127
124
  return {
128
125
  placement: "contentBlocks",
129
126
  content: {
@@ -134,7 +131,6 @@ class GoogleGenAIProvider extends import_provider.LLMProvider {
134
131
  }
135
132
  };
136
133
  } else {
137
- const data = await (0, import_utils2.encodeFile)(ctx, decodeURIComponent(url));
138
134
  return {
139
135
  placement: "contentBlocks",
140
136
  content: {
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { BaseChatModel } from '@langchain/core/language_models/chat_models';
10
10
  import { Model } from '@nocobase/database';
11
- import { AttachmentModel } from '@nocobase/plugin-file-manager';
11
+ import { type AttachmentModel } from '@nocobase/plugin-file-manager';
12
12
  import { Application } from '@nocobase/server';
13
13
  import { AIChatContext, AIMessageInput } from '../types/ai-chat-conversation.type';
14
14
  import { EmbeddingsInterface } from '@langchain/core/embeddings';
@@ -76,7 +76,8 @@ export declare abstract class LLMProvider {
76
76
  parseAttachment(ctx: Context, attachment: AttachmentModel): Promise<ParsedAttachmentResult>;
77
77
  protected isApiSupportedAttachment(attachment: AttachmentModel): boolean;
78
78
  protected isDocumentLoaderSupportedAttachment(attachment: AttachmentModel): boolean;
79
- protected convertToContent(ctx: Context, attachment: any): Promise<ParsedAttachmentResult>;
79
+ protected encodeAttachment(ctx: Context, attachment: AttachmentModel): Promise<string>;
80
+ protected convertToContent(ctx: Context, attachment: AttachmentModel): Promise<ParsedAttachmentResult>;
80
81
  protected loadDocument(ctx: Context, attachment: any): Promise<any>;
81
82
  getStructuredOutputOptions(structuredOutput: AIChatContext['structuredOutput']): any;
82
83
  testFlight(): Promise<{
@@ -189,6 +189,15 @@ class LLMProvider {
189
189
  return (0, import_utils2.stripToolCallTags)(chunk);
190
190
  }
191
191
  async parseAttachment(ctx, attachment) {
192
+ var _a;
193
+ const dataSourceKey = (_a = attachment == null ? void 0 : attachment.source) == null ? void 0 : _a.dataSourceKey;
194
+ const isExternalAttachment = Boolean(dataSourceKey && dataSourceKey !== "main");
195
+ if (!(attachment == null ? void 0 : attachment.storageId) && !isExternalAttachment || !(attachment == null ? void 0 : attachment.filename)) {
196
+ return {
197
+ placement: "system",
198
+ content: "The user provided an attachment, but it is unavailable or invalid and cannot be parsed. Do not use this attachment as evidence; tell the user the attachment is unavailable."
199
+ };
200
+ }
192
201
  if (this.isApiSupportedAttachment(attachment)) {
193
202
  return await this.convertToContent(ctx, attachment);
194
203
  } else if (this.isDocumentLoaderSupportedAttachment(attachment)) {
@@ -218,10 +227,24 @@ class LLMProvider {
218
227
  const ext = import_node_path.default.extname((attachment == null ? void 0 : attachment.filename) ?? "").toLocaleLowerCase();
219
228
  return import_document_loader.SUPPORTED_DOCUMENT_EXTNAMES.includes(ext);
220
229
  }
221
- async convertToContent(ctx, attachment) {
230
+ async encodeAttachment(ctx, attachment) {
222
231
  const fileManager = this.app.pm.get("file-manager");
223
- const url = await fileManager.getFileURL(attachment);
224
- const data = await (0, import_utils2.encodeFile)(ctx, decodeURIComponent(url));
232
+ if (typeof ctx.get !== "function") {
233
+ const { stream: stream2 } = await fileManager.getFileStream(attachment);
234
+ return await (0, import_utils2.encodeReadableStream)(stream2);
235
+ }
236
+ const { stream } = await fileManager.getFileStream(attachment, {
237
+ requestOptions: {
238
+ headers: {
239
+ Referer: ctx.get("referer") || "",
240
+ "User-Agent": ctx.get("user-agent") || ""
241
+ }
242
+ }
243
+ });
244
+ return await (0, import_utils2.encodeReadableStream)(stream);
245
+ }
246
+ async convertToContent(ctx, attachment) {
247
+ const data = await this.encodeAttachment(ctx, attachment);
225
248
  if (attachment.mimetype.startsWith("image/")) {
226
249
  return {
227
250
  placement: "contentBlocks",
@@ -60,6 +60,34 @@ function serializeQueryFieldValue(value) {
60
60
  }
61
61
  return value;
62
62
  }
63
+ function getRecordFieldValue(record, field) {
64
+ return field.split(".").reduce((current, key) => {
65
+ if (current === null || current === void 0) {
66
+ return current;
67
+ }
68
+ if (typeof current.get === "function") {
69
+ return current.get(key);
70
+ }
71
+ return current[key];
72
+ }, record);
73
+ }
74
+ function getQueryFieldOptions(collection, field) {
75
+ const parts = field.split(".");
76
+ let currentCollection = collection;
77
+ for (const [index, part] of parts.entries()) {
78
+ const currentField = currentCollection == null ? void 0 : currentCollection.getField(part);
79
+ if (!currentField) {
80
+ return;
81
+ }
82
+ if (index === parts.length - 1) {
83
+ return currentField.options;
84
+ }
85
+ if (!currentField.isRelationField()) {
86
+ return;
87
+ }
88
+ currentCollection = currentField.targetCollection();
89
+ }
90
+ }
63
91
  class AIContextDatasourceManager {
64
92
  constructor(plugin) {
65
93
  this.plugin = plugin;
@@ -149,17 +177,16 @@ class AIContextDatasourceManager {
149
177
  }
150
178
  });
151
179
  }
152
- const { fields, filter, sort, offset, limit } = options;
153
- const result = await collection.repository.find({ fields, filter, sort, offset: offset ?? 0, limit });
180
+ const { fields, appends, filter, sort, offset, limit } = options;
181
+ const result = await collection.repository.find({ fields, appends, filter, sort, offset: offset ?? 0, limit });
154
182
  const total = await collection.repository.count({ fields, filter });
155
183
  const records = result.map(
156
184
  (x) => fields.map((field) => {
157
- var _a2;
158
- const { name, type } = ((_a2 = collection.getField(field)) == null ? void 0 : _a2.options) || {};
159
- const value = serializeQueryFieldValue(x[field]);
185
+ const { name, type } = getQueryFieldOptions(collection, field) || {};
186
+ const value = serializeQueryFieldValue(getRecordFieldValue(x, field));
160
187
  return {
161
- name,
162
- type,
188
+ name: field.includes(".") ? field : name || field,
189
+ type: type || "unknown",
163
190
  value
164
191
  };
165
192
  })
@@ -80,6 +80,7 @@ var import_sub_agents = require("./ai-employees/sub-agents");
80
80
  var import_employee = require("./workflow/nodes/employee");
81
81
  var import_ai_knowledge_base = require("./ai-employees/ai-knowledge-base");
82
82
  var import_llm_stream_manager = require("./manager/llm-stream-manager");
83
+ var import_attachments = require("./attachments");
83
84
  class PluginAIServer extends import_server.Plugin {
84
85
  features = new import_ai_feature_manager.AIPluginFeatureManagerImpl();
85
86
  aiManager = new import_ai_manager.AIManager(this);
@@ -218,6 +219,19 @@ class PluginAIServer extends import_server.Plugin {
218
219
  },
219
220
  { before: "createMiddleware" }
220
221
  );
222
+ this.app.resourceManager.use(
223
+ async (ctx, next) => {
224
+ const { resourceName, actionName } = ctx.action;
225
+ if (resourceName === "aiFiles" && actionName === "create") {
226
+ (0, import_attachments.appendAIFileAttachmentSource)(ctx.action.params.values);
227
+ }
228
+ await next();
229
+ if (resourceName === "aiFiles" && actionName === "create") {
230
+ (0, import_attachments.appendAIFileAttachmentSource)(ctx.body);
231
+ }
232
+ },
233
+ { after: "createMiddleware" }
234
+ );
221
235
  Object.entries(aiEmployeeActions).forEach(([name, action]) => {
222
236
  this.app.resourceManager.registerActionHandler(`aiEmployees:${name}`, action);
223
237
  });
@@ -103,6 +103,33 @@ const saveUserMessages = async (ctx, sessionId, messages, messageId) => {
103
103
  await conversation.addMessages(userMessages);
104
104
  });
105
105
  };
106
+ function isRecord(value) {
107
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
108
+ }
109
+ function normalizeIncomingMessageAttachments(ctx, messages) {
110
+ for (const message of messages) {
111
+ if (message.attachments == null) {
112
+ continue;
113
+ }
114
+ if (!Array.isArray(message.attachments)) {
115
+ throw new import_utils.ResourceActionError(400, ctx.t("Invalid attachment"));
116
+ }
117
+ message.attachments = message.attachments.map((attachment) => {
118
+ if (!isRecord(attachment) || !isRecord(attachment.source)) {
119
+ throw new import_utils.ResourceActionError(400, ctx.t("Invalid attachment"));
120
+ }
121
+ const source = { ...attachment.source };
122
+ delete source.trustworthy;
123
+ if (typeof source.collectionName !== "string" || !source.collectionName) {
124
+ throw new import_utils.ResourceActionError(400, ctx.t("Invalid attachment"));
125
+ }
126
+ return {
127
+ ...attachment,
128
+ source
129
+ };
130
+ });
131
+ }
132
+ }
106
133
  var aiConversations_default = {
107
134
  name: "aiConversations",
108
135
  middlewares: [
@@ -329,6 +356,7 @@ var aiConversations_default = {
329
356
  if (!Array.isArray(messages)) {
330
357
  throw new import_utils.ResourceActionError(400, ctx.t("messages must be an array"));
331
358
  }
359
+ normalizeIncomingMessageAttachments(ctx, messages);
332
360
  const userMessage = messages.find((message) => message.role === "user");
333
361
  if (!userMessage) {
334
362
  throw new import_utils.ResourceActionError(400, ctx.t("user message is required"));
@@ -633,6 +661,13 @@ var aiConversations_default = {
633
661
  if (!message) {
634
662
  ctx.throw(400);
635
663
  }
664
+ const messageConversation = await plugin.aiConversationsManager.getConversation({
665
+ sessionId: message.sessionId,
666
+ userId
667
+ });
668
+ if (!messageConversation) {
669
+ ctx.throw(400);
670
+ }
636
671
  const toolCalls = message.toolCalls;
637
672
  if (!(toolCalls == null ? void 0 : toolCalls.length)) {
638
673
  ctx.throw(400);
@@ -730,6 +765,14 @@ var aiConversations_default = {
730
765
  sendErrorResponse(ctx, "message not found");
731
766
  return next();
732
767
  }
768
+ const messageConversation = await plugin.aiConversationsManager.getConversation({
769
+ sessionId: message.sessionId,
770
+ userId
771
+ });
772
+ if (!messageConversation) {
773
+ sendErrorResponse(ctx, "conversation not found");
774
+ return next();
775
+ }
733
776
  const tools = message.toolCalls;
734
777
  if (!(tools == null ? void 0 : tools.length)) {
735
778
  sendErrorResponse(ctx, "No tool calls found");
@@ -746,7 +789,7 @@ var aiConversations_default = {
746
789
  webSearch,
747
790
  model: resolvedModel
748
791
  });
749
- const userDecisions = await plugin.aiConversationsManager.getUserDecisions(messageId);
792
+ const userDecisions = await plugin.aiConversationsManager.getUserDecisions(message.messageId);
750
793
  await aiEmployee.stream({
751
794
  userDecisions
752
795
  });
@@ -6,9 +6,11 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
+ /// <reference types="node" />
9
10
  import { Model } from '@nocobase/database';
10
11
  import { Context } from '@nocobase/actions';
11
12
  import { ToolsEntry } from '@nocobase/ai';
13
+ import type { Readable } from 'stream';
12
14
  export declare function sendSSEError(ctx: Context, error: Error | string, errorName?: string): void;
13
15
  export declare function stripToolCallTags(content: string): string | null;
14
16
  export declare function parseResponseMessage(row: Model): {
@@ -17,8 +19,9 @@ export declare function parseResponseMessage(row: Model): {
17
19
  content: any;
18
20
  role: any;
19
21
  };
20
- export declare function encodeLocalFile(url: string): Promise<string>;
21
- export declare function encodeFile(ctx: Context, url: string): Promise<string>;
22
+ export declare function encodeLocalFile(_url: string): Promise<void>;
23
+ export declare function encodeFile(ctx: Context, url: string): Promise<string | void>;
24
+ export declare function encodeReadableStream(stream: Readable): Promise<string>;
22
25
  export declare function parseVariables(ctx: Context, value: string): Promise<any>;
23
26
  export declare const buildTool: (toolsEntry: ToolsEntry) => import("@langchain/core/dist/tools").DynamicTool<any, unknown>;
24
27
  export declare class ResourceActionError extends Error {