n8n-nodes-google-files-api 1.0.0 → 1.0.1

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.
@@ -7,13 +7,13 @@ class GoogleFileSearchStore {
7
7
  this.description = {
8
8
  displayName: 'Google File Search Store',
9
9
  name: 'googleFileSearchStore',
10
- icon: 'file:google-file-search.svg',
10
+ icon: 'file:googleGemini.svg',
11
11
  group: ['transform'],
12
12
  version: 1,
13
13
  subtitle: '={{$parameter["operation"]}}',
14
14
  description: 'Gemini File Search Store를 관리하고 RAG 기반 문서 검색을 수행합니다',
15
15
  defaults: {
16
- name: 'Google File Search Store',
16
+ name: 'File Search Store',
17
17
  },
18
18
  inputs: ['main'],
19
19
  outputs: ['main'],
@@ -24,101 +24,187 @@ class GoogleFileSearchStore {
24
24
  },
25
25
  ],
26
26
  properties: [
27
+ {
28
+ displayName: 'Resource',
29
+ name: 'resource',
30
+ type: 'options',
31
+ noDataExpression: true,
32
+ options: [
33
+ {
34
+ name: 'Store',
35
+ value: 'store',
36
+ description: 'File Search Store 관리',
37
+ },
38
+ {
39
+ name: 'Document',
40
+ value: 'document',
41
+ description: 'Store 내 문서 관리',
42
+ },
43
+ {
44
+ name: 'Operation Status',
45
+ value: 'operation',
46
+ description: '비동기 작업 상태 확인',
47
+ },
48
+ ],
49
+ default: 'store',
50
+ },
51
+ // Store 작업
27
52
  {
28
53
  displayName: 'Operation',
29
54
  name: 'operation',
30
55
  type: 'options',
31
56
  noDataExpression: true,
57
+ displayOptions: {
58
+ show: {
59
+ resource: ['store'],
60
+ },
61
+ },
32
62
  options: [
33
63
  {
34
- name: 'Create Store',
35
- value: 'createStore',
36
- action: 'Create a new file search store',
37
- description: ' File Search Store를 생성합니다',
64
+ name: 'Create',
65
+ value: 'create',
66
+ description: ' File Search Store 생성',
67
+ action: 'Create a store',
38
68
  },
39
69
  {
40
- name: 'List Stores',
41
- value: 'listStores',
42
- action: 'List all stores',
43
- description: '모든 File Search Store 목록을 조회합니다',
70
+ name: 'List',
71
+ value: 'list',
72
+ description: 'Store 목록 조회',
73
+ action: 'List stores',
44
74
  },
45
75
  {
46
- name: 'Get Store',
47
- value: 'getStore',
48
- action: 'Get store details',
49
- description: 'Store 상세 정보를 조회합니다',
76
+ name: 'Get',
77
+ value: 'get',
78
+ description: 'Store 상세 정보 조회',
79
+ action: 'Get store info',
50
80
  },
51
81
  {
52
- name: 'Delete Store',
53
- value: 'deleteStore',
82
+ name: 'Delete',
83
+ value: 'delete',
84
+ description: 'Store 삭제',
54
85
  action: 'Delete a store',
55
- description: 'Store를 삭제합니다',
56
86
  },
87
+ ],
88
+ default: 'create',
89
+ },
90
+ // Document 작업
91
+ {
92
+ displayName: 'Operation',
93
+ name: 'operation',
94
+ type: 'options',
95
+ noDataExpression: true,
96
+ displayOptions: {
97
+ show: {
98
+ resource: ['document'],
99
+ },
100
+ },
101
+ options: [
57
102
  {
58
- name: 'Upload Document',
59
- value: 'uploadDocument',
103
+ name: 'Upload',
104
+ value: 'upload',
105
+ description: 'Store에 문서 업로드',
60
106
  action: 'Upload document to store',
61
- description: 'Store에 문서를 업로드하고 인덱싱합니다',
62
107
  },
63
108
  {
64
- name: 'Query Documents',
65
- value: 'queryDocuments',
66
- action: 'Query documents in store',
67
- description: 'Store에서 시맨틱 검색을 수행합니다',
109
+ name: 'Import File',
110
+ value: 'importFile',
111
+ description: 'Files API 파일을 Store로 가져오기',
112
+ action: 'Import file to store',
113
+ },
114
+ {
115
+ name: 'List',
116
+ value: 'list',
117
+ description: 'Store 내 문서 목록 조회',
118
+ action: 'List documents',
119
+ },
120
+ {
121
+ name: 'Get',
122
+ value: 'get',
123
+ description: '문서 상세 정보 조회',
124
+ action: 'Get document info',
68
125
  },
69
126
  {
70
- name: 'Get Operation Status',
71
- value: 'getOperationStatus',
127
+ name: 'Delete',
128
+ value: 'delete',
129
+ description: '문서 삭제',
130
+ action: 'Delete a document',
131
+ },
132
+ {
133
+ name: 'Query',
134
+ value: 'query',
135
+ description: 'RAG 기반 시맨틱 검색',
136
+ action: 'Query documents with RAG',
137
+ },
138
+ ],
139
+ default: 'upload',
140
+ },
141
+ // Operation Status 작업
142
+ {
143
+ displayName: 'Operation',
144
+ name: 'operation',
145
+ type: 'options',
146
+ noDataExpression: true,
147
+ displayOptions: {
148
+ show: {
149
+ resource: ['operation'],
150
+ },
151
+ },
152
+ options: [
153
+ {
154
+ name: 'Get Status',
155
+ value: 'getStatus',
156
+ description: '비동기 작업 상태 확인',
72
157
  action: 'Get operation status',
73
- description: '비동기 작업의 상태를 확인합니다',
74
158
  },
75
159
  ],
76
- default: 'createStore',
160
+ default: 'getStatus',
77
161
  },
78
- // Create Store 파라미터
162
+ // Store 작업용 필드
79
163
  {
80
164
  displayName: 'Display Name',
81
- name: 'storeDisplayName',
165
+ name: 'displayName',
82
166
  type: 'string',
83
- required: true,
84
167
  default: '',
168
+ required: true,
85
169
  displayOptions: {
86
170
  show: {
87
- operation: ['createStore'],
171
+ resource: ['store'],
172
+ operation: ['create'],
88
173
  },
89
174
  },
90
- description: 'Store 표시 이름',
175
+ description: 'Store 표시 이름',
91
176
  },
92
- // Store 관련 공통 파라미터
93
177
  {
94
178
  displayName: 'Store Name',
95
179
  name: 'storeName',
96
180
  type: 'string',
97
- required: true,
98
181
  default: '',
182
+ required: true,
99
183
  displayOptions: {
100
184
  show: {
101
- operation: ['getStore', 'deleteStore', 'uploadDocument', 'queryDocuments'],
185
+ resource: ['store'],
186
+ operation: ['get', 'delete'],
102
187
  },
103
188
  },
104
- description: 'File Search Store 이름 (예: fileSearchStores/abc123)',
189
+ placeholder: 'fileSearchStores/abc123',
190
+ description: 'Store 이름',
105
191
  },
106
- // List Stores 파라미터
107
192
  {
108
193
  displayName: 'Page Size',
109
194
  name: 'pageSize',
110
195
  type: 'number',
196
+ default: 10,
111
197
  typeOptions: {
112
198
  minValue: 1,
113
- maxValue: 100,
199
+ maxValue: 20,
114
200
  },
115
- default: 100,
116
201
  displayOptions: {
117
202
  show: {
118
- operation: ['listStores'],
203
+ resource: ['store'],
204
+ operation: ['list'],
119
205
  },
120
206
  },
121
- description: ' 페이지에 가져올 Store 수',
207
+ description: '반환할 Store 수 (1-20)',
122
208
  },
123
209
  {
124
210
  displayName: 'Page Token',
@@ -127,12 +213,28 @@ class GoogleFileSearchStore {
127
213
  default: '',
128
214
  displayOptions: {
129
215
  show: {
130
- operation: ['listStores'],
216
+ resource: ['store'],
217
+ operation: ['list'],
218
+ },
219
+ },
220
+ description: '다음 페이지 토큰',
221
+ },
222
+ // Document 작업용 필드
223
+ {
224
+ displayName: 'Store Name',
225
+ name: 'storeName',
226
+ type: 'string',
227
+ default: '',
228
+ required: true,
229
+ displayOptions: {
230
+ show: {
231
+ resource: ['document'],
232
+ operation: ['upload', 'importFile', 'list', 'query'],
131
233
  },
132
234
  },
133
- description: '다음 페이지를 가져오기 위한 토큰',
235
+ placeholder: 'fileSearchStores/abc123',
236
+ description: 'Store 이름',
134
237
  },
135
- // Upload Document 파라미터
136
238
  {
137
239
  displayName: 'Binary Property',
138
240
  name: 'binaryPropertyName',
@@ -141,32 +243,152 @@ class GoogleFileSearchStore {
141
243
  required: true,
142
244
  displayOptions: {
143
245
  show: {
144
- operation: ['uploadDocument'],
246
+ resource: ['document'],
247
+ operation: ['upload'],
248
+ },
249
+ },
250
+ description: '업로드할 파일이 포함된 바이너리 속성명',
251
+ },
252
+ {
253
+ displayName: 'Display Name',
254
+ name: 'displayName',
255
+ type: 'string',
256
+ default: '',
257
+ displayOptions: {
258
+ show: {
259
+ resource: ['document'],
260
+ operation: ['upload'],
261
+ },
262
+ },
263
+ description: '문서 표시 이름 (선택사항)',
264
+ },
265
+ {
266
+ displayName: 'File Name',
267
+ name: 'fileName',
268
+ type: 'string',
269
+ default: '',
270
+ required: true,
271
+ displayOptions: {
272
+ show: {
273
+ resource: ['document'],
274
+ operation: ['importFile'],
275
+ },
276
+ },
277
+ placeholder: 'files/abc123',
278
+ description: 'Files API 파일 이름',
279
+ },
280
+ {
281
+ displayName: 'Document Name',
282
+ name: 'documentName',
283
+ type: 'string',
284
+ default: '',
285
+ required: true,
286
+ displayOptions: {
287
+ show: {
288
+ resource: ['document'],
289
+ operation: ['get', 'delete'],
290
+ },
291
+ },
292
+ placeholder: 'fileSearchStores/abc123/documents/xyz789',
293
+ description: '문서 이름',
294
+ },
295
+ {
296
+ displayName: 'Page Size',
297
+ name: 'pageSize',
298
+ type: 'number',
299
+ default: 10,
300
+ typeOptions: {
301
+ minValue: 1,
302
+ maxValue: 100,
303
+ },
304
+ displayOptions: {
305
+ show: {
306
+ resource: ['document'],
307
+ operation: ['list'],
308
+ },
309
+ },
310
+ description: '반환할 문서 수 (1-100)',
311
+ },
312
+ {
313
+ displayName: 'Page Token',
314
+ name: 'pageToken',
315
+ type: 'string',
316
+ default: '',
317
+ displayOptions: {
318
+ show: {
319
+ resource: ['document'],
320
+ operation: ['list'],
321
+ },
322
+ },
323
+ description: '다음 페이지 토큰',
324
+ },
325
+ // Query 작업용 필드
326
+ {
327
+ displayName: 'Query',
328
+ name: 'query',
329
+ type: 'string',
330
+ default: '',
331
+ required: true,
332
+ displayOptions: {
333
+ show: {
334
+ resource: ['document'],
335
+ operation: ['query'],
336
+ },
337
+ },
338
+ description: '검색 쿼리',
339
+ },
340
+ {
341
+ displayName: 'Model',
342
+ name: 'model',
343
+ type: 'options',
344
+ displayOptions: {
345
+ show: {
346
+ resource: ['document'],
347
+ operation: ['query'],
145
348
  },
146
349
  },
147
- description: '업로드할 바이너리 데이터가 있는 속성명',
350
+ options: [
351
+ {
352
+ name: 'Gemini 2.5 Flash',
353
+ value: 'gemini-2.5-flash-preview-05-20',
354
+ },
355
+ {
356
+ name: 'Gemini 2.5 Pro',
357
+ value: 'gemini-2.5-pro-preview-05-06',
358
+ },
359
+ {
360
+ name: 'Gemini 2.0 Flash',
361
+ value: 'gemini-2.0-flash',
362
+ },
363
+ ],
364
+ default: 'gemini-2.5-flash-preview-05-20',
365
+ description: '사용할 Gemini 모델',
148
366
  },
149
367
  {
150
- displayName: 'Document Display Name',
151
- name: 'documentDisplayName',
368
+ displayName: 'Metadata Filter',
369
+ name: 'metadataFilter',
152
370
  type: 'string',
153
371
  default: '',
154
372
  displayOptions: {
155
373
  show: {
156
- operation: ['uploadDocument'],
374
+ resource: ['document'],
375
+ operation: ['query'],
157
376
  },
158
377
  },
159
- description: '문서의 표시 이름 (비어있으면 파일명 사용)',
378
+ placeholder: 'author="John Doe"',
379
+ description: '메타데이터 필터 (선택사항)',
160
380
  },
381
+ // Chunking Options
161
382
  {
162
383
  displayName: 'Chunking Options',
163
384
  name: 'chunkingOptions',
164
385
  type: 'collection',
165
- placeholder: 'Add Option',
386
+ placeholder: 'Add Chunking Option',
166
387
  default: {},
167
388
  displayOptions: {
168
389
  show: {
169
- operation: ['uploadDocument'],
390
+ resource: ['document'],
391
+ operation: ['upload', 'importFile'],
170
392
  },
171
393
  },
172
394
  options: [
@@ -174,26 +396,19 @@ class GoogleFileSearchStore {
174
396
  displayName: 'Max Tokens Per Chunk',
175
397
  name: 'maxTokensPerChunk',
176
398
  type: 'number',
177
- typeOptions: {
178
- minValue: 50,
179
- maxValue: 1000,
180
- },
181
- default: 200,
399
+ default: 256,
182
400
  description: '청크당 최대 토큰 수',
183
401
  },
184
402
  {
185
403
  displayName: 'Max Overlap Tokens',
186
404
  name: 'maxOverlapTokens',
187
405
  type: 'number',
188
- typeOptions: {
189
- minValue: 0,
190
- maxValue: 100,
191
- },
192
- default: 20,
406
+ default: 64,
193
407
  description: '청크 간 중복 토큰 수',
194
408
  },
195
409
  ],
196
410
  },
411
+ // Custom Metadata
197
412
  {
198
413
  displayName: 'Custom Metadata',
199
414
  name: 'customMetadata',
@@ -201,16 +416,17 @@ class GoogleFileSearchStore {
201
416
  typeOptions: {
202
417
  multipleValues: true,
203
418
  },
419
+ placeholder: 'Add Metadata',
204
420
  default: {},
205
421
  displayOptions: {
206
422
  show: {
207
- operation: ['uploadDocument'],
423
+ resource: ['document'],
424
+ operation: ['upload', 'importFile'],
208
425
  },
209
426
  },
210
- description: '문서에 추가할 메타데이터',
211
427
  options: [
212
428
  {
213
- name: 'metadataValues',
429
+ name: 'metadata',
214
430
  displayName: 'Metadata',
215
431
  values: [
216
432
  {
@@ -220,17 +436,6 @@ class GoogleFileSearchStore {
220
436
  default: '',
221
437
  description: '메타데이터 키',
222
438
  },
223
- {
224
- displayName: 'Value Type',
225
- name: 'valueType',
226
- type: 'options',
227
- options: [
228
- { name: 'String', value: 'string' },
229
- { name: 'Numeric', value: 'numeric' },
230
- ],
231
- default: 'string',
232
- description: '값의 타입',
233
- },
234
439
  {
235
440
  displayName: 'Value',
236
441
  name: 'value',
@@ -238,80 +443,50 @@ class GoogleFileSearchStore {
238
443
  default: '',
239
444
  description: '메타데이터 값',
240
445
  },
446
+ {
447
+ displayName: 'Type',
448
+ name: 'type',
449
+ type: 'options',
450
+ options: [
451
+ { name: 'String', value: 'string' },
452
+ { name: 'Number', value: 'number' },
453
+ ],
454
+ default: 'string',
455
+ description: '값 타입',
456
+ },
241
457
  ],
242
458
  },
243
459
  ],
244
460
  },
245
- // Query Documents 파라미터
461
+ // Force Delete
246
462
  {
247
- displayName: 'Query',
248
- name: 'query',
249
- type: 'string',
250
- typeOptions: {
251
- rows: 3,
252
- },
253
- required: true,
254
- default: '',
255
- displayOptions: {
256
- show: {
257
- operation: ['queryDocuments'],
258
- },
259
- },
260
- description: '검색할 질문 또는 쿼리',
261
- },
262
- {
263
- displayName: 'Metadata Filter',
264
- name: 'metadataFilter',
265
- type: 'string',
266
- default: '',
267
- displayOptions: {
268
- show: {
269
- operation: ['queryDocuments'],
270
- },
271
- },
272
- description: 'AIP-160 형식의 메타데이터 필터 (예: author="홍길동")',
273
- },
274
- {
275
- displayName: 'Include Citations',
276
- name: 'includeCitations',
463
+ displayName: 'Force Delete',
464
+ name: 'forceDelete',
277
465
  type: 'boolean',
278
- default: true,
279
- displayOptions: {
280
- show: {
281
- operation: ['queryDocuments'],
282
- },
283
- },
284
- description: '응답에 인용 출처 포함 여부',
285
- },
286
- {
287
- displayName: 'Max Results',
288
- name: 'maxResults',
289
- type: 'number',
290
- typeOptions: {
291
- minValue: 1,
292
- maxValue: 20,
293
- },
294
- default: 10,
466
+ default: false,
295
467
  displayOptions: {
296
468
  show: {
297
- operation: ['queryDocuments'],
469
+ resource: ['document'],
470
+ operation: ['delete'],
298
471
  },
299
472
  },
300
- description: '반환할 최대 결과 수',
473
+ description: '강제 삭제 여부',
301
474
  },
302
- // Get Operation Status 파라미터
475
+ // Operation Status 필드
303
476
  {
304
477
  displayName: 'Operation Name',
305
478
  name: 'operationName',
306
479
  type: 'string',
307
- required: true,
308
480
  default: '',
481
+ required: true,
309
482
  displayOptions: {
310
483
  show: {
311
- operation: ['getOperationStatus'],
484
+ resource: ['operation'],
485
+ operation: ['getStatus'],
312
486
  },
313
487
  },
314
- description: '확인할 작업의 이름 (예: operations/abc123)',
488
+ placeholder: 'operations/abc123',
489
+ description: '작업 이름',
315
490
  },
316
491
  ],
317
492
  };
@@ -319,263 +494,234 @@ class GoogleFileSearchStore {
319
494
  async execute() {
320
495
  const items = this.getInputData();
321
496
  const returnData = [];
497
+ const resource = this.getNodeParameter('resource', 0);
322
498
  const operation = this.getNodeParameter('operation', 0);
323
- const credentials = await this.getCredentials('googleGeminiApi');
324
- const apiKey = credentials.apiKey;
325
499
  const baseUrl = 'https://generativelanguage.googleapis.com';
326
- const handleApiError = (error) => {
327
- const err = error;
328
- if (err.response) {
329
- const statusCode = err.response.status;
330
- const apiMessage = err.response.data?.error?.message || 'Unknown API error';
331
- if (statusCode === 400) {
332
- return new n8n_workflow_1.NodeOperationError(this.getNode(), `Bad request: ${apiMessage}`);
333
- }
334
- else if (statusCode === 403) {
335
- return new n8n_workflow_1.NodeOperationError(this.getNode(), 'API Key가 유효하지 않거나 권한이 없습니다');
336
- }
337
- else if (statusCode === 404) {
338
- return new n8n_workflow_1.NodeOperationError(this.getNode(), '리소스를 찾을 수 없습니다');
339
- }
340
- else if (statusCode === 429) {
341
- return new n8n_workflow_1.NodeOperationError(this.getNode(), 'API 요청 한도를 초과했습니다. 잠시 후 다시 시도해주세요');
342
- }
343
- return new n8n_workflow_1.NodeOperationError(this.getNode(), apiMessage);
344
- }
345
- return new n8n_workflow_1.NodeOperationError(this.getNode(), err.message || 'Unknown error occurred');
346
- };
347
500
  for (let i = 0; i < items.length; i++) {
348
501
  try {
349
- let responseData;
350
- if (operation === 'createStore') {
351
- // Create Store operation
352
- const displayName = this.getNodeParameter('storeDisplayName', i);
353
- try {
354
- responseData = await this.helpers.httpRequest({
502
+ if (resource === 'store') {
503
+ if (operation === 'create') {
504
+ const displayName = this.getNodeParameter('displayName', i);
505
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'googleGeminiApi', {
355
506
  method: 'POST',
356
507
  url: `${baseUrl}/v1beta/fileSearchStores`,
357
- qs: { key: apiKey },
358
- headers: {
359
- 'Content-Type': 'application/json',
360
- },
361
- body: {
362
- display_name: displayName,
363
- },
364
- json: true,
508
+ body: { displayName },
365
509
  });
510
+ returnData.push({ json: response });
366
511
  }
367
- catch (error) {
368
- throw handleApiError(error);
369
- }
370
- }
371
- else if (operation === 'listStores') {
372
- // List Stores operation
373
- const pageSize = this.getNodeParameter('pageSize', i);
374
- const pageToken = this.getNodeParameter('pageToken', i);
375
- const qs = {
376
- key: apiKey,
377
- pageSize,
378
- };
379
- if (pageToken) {
380
- qs.pageToken = pageToken;
381
- }
382
- try {
383
- responseData = await this.helpers.httpRequest({
512
+ else if (operation === 'list') {
513
+ const pageSize = this.getNodeParameter('pageSize', i);
514
+ const pageToken = this.getNodeParameter('pageToken', i);
515
+ const qs = { pageSize };
516
+ if (pageToken) {
517
+ qs.pageToken = pageToken;
518
+ }
519
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'googleGeminiApi', {
384
520
  method: 'GET',
385
521
  url: `${baseUrl}/v1beta/fileSearchStores`,
386
522
  qs,
387
- json: true,
388
523
  });
524
+ returnData.push({ json: response });
389
525
  }
390
- catch (error) {
391
- throw handleApiError(error);
392
- }
393
- }
394
- else if (operation === 'getStore') {
395
- // Get Store operation
396
- const storeName = this.getNodeParameter('storeName', i);
397
- const normalizedStoreName = storeName.startsWith('fileSearchStores/')
398
- ? storeName
399
- : `fileSearchStores/${storeName}`;
400
- try {
401
- responseData = await this.helpers.httpRequest({
526
+ else if (operation === 'get') {
527
+ const storeName = this.getNodeParameter('storeName', i);
528
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'googleGeminiApi', {
402
529
  method: 'GET',
403
- url: `${baseUrl}/v1beta/${normalizedStoreName}`,
404
- qs: { key: apiKey },
405
- json: true,
530
+ url: `${baseUrl}/v1beta/${storeName}`,
406
531
  });
532
+ returnData.push({ json: response });
407
533
  }
408
- catch (error) {
409
- throw handleApiError(error);
410
- }
411
- }
412
- else if (operation === 'deleteStore') {
413
- // Delete Store operation
414
- const storeName = this.getNodeParameter('storeName', i);
415
- const normalizedStoreName = storeName.startsWith('fileSearchStores/')
416
- ? storeName
417
- : `fileSearchStores/${storeName}`;
418
- try {
419
- await this.helpers.httpRequest({
534
+ else if (operation === 'delete') {
535
+ const storeName = this.getNodeParameter('storeName', i);
536
+ await this.helpers.httpRequestWithAuthentication.call(this, 'googleGeminiApi', {
420
537
  method: 'DELETE',
421
- url: `${baseUrl}/v1beta/${normalizedStoreName}`,
422
- qs: { key: apiKey },
423
- json: true,
538
+ url: `${baseUrl}/v1beta/${storeName}`,
424
539
  });
425
- responseData = { success: true, deletedStore: normalizedStoreName };
426
- }
427
- catch (error) {
428
- throw handleApiError(error);
540
+ returnData.push({ json: { success: true, deletedStore: storeName } });
429
541
  }
430
542
  }
431
- else if (operation === 'uploadDocument') {
432
- // Upload Document operation
433
- const storeName = this.getNodeParameter('storeName', i);
434
- const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
435
- const documentDisplayName = this.getNodeParameter('documentDisplayName', i);
436
- const chunkingOptions = this.getNodeParameter('chunkingOptions', i);
437
- const customMetadata = this.getNodeParameter('customMetadata', i);
438
- const normalizedStoreName = storeName.startsWith('fileSearchStores/')
439
- ? storeName
440
- : `fileSearchStores/${storeName}`;
441
- const binaryData = items[i].binary?.[binaryPropertyName];
442
- if (!binaryData) {
443
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `No binary data found for property "${binaryPropertyName}"`, { itemIndex: i });
444
- }
445
- const buffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
446
- const mimeType = binaryData.mimeType || 'application/octet-stream';
447
- const fileName = documentDisplayName || binaryData.fileName || 'unnamed_document';
448
- // Build metadata
449
- const metadata = {};
450
- if (customMetadata.metadataValues) {
451
- const metadataEntries = customMetadata.metadataValues;
452
- for (const entry of metadataEntries) {
453
- if (entry.key) {
454
- if (entry.valueType === 'numeric') {
455
- metadata[entry.key] = { numericValue: parseFloat(entry.value) };
456
- }
457
- else {
458
- metadata[entry.key] = { stringValue: entry.value };
459
- }
543
+ else if (resource === 'document') {
544
+ if (operation === 'upload') {
545
+ const storeName = this.getNodeParameter('storeName', i);
546
+ const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
547
+ const displayName = this.getNodeParameter('displayName', i);
548
+ const chunkingOptions = this.getNodeParameter('chunkingOptions', i);
549
+ const customMetadata = this.getNodeParameter('customMetadata', i);
550
+ const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
551
+ const buffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
552
+ const mimeType = binaryData.mimeType || 'application/octet-stream';
553
+ const fileName = displayName || binaryData.fileName || 'document';
554
+ const documentConfig = {
555
+ displayName: fileName,
556
+ };
557
+ if (chunkingOptions.maxTokensPerChunk || chunkingOptions.maxOverlapTokens) {
558
+ documentConfig.chunkingConfig = {};
559
+ if (chunkingOptions.maxTokensPerChunk) {
560
+ documentConfig.chunkingConfig.maxTokensPerChunk = chunkingOptions.maxTokensPerChunk;
561
+ }
562
+ if (chunkingOptions.maxOverlapTokens) {
563
+ documentConfig.chunkingConfig.maxOverlapTokens = chunkingOptions.maxOverlapTokens;
460
564
  }
461
565
  }
462
- }
463
- // Build chunking config
464
- const chunkingConfig = {};
465
- if (chunkingOptions.maxTokensPerChunk) {
466
- chunkingConfig.maxTokensPerChunk = chunkingOptions.maxTokensPerChunk;
467
- }
468
- if (chunkingOptions.maxOverlapTokens) {
469
- chunkingConfig.maxOverlapTokens = chunkingOptions.maxOverlapTokens;
470
- }
471
- const boundary = '---n8n-boundary-' + Date.now().toString(16);
472
- const documentConfig = {
473
- display_name: fileName,
474
- };
475
- if (Object.keys(metadata).length > 0) {
476
- documentConfig.custom_metadata = metadata;
477
- }
478
- if (Object.keys(chunkingConfig).length > 0) {
479
- documentConfig.chunking_config = chunkingConfig;
480
- }
481
- const metadataPart = JSON.stringify({ document: documentConfig });
482
- const bodyParts = [
483
- Buffer.from(`--${boundary}\r\n`),
484
- Buffer.from('Content-Type: application/json; charset=UTF-8\r\n\r\n'),
485
- Buffer.from(metadataPart + '\r\n'),
486
- Buffer.from(`--${boundary}\r\n`),
487
- Buffer.from(`Content-Type: ${mimeType}\r\n\r\n`),
488
- buffer,
489
- Buffer.from(`\r\n--${boundary}--\r\n`),
490
- ];
491
- const body = Buffer.concat(bodyParts);
492
- try {
493
- const response = await this.helpers.httpRequest({
566
+ if (customMetadata?.metadata?.length) {
567
+ documentConfig.customMetadata = customMetadata.metadata.map((m) => ({
568
+ key: m.key,
569
+ [m.type === 'number' ? 'numericValue' : 'stringValue']: m.type === 'number' ? Number(m.value) : m.value,
570
+ }));
571
+ }
572
+ const boundary = '---n8n-boundary-' + Date.now();
573
+ const metadata = JSON.stringify({ document: documentConfig });
574
+ const body = Buffer.concat([
575
+ Buffer.from(`--${boundary}\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n${metadata}\r\n--${boundary}\r\nContent-Type: ${mimeType}\r\n\r\n`),
576
+ buffer,
577
+ Buffer.from(`\r\n--${boundary}--`),
578
+ ]);
579
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'googleGeminiApi', {
494
580
  method: 'POST',
495
- url: `${baseUrl}/upload/v1beta/${normalizedStoreName}/documents`,
496
- qs: { key: apiKey },
581
+ url: `${baseUrl}/upload/v1beta/${storeName}:uploadToFileSearchStore`,
497
582
  headers: {
498
583
  'Content-Type': `multipart/related; boundary=${boundary}`,
499
- 'Content-Length': body.length.toString(),
500
584
  },
501
585
  body,
502
586
  json: false,
503
587
  });
504
- responseData = typeof response === 'string' ? JSON.parse(response) : response;
588
+ returnData.push({ json: JSON.parse(response) });
505
589
  }
506
- catch (error) {
507
- throw handleApiError(error);
590
+ else if (operation === 'importFile') {
591
+ const storeName = this.getNodeParameter('storeName', i);
592
+ const fileName = this.getNodeParameter('fileName', i);
593
+ const chunkingOptions = this.getNodeParameter('chunkingOptions', i);
594
+ const customMetadata = this.getNodeParameter('customMetadata', i);
595
+ const body = { source: { fileSource: { name: fileName } } };
596
+ if (chunkingOptions.maxTokensPerChunk || chunkingOptions.maxOverlapTokens) {
597
+ body.chunkingConfig = {};
598
+ if (chunkingOptions.maxTokensPerChunk) {
599
+ body.chunkingConfig.maxTokensPerChunk = chunkingOptions.maxTokensPerChunk;
600
+ }
601
+ if (chunkingOptions.maxOverlapTokens) {
602
+ body.chunkingConfig.maxOverlapTokens = chunkingOptions.maxOverlapTokens;
603
+ }
604
+ }
605
+ if (customMetadata?.metadata?.length) {
606
+ body.customMetadata = customMetadata.metadata.map((m) => ({
607
+ key: m.key,
608
+ [m.type === 'number' ? 'numericValue' : 'stringValue']: m.type === 'number' ? Number(m.value) : m.value,
609
+ }));
610
+ }
611
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'googleGeminiApi', {
612
+ method: 'POST',
613
+ url: `${baseUrl}/v1beta/${storeName}:importFile`,
614
+ body,
615
+ });
616
+ returnData.push({ json: response });
508
617
  }
509
- }
510
- else if (operation === 'queryDocuments') {
511
- // Query Documents operation
512
- const storeName = this.getNodeParameter('storeName', i);
513
- const query = this.getNodeParameter('query', i);
514
- const metadataFilter = this.getNodeParameter('metadataFilter', i);
515
- const includeCitations = this.getNodeParameter('includeCitations', i);
516
- const maxResults = this.getNodeParameter('maxResults', i);
517
- const normalizedStoreName = storeName.startsWith('fileSearchStores/')
518
- ? storeName
519
- : `fileSearchStores/${storeName}`;
520
- const requestBody = {
521
- query,
522
- maxResults,
523
- includeCitations,
524
- };
525
- if (metadataFilter) {
526
- requestBody.metadataFilter = metadataFilter;
618
+ else if (operation === 'list') {
619
+ const storeName = this.getNodeParameter('storeName', i);
620
+ const pageSize = this.getNodeParameter('pageSize', i);
621
+ const pageToken = this.getNodeParameter('pageToken', i);
622
+ const qs = { pageSize };
623
+ if (pageToken) {
624
+ qs.pageToken = pageToken;
625
+ }
626
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'googleGeminiApi', {
627
+ method: 'GET',
628
+ url: `${baseUrl}/v1beta/${storeName}/documents`,
629
+ qs,
630
+ });
631
+ returnData.push({ json: response });
632
+ }
633
+ else if (operation === 'get') {
634
+ const documentName = this.getNodeParameter('documentName', i);
635
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'googleGeminiApi', {
636
+ method: 'GET',
637
+ url: `${baseUrl}/v1beta/${documentName}`,
638
+ });
639
+ returnData.push({ json: response });
640
+ }
641
+ else if (operation === 'delete') {
642
+ const documentName = this.getNodeParameter('documentName', i);
643
+ const forceDelete = this.getNodeParameter('forceDelete', i);
644
+ const qs = {};
645
+ if (forceDelete) {
646
+ qs.force = true;
647
+ }
648
+ await this.helpers.httpRequestWithAuthentication.call(this, 'googleGeminiApi', {
649
+ method: 'DELETE',
650
+ url: `${baseUrl}/v1beta/${documentName}`,
651
+ qs,
652
+ });
653
+ returnData.push({ json: { success: true, deletedDocument: documentName } });
527
654
  }
528
- try {
529
- responseData = await this.helpers.httpRequest({
655
+ else if (operation === 'query') {
656
+ const storeName = this.getNodeParameter('storeName', i);
657
+ const query = this.getNodeParameter('query', i);
658
+ const model = this.getNodeParameter('model', i);
659
+ const metadataFilter = this.getNodeParameter('metadataFilter', i);
660
+ const fileSearchTool = {
661
+ fileSearch: {
662
+ fileSearchStores: [storeName],
663
+ },
664
+ };
665
+ if (metadataFilter) {
666
+ fileSearchTool.fileSearch.metadataFilter = metadataFilter;
667
+ }
668
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'googleGeminiApi', {
530
669
  method: 'POST',
531
- url: `${baseUrl}/v1beta/${normalizedStoreName}:query`,
532
- qs: { key: apiKey },
533
- headers: {
534
- 'Content-Type': 'application/json',
670
+ url: `${baseUrl}/v1beta/models/${model}:generateContent`,
671
+ body: {
672
+ contents: [
673
+ {
674
+ role: 'user',
675
+ parts: [{ text: query }],
676
+ },
677
+ ],
678
+ tools: [fileSearchTool],
679
+ },
680
+ });
681
+ // 응답 파싱
682
+ const result = {
683
+ answer: '',
684
+ sources: [],
685
+ };
686
+ if (response.candidates?.[0]?.content?.parts) {
687
+ for (const part of response.candidates[0].content.parts) {
688
+ if (part.text) {
689
+ result.answer += part.text;
690
+ }
691
+ }
692
+ }
693
+ if (response.candidates?.[0]?.groundingMetadata?.groundingChunks) {
694
+ result.sources = response.candidates[0].groundingMetadata.groundingChunks.map((chunk) => ({
695
+ document: chunk.retrievedContext?.title || '',
696
+ uri: chunk.retrievedContext?.uri || '',
697
+ text: chunk.retrievedContext?.text || '',
698
+ }));
699
+ }
700
+ returnData.push({
701
+ json: {
702
+ ...result,
703
+ rawResponse: response,
535
704
  },
536
- body: requestBody,
537
- json: true,
538
705
  });
539
- }
540
- catch (error) {
541
- throw handleApiError(error);
542
706
  }
543
707
  }
544
- else if (operation === 'getOperationStatus') {
545
- // Get Operation Status
546
- const operationName = this.getNodeParameter('operationName', i);
547
- const normalizedOperationName = operationName.startsWith('operations/')
548
- ? operationName
549
- : `operations/${operationName}`;
550
- try {
551
- responseData = await this.helpers.httpRequest({
708
+ else if (resource === 'operation') {
709
+ if (operation === 'getStatus') {
710
+ const operationName = this.getNodeParameter('operationName', i);
711
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'googleGeminiApi', {
552
712
  method: 'GET',
553
- url: `${baseUrl}/v1beta/${normalizedOperationName}`,
554
- qs: { key: apiKey },
555
- json: true,
713
+ url: `${baseUrl}/v1beta/${operationName}`,
556
714
  });
715
+ returnData.push({ json: response });
557
716
  }
558
- catch (error) {
559
- throw handleApiError(error);
560
- }
561
- }
562
- else {
563
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`);
564
717
  }
565
- returnData.push({
566
- json: responseData,
567
- pairedItem: { item: i },
568
- });
569
718
  }
570
719
  catch (error) {
571
720
  if (this.continueOnFail()) {
572
- returnData.push({
573
- json: { error: error.message },
574
- pairedItem: { item: i },
575
- });
721
+ returnData.push({ json: { error: error.message }, pairedItem: { item: i } });
576
722
  continue;
577
723
  }
578
- throw error;
724
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), error.message, { itemIndex: i });
579
725
  }
580
726
  }
581
727
  return [returnData];