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/dist/client/index.js +1 -1
- package/dist/client-v2/index.js +1 -1
- package/dist/externalVersion.js +8 -8
- package/dist/server/ocr/tesseract-worker.js +9 -3
- package/dist/server/plugin.js +65 -36
- package/package.json +57 -57
- package/src/client/AIFilePreviewAction.tsx +16 -42
- package/src/client/__tests__/ocr-utils.test.ts +103 -85
- package/src/client/index.tsx +1818 -1807
- package/src/server/ocr/tesseract-worker.ts +4 -1
- package/src/server/plugin.ts +43 -5
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.
|
|
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,
|
|
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
|
-
|
|
200
|
-
|
|
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
|
-
<
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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 {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
it('
|
|
12
|
-
expect(normalizeOcrAttachmentId(
|
|
13
|
-
expect(normalizeOcrAttachmentId('
|
|
14
|
-
expect(normalizeOcrAttachmentId('
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
expect(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
expect(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
it('unwraps
|
|
71
|
-
const record = extractOcrStatusRecord({
|
|
72
|
-
data: {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
status: '
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
+
});
|