plugin-file-preview-auth 1.3.9 → 1.3.11

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.
package/package.json CHANGED
@@ -1,57 +1,57 @@
1
- {
2
- "name": "plugin-file-preview-auth",
3
- "displayName": "Authenticated File Previewer",
4
- "displayName.vi-VN": "Xem trước file có xác thực",
5
- "displayName.zh-CN": "认证文件预览",
6
- "description": "Preview PDF, image, and text files with Bearer token authentication via blob URLs.",
7
- "description.vi-VN": "Xem trước file PDF, hình ảnh và văn bản với xác thực Bearer token qua blob URL.",
8
- "description.zh-CN": "通过 Bearer 令牌认证和 Blob URL 预览 PDF、图片和文本文件。",
9
- "version": "1.3.9",
10
- "main": "dist/server/index.js",
11
- "files": [
12
- "dist",
13
- "public",
14
- "client.js",
15
- "client.d.ts",
16
- "server.js",
17
- "server.d.ts",
18
- "README.md",
19
- "client-v2.js",
20
- "client-v2.d.ts",
21
- "src"
22
- ],
23
- "peerDependencies": {
24
- "@aws-sdk/client-s3": "*",
25
- "@nocobase/client": "2.x",
26
- "@nocobase/database": "2.x",
27
- "@nocobase/plugin-ai": "2.x",
28
- "@nocobase/plugin-file-manager": "2.x",
29
- "@nocobase/server": "2.x",
30
- "@nocobase/utils": "2.x",
31
- "@nocobase/client-v2": "2.x",
32
- "@nocobase/flow-engine": "2.x"
33
- },
34
- "peerDependenciesMeta": {
35
- "@nocobase/plugin-ai": {
36
- "optional": true
37
- }
38
- },
39
- "devDependencies": {},
40
- "keywords": [
41
- "File manager",
42
- "File preview",
43
- "Authentication"
44
- ],
45
- "license": "Apache-2.0",
46
- "dependencies": {
47
- "docx-preview": "^0.3.3",
48
- "react-pptx-preview-kit": "^0.1.9",
49
- "xlsx": "^0.18.5"
50
- },
51
- "nocobase": {
52
- "supportedVersions": [
53
- "2.x"
54
- ],
55
- "editionLevel": 0
56
- }
57
- }
1
+ {
2
+ "name": "plugin-file-preview-auth",
3
+ "displayName": "Authenticated File Previewer",
4
+ "displayName.vi-VN": "Xem trước file có xác thực",
5
+ "displayName.zh-CN": "认证文件预览",
6
+ "description": "Preview PDF, image, and text files with Bearer token authentication via blob URLs.",
7
+ "description.vi-VN": "Xem trước file PDF, hình ảnh và văn bản với xác thực Bearer token qua blob URL.",
8
+ "description.zh-CN": "通过 Bearer 令牌认证和 Blob URL 预览 PDF、图片和文本文件。",
9
+ "version": "1.3.11",
10
+ "main": "dist/server/index.js",
11
+ "files": [
12
+ "dist",
13
+ "public",
14
+ "client.js",
15
+ "client.d.ts",
16
+ "server.js",
17
+ "server.d.ts",
18
+ "README.md",
19
+ "client-v2.js",
20
+ "client-v2.d.ts",
21
+ "src"
22
+ ],
23
+ "peerDependencies": {
24
+ "@aws-sdk/client-s3": "*",
25
+ "@nocobase/client": "2.x",
26
+ "@nocobase/database": "2.x",
27
+ "@nocobase/plugin-ai": "2.x",
28
+ "@nocobase/plugin-file-manager": "2.x",
29
+ "@nocobase/server": "2.x",
30
+ "@nocobase/utils": "2.x",
31
+ "@nocobase/client-v2": "2.x",
32
+ "@nocobase/flow-engine": "2.x"
33
+ },
34
+ "peerDependenciesMeta": {
35
+ "@nocobase/plugin-ai": {
36
+ "optional": true
37
+ }
38
+ },
39
+ "devDependencies": {},
40
+ "keywords": [
41
+ "File manager",
42
+ "File preview",
43
+ "Authentication"
44
+ ],
45
+ "license": "Apache-2.0",
46
+ "dependencies": {
47
+ "docx-preview": "^0.3.3",
48
+ "react-pptx-preview-kit": "^0.1.9",
49
+ "xlsx": "^0.18.5"
50
+ },
51
+ "nocobase": {
52
+ "supportedVersions": [
53
+ "2.x"
54
+ ],
55
+ "editionLevel": 0
56
+ }
57
+ }
@@ -9,8 +9,7 @@
9
9
 
10
10
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
11
11
  import { FileTextOutlined, RobotOutlined } from '@ant-design/icons';
12
- import { Button, Dropdown, Space, Tooltip, message } from 'antd';
13
- import type { MenuProps } from 'antd';
12
+ import { Button, Space, Tooltip, message } from 'antd';
14
13
  import type { Application } from '@nocobase/client';
15
14
  import { useChatBoxActions, useAIConfigRepository, type AIEmployee } from '@nocobase/plugin-ai/client';
16
15
  import { useT } from './locale';
@@ -80,10 +79,6 @@ function setStoredAIEmployeeUsername(username: string) {
80
79
  }
81
80
  }
82
81
 
83
- function getEmployeeLabel(employee: AIEmployee) {
84
- return employee?.nickname || employee?.username || '';
85
- }
86
-
87
82
  class AIFilePreviewActionBoundary extends React.Component<{ children: React.ReactNode }, { hasError: boolean }> {
88
83
  constructor(props: { children: React.ReactNode }) {
89
84
  super(props);
@@ -186,48 +181,27 @@ const AIFilePreviewActionInner: React.FC<{ file: any }> = ({ file }) => {
186
181
  [file, t, triggerTask],
187
182
  );
188
183
 
189
- const menuItems: MenuProps['items'] = orderedEmployees.map((employee) => ({
190
- key: employee.username,
191
- label: getEmployeeLabel(employee),
192
- onClick: () => openAIChat(employee),
193
- }));
194
-
195
184
  if (!loading && !orderedEmployees.length) {
196
185
  return null;
197
186
  }
198
187
 
199
- if (orderedEmployees.length === 1) {
200
- return (
201
- <Tooltip title={t('Ask AI')}>
202
- <Button
203
- type="text"
204
- size="small"
205
- icon={<RobotOutlined />}
206
- loading={asking || loading}
207
- onClick={(event) => {
208
- event.stopPropagation();
209
- openAIChat(orderedEmployees[0]);
210
- }}
211
- >
212
- {t('Ask AI')}
213
- </Button>
214
- </Tooltip>
215
- );
216
- }
217
-
188
+ // Single click attaches the file content to the chat box using the preferred
189
+ // employee (last used, otherwise the first available) without listing employees.
218
190
  return (
219
191
  <Tooltip title={t('Ask AI')}>
220
- <Dropdown menu={{ items: menuItems }} trigger={['click']} placement="bottomRight" disabled={asking || loading}>
221
- <Button
222
- type="text"
223
- size="small"
224
- icon={<RobotOutlined />}
225
- loading={asking || loading}
226
- onClick={(event) => event.stopPropagation()}
227
- >
228
- {t('Ask AI')}
229
- </Button>
230
- </Dropdown>
192
+ <Button
193
+ type="text"
194
+ size="small"
195
+ icon={<RobotOutlined />}
196
+ loading={asking || loading}
197
+ disabled={!orderedEmployees.length}
198
+ onClick={(event) => {
199
+ event.stopPropagation();
200
+ openAIChat(orderedEmployees[0]);
201
+ }}
202
+ >
203
+ {t('Ask AI')}
204
+ </Button>
231
205
  </Tooltip>
232
206
  );
233
207
  };
@@ -1,85 +1,103 @@
1
- import { extractOcrStatusRecord, getOcrAttachmentId, isOcrCompleteStatus, normalizeOcrAttachmentId } from '../index';
2
-
3
- describe('file preview OCR client utils', () => {
4
- describe('normalizeOcrAttachmentId', () => {
5
- it('accepts numeric attachment ids', () => {
6
- expect(normalizeOcrAttachmentId(12)).toBe(12);
7
- expect(normalizeOcrAttachmentId('12')).toBe('12');
8
- expect(normalizeOcrAttachmentId(' 12 ')).toBe('12');
9
- });
10
-
11
- it('rejects URLs and upload-only ids', () => {
12
- expect(normalizeOcrAttachmentId('http://localhost/storage/uploads/test.pdf')).toBeNull();
13
- expect(normalizeOcrAttachmentId('/storage/uploads/test.pdf')).toBeNull();
14
- expect(normalizeOcrAttachmentId('rc-upload-1710000000000-1')).toBeNull();
15
- });
16
- });
17
-
18
- describe('getOcrAttachmentId', () => {
19
- it('uses response.id when file.id is a storage URL', () => {
20
- expect(
21
- getOcrAttachmentId({
22
- id: 'http://localhost/storage/uploads/yyyy-test%20(1)-lcqadd.pdf',
23
- uid: 'rc-upload-1',
24
- response: {
25
- id: 42,
26
- },
27
- }),
28
- ).toBe(42);
29
- });
30
-
31
- it('falls back to a numeric uid for persisted attachment records', () => {
32
- expect(
33
- getOcrAttachmentId({
34
- id: '/storage/uploads/yyyy-test.pdf',
35
- uid: '88',
36
- }),
37
- ).toBe('88');
38
- });
39
- });
40
-
41
- describe('isOcrCompleteStatus', () => {
42
- it('only treats result statuses as completed', () => {
43
- expect(isOcrCompleteStatus('waiting-verify')).toBe(true);
44
- expect(isOcrCompleteStatus('success')).toBe(true);
45
- expect(isOcrCompleteStatus('no-ocr')).toBe(false);
46
- expect(isOcrCompleteStatus('pending-ocr')).toBe(false);
47
- expect(isOcrCompleteStatus('failed')).toBe(false);
48
- });
49
- });
50
-
51
- describe('extractOcrStatusRecord', () => {
52
- it('unwraps NocoBase double data responses', () => {
53
- const record = extractOcrStatusRecord({
54
- data: {
55
- data: {
56
- id: 4,
57
- attachmentId: 57,
58
- status: 'waiting-verify',
59
- data: { pages: [] },
60
- error: null,
61
- },
62
- },
63
- });
64
-
65
- expect(record?.id).toBe(4);
66
- expect(record?.attachmentId).toBe(57);
67
- expect(record?.status).toBe('waiting-verify');
68
- });
69
-
70
- it('unwraps action responses with ok and data', () => {
71
- const record = extractOcrStatusRecord({
72
- data: {
73
- ok: true,
74
- data: {
75
- id: 5,
76
- status: 'pending-ocr',
77
- },
78
- },
79
- });
80
-
81
- expect(record?.id).toBe(5);
82
- expect(record?.status).toBe('pending-ocr');
83
- });
84
- });
85
- });
1
+ import {
2
+ extractOcrStatusRecord,
3
+ getOcrAttachmentId,
4
+ isOcrCapableCollection,
5
+ isOcrCompleteStatus,
6
+ normalizeOcrAttachmentId,
7
+ } from '../index';
8
+
9
+ describe('file preview OCR client utils', () => {
10
+ describe('normalizeOcrAttachmentId', () => {
11
+ it('accepts numeric attachment ids', () => {
12
+ expect(normalizeOcrAttachmentId(12)).toBe(12);
13
+ expect(normalizeOcrAttachmentId('12')).toBe('12');
14
+ expect(normalizeOcrAttachmentId(' 12 ')).toBe('12');
15
+ });
16
+
17
+ it('rejects URLs and upload-only ids', () => {
18
+ expect(normalizeOcrAttachmentId('http://localhost/storage/uploads/test.pdf')).toBeNull();
19
+ expect(normalizeOcrAttachmentId('/storage/uploads/test.pdf')).toBeNull();
20
+ expect(normalizeOcrAttachmentId('rc-upload-1710000000000-1')).toBeNull();
21
+ });
22
+ });
23
+
24
+ describe('getOcrAttachmentId', () => {
25
+ it('uses response.id when file.id is a storage URL', () => {
26
+ expect(
27
+ getOcrAttachmentId({
28
+ id: 'http://localhost/storage/uploads/yyyy-test%20(1)-lcqadd.pdf',
29
+ uid: 'rc-upload-1',
30
+ response: {
31
+ id: 42,
32
+ },
33
+ }),
34
+ ).toBe(42);
35
+ });
36
+
37
+ it('falls back to a numeric uid for persisted attachment records', () => {
38
+ expect(
39
+ getOcrAttachmentId({
40
+ id: '/storage/uploads/yyyy-test.pdf',
41
+ uid: '88',
42
+ }),
43
+ ).toBe('88');
44
+ });
45
+ });
46
+
47
+ describe('isOcrCapableCollection', () => {
48
+ it('allows attachments and collection-less records', () => {
49
+ expect(isOcrCapableCollection({ id: 1 })).toBe(true);
50
+ expect(isOcrCapableCollection({ id: 1, collectionName: 'attachments' })).toBe(true);
51
+ expect(isOcrCapableCollection({ id: 1, collectionName: '' })).toBe(true);
52
+ });
53
+
54
+ it('rejects non-attachment collections like aiFiles', () => {
55
+ expect(isOcrCapableCollection({ id: 1, collectionName: 'aiFiles' })).toBe(false);
56
+ });
57
+ });
58
+
59
+ describe('isOcrCompleteStatus', () => {
60
+ it('only treats result statuses as completed', () => {
61
+ expect(isOcrCompleteStatus('waiting-verify')).toBe(true);
62
+ expect(isOcrCompleteStatus('success')).toBe(true);
63
+ expect(isOcrCompleteStatus('no-ocr')).toBe(false);
64
+ expect(isOcrCompleteStatus('pending-ocr')).toBe(false);
65
+ expect(isOcrCompleteStatus('failed')).toBe(false);
66
+ });
67
+ });
68
+
69
+ describe('extractOcrStatusRecord', () => {
70
+ it('unwraps NocoBase double data responses', () => {
71
+ const record = extractOcrStatusRecord({
72
+ data: {
73
+ data: {
74
+ id: 4,
75
+ attachmentId: 57,
76
+ status: 'waiting-verify',
77
+ data: { pages: [] },
78
+ error: null,
79
+ },
80
+ },
81
+ });
82
+
83
+ expect(record?.id).toBe(4);
84
+ expect(record?.attachmentId).toBe(57);
85
+ expect(record?.status).toBe('waiting-verify');
86
+ });
87
+
88
+ it('unwraps action responses with ok and data', () => {
89
+ const record = extractOcrStatusRecord({
90
+ data: {
91
+ ok: true,
92
+ data: {
93
+ id: 5,
94
+ status: 'pending-ocr',
95
+ },
96
+ },
97
+ });
98
+
99
+ expect(record?.id).toBe(5);
100
+ expect(record?.status).toBe('pending-ocr');
101
+ });
102
+ });
103
+ });