@chenchaolong/plugin-trade-compliance-workbench 0.1.35 → 0.1.37

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