hazo_collab_forms 3.0.13 → 3.0.17

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 (203) hide show
  1. package/CHANGE_LOG.md +58 -0
  2. package/README.md +5 -38
  3. package/dist/components/clarification/clarification_card.d.ts +2 -0
  4. package/dist/components/clarification/clarification_card.d.ts.map +1 -1
  5. package/dist/components/clarification/clarification_card.js +13 -4
  6. package/dist/components/clarification/clarification_card.js.map +1 -1
  7. package/dist/components/clarification/clarification_doc_reference.js +1 -1
  8. package/dist/components/clarification/clarification_doc_reference.js.map +1 -1
  9. package/dist/components/clarification/clarification_group_card.d.ts +2 -0
  10. package/dist/components/clarification/clarification_group_card.d.ts.map +1 -1
  11. package/dist/components/clarification/clarification_group_card.js +2 -2
  12. package/dist/components/clarification/clarification_group_card.js.map +1 -1
  13. package/dist/components/clarification/clarification_item_body.d.ts +2 -0
  14. package/dist/components/clarification/clarification_item_body.d.ts.map +1 -1
  15. package/dist/components/clarification/clarification_item_body.js +7 -2
  16. package/dist/components/clarification/clarification_item_body.js.map +1 -1
  17. package/dist/components/clarification/clarification_response_form.d.ts +2 -0
  18. package/dist/components/clarification/clarification_response_form.d.ts.map +1 -1
  19. package/dist/components/clarification/clarification_response_form.js +2 -2
  20. package/dist/components/clarification/clarification_response_form.js.map +1 -1
  21. package/dist/components/clarification/clarification_section.d.ts +2 -0
  22. package/dist/components/clarification/clarification_section.d.ts.map +1 -1
  23. package/dist/components/clarification/clarification_section.js +2 -2
  24. package/dist/components/clarification/clarification_section.js.map +1 -1
  25. package/dist/components/clarification/clarification_thread.d.ts +19 -0
  26. package/dist/components/clarification/clarification_thread.d.ts.map +1 -0
  27. package/dist/components/clarification/clarification_thread.js +94 -0
  28. package/dist/components/clarification/clarification_thread.js.map +1 -0
  29. package/dist/components/collab_form_file_upload.js +1 -2
  30. package/dist/components/collab_form_file_upload.js.map +1 -1
  31. package/dist/components/data_ok_multi_state.d.ts.map +1 -1
  32. package/dist/components/data_ok_multi_state.js +1 -2
  33. package/dist/components/data_ok_multi_state.js.map +1 -1
  34. package/dist/components/hazo_collab_form_base.d.ts.map +1 -1
  35. package/dist/components/hazo_collab_form_base.js +6 -8
  36. package/dist/components/hazo_collab_form_base.js.map +1 -1
  37. package/dist/components/hazo_collab_form_combo.d.ts.map +1 -1
  38. package/dist/components/hazo_collab_form_combo.js +8 -10
  39. package/dist/components/hazo_collab_form_combo.js.map +1 -1
  40. package/dist/components/hazo_collab_form_data_table.d.ts.map +1 -1
  41. package/dist/components/hazo_collab_form_data_table.js +7 -4
  42. package/dist/components/hazo_collab_form_data_table.js.map +1 -1
  43. package/dist/components/hazo_collab_form_date.d.ts.map +1 -1
  44. package/dist/components/hazo_collab_form_date.js +3 -6
  45. package/dist/components/hazo_collab_form_date.js.map +1 -1
  46. package/dist/components/hazo_collab_form_file_textbox/validation_dialog.d.ts.map +1 -1
  47. package/dist/components/hazo_collab_form_file_textbox/validation_dialog.js +1 -1
  48. package/dist/components/hazo_collab_form_file_textbox/validation_dialog.js.map +1 -1
  49. package/dist/components/hazo_fb_form/components/draft_clarification_card.d.ts.map +1 -1
  50. package/dist/components/hazo_fb_form/components/draft_clarification_card.js +19 -11
  51. package/dist/components/hazo_fb_form/components/draft_clarification_card.js.map +1 -1
  52. package/dist/components/hazo_fb_form/components/reject_clarification_dialog.d.ts +15 -0
  53. package/dist/components/hazo_fb_form/components/reject_clarification_dialog.d.ts.map +1 -0
  54. package/dist/components/hazo_fb_form/components/reject_clarification_dialog.js +26 -0
  55. package/dist/components/hazo_fb_form/components/reject_clarification_dialog.js.map +1 -0
  56. package/dist/components/hazo_fb_form/components/run_details_dialog.d.ts.map +1 -1
  57. package/dist/components/hazo_fb_form/components/run_details_dialog.js +4 -4
  58. package/dist/components/hazo_fb_form/components/run_details_dialog.js.map +1 -1
  59. package/dist/components/hazo_fb_form/components/sent_clarification_group.d.ts +2 -1
  60. package/dist/components/hazo_fb_form/components/sent_clarification_group.d.ts.map +1 -1
  61. package/dist/components/hazo_fb_form/components/sent_clarification_group.js +2 -2
  62. package/dist/components/hazo_fb_form/components/sent_clarification_group.js.map +1 -1
  63. package/dist/components/hazo_fb_form/context.d.ts +39 -2
  64. package/dist/components/hazo_fb_form/context.d.ts.map +1 -1
  65. package/dist/components/hazo_fb_form/context.js.map +1 -1
  66. package/dist/components/hazo_fb_form/hazo_fb_form.d.ts.map +1 -1
  67. package/dist/components/hazo_fb_form/hazo_fb_form.js +272 -45
  68. package/dist/components/hazo_fb_form/hazo_fb_form.js.map +1 -1
  69. package/dist/components/hazo_fb_form/hooks/use_fb_form_state.d.ts +14 -3
  70. package/dist/components/hazo_fb_form/hooks/use_fb_form_state.d.ts.map +1 -1
  71. package/dist/components/hazo_fb_form/hooks/use_fb_form_state.js +293 -5
  72. package/dist/components/hazo_fb_form/hooks/use_fb_form_state.js.map +1 -1
  73. package/dist/components/hazo_fb_form/hooks/use_llm_run.d.ts.map +1 -1
  74. package/dist/components/hazo_fb_form/hooks/use_llm_run.js +252 -50
  75. package/dist/components/hazo_fb_form/hooks/use_llm_run.js.map +1 -1
  76. package/dist/components/hazo_fb_form/index.d.ts +5 -0
  77. package/dist/components/hazo_fb_form/index.d.ts.map +1 -1
  78. package/dist/components/hazo_fb_form/index.js +3 -0
  79. package/dist/components/hazo_fb_form/index.js.map +1 -1
  80. package/dist/components/hazo_fb_form/shared/agent_stepper.d.ts +9 -0
  81. package/dist/components/hazo_fb_form/shared/agent_stepper.d.ts.map +1 -0
  82. package/dist/components/hazo_fb_form/shared/agent_stepper.js +17 -0
  83. package/dist/components/hazo_fb_form/shared/agent_stepper.js.map +1 -0
  84. package/dist/components/hazo_fb_form/shared/format.d.ts +3 -0
  85. package/dist/components/hazo_fb_form/shared/format.d.ts.map +1 -1
  86. package/dist/components/hazo_fb_form/shared/format.js +16 -0
  87. package/dist/components/hazo_fb_form/shared/format.js.map +1 -1
  88. package/dist/components/hazo_fb_form/shared/group_debug_icon.d.ts +15 -0
  89. package/dist/components/hazo_fb_form/shared/group_debug_icon.d.ts.map +1 -0
  90. package/dist/components/hazo_fb_form/shared/group_debug_icon.js +48 -0
  91. package/dist/components/hazo_fb_form/shared/group_debug_icon.js.map +1 -0
  92. package/dist/components/hazo_fb_form/shared/pdf_side_panel.js +1 -1
  93. package/dist/components/hazo_fb_form/shared/pdf_side_panel.js.map +1 -1
  94. package/dist/components/hazo_fb_form/shared/send_back_item_card.d.ts +31 -0
  95. package/dist/components/hazo_fb_form/shared/send_back_item_card.d.ts.map +1 -0
  96. package/dist/components/hazo_fb_form/shared/send_back_item_card.js +41 -0
  97. package/dist/components/hazo_fb_form/shared/send_back_item_card.js.map +1 -0
  98. package/dist/components/hazo_fb_form/types.d.ts +47 -4
  99. package/dist/components/hazo_fb_form/types.d.ts.map +1 -1
  100. package/dist/components/hazo_fb_form/views/back_office_view.d.ts.map +1 -1
  101. package/dist/components/hazo_fb_form/views/back_office_view.js +69 -18
  102. package/dist/components/hazo_fb_form/views/back_office_view.js.map +1 -1
  103. package/dist/components/hazo_fb_form/views/clarifications_view.d.ts.map +1 -1
  104. package/dist/components/hazo_fb_form/views/clarifications_view.js +18 -7
  105. package/dist/components/hazo_fb_form/views/clarifications_view.js.map +1 -1
  106. package/dist/components/hazo_fb_form/views/client_data_view.d.ts.map +1 -1
  107. package/dist/components/hazo_fb_form/views/client_data_view.js +5 -2
  108. package/dist/components/hazo_fb_form/views/client_data_view.js.map +1 -1
  109. package/dist/components/hazo_fb_form/views/front_office_view.d.ts.map +1 -1
  110. package/dist/components/hazo_fb_form/views/front_office_view.js +121 -41
  111. package/dist/components/hazo_fb_form/views/front_office_view.js.map +1 -1
  112. package/dist/components/hazo_fb_form/views/interim_view.d.ts.map +1 -1
  113. package/dist/components/hazo_fb_form/views/interim_view.js +174 -55
  114. package/dist/components/hazo_fb_form/views/interim_view.js.map +1 -1
  115. package/dist/components/hazo_fb_form/views/review_queue_view.d.ts +14 -0
  116. package/dist/components/hazo_fb_form/views/review_queue_view.d.ts.map +1 -0
  117. package/dist/components/hazo_fb_form/views/review_queue_view.js +162 -0
  118. package/dist/components/hazo_fb_form/views/review_queue_view.js.map +1 -0
  119. package/dist/components/hazo_fb_form/views/send_back_view.d.ts +13 -0
  120. package/dist/components/hazo_fb_form/views/send_back_view.d.ts.map +1 -0
  121. package/dist/components/hazo_fb_form/views/send_back_view.js +203 -0
  122. package/dist/components/hazo_fb_form/views/send_back_view.js.map +1 -0
  123. package/dist/components/hazo_fb_form/views/summary_review_view.d.ts +3 -1
  124. package/dist/components/hazo_fb_form/views/summary_review_view.d.ts.map +1 -1
  125. package/dist/components/hazo_fb_form/views/summary_review_view.js +75 -14
  126. package/dist/components/hazo_fb_form/views/summary_review_view.js.map +1 -1
  127. package/dist/components/hazo_field_selector_dialog/components/field_autocomplete.d.ts.map +1 -1
  128. package/dist/components/hazo_field_selector_dialog/components/field_autocomplete.js +7 -8
  129. package/dist/components/hazo_field_selector_dialog/components/field_autocomplete.js.map +1 -1
  130. package/dist/components/hazo_field_selector_dialog/components/sortable_field_item.d.ts.map +1 -1
  131. package/dist/components/hazo_field_selector_dialog/components/sortable_field_item.js +1 -2
  132. package/dist/components/hazo_field_selector_dialog/components/sortable_field_item.js.map +1 -1
  133. package/dist/components/hazo_template_generator/types.d.ts.map +1 -1
  134. package/dist/components/hazo_template_generator/types.js +1 -0
  135. package/dist/components/hazo_template_generator/types.js.map +1 -1
  136. package/dist/components/hazo_validation_rule_editor/components/rule_editor.js +1 -1
  137. package/dist/components/hazo_validation_rule_editor/components/rule_editor.js.map +1 -1
  138. package/dist/components/index.d.ts +2 -2
  139. package/dist/components/index.d.ts.map +1 -1
  140. package/dist/components/index.js +1 -1
  141. package/dist/components/index.js.map +1 -1
  142. package/dist/components/multi_state_radio.d.ts.map +1 -1
  143. package/dist/components/multi_state_radio.js +1 -2
  144. package/dist/components/multi_state_radio.js.map +1 -1
  145. package/dist/components/shared/field_tooltip/field_tooltip.d.ts.map +1 -1
  146. package/dist/components/shared/field_tooltip/field_tooltip.js +1 -2
  147. package/dist/components/shared/field_tooltip/field_tooltip.js.map +1 -1
  148. package/dist/components/shared/file_bar/file_bar.d.ts +12 -2
  149. package/dist/components/shared/file_bar/file_bar.d.ts.map +1 -1
  150. package/dist/components/shared/file_bar/file_bar.js +8 -9
  151. package/dist/components/shared/file_bar/file_bar.js.map +1 -1
  152. package/dist/components/shared/file_bar/file_bar_validation.d.ts +1 -1
  153. package/dist/components/shared/file_bar/file_bar_validation.d.ts.map +1 -1
  154. package/dist/components/shared/file_bar/file_bar_validation.js +9 -0
  155. package/dist/components/shared/file_bar/file_bar_validation.js.map +1 -1
  156. package/dist/components/shared/file_bar/file_bar_validation_dialog.d.ts +16 -1
  157. package/dist/components/shared/file_bar/file_bar_validation_dialog.d.ts.map +1 -1
  158. package/dist/components/shared/file_bar/file_bar_validation_dialog.js +31 -26
  159. package/dist/components/shared/file_bar/file_bar_validation_dialog.js.map +1 -1
  160. package/dist/components/shared/file_bar/file_validation_issues_dialog.d.ts +3 -1
  161. package/dist/components/shared/file_bar/file_validation_issues_dialog.d.ts.map +1 -1
  162. package/dist/components/shared/file_bar/file_validation_issues_dialog.js +2 -2
  163. package/dist/components/shared/file_bar/file_validation_issues_dialog.js.map +1 -1
  164. package/dist/components/shared/role_utils/role_utils.d.ts +1 -0
  165. package/dist/components/shared/role_utils/role_utils.d.ts.map +1 -1
  166. package/dist/components/shared/role_utils/role_utils.js +3 -0
  167. package/dist/components/shared/role_utils/role_utils.js.map +1 -1
  168. package/dist/components/shared/rule_result_card.d.ts +31 -0
  169. package/dist/components/shared/rule_result_card.d.ts.map +1 -0
  170. package/dist/components/shared/rule_result_card.js +72 -0
  171. package/dist/components/shared/rule_result_card.js.map +1 -0
  172. package/dist/components/shared/unified_field_controls/constants.d.ts.map +1 -1
  173. package/dist/components/shared/unified_field_controls/constants.js +1 -0
  174. package/dist/components/shared/unified_field_controls/constants.js.map +1 -1
  175. package/dist/components/shared/unified_field_controls/resolve_field_controls.d.ts +2 -0
  176. package/dist/components/shared/unified_field_controls/resolve_field_controls.d.ts.map +1 -1
  177. package/dist/components/shared/unified_field_controls/resolve_field_controls.js +4 -0
  178. package/dist/components/shared/unified_field_controls/resolve_field_controls.js.map +1 -1
  179. package/dist/components/shared/unified_field_controls/types.d.ts +5 -0
  180. package/dist/components/shared/unified_field_controls/types.d.ts.map +1 -1
  181. package/dist/components/shared/unified_field_controls/unified_field_controls.d.ts.map +1 -1
  182. package/dist/components/shared/unified_field_controls/unified_field_controls.js +13 -2
  183. package/dist/components/shared/unified_field_controls/unified_field_controls.js.map +1 -1
  184. package/dist/components/shared/use_base_form_field.d.ts +2 -0
  185. package/dist/components/shared/use_base_form_field.d.ts.map +1 -1
  186. package/dist/components/shared/use_base_form_field.js +4 -0
  187. package/dist/components/shared/use_base_form_field.js.map +1 -1
  188. package/dist/lib/autofill_handler.d.ts +5 -0
  189. package/dist/lib/autofill_handler.d.ts.map +1 -1
  190. package/dist/lib/autofill_handler.js +27 -10
  191. package/dist/lib/autofill_handler.js.map +1 -1
  192. package/dist/lib/fb_form_handler.d.ts.map +1 -1
  193. package/dist/lib/fb_form_handler.js +45 -0
  194. package/dist/lib/fb_form_handler.js.map +1 -1
  195. package/dist/types/clarification.d.ts +22 -1
  196. package/dist/types/clarification.d.ts.map +1 -1
  197. package/dist/types/icons_behaviour.d.ts +1 -1
  198. package/dist/types/icons_behaviour.d.ts.map +1 -1
  199. package/dist/types/icons_behaviour.js +3 -1
  200. package/dist/types/icons_behaviour.js.map +1 -1
  201. package/dist/types/validation.d.ts +6 -0
  202. package/dist/types/validation.d.ts.map +1 -1
  203. package/package.json +4 -8
@@ -44,7 +44,7 @@ function build_tag_group_map(back_sections) {
44
44
  }
45
45
  /** Route classified files to back-office groups and trigger autofill */
46
46
  async function route_files_to_back_office(options) {
47
- const { classifications, back_sections, front_form_data, on_back_change, back_form_data, autofill_api_endpoint, file_manager, update_progress, set_group_autofill_log, run_log, tracker, on_autofill_file, force_autofill } = options;
47
+ const { classifications, back_sections, front_form_data, on_back_change, back_form_data, autofill_api_endpoint, file_manager, update_progress, set_group_autofill_log, run_log, tracker, on_autofill_file, on_queue_files, force_autofill } = options;
48
48
  const tag_group_map = build_tag_group_map(back_sections);
49
49
  const errors = [];
50
50
  const unassigned = [];
@@ -54,7 +54,17 @@ async function route_files_to_back_office(options) {
54
54
  const field_files = get_files_from_value(front_form_data[cls.field_id]);
55
55
  for (const fc of cls.file_classifications) {
56
56
  const file_info = field_files.find((f) => f.file_id === fc.file_id);
57
- all_files.push({ ...fc, source_field_id: cls.field_id, attachment: file_info?.attachment });
57
+ // Build attachment from classification data if not found in front_form_data
58
+ const attachment = file_info?.attachment ?? {
59
+ file_id: fc.file_id,
60
+ ref_id: `ref_${fc.file_id}`,
61
+ file_name: fc.file_name,
62
+ file_size: 0,
63
+ mime_type: 'application/octet-stream',
64
+ visibility: 'public',
65
+ attached_at: new Date().toISOString(),
66
+ };
67
+ all_files.push({ ...fc, source_field_id: cls.field_id, attachment });
58
68
  }
59
69
  }
60
70
  // Track accumulated files per group locally to avoid stale state reads.
@@ -134,10 +144,17 @@ async function route_files_to_back_office(options) {
134
144
  let autofill_done = 0;
135
145
  // Count total NEW files across all groups for progress
136
146
  let total_autofills = 0;
147
+ const all_autofill_file_ids = [];
137
148
  for (const gid of groups_to_autofill) {
138
149
  const fk = `__files_${gid}`;
139
150
  const new_files = newly_routed_files.get(fk) ?? [];
140
151
  total_autofills += Math.max(new_files.length, 1);
152
+ for (const f of new_files)
153
+ all_autofill_file_ids.push(f.file_id);
154
+ }
155
+ // Mark all files as queued before autofill starts
156
+ if (all_autofill_file_ids.length > 0) {
157
+ on_queue_files?.(all_autofill_file_ids, true);
141
158
  }
142
159
  // Add autofill actions to the pipeline tracker (now that we know the count)
143
160
  if (tracker) {
@@ -180,6 +197,7 @@ async function route_files_to_back_office(options) {
180
197
  current_step: `${pct}Auto-filling ${group_id} — file ${fi + 1}/${group_files.length} (${autofill_done}/${total_autofills})`,
181
198
  overall_percent: tracker ? Math.round((tracker.completed / tracker.total) * 100) : undefined,
182
199
  });
200
+ on_queue_files?.([f.file_id], false);
183
201
  on_autofill_file?.(f.file_id, true);
184
202
  try {
185
203
  const download_url = file_manager?.callbacks?.get_download_url?.(f.file_id, 'public') ?? '';
@@ -265,6 +283,8 @@ async function route_files_to_back_office(options) {
265
283
  on_autofill_file?.(f.file_id, false);
266
284
  }
267
285
  }
286
+ // Collect run_log entries for this group
287
+ const group_run_log = run_log.filter((entry) => entry.step === 'autofill' && entry.label.startsWith(`Autofill: ${group_id}`));
268
288
  // Set autofill log for this group
269
289
  if (group_error && group_fields_populated === 0) {
270
290
  // If there was an error and no fields were populated, check if it's a real error or just empty
@@ -275,6 +295,7 @@ async function route_files_to_back_office(options) {
275
295
  status: is_empty ? 'empty' : 'error',
276
296
  message: group_error,
277
297
  timestamp: Date.now(),
298
+ run_log: group_run_log.length > 0 ? group_run_log : undefined,
278
299
  },
279
300
  }));
280
301
  }
@@ -287,6 +308,7 @@ async function route_files_to_back_office(options) {
287
308
  message: `Populated ${group_fields_populated} field(s) from ${file_label}`,
288
309
  timestamp: Date.now(),
289
310
  details,
311
+ run_log: group_run_log.length > 0 ? group_run_log : undefined,
290
312
  },
291
313
  }));
292
314
  }
@@ -297,6 +319,7 @@ async function route_files_to_back_office(options) {
297
319
  status: 'empty',
298
320
  message: 'No matching data found in document',
299
321
  timestamp: Date.now(),
322
+ run_log: group_run_log.length > 0 ? group_run_log : undefined,
300
323
  },
301
324
  }));
302
325
  }
@@ -384,9 +407,11 @@ async function validate_classified_files(options) {
384
407
  const check_type_filter = options.check_type_filter;
385
408
  // check_type filter: rules without check_type default to 'immediate' context.
386
409
  // When filtering for 'backoffice', only explicitly backoffice rules run.
410
+ // check_type filter: rules without check_type run in ALL contexts (immediate + backoffice).
411
+ // Only rules with an explicit check_type are restricted to that context.
387
412
  const matching_rules = validation_rules.filter((r) => r.enabled && (file.document_types.includes(r.document_type) ||
388
413
  r.document_type === 'general' ||
389
- file.document_types.length === 0) && (!check_type_filter || r.check_type === check_type_filter || (!r.check_type && check_type_filter === 'immediate')));
414
+ file.document_types.length === 0) && (!check_type_filter || !r.check_type || r.check_type === check_type_filter));
390
415
  if (matching_rules.length === 0) {
391
416
  // No rules match — preserve existing result if available (e.g. immediate passed),
392
417
  // otherwise mark as skipped
@@ -1140,13 +1165,14 @@ export function use_llm_run({ props, update_progress, classification_results, se
1140
1165
  }
1141
1166
  }
1142
1167
  }
1143
- // Determine eligible files: passed/skipped validation, OR failed but all clarifications resolved
1168
+ // Determine eligible files: passed/skipped/pending/validating, OR failed but all clarifications resolved
1144
1169
  const eligible_classifications = [];
1145
1170
  for (const cls of classification_results) {
1146
1171
  const eligible_file_cls = [];
1147
1172
  for (const fc of cls.file_classifications) {
1148
1173
  const vr = file_validation_results[fc.file_id];
1149
- if (!vr || vr.status === 'passed' || vr.status === 'skipped') {
1174
+ if (!vr || vr.status === 'passed' || vr.status === 'skipped' || vr.status === 'pending' || vr.status === 'validating' || vr.status === 'manual_review') {
1175
+ // No validation, passed, skipped, manual_review, or validation not yet complete — eligible
1150
1176
  eligible_file_cls.push(fc);
1151
1177
  }
1152
1178
  else if (vr.status === 'failed') {
@@ -1161,8 +1187,17 @@ export function use_llm_run({ props, update_progress, classification_results, se
1161
1187
  if (all_resolved) {
1162
1188
  eligible_file_cls.push(fc);
1163
1189
  }
1190
+ else {
1191
+ logger.warn('[use_llm_run] file ineligible: failed validation with unresolved errors', { file_id: fc.file_id, file_name: fc.file_name, error_count: vr.errors.length });
1192
+ }
1193
+ }
1194
+ else if (vr.status === 'manual_review') {
1195
+ // Client requested manual review — still eligible for routing/autofill
1196
+ eligible_file_cls.push(fc);
1197
+ }
1198
+ else {
1199
+ logger.warn('[use_llm_run] file ineligible: unexpected validation status', { file_id: fc.file_id, status: vr.status });
1164
1200
  }
1165
- // manual_review and other statuses: not eligible unless explicitly skipped
1166
1201
  }
1167
1202
  if (eligible_file_cls.length > 0) {
1168
1203
  eligible_classifications.push({
@@ -1233,6 +1268,12 @@ export function use_llm_run({ props, update_progress, classification_results, se
1233
1268
  set_group_autofill_log,
1234
1269
  run_log,
1235
1270
  on_autofill_file: handle_autofill_file,
1271
+ on_queue_files: (file_ids, queued) => {
1272
+ if (queued)
1273
+ add_queued_files(file_ids);
1274
+ else
1275
+ remove_queued_files(file_ids);
1276
+ },
1236
1277
  });
1237
1278
  set_unassigned_files(routing_result.unassigned);
1238
1279
  logger.info('[use_llm_run] trigger_complete_done', {
@@ -1262,39 +1303,86 @@ export function use_llm_run({ props, update_progress, classification_results, se
1262
1303
  classification_count: classification_results.length,
1263
1304
  has_validation: !!(validation_api_endpoint && validation_rules?.length),
1264
1305
  rule_count: validation_rules?.length ?? 0,
1306
+ has_on_back_change: !!on_back_change,
1307
+ has_autofill_endpoint: !!autofill_api_endpoint,
1308
+ back_sections_count: back_sections.length,
1309
+ back_section_groups: back_sections.map(s => (s.groups ?? []).map((g) => ({ id: g.id, tag_id: g.tag_id, prompt_area: g.prompt_area, prompt_key: g.prompt_key }))),
1265
1310
  });
1266
- if (!on_back_change)
1311
+ if (!on_back_change) {
1312
+ logger.warn('[use_llm_run] trigger_backoffice_run: no on_back_change — aborting');
1267
1313
  return;
1268
- if (classification_results.length === 0)
1314
+ }
1315
+ if (classification_results.length === 0) {
1316
+ logger.warn('[use_llm_run] trigger_backoffice_run: no classification_results — aborting');
1269
1317
  return;
1318
+ }
1270
1319
  set_group_autofill_log({});
1271
1320
  const run_log = [];
1272
1321
  const errors = [];
1322
+ // ── Queue all classified files as "queued" so UI shows pending state ──
1323
+ const all_classified_file_ids = classification_results.flatMap(c => c.file_classifications.map(fc => fc.file_id));
1324
+ if (all_classified_file_ids.length > 0) {
1325
+ add_queued_files(all_classified_file_ids);
1326
+ }
1327
+ // ── Snapshot refs BEFORE backoffice validation ──
1328
+ // Backoffice validation triggers set_file_validation_results → useEffect clears/replaces
1329
+ // draft_clarifications via React state flush during await. Capture stable copies now.
1330
+ const snapshot_sent = [...sent_clarifications_ref.current];
1331
+ const snapshot_pending = new Map(pending_responses_ref.current);
1332
+ const snapshot_drafts = [...draft_clarifications_ref.current];
1333
+ // Build unified clarification items view from the snapshot
1334
+ const all_clarification_items = new Map();
1335
+ for (const item of snapshot_drafts)
1336
+ all_clarification_items.set(item.id, item);
1337
+ for (const item of snapshot_sent)
1338
+ all_clarification_items.set(item.id, item);
1339
+ for (const [clar_id, response] of snapshot_pending) {
1340
+ const existing = all_clarification_items.get(clar_id);
1341
+ if (existing && response.response_choice) {
1342
+ all_clarification_items.set(clar_id, {
1343
+ ...existing,
1344
+ status: 'responded',
1345
+ response_choice: response.response_choice,
1346
+ user_comment: response.user_comment,
1347
+ response_files: response.response_files ?? existing.response_files ?? [],
1348
+ });
1349
+ }
1350
+ }
1351
+ // Build resolved rules map from snapshot (for backoffice validation to skip already-resolved rules)
1352
+ let effective_clarifications_for_rules = snapshot_sent;
1353
+ if (snapshot_pending.size > 0) {
1354
+ const sent_responded_ids = new Set(snapshot_sent.filter(c => c.status === 'responded' || c.status === 'resolved').map(c => c.id));
1355
+ const pending_items = [];
1356
+ for (const [clar_id, response] of snapshot_pending) {
1357
+ if (!response.response_choice)
1358
+ continue;
1359
+ if (sent_responded_ids.has(clar_id))
1360
+ continue;
1361
+ const original = all_clarification_items.get(clar_id);
1362
+ if (original) {
1363
+ pending_items.push({ ...original, status: 'responded', response_choice: response.response_choice, user_comment: response.user_comment });
1364
+ }
1365
+ }
1366
+ if (pending_items.length > 0) {
1367
+ effective_clarifications_for_rules = [...snapshot_sent, ...pending_items];
1368
+ }
1369
+ }
1273
1370
  // ── Step 1: Backoffice validation ──
1371
+ let effective_file_validation = { ...file_validation_results };
1274
1372
  const has_backoffice_validation = !!(validation_api_endpoint && validation_rules?.length);
1275
1373
  if (has_backoffice_validation) {
1276
1374
  update_progress({ status: 'validating', total_steps: 1, completed_steps: 0, current_step: 'Running back-office validation...', error: undefined });
1277
- const current_sent = sent_clarifications_ref.current;
1278
- const current_pending = pending_responses_ref.current;
1279
- const current_drafts = draft_clarifications_ref.current;
1280
- let effective_clarifications = current_sent;
1281
- if (current_pending.size > 0) {
1282
- const pending_items = [];
1283
- for (const [clar_id, response] of current_pending) {
1284
- if (!response.response_choice)
1285
- continue;
1286
- if (current_sent.some(c => c.id === clar_id && (c.status === 'responded' || c.status === 'resolved')))
1287
- continue;
1288
- const original = current_drafts.find(c => c.id === clar_id) ?? current_sent.find(c => c.id === clar_id);
1289
- if (original) {
1290
- pending_items.push({ ...original, status: 'responded', response_choice: response.response_choice, user_comment: response.user_comment });
1375
+ const resolved_rules = build_resolved_rules_map(effective_clarifications_for_rules);
1376
+ // Also mark accepted rules as resolved so they skip re-validation
1377
+ for (const [, result] of Object.entries(effective_file_validation)) {
1378
+ if (result.rule_results) {
1379
+ for (const rr of result.rule_results) {
1380
+ if (rr.accepted && rr.rule_id) {
1381
+ resolved_rules.set(rr.rule_id, { response_choice: rr.user_resolution?.response_choice ?? 'accepted', user_comment: rr.user_resolution?.user_comment });
1382
+ }
1291
1383
  }
1292
1384
  }
1293
- if (pending_items.length > 0) {
1294
- effective_clarifications = [...current_sent, ...pending_items];
1295
- }
1296
1385
  }
1297
- const resolved_rules = build_resolved_rules_map(effective_clarifications);
1298
1386
  const vr = await validate_classified_files({
1299
1387
  classifications: classification_results,
1300
1388
  validation_api_endpoint: validation_api_endpoint,
@@ -1309,37 +1397,140 @@ export function use_llm_run({ props, update_progress, classification_results, se
1309
1397
  resolved_rules,
1310
1398
  });
1311
1399
  errors.push(...vr.errors);
1400
+ // Merge backoffice validation results into local copy for eligibility check
1401
+ for (const result of vr.all_validation_results) {
1402
+ effective_file_validation[result.file_id] = result;
1403
+ }
1312
1404
  }
1313
- // ── Step 2: Determine eligible files and route + autofill ──
1314
- update_progress({ status: 'routing', total_steps: 1, completed_steps: 0, current_step: 'Routing files to back-office...', error: undefined });
1315
- const current_sent = sent_clarifications_ref.current;
1316
- const current_pending = pending_responses_ref.current;
1317
- const current_drafts = draft_clarifications_ref.current;
1318
- // Build unified clarification items view
1319
- const all_clarification_items = new Map();
1320
- for (const item of current_drafts)
1321
- all_clarification_items.set(item.id, item);
1322
- for (const item of current_sent)
1323
- all_clarification_items.set(item.id, item);
1324
- for (const [clar_id, response] of current_pending) {
1325
- const existing = all_clarification_items.get(clar_id);
1326
- if (existing && response.response_choice) {
1327
- all_clarification_items.set(clar_id, {
1328
- ...existing,
1329
- status: 'responded',
1330
- response_choice: response.response_choice,
1331
- user_comment: response.user_comment,
1332
- response_files: response.response_files ?? existing.response_files ?? [],
1333
- });
1405
+ // ── Step 1b: Text classification ──
1406
+ // Extract text from front-office fields (non-file content) and classify it
1407
+ const { field_textbox_configs, llm_api_endpoint } = props;
1408
+ if (field_textbox_configs && llm_api_endpoint) {
1409
+ const text_entries = [];
1410
+ for (const field_id of Object.keys(field_textbox_configs)) {
1411
+ const text = get_text_from_value(front_form_data[field_id]);
1412
+ if (text.trim().length > 0) {
1413
+ text_entries.push({ field_id, text });
1414
+ }
1415
+ }
1416
+ if (text_entries.length > 0) {
1417
+ update_progress({ current_step: `Classifying ${text_entries.length} text entry(ies)...` });
1418
+ for (const entry of text_entries) {
1419
+ try {
1420
+ const classify_text_start = Date.now();
1421
+ const response = await fetch(llm_api_endpoint, {
1422
+ method: 'POST',
1423
+ headers: { 'Content-Type': 'application/json' },
1424
+ body: JSON.stringify({
1425
+ action: 'classify_text',
1426
+ field_id: entry.field_id,
1427
+ text: entry.text,
1428
+ available_tags: field_textbox_configs[entry.field_id]?.classification?.available_tags ?? props.available_tags ?? [],
1429
+ ...(props.available_document_types?.length ? { available_document_types: props.available_document_types } : {}),
1430
+ }),
1431
+ });
1432
+ const result = await response.json();
1433
+ run_log.push({
1434
+ step: 'classify_text',
1435
+ label: `Text Classification: ${entry.field_id}`,
1436
+ request: { action: 'classify_text', field_id: entry.field_id, text_length: entry.text.length },
1437
+ response: result,
1438
+ timestamp: classify_text_start,
1439
+ duration_ms: Date.now() - classify_text_start,
1440
+ });
1441
+ if (result.success && result.tags?.length > 0) {
1442
+ // Add text classification to classification_results so review queue can use categories
1443
+ set_classification_results((prev) => {
1444
+ const text_cls_id = `__text_${entry.field_id}`;
1445
+ const text_file_cls = {
1446
+ file_id: text_cls_id,
1447
+ file_name: `Text from ${entry.field_id}`,
1448
+ tags: result.tags,
1449
+ document_nature: result.document_nature,
1450
+ };
1451
+ const existing = prev.find((c) => c.field_id === entry.field_id);
1452
+ if (existing) {
1453
+ // Merge with existing file classifications
1454
+ const filtered = existing.file_classifications.filter((fc) => fc.file_id !== text_cls_id);
1455
+ return [
1456
+ ...prev.filter((c) => c.field_id !== entry.field_id),
1457
+ {
1458
+ ...existing,
1459
+ tags: [...new Set([...existing.tags, ...result.tags])],
1460
+ file_classifications: [...filtered, text_file_cls],
1461
+ },
1462
+ ];
1463
+ }
1464
+ return [...prev, { field_id: entry.field_id, tags: result.tags, file_classifications: [text_file_cls] }];
1465
+ });
1466
+ }
1467
+ // Create draft clarifications from text issues
1468
+ if (result.success && result.issues?.length > 0) {
1469
+ const text_clarifications = result.issues.map((issue, idx) => ({
1470
+ id: `text_issue_${entry.field_id}_${idx}_${Date.now()}`,
1471
+ type: 'validation_issue',
1472
+ status: 'pending',
1473
+ target_field_id: entry.field_id,
1474
+ target_label: `Text from ${entry.field_id}`,
1475
+ issue_description: issue.description,
1476
+ severity: issue.severity === 'error' ? 'error' : 'warning',
1477
+ doc_references: [{
1478
+ file_id: `__text_${entry.field_id}`,
1479
+ file_name: `Text from ${entry.field_id}`,
1480
+ mime_type: 'text/plain',
1481
+ }],
1482
+ response_options: [
1483
+ { value: 'acknowledge', label: 'Acknowledge' },
1484
+ { value: 'will_fix', label: 'Will fix' },
1485
+ { value: 'ignore', label: 'Ignore' },
1486
+ ],
1487
+ created_at: new Date().toISOString(),
1488
+ }));
1489
+ set_file_validation_results((prev) => ({
1490
+ ...prev,
1491
+ [`__text_${entry.field_id}`]: {
1492
+ file_id: `__text_${entry.field_id}`,
1493
+ file_name: `Text from ${entry.field_id}`,
1494
+ status: 'failed',
1495
+ errors: text_clarifications,
1496
+ document_types: [],
1497
+ },
1498
+ }));
1499
+ }
1500
+ }
1501
+ catch (err) {
1502
+ errors.push({ step: `classify_text:${entry.field_id}`, error: err instanceof Error ? err.message : 'Text classification failed' });
1503
+ logger.error('[use_llm_run] classify_text_error', { field_id: entry.field_id, error: err instanceof Error ? err.message : String(err) });
1504
+ }
1505
+ }
1506
+ logger.info('[use_llm_run] text_classification_phase_complete', { text_entry_count: text_entries.length });
1334
1507
  }
1335
1508
  }
1336
- // Determine eligible files
1509
+ // Clear queued state — autofill will re-queue eligible files via on_queue_files
1510
+ if (all_classified_file_ids.length > 0) {
1511
+ remove_queued_files(all_classified_file_ids);
1512
+ }
1513
+ // ── Step 2: Determine eligible files and route + autofill ──
1514
+ update_progress({ status: 'routing', total_steps: 1, completed_steps: 0, current_step: 'Routing files to back-office...', error: undefined });
1515
+ // Determine eligible files — use effective_file_validation which merges backoffice results
1337
1516
  const eligible_classifications = [];
1338
1517
  for (const cls of classification_results) {
1339
1518
  const eligible_file_cls = [];
1340
1519
  for (const fc of cls.file_classifications) {
1341
- const vr = file_validation_results[fc.file_id];
1342
- if (!vr || vr.status === 'passed' || vr.status === 'skipped') {
1520
+ const vr = effective_file_validation[fc.file_id];
1521
+ // Exclude files with any rule sent back to client (pending client fix)
1522
+ const has_sent_back = vr?.rule_results?.some(rr => rr.sent_back);
1523
+ if (has_sent_back) {
1524
+ logger.warn('[use_llm_run] backoffice: file ineligible — has sent-back rule pending client response', { file_id: fc.file_id });
1525
+ continue;
1526
+ }
1527
+ // Exclude files with unreviewed resolved rules (pending agent acceptance)
1528
+ const resolved_rules_for_file = vr?.rule_results?.filter(rr => rr.has_issue && rr.user_resolved) ?? [];
1529
+ if (resolved_rules_for_file.length > 0 && !resolved_rules_for_file.every(rr => rr.accepted)) {
1530
+ logger.warn('[use_llm_run] backoffice: file ineligible — has unaccepted resolved rules', { file_id: fc.file_id });
1531
+ continue;
1532
+ }
1533
+ if (!vr || vr.status === 'passed' || vr.status === 'skipped' || vr.status === 'pending' || vr.status === 'validating' || vr.status === 'manual_review') {
1343
1534
  eligible_file_cls.push(fc);
1344
1535
  }
1345
1536
  else if (vr.status === 'failed') {
@@ -1351,6 +1542,11 @@ export function use_llm_run({ props, update_progress, classification_results, se
1351
1542
  });
1352
1543
  if (all_resolved)
1353
1544
  eligible_file_cls.push(fc);
1545
+ else
1546
+ logger.warn('[use_llm_run] backoffice: file ineligible — failed with unresolved errors', { file_id: fc.file_id, error_count: vr.errors.length, errors: vr.errors.map(e => ({ id: e.id, status: e.status, rule_id: e.rule_id })) });
1547
+ }
1548
+ else {
1549
+ logger.warn('[use_llm_run] backoffice: file ineligible — unexpected status', { file_id: fc.file_id, status: vr.status });
1354
1550
  }
1355
1551
  }
1356
1552
  if (eligible_file_cls.length > 0) {
@@ -1415,6 +1611,12 @@ export function use_llm_run({ props, update_progress, classification_results, se
1415
1611
  set_group_autofill_log,
1416
1612
  run_log,
1417
1613
  on_autofill_file: handle_autofill_file,
1614
+ on_queue_files: (file_ids, queued) => {
1615
+ if (queued)
1616
+ add_queued_files(file_ids);
1617
+ else
1618
+ remove_queued_files(file_ids);
1619
+ },
1418
1620
  force_autofill: true,
1419
1621
  });
1420
1622
  set_unassigned_files(routing_result.unassigned);