@chenchaolong/plugin-trade-compliance-workbench 0.1.35 → 0.1.36
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/lib/constants.d.ts +0 -1
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +0 -1
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/remote-components/trade-compliance-workbench/app.js +1 -8
- package/dist/lib/trade-compliance-workbench-view.provider.d.ts +5 -19
- package/dist/lib/trade-compliance-workbench-view.provider.d.ts.map +1 -1
- package/dist/lib/trade-compliance-workbench-view.provider.js +644 -126
- package/dist/lib/trade-compliance-workbench-view.provider.js.map +1 -1
- package/dist/lib/trade-compliance-workbench.middleware.d.ts +0 -1
- package/dist/lib/trade-compliance-workbench.middleware.d.ts.map +1 -1
- package/dist/lib/trade-compliance-workbench.middleware.js +3 -25
- package/dist/lib/trade-compliance-workbench.middleware.js.map +1 -1
- package/dist/lib/trade-compliance-workbench.service.d.ts +0 -2
- package/dist/lib/trade-compliance-workbench.service.d.ts.map +1 -1
- package/dist/lib/trade-compliance-workbench.service.js +37 -67
- package/dist/lib/trade-compliance-workbench.service.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
2
|
import { Injectable } from '@nestjs/common';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { readFile } from 'fs/promises';
|
|
4
|
+
import { createRequire } from 'module';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { renderRemoteReactIframeHtml, ViewExtensionProvider } from '@xpert-ai/plugin-sdk';
|
|
8
|
+
import { AGENT_WORKBENCH_FIXED_SLOT, AGENT_WORKBENCH_MAIN_SLOT, TRADE_COMPLIANCE_FEATURE, TRADE_COMPLIANCE_ICON, TRADE_COMPLIANCE_PLUGIN_NAME, TRADE_COMPLIANCE_PROVIDER_KEY, TRADE_COMPLIANCE_REMOTE_ENTRY_KEY, TRADE_COMPLIANCE_VIEW_KEY } from './constants.js';
|
|
5
9
|
import { TradeComplianceWorkbenchService } from './trade-compliance-workbench.service.js';
|
|
10
|
+
import { buildCustomsWorkbookModel, buildCustomsWorkbookTemplateFileName, createCustomsWorkbookFromTemplateBuffer, readCustomsWorkbookTemplateSheetNames } from './trade-compliance-workbook.js';
|
|
11
|
+
import { parseControlledGoodsFile } from './controlled-goods-file-parser.js';
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
const requireFromHere = createRequire(__filename);
|
|
6
15
|
const text = (en_US, zh_Hans) => ({ en_US, zh_Hans });
|
|
7
|
-
const
|
|
16
|
+
const CONTROLLED_GOODS_BATCH_SIZE = 40;
|
|
8
17
|
let TradeComplianceWorkbenchViewProvider = class TradeComplianceWorkbenchViewProvider {
|
|
9
18
|
constructor(service) {
|
|
10
19
|
this.service = service;
|
|
@@ -13,14 +22,14 @@ let TradeComplianceWorkbenchViewProvider = class TradeComplianceWorkbenchViewPro
|
|
|
13
22
|
return context.hostType === 'agent';
|
|
14
23
|
}
|
|
15
24
|
getViewManifests(_context, slot) {
|
|
16
|
-
if (
|
|
25
|
+
if (slot !== AGENT_WORKBENCH_MAIN_SLOT && slot !== AGENT_WORKBENCH_FIXED_SLOT) {
|
|
17
26
|
return [];
|
|
18
27
|
}
|
|
19
28
|
return [
|
|
20
29
|
{
|
|
21
30
|
key: TRADE_COMPLIANCE_VIEW_KEY,
|
|
22
|
-
title: text('Trade Compliance
|
|
23
|
-
description: text('
|
|
31
|
+
title: text('Trade Compliance Workbench', '外贸合规工作台'),
|
|
32
|
+
description: text('Manage controlled goods, supplier products, and generated customs workbooks.', '管理管控商品、供应商商品和生成的报关资料。'),
|
|
24
33
|
icon: {
|
|
25
34
|
type: 'svg',
|
|
26
35
|
value: TRADE_COMPLIANCE_ICON
|
|
@@ -29,31 +38,23 @@ let TradeComplianceWorkbenchViewProvider = class TradeComplianceWorkbenchViewPro
|
|
|
29
38
|
slot,
|
|
30
39
|
order: 40,
|
|
31
40
|
refreshable: true,
|
|
41
|
+
activation: {
|
|
42
|
+
requiredFeatures: [TRADE_COMPLIANCE_FEATURE]
|
|
43
|
+
},
|
|
32
44
|
source: {
|
|
33
45
|
provider: TRADE_COMPLIANCE_PROVIDER_KEY,
|
|
34
46
|
plugin: TRADE_COMPLIANCE_PLUGIN_NAME
|
|
35
47
|
},
|
|
36
48
|
view: {
|
|
37
|
-
type: '
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
{ key: 'supplierName', label: text('Supplier', '供应商'), dataType: 'text', searchable: true },
|
|
44
|
-
{ key: 'productName', label: text('Product', '商品'), dataType: 'text', searchable: true },
|
|
45
|
-
{ key: 'model', label: text('Model', '型号'), dataType: 'text', searchable: true },
|
|
46
|
-
{ key: 'hsCode', label: text('HS Code', '海关编码'), dataType: 'text', searchable: true, sortable: true },
|
|
47
|
-
{ key: 'taxRefundRate', label: text('Refund Rate', '退税率'), dataType: 'text' },
|
|
48
|
-
{ key: 'sourceLocation', label: text('Source', '来源'), dataType: 'text' }
|
|
49
|
-
],
|
|
50
|
-
pagination: {
|
|
51
|
-
enabled: true,
|
|
52
|
-
pageSize: 20
|
|
49
|
+
type: 'remote_component',
|
|
50
|
+
runtime: 'react',
|
|
51
|
+
protocolVersion: 1,
|
|
52
|
+
component: {
|
|
53
|
+
isolation: 'iframe',
|
|
54
|
+
entry: TRADE_COMPLIANCE_REMOTE_ENTRY_KEY
|
|
53
55
|
},
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
placeholder: text('Search product, supplier, HS code, or source', '搜索商品、供应商、海关编码或来源')
|
|
56
|
+
dataSource: {
|
|
57
|
+
mode: 'platform'
|
|
57
58
|
}
|
|
58
59
|
},
|
|
59
60
|
dataSource: {
|
|
@@ -61,72 +62,371 @@ let TradeComplianceWorkbenchViewProvider = class TradeComplianceWorkbenchViewPro
|
|
|
61
62
|
querySchema: {
|
|
62
63
|
supportsPagination: true,
|
|
63
64
|
supportsSearch: true,
|
|
64
|
-
|
|
65
|
+
supportsParameters: true,
|
|
65
66
|
defaultPageSize: 20
|
|
66
67
|
},
|
|
67
68
|
cache: {
|
|
68
69
|
enabled: false
|
|
69
70
|
}
|
|
70
71
|
},
|
|
72
|
+
clientCommands: [
|
|
73
|
+
{
|
|
74
|
+
key: 'assistant.chat.send_message',
|
|
75
|
+
label: text('Send to Assistant Chat', '发送到 Assistant 对话')
|
|
76
|
+
}
|
|
77
|
+
],
|
|
71
78
|
actions: [
|
|
72
79
|
{ key: 'refresh', label: text('Refresh', '刷新'), icon: 'ri-refresh-line', placement: 'toolbar', actionType: 'refresh' },
|
|
73
|
-
{ key: 'confirm_review_item', label: text('Confirm', '确认'), icon: 'ri-check-line', placement: 'row', actionType: 'invoke' },
|
|
74
|
-
{ key: 'reject_review_item', label: text('Reject', '驳回'), icon: 'ri-close-line', placement: 'row', actionType: 'invoke' },
|
|
75
80
|
{
|
|
76
|
-
key: '
|
|
77
|
-
label: text('
|
|
78
|
-
icon: 'ri-
|
|
79
|
-
placement: '
|
|
81
|
+
key: 'upload_controlled_goods_file',
|
|
82
|
+
label: text('Upload Controlled Goods File', '上传管控商品文件'),
|
|
83
|
+
icon: 'ri-upload-cloud-line',
|
|
84
|
+
placement: 'toolbar',
|
|
80
85
|
actionType: 'invoke',
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
transport: 'file'
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
key: 'upload_supplier_contract',
|
|
90
|
+
label: text('Upload Supplier Contract', '上传供应商合同'),
|
|
91
|
+
icon: 'ri-file-list-3-line',
|
|
92
|
+
placement: 'toolbar',
|
|
93
|
+
actionType: 'invoke',
|
|
94
|
+
transport: 'file'
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
key: 'upload_sales_contract',
|
|
98
|
+
label: text('Upload Sales Contract', '上传购销合同'),
|
|
99
|
+
icon: 'ri-file-excel-2-line',
|
|
100
|
+
placement: 'toolbar',
|
|
101
|
+
actionType: 'invoke',
|
|
102
|
+
transport: 'file'
|
|
103
|
+
},
|
|
104
|
+
{ key: 'confirm_review_item', label: text('Confirm Item', '确认单条'), icon: 'ri-check-line', actionType: 'invoke' },
|
|
105
|
+
{ key: 'confirm_review_items', label: text('Confirm Selected', '批量确认'), icon: 'ri-check-double-line', actionType: 'invoke' },
|
|
106
|
+
{ key: 'reject_review_item', label: text('Reject Item', '驳回单条'), icon: 'ri-close-line', actionType: 'invoke' },
|
|
107
|
+
{ key: 'reject_review_items', label: text('Reject Selected', '批量驳回'), icon: 'ri-close-circle-line', actionType: 'invoke' },
|
|
108
|
+
{ key: 'save_controlled_goods', label: text('Save Controlled Goods', '保存管控商品'), icon: 'ri-save-line', actionType: 'invoke' },
|
|
109
|
+
{ key: 'save_supplier_product', label: text('Save Supplier Product', '保存供应商商品'), icon: 'ri-save-line', actionType: 'invoke' },
|
|
110
|
+
{ key: 'update_review_item', label: text('Update Review Item', '更新审核项'), icon: 'ri-edit-line', actionType: 'invoke' },
|
|
111
|
+
{ key: 'delete_review_item', label: text('Delete Review Item', '删除审核项'), icon: 'ri-delete-bin-line', actionType: 'invoke' },
|
|
112
|
+
{ key: 'update_controlled_goods', label: text('Update Controlled Goods', '更新管控商品'), icon: 'ri-edit-line', actionType: 'invoke' },
|
|
113
|
+
{ key: 'delete_controlled_goods', label: text('Delete Controlled Goods', '删除管控商品'), icon: 'ri-delete-bin-line', actionType: 'invoke' },
|
|
114
|
+
{ key: 'update_supplier_product', label: text('Update Supplier Product', '更新供应商商品'), icon: 'ri-edit-line', actionType: 'invoke' },
|
|
115
|
+
{ key: 'delete_supplier_product', label: text('Delete Supplier Product', '删除供应商商品'), icon: 'ri-delete-bin-line', actionType: 'invoke' },
|
|
116
|
+
{ key: 'delete_customs_workbook', label: text('Delete Workbook', '删除销售发票'), icon: 'ri-delete-bin-line', actionType: 'invoke' },
|
|
117
|
+
{ key: 'download_customs_workbook', label: text('Download Workbook', '下载销售发票'), icon: 'ri-download-line', actionType: 'invoke' },
|
|
118
|
+
{ key: 'generate_customs_workbook', label: text('Generate Workbook', '生成报关资料'), icon: 'ri-file-excel-line', actionType: 'invoke' }
|
|
86
119
|
]
|
|
87
120
|
}
|
|
88
121
|
];
|
|
89
122
|
}
|
|
90
|
-
async
|
|
123
|
+
async getRemoteComponentEntry(_context, viewKey, component) {
|
|
124
|
+
if (viewKey !== TRADE_COMPLIANCE_VIEW_KEY || component.entry !== TRADE_COMPLIANCE_REMOTE_ENTRY_KEY) {
|
|
125
|
+
return {
|
|
126
|
+
html: '<!doctype html><html><body>Unsupported trade compliance component.</body></html>',
|
|
127
|
+
contentType: 'text/html; charset=utf-8'
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const appScript = await readFile(join(__dirname, 'remote-components', TRADE_COMPLIANCE_REMOTE_ENTRY_KEY, 'app.js'), 'utf8');
|
|
131
|
+
const reactUmd = await readPackageFile('react', 'umd/react.production.min.js');
|
|
132
|
+
const reactDomUmd = await readPackageFile('react-dom', 'umd/react-dom.production.min.js');
|
|
133
|
+
return {
|
|
134
|
+
html: renderRemoteReactIframeHtml({
|
|
135
|
+
title: 'Trade Compliance Workbench',
|
|
136
|
+
lang: 'zh-Hans',
|
|
137
|
+
reactUmd,
|
|
138
|
+
reactDomUmd,
|
|
139
|
+
appScript
|
|
140
|
+
}),
|
|
141
|
+
contentType: 'text/html; charset=utf-8'
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async getViewData(context, _viewKey, _query) {
|
|
91
145
|
const scope = scopeFromContext(context);
|
|
92
|
-
const reviewItems = await
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const offset = Math.max(page - 1, 0) * pageSize;
|
|
146
|
+
const [reviewItems, controlledGoods, products, workbookGenerations] = await Promise.all([
|
|
147
|
+
this.service.listReviewItems(scope),
|
|
148
|
+
this.service.listControlledGoods(scope),
|
|
149
|
+
this.service.listProducts(scope),
|
|
150
|
+
this.service.listWorkbookGenerations(scope)
|
|
151
|
+
]);
|
|
99
152
|
return {
|
|
100
|
-
items:
|
|
101
|
-
total:
|
|
153
|
+
items: reviewItems,
|
|
154
|
+
total: reviewItems.length,
|
|
102
155
|
summary: {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
156
|
+
reviewItems,
|
|
157
|
+
controlledGoods,
|
|
158
|
+
products,
|
|
159
|
+
workbookGenerations
|
|
107
160
|
}
|
|
108
161
|
};
|
|
109
162
|
}
|
|
110
163
|
async executeViewAction(context, _viewKey, actionKey, request) {
|
|
111
164
|
const scope = scopeFromContext(context);
|
|
112
|
-
const itemId = request.targetId;
|
|
113
|
-
if (actionKey === 'refresh') {
|
|
114
|
-
return { success: true, message: text('Refreshed', '已刷新'), refresh: true };
|
|
115
|
-
}
|
|
116
|
-
if (!itemId) {
|
|
117
|
-
return failure('targetId is required', '缺少 targetId');
|
|
118
|
-
}
|
|
119
165
|
if (actionKey === 'confirm_review_item') {
|
|
120
|
-
const
|
|
166
|
+
const itemId = readString(request.input, 'itemId');
|
|
167
|
+
if (!itemId) {
|
|
168
|
+
return failure('itemId is required', '缺少 itemId');
|
|
169
|
+
}
|
|
170
|
+
const item = await this.service.confirmReviewItem(scope, itemId, readRecord(request.input, 'confirmedData'));
|
|
121
171
|
return success(await this.materializeConfirmedReviewItem(scope, item));
|
|
122
172
|
}
|
|
173
|
+
if (actionKey === 'confirm_review_items') {
|
|
174
|
+
const itemIds = readStringArray(request.input, 'itemIds');
|
|
175
|
+
const items = await this.service.confirmReviewItems(scope, itemIds);
|
|
176
|
+
return success(await Promise.all(items.map((item) => this.materializeConfirmedReviewItem(scope, item))));
|
|
177
|
+
}
|
|
123
178
|
if (actionKey === 'reject_review_item') {
|
|
179
|
+
const itemId = readString(request.input, 'itemId');
|
|
180
|
+
if (!itemId) {
|
|
181
|
+
return failure('itemId is required', '缺少 itemId');
|
|
182
|
+
}
|
|
124
183
|
return success(await this.service.rejectReviewItem(scope, itemId));
|
|
125
184
|
}
|
|
185
|
+
if (actionKey === 'reject_review_items') {
|
|
186
|
+
const itemIds = readStringArray(request.input, 'itemIds');
|
|
187
|
+
return success(await this.service.rejectReviewItems(scope, itemIds));
|
|
188
|
+
}
|
|
189
|
+
if (actionKey === 'update_review_item') {
|
|
190
|
+
const itemId = readString(request.input, 'itemId');
|
|
191
|
+
const confirmedData = readRecord(request.input, 'confirmedData');
|
|
192
|
+
if (!itemId || !confirmedData)
|
|
193
|
+
return failure('itemId and confirmedData are required', '缺少 itemId 或更新数据');
|
|
194
|
+
return success(await this.service.updateReviewItem(scope, itemId, confirmedData));
|
|
195
|
+
}
|
|
126
196
|
if (actionKey === 'delete_review_item') {
|
|
197
|
+
const itemId = readString(request.input, 'itemId');
|
|
198
|
+
if (!itemId)
|
|
199
|
+
return failure('itemId is required', '缺少 itemId');
|
|
127
200
|
return success(await this.service.deleteReviewItem(scope, itemId));
|
|
128
201
|
}
|
|
129
|
-
|
|
202
|
+
if (actionKey === 'refresh') {
|
|
203
|
+
return { success: true, message: text('Refreshed', '已刷新'), refresh: true };
|
|
204
|
+
}
|
|
205
|
+
if (actionKey === 'save_controlled_goods') {
|
|
206
|
+
const productName = readString(request.input, 'productName');
|
|
207
|
+
if (!productName) {
|
|
208
|
+
return failure('productName is required', '缺少商品名称');
|
|
209
|
+
}
|
|
210
|
+
return success(await this.service.saveControlledGoods(scope, {
|
|
211
|
+
productName,
|
|
212
|
+
hsCode: readString(request.input, 'hsCode'),
|
|
213
|
+
keywords: readStringArray(request.input, 'keywords'),
|
|
214
|
+
controlNote: readString(request.input, 'controlNote'),
|
|
215
|
+
enabled: readBoolean(request.input, 'enabled') ?? true,
|
|
216
|
+
sourceFileName: readString(request.input, 'sourceFileName'),
|
|
217
|
+
sourceLocation: readString(request.input, 'sourceLocation')
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
if (actionKey === 'save_supplier_product') {
|
|
221
|
+
const supplierName = readString(request.input, 'supplierName');
|
|
222
|
+
const productName = readString(request.input, 'productName');
|
|
223
|
+
if (!supplierName || !productName) {
|
|
224
|
+
return failure('supplierName and productName are required', '缺少供应商或商品名称');
|
|
225
|
+
}
|
|
226
|
+
return success(await this.service.saveSupplierProduct(scope, {
|
|
227
|
+
supplierName,
|
|
228
|
+
supplierCreditCode: readString(request.input, 'supplierCreditCode'),
|
|
229
|
+
supplierAddress: readString(request.input, 'supplierAddress'),
|
|
230
|
+
productName,
|
|
231
|
+
model: readString(request.input, 'model'),
|
|
232
|
+
description: readString(request.input, 'description'),
|
|
233
|
+
quantity: readNumber(request.input, 'quantity'),
|
|
234
|
+
unit: readString(request.input, 'unit'),
|
|
235
|
+
taxInclusiveUnitPrice: readNumber(request.input, 'taxInclusiveUnitPrice'),
|
|
236
|
+
taxInclusiveTotalAmount: readNumber(request.input, 'taxInclusiveTotalAmount'),
|
|
237
|
+
contractHsCode: readString(request.input, 'contractHsCode'),
|
|
238
|
+
enrichedHsCode: readString(request.input, 'enrichedHsCode'),
|
|
239
|
+
taxRefundRate: readString(request.input, 'taxRefundRate'),
|
|
240
|
+
englishName: readString(request.input, 'englishName'),
|
|
241
|
+
controlledStatus: readControlledGoodsStatus(request.input, 'controlledStatus'),
|
|
242
|
+
controlNote: readString(request.input, 'controlNote')
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
if (actionKey === 'update_controlled_goods') {
|
|
246
|
+
const id = readString(request.input, 'id');
|
|
247
|
+
if (!id)
|
|
248
|
+
return failure('id is required', '缺少 id');
|
|
249
|
+
return success(await this.service.updateControlledGoods(scope, id, {
|
|
250
|
+
productName: readString(request.input, 'productName'),
|
|
251
|
+
hsCode: readString(request.input, 'hsCode'),
|
|
252
|
+
keywords: readStringArray(request.input, 'keywords'),
|
|
253
|
+
controlNote: readString(request.input, 'controlNote'),
|
|
254
|
+
enabled: readBoolean(request.input, 'enabled'),
|
|
255
|
+
sourceFileName: readString(request.input, 'sourceFileName'),
|
|
256
|
+
sourceLocation: readString(request.input, 'sourceLocation')
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
if (actionKey === 'delete_controlled_goods') {
|
|
260
|
+
const id = readString(request.input, 'id');
|
|
261
|
+
if (!id)
|
|
262
|
+
return failure('id is required', '缺少 id');
|
|
263
|
+
return success(await this.service.deleteControlledGoods(scope, id));
|
|
264
|
+
}
|
|
265
|
+
if (actionKey === 'update_supplier_product') {
|
|
266
|
+
const id = readString(request.input, 'id');
|
|
267
|
+
if (!id)
|
|
268
|
+
return failure('id is required', '缺少 id');
|
|
269
|
+
return success(await this.service.updateSupplierProduct(scope, id, {
|
|
270
|
+
supplierName: readString(request.input, 'supplierName'),
|
|
271
|
+
productName: readString(request.input, 'productName'),
|
|
272
|
+
model: readString(request.input, 'model'),
|
|
273
|
+
description: readString(request.input, 'description'),
|
|
274
|
+
quantity: readNumber(request.input, 'quantity'),
|
|
275
|
+
unit: readString(request.input, 'unit'),
|
|
276
|
+
contractHsCode: readString(request.input, 'contractHsCode'),
|
|
277
|
+
enrichedHsCode: readString(request.input, 'enrichedHsCode'),
|
|
278
|
+
taxRefundRate: readString(request.input, 'taxRefundRate'),
|
|
279
|
+
englishName: readString(request.input, 'englishName'),
|
|
280
|
+
controlledStatus: readControlledGoodsStatus(request.input, 'controlledStatus'),
|
|
281
|
+
controlNote: readString(request.input, 'controlNote')
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
if (actionKey === 'delete_supplier_product') {
|
|
285
|
+
const id = readString(request.input, 'id');
|
|
286
|
+
if (!id)
|
|
287
|
+
return failure('id is required', '缺少 id');
|
|
288
|
+
return success(await this.service.deleteSupplierProduct(scope, id));
|
|
289
|
+
}
|
|
290
|
+
if (actionKey === 'delete_customs_workbook') {
|
|
291
|
+
const id = readString(request.input, 'id');
|
|
292
|
+
if (!id)
|
|
293
|
+
return failure('id is required', '缺少 id');
|
|
294
|
+
return success(await this.service.deleteWorkbookGeneration(scope, id));
|
|
295
|
+
}
|
|
296
|
+
if (actionKey === 'download_customs_workbook') {
|
|
297
|
+
const id = readString(request.input, 'id');
|
|
298
|
+
if (!id)
|
|
299
|
+
return failure('id is required', '缺少 id');
|
|
300
|
+
const item = await this.service.getWorkbookGeneration(scope, id);
|
|
301
|
+
const template = await readFile(join(__dirname, '..', 'assets', 'customs-workbook-template.xls'));
|
|
302
|
+
const source = normalizeWorkbookSource({
|
|
303
|
+
...(item.workbookData || {}),
|
|
304
|
+
invoiceNo: item.invoiceNo,
|
|
305
|
+
contractNo: item.contractNo,
|
|
306
|
+
sourceFileName: item.sourceFileName
|
|
307
|
+
});
|
|
308
|
+
const workbook = await createCustomsWorkbookFromTemplateBuffer(buildCustomsWorkbookModel(source, {}), template);
|
|
309
|
+
return success({
|
|
310
|
+
fileName: workbook.fileName || item.fileName,
|
|
311
|
+
mimeType: workbook.mimeType,
|
|
312
|
+
bookType: workbook.bookType,
|
|
313
|
+
base64: workbook.buffer.toString('base64')
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
if (actionKey === 'generate_customs_workbook') {
|
|
317
|
+
const now = new Date();
|
|
318
|
+
const invoiceNo = readString(request.input, 'invoiceNo') ?? `INV-${now.toISOString().slice(0, 10).replace(/-/g, '')}`;
|
|
319
|
+
const template = await readFile(join(__dirname, '..', 'assets', 'customs-workbook-template.xls'));
|
|
320
|
+
const result = await this.service.recordWorkbookGeneration(scope, {
|
|
321
|
+
sourceFileName: readString(request.input, 'sourceFileName') ?? readString(request.input, 'contractName'),
|
|
322
|
+
invoiceNo,
|
|
323
|
+
contractNo: readString(request.input, 'contractNo'),
|
|
324
|
+
fileName: buildCustomsWorkbookTemplateFileName(invoiceNo),
|
|
325
|
+
sheetNames: readCustomsWorkbookTemplateSheetNames(template),
|
|
326
|
+
workbookData: readRecord(request.input, 'workbookData') ?? {
|
|
327
|
+
invoiceNo,
|
|
328
|
+
contractNo: readString(request.input, 'contractNo'),
|
|
329
|
+
generatedFrom: 'trade-compliance-workbench'
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
return success(result);
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
success: true,
|
|
336
|
+
message: text('Action accepted', '操作已接收'),
|
|
337
|
+
refresh: true
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
async executeViewFileAction(context, viewKey, actionKey, request, file) {
|
|
341
|
+
if (viewKey !== TRADE_COMPLIANCE_VIEW_KEY) {
|
|
342
|
+
return failure('Unsupported view', '不支持的视图');
|
|
343
|
+
}
|
|
344
|
+
const scope = scopeFromContext(context);
|
|
345
|
+
const fileName = getFileDisplayName(file, request.input);
|
|
346
|
+
const assistantFile = viewFileToAssistantFile(file, fileName, roleForUploadAction(actionKey) ?? 'source_file', request.input);
|
|
347
|
+
if (!hasAssistantReadableFileHandle(assistantFile)) {
|
|
348
|
+
return failure('Uploaded file was not registered as an assistant-readable workspace file.', '文件未写入智能体工作空间,请重新上传后再解析。');
|
|
349
|
+
}
|
|
350
|
+
if (actionKey === 'upload_controlled_goods_file') {
|
|
351
|
+
const parsed = await parseControlledGoodsFile({
|
|
352
|
+
buffer: getFileBuffer(file),
|
|
353
|
+
fileName,
|
|
354
|
+
mimeType: getFileMimeType(file)
|
|
355
|
+
});
|
|
356
|
+
const result = await this.service.createReviewBatch(scope, {
|
|
357
|
+
type: 'controlled_goods_file',
|
|
358
|
+
sourceFileName: fileName,
|
|
359
|
+
metadata: {
|
|
360
|
+
fileName,
|
|
361
|
+
size: getFileSize(file),
|
|
362
|
+
mimeType: getFileMimeType(file),
|
|
363
|
+
candidateCount: parsed.candidates.length,
|
|
364
|
+
textLength: parsed.textLength
|
|
365
|
+
},
|
|
366
|
+
items: []
|
|
367
|
+
});
|
|
368
|
+
if (parsed.candidates.length > 0) {
|
|
369
|
+
const chunks = chunkArray(parsed.candidates, CONTROLLED_GOODS_BATCH_SIZE);
|
|
370
|
+
return parsingStarted({
|
|
371
|
+
messages: chunks.map((candidates, index) => buildAssistantCommand({
|
|
372
|
+
action: 'parse_controlled_goods_file',
|
|
373
|
+
file: assistantFile,
|
|
374
|
+
fileName,
|
|
375
|
+
role: 'controlled_goods_file',
|
|
376
|
+
batchId: result.batch.id,
|
|
377
|
+
messageId: `trade-compliance:parse_controlled_goods_file:${result.batch.id}:${index + 1}`,
|
|
378
|
+
text: buildControlledGoodsCandidateBatchPrompt(fileName, result.batch.id, candidates, index, chunks.length, parsed.candidates.length)
|
|
379
|
+
})),
|
|
380
|
+
batchId: result.batch.id,
|
|
381
|
+
role: 'controlled_goods_file',
|
|
382
|
+
expectedCount: parsed.candidates.length
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
return parsingStarted({
|
|
386
|
+
...buildAssistantCommand({
|
|
387
|
+
action: 'parse_controlled_goods_file',
|
|
388
|
+
file: assistantFile,
|
|
389
|
+
fileName,
|
|
390
|
+
role: 'controlled_goods_file',
|
|
391
|
+
batchId: result.batch.id,
|
|
392
|
+
text: buildControlledGoodsParsePrompt(fileName, result.batch.id)
|
|
393
|
+
}),
|
|
394
|
+
expectedCount: 0
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
if (actionKey === 'upload_supplier_contract') {
|
|
398
|
+
const result = await this.service.createReviewBatch(scope, {
|
|
399
|
+
type: 'supplier_contract',
|
|
400
|
+
sourceFileName: fileName,
|
|
401
|
+
metadata: { fileName, size: getFileSize(file), mimeType: getFileMimeType(file) },
|
|
402
|
+
items: []
|
|
403
|
+
});
|
|
404
|
+
return parsingStarted(buildAssistantCommand({
|
|
405
|
+
action: 'parse_supplier_contract',
|
|
406
|
+
file: assistantFile,
|
|
407
|
+
fileName,
|
|
408
|
+
role: 'supplier_contract',
|
|
409
|
+
batchId: result.batch.id,
|
|
410
|
+
text: buildSupplierContractParsePrompt(fileName, result.batch.id)
|
|
411
|
+
}));
|
|
412
|
+
}
|
|
413
|
+
if (actionKey === 'upload_sales_contract') {
|
|
414
|
+
const result = await this.service.createReviewBatch(scope, {
|
|
415
|
+
type: 'sales_contract',
|
|
416
|
+
sourceFileName: fileName,
|
|
417
|
+
metadata: { fileName, size: getFileSize(file), mimeType: getFileMimeType(file) },
|
|
418
|
+
items: []
|
|
419
|
+
});
|
|
420
|
+
return parsingStarted(buildAssistantCommand({
|
|
421
|
+
action: 'parse_sales_contract',
|
|
422
|
+
file: assistantFile,
|
|
423
|
+
fileName,
|
|
424
|
+
role: 'sales_contract',
|
|
425
|
+
batchId: result.batch.id,
|
|
426
|
+
text: buildSalesContractParsePrompt(fileName, result.batch.id)
|
|
427
|
+
}));
|
|
428
|
+
}
|
|
429
|
+
return failure('Unsupported file action', '不支持的文件操作');
|
|
130
430
|
}
|
|
131
431
|
async materializeConfirmedReviewItem(scope, item) {
|
|
132
432
|
const data = mergeReviewData(item.defaultData, item.extractedData, item.confirmedData);
|
|
@@ -180,6 +480,10 @@ TradeComplianceWorkbenchViewProvider = __decorate([
|
|
|
180
480
|
__metadata("design:paramtypes", [TradeComplianceWorkbenchService])
|
|
181
481
|
], TradeComplianceWorkbenchViewProvider);
|
|
182
482
|
export { TradeComplianceWorkbenchViewProvider };
|
|
483
|
+
async function readPackageFile(packageName, filePath) {
|
|
484
|
+
const packageRoot = dirname(requireFromHere.resolve(`${packageName}/package.json`));
|
|
485
|
+
return readFile(join(packageRoot, filePath), 'utf8');
|
|
486
|
+
}
|
|
183
487
|
function scopeFromContext(context) {
|
|
184
488
|
return {
|
|
185
489
|
tenantId: context.tenantId,
|
|
@@ -189,70 +493,6 @@ function scopeFromContext(context) {
|
|
|
189
493
|
userId: context.userId
|
|
190
494
|
};
|
|
191
495
|
}
|
|
192
|
-
function toReviewTableRow(item) {
|
|
193
|
-
const data = mergeReviewData(item.defaultData, item.extractedData, item.confirmedData);
|
|
194
|
-
return {
|
|
195
|
-
id: item.id ?? '',
|
|
196
|
-
recordKind: '审核记录',
|
|
197
|
-
type: item.type ?? '',
|
|
198
|
-
typeLabel: typeLabel(item.type),
|
|
199
|
-
statusLabel: statusLabel(item.reviewStatus),
|
|
200
|
-
title: item.title ?? readString(data, 'productName') ?? readString(data, 'fileName') ?? '-',
|
|
201
|
-
supplierName: readString(data, 'supplierName'),
|
|
202
|
-
productName: readString(data, 'productName'),
|
|
203
|
-
model: readString(data, 'model'),
|
|
204
|
-
hsCode: readString(data, 'enrichedHsCode') ?? readString(data, 'contractHsCode') ?? readString(data, 'hsCode'),
|
|
205
|
-
taxRefundRate: readString(data, 'taxRefundRate'),
|
|
206
|
-
englishName: readString(data, 'englishName'),
|
|
207
|
-
sourceLocation: item.sourceLocation ?? readString(data, 'sourceLocation') ?? readString(data, 'sourceFileName')
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
function applySearch(rows, search) {
|
|
211
|
-
const keyword = normalizeText(search);
|
|
212
|
-
if (!keyword)
|
|
213
|
-
return rows;
|
|
214
|
-
return rows.filter((row) => [
|
|
215
|
-
row.recordKind,
|
|
216
|
-
row.typeLabel,
|
|
217
|
-
row.statusLabel,
|
|
218
|
-
row.title,
|
|
219
|
-
row.supplierName,
|
|
220
|
-
row.productName,
|
|
221
|
-
row.model,
|
|
222
|
-
row.hsCode,
|
|
223
|
-
row.taxRefundRate,
|
|
224
|
-
row.englishName,
|
|
225
|
-
row.sourceLocation
|
|
226
|
-
].some((value) => normalizeText(value).includes(keyword)));
|
|
227
|
-
}
|
|
228
|
-
function applySort(rows, sortBy, direction) {
|
|
229
|
-
if (!sortBy)
|
|
230
|
-
return rows;
|
|
231
|
-
const multiplier = direction === 'desc' ? -1 : 1;
|
|
232
|
-
return [...rows].sort((left, right) => {
|
|
233
|
-
const leftValue = normalizeText(Reflect.get(left, sortBy));
|
|
234
|
-
const rightValue = normalizeText(Reflect.get(right, sortBy));
|
|
235
|
-
return leftValue.localeCompare(rightValue, 'zh-Hans') * multiplier;
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
function typeLabel(type) {
|
|
239
|
-
if (type === 'controlled_goods')
|
|
240
|
-
return '管控商品';
|
|
241
|
-
if (type === 'supplier_product')
|
|
242
|
-
return '供应商商品';
|
|
243
|
-
if (type === 'customs_workbook')
|
|
244
|
-
return '报关资料';
|
|
245
|
-
return type ?? '-';
|
|
246
|
-
}
|
|
247
|
-
function statusLabel(status) {
|
|
248
|
-
if (status === 'confirmed')
|
|
249
|
-
return '已确认';
|
|
250
|
-
if (status === 'rejected')
|
|
251
|
-
return '已驳回';
|
|
252
|
-
if (status === 'needs_revision')
|
|
253
|
-
return '需修订';
|
|
254
|
-
return '待审核';
|
|
255
|
-
}
|
|
256
496
|
function success(data) {
|
|
257
497
|
return {
|
|
258
498
|
success: true,
|
|
@@ -261,6 +501,14 @@ function success(data) {
|
|
|
261
501
|
refresh: true
|
|
262
502
|
};
|
|
263
503
|
}
|
|
504
|
+
function parsingStarted(data) {
|
|
505
|
+
return {
|
|
506
|
+
success: true,
|
|
507
|
+
message: text('Parsing request sent to assistant', '已发送给智能体解析'),
|
|
508
|
+
data,
|
|
509
|
+
refresh: false
|
|
510
|
+
};
|
|
511
|
+
}
|
|
264
512
|
function failure(en_US, zh_Hans) {
|
|
265
513
|
return {
|
|
266
514
|
success: false,
|
|
@@ -273,6 +521,10 @@ function readString(input, key) {
|
|
|
273
521
|
? String(Reflect.get(input, key))
|
|
274
522
|
: undefined;
|
|
275
523
|
}
|
|
524
|
+
function readRecord(input, key) {
|
|
525
|
+
const value = typeof input === 'object' && input != null ? Reflect.get(input, key) : undefined;
|
|
526
|
+
return typeof value === 'object' && value != null && !Array.isArray(value) ? value : undefined;
|
|
527
|
+
}
|
|
276
528
|
function readStringArray(input, key) {
|
|
277
529
|
const value = typeof input === 'object' && input != null ? Reflect.get(input, key) : undefined;
|
|
278
530
|
return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
|
|
@@ -303,10 +555,276 @@ function readControlledGoodsStatus(input, key) {
|
|
|
303
555
|
? value
|
|
304
556
|
: undefined;
|
|
305
557
|
}
|
|
558
|
+
function roleForUploadAction(actionKey) {
|
|
559
|
+
if (actionKey === 'upload_controlled_goods_file')
|
|
560
|
+
return 'controlled_goods_file';
|
|
561
|
+
if (actionKey === 'upload_supplier_contract')
|
|
562
|
+
return 'supplier_contract';
|
|
563
|
+
if (actionKey === 'upload_sales_contract')
|
|
564
|
+
return 'sales_contract';
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
function normalizeWorkbookSource(input) {
|
|
568
|
+
const itemsValue = Array.isArray(input.items) ? input.items : [];
|
|
569
|
+
return {
|
|
570
|
+
invoiceNo: stringValue(input.invoiceNo),
|
|
571
|
+
contractNo: stringValue(input.contractNo),
|
|
572
|
+
date: stringValue(input.date),
|
|
573
|
+
buyerName: stringValue(input.buyerName),
|
|
574
|
+
buyerAddress: stringValue(input.buyerAddress),
|
|
575
|
+
sellerName: stringValue(input.sellerName),
|
|
576
|
+
sellerEnglishName: stringValue(input.sellerEnglishName) ?? stringValue(input.sellerName),
|
|
577
|
+
sellerEnglishAddress: stringValue(input.sellerEnglishAddress),
|
|
578
|
+
paymentTerm: stringValue(input.paymentTerm),
|
|
579
|
+
tradeTerm: stringValue(input.tradeTerm),
|
|
580
|
+
origin: stringValue(input.origin),
|
|
581
|
+
destination: stringValue(input.destination),
|
|
582
|
+
currency: stringValue(input.currency) ?? '人民币',
|
|
583
|
+
exchangeRate: numberValue(input.exchangeRate),
|
|
584
|
+
packageType: stringValue(input.packageType),
|
|
585
|
+
supervisionMode: stringValue(input.supervisionMode),
|
|
586
|
+
taxExemptionNature: stringValue(input.taxExemptionNature ?? input.taxNature),
|
|
587
|
+
domesticSourceLocation: stringValue(input.domesticSourceLocation),
|
|
588
|
+
bankBeneficiary: stringValue(input.bankBeneficiary),
|
|
589
|
+
bankName: stringValue(input.bankName),
|
|
590
|
+
bankAddress: stringValue(input.bankAddress),
|
|
591
|
+
bankAccountNo: stringValue(input.bankAccountNo),
|
|
592
|
+
cnapsCode: stringValue(input.cnapsCode),
|
|
593
|
+
swiftCode: stringValue(input.swiftCode),
|
|
594
|
+
items: itemsValue
|
|
595
|
+
.filter((item) => typeof item === 'object' && item != null && !Array.isArray(item))
|
|
596
|
+
.map((item) => ({
|
|
597
|
+
productName: stringValue(item.productName),
|
|
598
|
+
englishName: stringValue(item.englishName),
|
|
599
|
+
model: stringValue(item.model),
|
|
600
|
+
description: stringValue(item.description),
|
|
601
|
+
quantity: numberValue(item.quantity),
|
|
602
|
+
unit: stringValue(item.unit),
|
|
603
|
+
unitPrice: numberValue(item.unitPrice ?? item.taxInclusiveUnitPrice),
|
|
604
|
+
amount: numberValue(item.amount ?? item.taxInclusiveTotalAmount),
|
|
605
|
+
hsCode: stringValue(item.hsCode ?? item.contractHsCode ?? item.enrichedHsCode),
|
|
606
|
+
cartonNo: stringValue(item.cartonNo),
|
|
607
|
+
dimension: stringValue(item.dimension),
|
|
608
|
+
netWeight: numberValue(item.netWeight),
|
|
609
|
+
grossWeight: numberValue(item.grossWeight)
|
|
610
|
+
}))
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function stringValue(value) {
|
|
614
|
+
return value === undefined || value === null || value === '' ? undefined : String(value);
|
|
615
|
+
}
|
|
616
|
+
function numberValue(value) {
|
|
617
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
618
|
+
return value;
|
|
619
|
+
if (typeof value === 'string' && value.trim()) {
|
|
620
|
+
const parsed = Number(value.replace(/,/g, ''));
|
|
621
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
622
|
+
}
|
|
623
|
+
return undefined;
|
|
624
|
+
}
|
|
306
625
|
function mergeReviewData(...records) {
|
|
307
626
|
return Object.assign({}, ...records.filter(Boolean));
|
|
308
627
|
}
|
|
309
|
-
function
|
|
310
|
-
return
|
|
628
|
+
function getFileDisplayName(file, input) {
|
|
629
|
+
return readString(input, 'name') ?? readString(input, 'fileName') ?? getFileName(file) ?? 'uploaded-document';
|
|
630
|
+
}
|
|
631
|
+
function getFileName(file) {
|
|
632
|
+
const record = file;
|
|
633
|
+
return typeof record.name === 'string'
|
|
634
|
+
? record.name
|
|
635
|
+
: typeof record.originalname === 'string'
|
|
636
|
+
? record.originalname
|
|
637
|
+
: typeof record.originalName === 'string'
|
|
638
|
+
? record.originalName
|
|
639
|
+
: undefined;
|
|
640
|
+
}
|
|
641
|
+
function getFileMimeType(file) {
|
|
642
|
+
const record = file;
|
|
643
|
+
return typeof record.mimetype === 'string'
|
|
644
|
+
? record.mimetype
|
|
645
|
+
: typeof record.type === 'string'
|
|
646
|
+
? record.type
|
|
647
|
+
: undefined;
|
|
648
|
+
}
|
|
649
|
+
function getFileSize(file) {
|
|
650
|
+
const record = file;
|
|
651
|
+
return typeof record.size === 'number' ? record.size : undefined;
|
|
652
|
+
}
|
|
653
|
+
function getFileBuffer(file) {
|
|
654
|
+
const record = file;
|
|
655
|
+
const buffer = record.buffer;
|
|
656
|
+
if (Buffer.isBuffer(buffer))
|
|
657
|
+
return buffer;
|
|
658
|
+
if (buffer instanceof ArrayBuffer)
|
|
659
|
+
return Buffer.from(buffer);
|
|
660
|
+
if (ArrayBuffer.isView(buffer))
|
|
661
|
+
return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
662
|
+
return undefined;
|
|
663
|
+
}
|
|
664
|
+
function chunkArray(items, size) {
|
|
665
|
+
const chunks = [];
|
|
666
|
+
for (let index = 0; index < items.length; index += size) {
|
|
667
|
+
chunks.push(items.slice(index, index + size));
|
|
668
|
+
}
|
|
669
|
+
return chunks;
|
|
670
|
+
}
|
|
671
|
+
function buildAssistantCommand(input) {
|
|
672
|
+
const files = [input.file];
|
|
673
|
+
const workspacePath = input.file.workspacePath ?? input.file.filePath;
|
|
674
|
+
const references = files
|
|
675
|
+
.map((file) => toChatReference(file, input.fileName, input.role))
|
|
676
|
+
.filter((item) => Boolean(item));
|
|
677
|
+
return {
|
|
678
|
+
commandKey: 'assistant.chat.send_message',
|
|
679
|
+
payload: {
|
|
680
|
+
text: input.text,
|
|
681
|
+
clientMessageId: input.messageId ?? `trade-compliance:${input.action}:${input.batchId ?? Date.now()}`,
|
|
682
|
+
files,
|
|
683
|
+
attachments: files.map(toChatAttachment).filter((item) => Boolean(item)),
|
|
684
|
+
references,
|
|
685
|
+
followUpMode: 'queue',
|
|
686
|
+
state: {
|
|
687
|
+
tradeComplianceWorkbench: {
|
|
688
|
+
action: input.action,
|
|
689
|
+
batchId: input.batchId,
|
|
690
|
+
sourceFileName: input.fileName,
|
|
691
|
+
workspacePath
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
batchId: input.batchId,
|
|
696
|
+
role: input.role
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
function hasAssistantReadableFileHandle(file) {
|
|
700
|
+
return Boolean(file.fileAssetId || file.fileId || file.workspacePath || file.filePath);
|
|
701
|
+
}
|
|
702
|
+
function viewFileToAssistantFile(file, fileName, role, input) {
|
|
703
|
+
const record = file;
|
|
704
|
+
const inputRecord = typeof input === 'object' && input != null ? input : {};
|
|
705
|
+
const workspaceFile = readRecord(inputRecord, 'workspaceFile') ?? {};
|
|
706
|
+
const workspacePath = readOptionalFileString(workspaceFile, 'workspacePath') ??
|
|
707
|
+
readOptionalFileString(workspaceFile, 'filePath') ??
|
|
708
|
+
readOptionalFileString(inputRecord, 'workspacePath') ??
|
|
709
|
+
readOptionalFileString(inputRecord, 'filePath');
|
|
710
|
+
const fileAssetId = readOptionalFileString(inputRecord, 'fileAssetId') ??
|
|
711
|
+
readOptionalFileString(inputRecord, 'fileId') ??
|
|
712
|
+
readOptionalFileString(record, 'fileAssetId') ??
|
|
713
|
+
readOptionalFileString(record, 'fileId');
|
|
714
|
+
const fileId = fileAssetId;
|
|
715
|
+
const storageFileId = readOptionalFileString(inputRecord, 'storageFileId') ?? readOptionalFileString(record, 'storageFileId');
|
|
716
|
+
return {
|
|
717
|
+
id: fileAssetId ?? storageFileId,
|
|
718
|
+
fileId,
|
|
719
|
+
fileAssetId,
|
|
720
|
+
storageFileId,
|
|
721
|
+
workspacePath,
|
|
722
|
+
filePath: workspacePath,
|
|
723
|
+
fileUrl: readOptionalFileString(workspaceFile, 'fileUrl') ?? readOptionalFileString(workspaceFile, 'url'),
|
|
724
|
+
url: readOptionalFileString(workspaceFile, 'fileUrl') ?? readOptionalFileString(workspaceFile, 'url'),
|
|
725
|
+
name: readOptionalFileString(workspaceFile, 'name') ?? readOptionalFileString(inputRecord, 'name') ?? fileName,
|
|
726
|
+
originalName: readOptionalFileString(workspaceFile, 'originalName') ?? readOptionalFileString(inputRecord, 'originalName') ?? fileName,
|
|
727
|
+
mimeType: readOptionalFileString(workspaceFile, 'mimeType') ?? readOptionalFileString(inputRecord, 'mimeType') ?? getFileMimeType(file),
|
|
728
|
+
mimetype: readOptionalFileString(workspaceFile, 'mimeType') ?? readOptionalFileString(inputRecord, 'mimeType') ?? getFileMimeType(file),
|
|
729
|
+
size: readNumber(workspaceFile, 'size') ?? readNumber(inputRecord, 'size') ?? getFileSize(file),
|
|
730
|
+
role
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
function readOptionalFileString(record, key) {
|
|
734
|
+
const value = record[key];
|
|
735
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
736
|
+
}
|
|
737
|
+
function toChatAttachment(file) {
|
|
738
|
+
const id = file.fileAssetId ?? file.fileId ?? file.id ?? file.storageFileId ?? file.workspacePath;
|
|
739
|
+
if (!id)
|
|
740
|
+
return null;
|
|
741
|
+
return {
|
|
742
|
+
type: 'file',
|
|
743
|
+
id,
|
|
744
|
+
name: file.name,
|
|
745
|
+
mime_type: file.mimeType ?? 'application/octet-stream',
|
|
746
|
+
workspacePath: file.workspacePath,
|
|
747
|
+
filePath: file.filePath
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
function toChatReference(file, fileName, role) {
|
|
751
|
+
const fileId = file.fileAssetId ?? file.fileId;
|
|
752
|
+
const workspacePath = file.workspacePath ?? file.filePath;
|
|
753
|
+
if (!fileId && !workspacePath)
|
|
754
|
+
return null;
|
|
755
|
+
return {
|
|
756
|
+
type: 'quote',
|
|
757
|
+
label: fileName,
|
|
758
|
+
source: 'Trade Compliance Workbench uploaded document',
|
|
759
|
+
text: [
|
|
760
|
+
`role=${role}`,
|
|
761
|
+
`name=${fileName}`,
|
|
762
|
+
`fileId=${fileId}`,
|
|
763
|
+
`workspacePath=${workspacePath ?? '-'}`,
|
|
764
|
+
`mimeType=${file.mimeType ?? '-'}`,
|
|
765
|
+
`size=${file.size ?? '-'}`
|
|
766
|
+
].join('\n')
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function buildControlledGoodsParsePrompt(fileName, batchId) {
|
|
770
|
+
return [
|
|
771
|
+
`请解析我刚上传的管控商品文件《${fileName}》。`,
|
|
772
|
+
'文件已写入智能体工作空间,请优先根据 state.tradeComplianceWorkbench.workspacePath 使用 SandboxFile 等沙箱文件工具按路径读取;如果同时存在附件能力,也可以使用 file_preview、file_search、file_read 等文件理解工具读取。',
|
|
773
|
+
'目标:识别文件中的管控商品、关键词、海关编码、管控说明、来源页码或条款。',
|
|
774
|
+
`请调用工具 trade_compliance_save_controlled_goods_extraction 保存待审核结果,batchId 必须传 ${batchId ?? '-'}。如果内容很多需要分批调用工具,每次都必须使用同一个 batchId。`,
|
|
775
|
+
'每个 items 条目的 type 必须是 controlled_goods;extractedData 中请包含 productName、hsCode、keywords、controlNote;sourceLocation 记录证据位置。',
|
|
776
|
+
'不要只回复文字,必须调用工具把识别结果写入插件。回复中的识别数量必须等于工具实际保存的 items 总数。'
|
|
777
|
+
].join('\n');
|
|
778
|
+
}
|
|
779
|
+
function buildControlledGoodsCandidateBatchPrompt(fileName, batchId, candidates, batchIndex, batchTotal, totalCount) {
|
|
780
|
+
const start = batchIndex * CONTROLLED_GOODS_BATCH_SIZE + 1;
|
|
781
|
+
const lines = candidates.map((candidate, index) => {
|
|
782
|
+
const number = start + index;
|
|
783
|
+
const parts = [
|
|
784
|
+
`候选行 ${number}/${totalCount}`,
|
|
785
|
+
`序号:${candidate.sequence}`,
|
|
786
|
+
candidate.category ? `分类:${candidate.category}` : undefined,
|
|
787
|
+
candidate.sourceLocation ? `来源:${candidate.sourceLocation}` : undefined,
|
|
788
|
+
candidate.hsCodes.length ? `HS Code:${candidate.hsCodes.join(', ')}` : undefined,
|
|
789
|
+
`原文:${candidate.rawText}`
|
|
790
|
+
].filter(Boolean);
|
|
791
|
+
return parts.join('\n');
|
|
792
|
+
});
|
|
793
|
+
return [
|
|
794
|
+
`请处理管控商品文件《${fileName}》的候选行批次 ${batchIndex + 1}/${batchTotal}。`,
|
|
795
|
+
'插件已经从原始文件中预提取出候选行;原始文件已写入智能体工作空间,请优先根据 state.tradeComplianceWorkbench.workspacePath 使用 SandboxFile 等沙箱文件工具按路径核对来源,不要重新从整份文件自由扩展识别范围。',
|
|
796
|
+
`本批必须只处理下面 ${candidates.length} 条候选行,保存数量应等于候选行数量;除非某一行明确不是商品或管控条目,否则不要丢弃。`,
|
|
797
|
+
`请调用 trade_compliance_save_controlled_goods_extraction 保存待审核结果,batchId 必须传 ${batchId}。`,
|
|
798
|
+
'每个 items 条目的 type 必须是 controlled_goods;title 使用商品名称或 HS Code。',
|
|
799
|
+
'extractedData 必须包含 productName、hsCode、keywords、controlNote、enabled;enabled 默认 true。',
|
|
800
|
+
'sourceLocation 使用候选行的来源页码/工作表/行号;confidence 使用 0 到 1 的数字。',
|
|
801
|
+
'所有外语都翻译成中文,除非 HS Code、CAS、型号、标准号等特定语义必须保留外语或代码。',
|
|
802
|
+
'不要只回复文字,必须调用工具写入工作台;回复中的识别数量必须等于本批工具实际保存的 items 数量。',
|
|
803
|
+
'',
|
|
804
|
+
lines.join('\n\n')
|
|
805
|
+
].join('\n');
|
|
806
|
+
}
|
|
807
|
+
function buildSupplierContractParsePrompt(fileName, batchId) {
|
|
808
|
+
return [
|
|
809
|
+
`请解析我刚上传的供应商合同《${fileName}》。`,
|
|
810
|
+
'文件已写入智能体工作空间,请优先根据 state.tradeComplianceWorkbench.workspacePath 使用 SandboxFile 等沙箱文件工具按路径读取;如果同时存在附件能力,也可以使用 file_preview、file_search、file_read 等文件理解工具读取。',
|
|
811
|
+
'目标:识别供应商信息和商品信息,商品信息包括商品名称、型号、描述、数量、单位、含税单价、含税金额、合同里的海关编码。',
|
|
812
|
+
'对每个商品必须调用 trade_compliance_enrich_product 获取海关编码、退税率、英文品名;该工具会请求 https://hsbianma.com/search?keywords=...&filterFailureCode=true&displayenname=true 并从返回 HTML 的结果表格读取数据。',
|
|
813
|
+
'不要调用其他海关编码接口,也不要只根据模型知识生成编码;如果 trade_compliance_enrich_product 返回 error,请把 error 写入 controlNote 并继续保存待审核记录。',
|
|
814
|
+
'再结合已有管控商品记录调用 trade_compliance_match_controlled_goods 判断是否为管控商品。',
|
|
815
|
+
`最后必须调用 trade_compliance_save_supplier_contract_extraction 保存待审核结果,batchId 必须传 ${batchId ?? '-'};只有调用该保存工具后,工作台左侧供应商商品列表才会出现记录。`,
|
|
816
|
+
'每个 items 条目的 type 必须是 supplier_product;extractedData/defaultData 中请包含 supplierName、supplierCreditCode、supplierAddress、productName、model、description、quantity、unit、taxInclusiveUnitPrice、taxInclusiveTotalAmount、contractHsCode、enrichedHsCode、taxRefundRate、englishName、controlledStatus、controlNote。',
|
|
817
|
+
'不要只回复文字,必须调用工具把识别结果写入插件;回复中的识别数量必须等于本次工具实际保存的 items 数量。'
|
|
818
|
+
].join('\n');
|
|
819
|
+
}
|
|
820
|
+
function buildSalesContractParsePrompt(fileName, batchId) {
|
|
821
|
+
return [
|
|
822
|
+
`请解析我刚上传的购销合同《${fileName}》。`,
|
|
823
|
+
'文件已写入智能体工作空间,请优先根据 state.tradeComplianceWorkbench.workspacePath 使用 SandboxFile 等沙箱文件工具按路径读取;如果同时存在附件能力,也可以使用 file_preview、file_search、file_read 等文件理解工具读取。',
|
|
824
|
+
'目标:识别销售发票和报关资料所需字段,包括发票号、合同号、买方、卖方、币种、贸易术语、付款方式、商品明细、数量、单价、金额、包装、毛重、净重等。',
|
|
825
|
+
`请调用 trade_compliance_save_sales_contract_extraction 保存待审核结果,batchId 可记录为 ${batchId ?? '-'}。`,
|
|
826
|
+
'每个 items 条目的 type 必须是 customs_workbook;extractedData 中请包含 invoiceNo、contractNo、buyerName、sellerName、currency、items 等字段。',
|
|
827
|
+
'不要只回复文字,必须调用工具把识别结果写入插件。'
|
|
828
|
+
].join('\n');
|
|
311
829
|
}
|
|
312
830
|
//# sourceMappingURL=trade-compliance-workbench-view.provider.js.map
|