hazo_collab_forms 2.2.8 → 3.0.2

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 (370) hide show
  1. package/CHANGE_LOG.md +47 -0
  2. package/dist/components/_internal_form_set.d.ts +17 -0
  3. package/dist/components/_internal_form_set.d.ts.map +1 -1
  4. package/dist/components/_internal_form_set.js +38 -9
  5. package/dist/components/_internal_form_set.js.map +1 -1
  6. package/dist/components/clarification/clarification_card.d.ts +36 -13
  7. package/dist/components/clarification/clarification_card.d.ts.map +1 -1
  8. package/dist/components/clarification/clarification_card.js +12 -32
  9. package/dist/components/clarification/clarification_card.js.map +1 -1
  10. package/dist/components/clarification/clarification_dialog.d.ts.map +1 -1
  11. package/dist/components/clarification/clarification_dialog.js +11 -5
  12. package/dist/components/clarification/clarification_dialog.js.map +1 -1
  13. package/dist/components/clarification/clarification_doc_reference.d.ts +6 -0
  14. package/dist/components/clarification/clarification_doc_reference.d.ts.map +1 -1
  15. package/dist/components/clarification/clarification_doc_reference.js +20 -11
  16. package/dist/components/clarification/clarification_doc_reference.js.map +1 -1
  17. package/dist/components/clarification/clarification_group_card.d.ts +65 -0
  18. package/dist/components/clarification/clarification_group_card.d.ts.map +1 -0
  19. package/dist/components/clarification/clarification_group_card.js +84 -0
  20. package/dist/components/clarification/clarification_group_card.js.map +1 -0
  21. package/dist/components/clarification/clarification_item_body.d.ts +80 -0
  22. package/dist/components/clarification/clarification_item_body.d.ts.map +1 -0
  23. package/dist/components/clarification/clarification_item_body.js +55 -0
  24. package/dist/components/clarification/clarification_item_body.js.map +1 -0
  25. package/dist/components/clarification/clarification_response_form.d.ts +25 -1
  26. package/dist/components/clarification/clarification_response_form.d.ts.map +1 -1
  27. package/dist/components/clarification/clarification_response_form.js +103 -9
  28. package/dist/components/clarification/clarification_response_form.js.map +1 -1
  29. package/dist/components/clarification/clarification_section.d.ts +36 -2
  30. package/dist/components/clarification/clarification_section.d.ts.map +1 -1
  31. package/dist/components/clarification/clarification_section.js +55 -19
  32. package/dist/components/clarification/clarification_section.js.map +1 -1
  33. package/dist/components/clarification/clarification_status_config.d.ts +57 -0
  34. package/dist/components/clarification/clarification_status_config.d.ts.map +1 -0
  35. package/dist/components/clarification/clarification_status_config.js +131 -0
  36. package/dist/components/clarification/clarification_status_config.js.map +1 -0
  37. package/dist/components/clarification/index.d.ts +6 -0
  38. package/dist/components/clarification/index.d.ts.map +1 -1
  39. package/dist/components/clarification/index.js +3 -0
  40. package/dist/components/clarification/index.js.map +1 -1
  41. package/dist/components/collab_form_file_upload.d.ts +3 -1
  42. package/dist/components/collab_form_file_upload.d.ts.map +1 -1
  43. package/dist/components/collab_form_file_upload.js +6 -6
  44. package/dist/components/collab_form_file_upload.js.map +1 -1
  45. package/dist/components/hazo_add_field_dialog/components/pending_field_item.d.ts +14 -2
  46. package/dist/components/hazo_add_field_dialog/components/pending_field_item.d.ts.map +1 -1
  47. package/dist/components/hazo_add_field_dialog/components/pending_field_item.js +11 -9
  48. package/dist/components/hazo_add_field_dialog/components/pending_field_item.js.map +1 -1
  49. package/dist/components/hazo_add_field_dialog/components/pending_field_list.d.ts +14 -2
  50. package/dist/components/hazo_add_field_dialog/components/pending_field_list.d.ts.map +1 -1
  51. package/dist/components/hazo_add_field_dialog/components/pending_field_list.js +2 -2
  52. package/dist/components/hazo_add_field_dialog/components/pending_field_list.js.map +1 -1
  53. package/dist/components/hazo_add_field_dialog/components/question_input.d.ts +6 -1
  54. package/dist/components/hazo_add_field_dialog/components/question_input.d.ts.map +1 -1
  55. package/dist/components/hazo_add_field_dialog/components/question_input.js +2 -2
  56. package/dist/components/hazo_add_field_dialog/components/question_input.js.map +1 -1
  57. package/dist/components/hazo_add_field_dialog/hazo_add_field_dialog.d.ts +6 -1
  58. package/dist/components/hazo_add_field_dialog/hazo_add_field_dialog.d.ts.map +1 -1
  59. package/dist/components/hazo_add_field_dialog/hazo_add_field_dialog.js +67 -17
  60. package/dist/components/hazo_add_field_dialog/hazo_add_field_dialog.js.map +1 -1
  61. package/dist/components/hazo_add_field_dialog/index.d.ts +3 -1
  62. package/dist/components/hazo_add_field_dialog/index.d.ts.map +1 -1
  63. package/dist/components/hazo_add_field_dialog/index.js +2 -0
  64. package/dist/components/hazo_add_field_dialog/index.js.map +1 -1
  65. package/dist/components/hazo_add_field_dialog/options/file_textbox_options.d.ts +31 -0
  66. package/dist/components/hazo_add_field_dialog/options/file_textbox_options.d.ts.map +1 -0
  67. package/dist/components/hazo_add_field_dialog/options/file_textbox_options.js +54 -0
  68. package/dist/components/hazo_add_field_dialog/options/file_textbox_options.js.map +1 -0
  69. package/dist/components/hazo_add_field_dialog/options/index.d.ts +1 -0
  70. package/dist/components/hazo_add_field_dialog/options/index.d.ts.map +1 -1
  71. package/dist/components/hazo_add_field_dialog/options/index.js +1 -0
  72. package/dist/components/hazo_add_field_dialog/options/index.js.map +1 -1
  73. package/dist/components/hazo_add_field_dialog/types.d.ts +51 -2
  74. package/dist/components/hazo_add_field_dialog/types.d.ts.map +1 -1
  75. package/dist/components/hazo_add_field_dialog/types.js +7 -0
  76. package/dist/components/hazo_add_field_dialog/types.js.map +1 -1
  77. package/dist/components/hazo_collab_form_base.d.ts +6 -1
  78. package/dist/components/hazo_collab_form_base.d.ts.map +1 -1
  79. package/dist/components/hazo_collab_form_base.js +7 -15
  80. package/dist/components/hazo_collab_form_base.js.map +1 -1
  81. package/dist/components/hazo_collab_form_data_table.d.ts.map +1 -1
  82. package/dist/components/hazo_collab_form_data_table.js +4 -2
  83. package/dist/components/hazo_collab_form_data_table.js.map +1 -1
  84. package/dist/components/hazo_collab_form_file_textbox/file_chip.d.ts +37 -0
  85. package/dist/components/hazo_collab_form_file_textbox/file_chip.d.ts.map +1 -0
  86. package/dist/components/hazo_collab_form_file_textbox/file_chip.js +45 -0
  87. package/dist/components/hazo_collab_form_file_textbox/file_chip.js.map +1 -0
  88. package/dist/components/hazo_collab_form_file_textbox/file_group.d.ts +35 -0
  89. package/dist/components/hazo_collab_form_file_textbox/file_group.d.ts.map +1 -0
  90. package/dist/components/hazo_collab_form_file_textbox/file_group.js +54 -0
  91. package/dist/components/hazo_collab_form_file_textbox/file_group.js.map +1 -0
  92. package/dist/components/hazo_collab_form_file_textbox/file_textbox_panel.d.ts +46 -0
  93. package/dist/components/hazo_collab_form_file_textbox/file_textbox_panel.d.ts.map +1 -0
  94. package/dist/components/hazo_collab_form_file_textbox/file_textbox_panel.js +104 -0
  95. package/dist/components/hazo_collab_form_file_textbox/file_textbox_panel.js.map +1 -0
  96. package/dist/components/hazo_collab_form_file_textbox/file_upload_zone.d.ts +20 -0
  97. package/dist/components/hazo_collab_form_file_textbox/file_upload_zone.d.ts.map +1 -0
  98. package/dist/components/hazo_collab_form_file_textbox/file_upload_zone.js +14 -0
  99. package/dist/components/hazo_collab_form_file_textbox/file_upload_zone.js.map +1 -0
  100. package/dist/components/hazo_collab_form_file_textbox/hazo_collab_form_file_textbox.d.ts +63 -0
  101. package/dist/components/hazo_collab_form_file_textbox/hazo_collab_form_file_textbox.d.ts.map +1 -0
  102. package/dist/components/hazo_collab_form_file_textbox/hazo_collab_form_file_textbox.js +226 -0
  103. package/dist/components/hazo_collab_form_file_textbox/hazo_collab_form_file_textbox.js.map +1 -0
  104. package/dist/components/hazo_collab_form_file_textbox/index.d.ts +24 -0
  105. package/dist/components/hazo_collab_form_file_textbox/index.d.ts.map +1 -0
  106. package/dist/components/hazo_collab_form_file_textbox/index.js +16 -0
  107. package/dist/components/hazo_collab_form_file_textbox/index.js.map +1 -0
  108. package/dist/components/hazo_collab_form_file_textbox/tag_pills.d.ts +11 -0
  109. package/dist/components/hazo_collab_form_file_textbox/tag_pills.d.ts.map +1 -0
  110. package/dist/components/hazo_collab_form_file_textbox/tag_pills.js +17 -0
  111. package/dist/components/hazo_collab_form_file_textbox/tag_pills.js.map +1 -0
  112. package/dist/components/hazo_collab_form_file_textbox/utils.d.ts +14 -0
  113. package/dist/components/hazo_collab_form_file_textbox/utils.d.ts.map +1 -0
  114. package/dist/components/hazo_collab_form_file_textbox/utils.js +58 -0
  115. package/dist/components/hazo_collab_form_file_textbox/utils.js.map +1 -0
  116. package/dist/components/hazo_collab_form_file_textbox/validation_dialog.d.ts +29 -0
  117. package/dist/components/hazo_collab_form_file_textbox/validation_dialog.d.ts.map +1 -0
  118. package/dist/components/hazo_collab_form_file_textbox/validation_dialog.js +182 -0
  119. package/dist/components/hazo_collab_form_file_textbox/validation_dialog.js.map +1 -0
  120. package/dist/components/hazo_collab_form_group.d.ts.map +1 -1
  121. package/dist/components/hazo_collab_form_group.js +2 -2
  122. package/dist/components/hazo_collab_form_group.js.map +1 -1
  123. package/dist/components/hazo_collab_form_view/context.d.ts.map +1 -1
  124. package/dist/components/hazo_collab_form_view/context.js +10 -3
  125. package/dist/components/hazo_collab_form_view/context.js.map +1 -1
  126. package/dist/components/hazo_collab_form_view/index.d.ts +1 -1
  127. package/dist/components/hazo_collab_form_view/index.d.ts.map +1 -1
  128. package/dist/components/hazo_collab_form_view/index.js.map +1 -1
  129. package/dist/components/hazo_collab_form_view/types.d.ts +32 -4
  130. package/dist/components/hazo_collab_form_view/types.d.ts.map +1 -1
  131. package/dist/components/hazo_collab_form_view/views/edit_view.d.ts.map +1 -1
  132. package/dist/components/hazo_collab_form_view/views/edit_view.js +4 -2
  133. package/dist/components/hazo_collab_form_view/views/edit_view.js.map +1 -1
  134. package/dist/components/hazo_collab_form_view/views/summary_view.d.ts.map +1 -1
  135. package/dist/components/hazo_collab_form_view/views/summary_view.js +2 -2
  136. package/dist/components/hazo_collab_form_view/views/summary_view.js.map +1 -1
  137. package/dist/components/hazo_data_form/hazo_data_form.d.ts.map +1 -1
  138. package/dist/components/hazo_data_form/hazo_data_form.js +4 -2
  139. package/dist/components/hazo_data_form/hazo_data_form.js.map +1 -1
  140. package/dist/components/hazo_data_form/pdf_panel.d.ts.map +1 -1
  141. package/dist/components/hazo_data_form/pdf_panel.js +7 -2
  142. package/dist/components/hazo_data_form/pdf_panel.js.map +1 -1
  143. package/dist/components/hazo_fb_form/components/backoffice_run_button.d.ts +16 -0
  144. package/dist/components/hazo_fb_form/components/backoffice_run_button.d.ts.map +1 -0
  145. package/dist/components/hazo_fb_form/components/backoffice_run_button.js +23 -0
  146. package/dist/components/hazo_fb_form/components/backoffice_run_button.js.map +1 -0
  147. package/dist/components/hazo_fb_form/components/draft_clarification_card.d.ts +25 -0
  148. package/dist/components/hazo_fb_form/components/draft_clarification_card.d.ts.map +1 -0
  149. package/dist/components/hazo_fb_form/components/draft_clarification_card.js +71 -0
  150. package/dist/components/hazo_fb_form/components/draft_clarification_card.js.map +1 -0
  151. package/dist/components/hazo_fb_form/components/fb_document_type_editor.d.ts +11 -0
  152. package/dist/components/hazo_fb_form/components/fb_document_type_editor.d.ts.map +1 -0
  153. package/dist/components/hazo_fb_form/components/fb_document_type_editor.js +82 -0
  154. package/dist/components/hazo_fb_form/components/fb_document_type_editor.js.map +1 -0
  155. package/dist/components/hazo_fb_form/components/fb_tag_editor.d.ts +11 -0
  156. package/dist/components/hazo_fb_form/components/fb_tag_editor.d.ts.map +1 -0
  157. package/dist/components/hazo_fb_form/components/fb_tag_editor.js +107 -0
  158. package/dist/components/hazo_fb_form/components/fb_tag_editor.js.map +1 -0
  159. package/dist/components/hazo_fb_form/components/front_office_stepper.d.ts +15 -0
  160. package/dist/components/hazo_fb_form/components/front_office_stepper.d.ts.map +1 -0
  161. package/dist/components/hazo_fb_form/components/front_office_stepper.js +21 -0
  162. package/dist/components/hazo_fb_form/components/front_office_stepper.js.map +1 -0
  163. package/dist/components/hazo_fb_form/components/instance_sidebar.d.ts +21 -0
  164. package/dist/components/hazo_fb_form/components/instance_sidebar.d.ts.map +1 -0
  165. package/dist/components/hazo_fb_form/components/instance_sidebar.js +58 -0
  166. package/dist/components/hazo_fb_form/components/instance_sidebar.js.map +1 -0
  167. package/dist/components/hazo_fb_form/components/run_button.d.ts +19 -0
  168. package/dist/components/hazo_fb_form/components/run_button.d.ts.map +1 -0
  169. package/dist/components/hazo_fb_form/components/run_button.js +38 -0
  170. package/dist/components/hazo_fb_form/components/run_button.js.map +1 -0
  171. package/dist/components/hazo_fb_form/components/run_details_dialog.d.ts +17 -0
  172. package/dist/components/hazo_fb_form/components/run_details_dialog.d.ts.map +1 -0
  173. package/dist/components/hazo_fb_form/components/run_details_dialog.js +35 -0
  174. package/dist/components/hazo_fb_form/components/run_details_dialog.js.map +1 -0
  175. package/dist/components/hazo_fb_form/components/tag_pill.d.ts +15 -0
  176. package/dist/components/hazo_fb_form/components/tag_pill.d.ts.map +1 -0
  177. package/dist/components/hazo_fb_form/components/tag_pill.js +15 -0
  178. package/dist/components/hazo_fb_form/components/tag_pill.js.map +1 -0
  179. package/dist/components/hazo_fb_form/context.d.ts +90 -0
  180. package/dist/components/hazo_fb_form/context.d.ts.map +1 -0
  181. package/dist/components/hazo_fb_form/context.js +13 -0
  182. package/dist/components/hazo_fb_form/context.js.map +1 -0
  183. package/dist/components/hazo_fb_form/hazo_fb_form.d.ts +13 -0
  184. package/dist/components/hazo_fb_form/hazo_fb_form.d.ts.map +1 -0
  185. package/dist/components/hazo_fb_form/hazo_fb_form.js +401 -0
  186. package/dist/components/hazo_fb_form/hazo_fb_form.js.map +1 -0
  187. package/dist/components/hazo_fb_form/hooks/use_fb_form_state.d.ts +45 -0
  188. package/dist/components/hazo_fb_form/hooks/use_fb_form_state.d.ts.map +1 -0
  189. package/dist/components/hazo_fb_form/hooks/use_fb_form_state.js +223 -0
  190. package/dist/components/hazo_fb_form/hooks/use_fb_form_state.js.map +1 -0
  191. package/dist/components/hazo_fb_form/hooks/use_llm_run.d.ts +46 -0
  192. package/dist/components/hazo_fb_form/hooks/use_llm_run.d.ts.map +1 -0
  193. package/dist/components/hazo_fb_form/hooks/use_llm_run.js +1435 -0
  194. package/dist/components/hazo_fb_form/hooks/use_llm_run.js.map +1 -0
  195. package/dist/components/hazo_fb_form/index.d.ts +24 -0
  196. package/dist/components/hazo_fb_form/index.d.ts.map +1 -0
  197. package/dist/components/hazo_fb_form/index.js +16 -0
  198. package/dist/components/hazo_fb_form/index.js.map +1 -0
  199. package/dist/components/hazo_fb_form/shared/clarification_helpers.d.ts +15 -0
  200. package/dist/components/hazo_fb_form/shared/clarification_helpers.d.ts.map +1 -0
  201. package/dist/components/hazo_fb_form/shared/clarification_helpers.js +23 -0
  202. package/dist/components/hazo_fb_form/shared/clarification_helpers.js.map +1 -0
  203. package/dist/components/hazo_fb_form/shared/file_utils.d.ts +9 -0
  204. package/dist/components/hazo_fb_form/shared/file_utils.d.ts.map +1 -0
  205. package/dist/components/hazo_fb_form/shared/file_utils.js +31 -0
  206. package/dist/components/hazo_fb_form/shared/file_utils.js.map +1 -0
  207. package/dist/components/hazo_fb_form/shared/format.d.ts +12 -0
  208. package/dist/components/hazo_fb_form/shared/format.d.ts.map +1 -0
  209. package/dist/components/hazo_fb_form/shared/format.js +45 -0
  210. package/dist/components/hazo_fb_form/shared/format.js.map +1 -0
  211. package/dist/components/hazo_fb_form/shared/index.d.ts +10 -0
  212. package/dist/components/hazo_fb_form/shared/index.d.ts.map +1 -0
  213. package/dist/components/hazo_fb_form/shared/index.js +9 -0
  214. package/dist/components/hazo_fb_form/shared/index.js.map +1 -0
  215. package/dist/components/hazo_fb_form/shared/pdf_side_panel.d.ts +22 -0
  216. package/dist/components/hazo_fb_form/shared/pdf_side_panel.d.ts.map +1 -0
  217. package/dist/components/hazo_fb_form/shared/pdf_side_panel.js +10 -0
  218. package/dist/components/hazo_fb_form/shared/pdf_side_panel.js.map +1 -0
  219. package/dist/components/hazo_fb_form/shared/use_pdf_viewer.d.ts +26 -0
  220. package/dist/components/hazo_fb_form/shared/use_pdf_viewer.d.ts.map +1 -0
  221. package/dist/components/hazo_fb_form/shared/use_pdf_viewer.js +41 -0
  222. package/dist/components/hazo_fb_form/shared/use_pdf_viewer.js.map +1 -0
  223. package/dist/components/hazo_fb_form/types.d.ts +283 -0
  224. package/dist/components/hazo_fb_form/types.d.ts.map +1 -0
  225. package/dist/components/hazo_fb_form/types.js +5 -0
  226. package/dist/components/hazo_fb_form/types.js.map +1 -0
  227. package/dist/components/hazo_fb_form/views/back_office_view.d.ts +7 -0
  228. package/dist/components/hazo_fb_form/views/back_office_view.d.ts.map +1 -0
  229. package/dist/components/hazo_fb_form/views/back_office_view.js +327 -0
  230. package/dist/components/hazo_fb_form/views/back_office_view.js.map +1 -0
  231. package/dist/components/hazo_fb_form/views/clarifications_view.d.ts +16 -0
  232. package/dist/components/hazo_fb_form/views/clarifications_view.d.ts.map +1 -0
  233. package/dist/components/hazo_fb_form/views/clarifications_view.js +262 -0
  234. package/dist/components/hazo_fb_form/views/clarifications_view.js.map +1 -0
  235. package/dist/components/hazo_fb_form/views/front_office_view.d.ts +6 -0
  236. package/dist/components/hazo_fb_form/views/front_office_view.d.ts.map +1 -0
  237. package/dist/components/hazo_fb_form/views/front_office_view.js +1097 -0
  238. package/dist/components/hazo_fb_form/views/front_office_view.js.map +1 -0
  239. package/dist/components/hazo_fb_form/views/interim_view.d.ts +8 -0
  240. package/dist/components/hazo_fb_form/views/interim_view.d.ts.map +1 -0
  241. package/dist/components/hazo_fb_form/views/interim_view.js +416 -0
  242. package/dist/components/hazo_fb_form/views/interim_view.js.map +1 -0
  243. package/dist/components/hazo_fb_form/views/summary_review_view.d.ts +15 -0
  244. package/dist/components/hazo_fb_form/views/summary_review_view.d.ts.map +1 -0
  245. package/dist/components/hazo_fb_form/views/summary_review_view.js +173 -0
  246. package/dist/components/hazo_fb_form/views/summary_review_view.js.map +1 -0
  247. package/dist/components/hazo_field_library/components/template_loader_dialog.d.ts.map +1 -1
  248. package/dist/components/hazo_field_library/components/template_loader_dialog.js +7 -7
  249. package/dist/components/hazo_field_library/components/template_loader_dialog.js.map +1 -1
  250. package/dist/components/hazo_validation_rule_editor/components/clarification_settings.d.ts.map +1 -1
  251. package/dist/components/hazo_validation_rule_editor/components/clarification_settings.js +29 -2
  252. package/dist/components/hazo_validation_rule_editor/components/clarification_settings.js.map +1 -1
  253. package/dist/components/hazo_validation_rule_editor/components/rule_editor.d.ts.map +1 -1
  254. package/dist/components/hazo_validation_rule_editor/components/rule_editor.js +4 -4
  255. package/dist/components/hazo_validation_rule_editor/components/rule_editor.js.map +1 -1
  256. package/dist/components/hazo_validation_rule_editor/context.d.ts +3 -2
  257. package/dist/components/hazo_validation_rule_editor/context.d.ts.map +1 -1
  258. package/dist/components/hazo_validation_rule_editor/context.js +5 -1
  259. package/dist/components/hazo_validation_rule_editor/context.js.map +1 -1
  260. package/dist/components/hazo_validation_rule_editor/hooks/use_file_validation.d.ts.map +1 -1
  261. package/dist/components/hazo_validation_rule_editor/hooks/use_file_validation.js +23 -7
  262. package/dist/components/hazo_validation_rule_editor/hooks/use_file_validation.js.map +1 -1
  263. package/dist/components/hazo_validation_rule_editor/index.d.ts +1 -1
  264. package/dist/components/hazo_validation_rule_editor/index.d.ts.map +1 -1
  265. package/dist/components/hazo_validation_rule_editor/index.js.map +1 -1
  266. package/dist/components/hazo_validation_rule_editor/types.d.ts +10 -0
  267. package/dist/components/hazo_validation_rule_editor/types.d.ts.map +1 -1
  268. package/dist/components/hazo_validation_rule_editor/validation_rule_editor.d.ts +1 -1
  269. package/dist/components/hazo_validation_rule_editor/validation_rule_editor.d.ts.map +1 -1
  270. package/dist/components/hazo_validation_rule_editor/validation_rule_editor.js +2 -2
  271. package/dist/components/hazo_validation_rule_editor/validation_rule_editor.js.map +1 -1
  272. package/dist/components/index.d.ts +10 -2
  273. package/dist/components/index.d.ts.map +1 -1
  274. package/dist/components/index.js +5 -0
  275. package/dist/components/index.js.map +1 -1
  276. package/dist/components/shared/editor_theme/components.d.ts.map +1 -1
  277. package/dist/components/shared/editor_theme/components.js +35 -3
  278. package/dist/components/shared/editor_theme/components.js.map +1 -1
  279. package/dist/components/shared/file_bar/file_bar.d.ts +51 -0
  280. package/dist/components/shared/file_bar/file_bar.d.ts.map +1 -0
  281. package/dist/components/shared/file_bar/file_bar.js +91 -0
  282. package/dist/components/shared/file_bar/file_bar.js.map +1 -0
  283. package/dist/components/shared/file_bar/file_bar_tag.d.ts +10 -0
  284. package/dist/components/shared/file_bar/file_bar_tag.d.ts.map +1 -0
  285. package/dist/components/shared/file_bar/file_bar_tag.js +15 -0
  286. package/dist/components/shared/file_bar/file_bar_tag.js.map +1 -0
  287. package/dist/components/shared/file_bar/file_bar_types.d.ts +29 -0
  288. package/dist/components/shared/file_bar/file_bar_types.d.ts.map +1 -0
  289. package/dist/components/shared/file_bar/file_bar_types.js +5 -0
  290. package/dist/components/shared/file_bar/file_bar_types.js.map +1 -0
  291. package/dist/components/shared/file_bar/file_bar_validation.d.ts +11 -0
  292. package/dist/components/shared/file_bar/file_bar_validation.d.ts.map +1 -0
  293. package/dist/components/shared/file_bar/file_bar_validation.js +21 -0
  294. package/dist/components/shared/file_bar/file_bar_validation.js.map +1 -0
  295. package/dist/components/shared/file_bar/file_bar_validation_dialog.d.ts +13 -0
  296. package/dist/components/shared/file_bar/file_bar_validation_dialog.d.ts.map +1 -0
  297. package/dist/components/shared/file_bar/file_bar_validation_dialog.js +74 -0
  298. package/dist/components/shared/file_bar/file_bar_validation_dialog.js.map +1 -0
  299. package/dist/components/shared/file_bar/file_validation_issues_dialog.d.ts +24 -0
  300. package/dist/components/shared/file_bar/file_validation_issues_dialog.d.ts.map +1 -0
  301. package/dist/components/shared/file_bar/file_validation_issues_dialog.js +22 -0
  302. package/dist/components/shared/file_bar/file_validation_issues_dialog.js.map +1 -0
  303. package/dist/components/shared/file_bar/index.d.ts +13 -0
  304. package/dist/components/shared/file_bar/index.d.ts.map +1 -0
  305. package/dist/components/shared/file_bar/index.js +8 -0
  306. package/dist/components/shared/file_bar/index.js.map +1 -0
  307. package/dist/components/shared/strip_base_props.d.ts.map +1 -1
  308. package/dist/components/shared/strip_base_props.js +2 -0
  309. package/dist/components/shared/strip_base_props.js.map +1 -1
  310. package/dist/components/shared/summary_files/summary_files.d.ts +2 -2
  311. package/dist/components/shared/summary_files/summary_files.d.ts.map +1 -1
  312. package/dist/components/shared/summary_files/summary_files.js +31 -72
  313. package/dist/components/shared/summary_files/summary_files.js.map +1 -1
  314. package/dist/components/shared/use_base_form_field.d.ts +1 -0
  315. package/dist/components/shared/use_base_form_field.d.ts.map +1 -1
  316. package/dist/components/shared/use_base_form_field.js +2 -1
  317. package/dist/components/shared/use_base_form_field.js.map +1 -1
  318. package/dist/config/clarification_templates.d.ts.map +1 -1
  319. package/dist/config/clarification_templates.js +15 -5
  320. package/dist/config/clarification_templates.js.map +1 -1
  321. package/dist/lib/autofill_handler.d.ts +42 -23
  322. package/dist/lib/autofill_handler.d.ts.map +1 -1
  323. package/dist/lib/autofill_handler.js +74 -197
  324. package/dist/lib/autofill_handler.js.map +1 -1
  325. package/dist/lib/fb_form_handler.d.ts +63 -0
  326. package/dist/lib/fb_form_handler.d.ts.map +1 -0
  327. package/dist/lib/fb_form_handler.js +232 -0
  328. package/dist/lib/fb_form_handler.js.map +1 -0
  329. package/dist/lib/index.d.ts +2 -0
  330. package/dist/lib/index.d.ts.map +1 -1
  331. package/dist/lib/index.js +1 -0
  332. package/dist/lib/index.js.map +1 -1
  333. package/dist/lib/validation_handler.d.ts.map +1 -1
  334. package/dist/lib/validation_handler.js +153 -0
  335. package/dist/lib/validation_handler.js.map +1 -1
  336. package/dist/types/clarification.d.ts +14 -1
  337. package/dist/types/clarification.d.ts.map +1 -1
  338. package/dist/types/fb_form_instance.d.ts +44 -0
  339. package/dist/types/fb_form_instance.d.ts.map +1 -0
  340. package/dist/types/fb_form_instance.js +10 -0
  341. package/dist/types/fb_form_instance.js.map +1 -0
  342. package/dist/types/file_manager.d.ts +2 -0
  343. package/dist/types/file_manager.d.ts.map +1 -1
  344. package/dist/types/file_textbox.d.ts +36 -0
  345. package/dist/types/file_textbox.d.ts.map +1 -0
  346. package/dist/types/file_textbox.js +59 -0
  347. package/dist/types/file_textbox.js.map +1 -0
  348. package/dist/types/index.d.ts +6 -2
  349. package/dist/types/index.d.ts.map +1 -1
  350. package/dist/types/index.js +2 -0
  351. package/dist/types/index.js.map +1 -1
  352. package/dist/types/validation.d.ts +34 -2
  353. package/dist/types/validation.d.ts.map +1 -1
  354. package/dist/utils/dev_file_manager.d.ts +12 -0
  355. package/dist/utils/dev_file_manager.d.ts.map +1 -0
  356. package/dist/utils/dev_file_manager.js +54 -0
  357. package/dist/utils/dev_file_manager.js.map +1 -0
  358. package/dist/utils/index.d.ts +4 -0
  359. package/dist/utils/index.d.ts.map +1 -1
  360. package/dist/utils/index.js +8 -0
  361. package/dist/utils/index.js.map +1 -1
  362. package/dist/utils/rule_to_execution.d.ts +7 -0
  363. package/dist/utils/rule_to_execution.d.ts.map +1 -0
  364. package/dist/utils/rule_to_execution.js +18 -0
  365. package/dist/utils/rule_to_execution.js.map +1 -0
  366. package/dist/utils/section_helpers.d.ts +20 -0
  367. package/dist/utils/section_helpers.d.ts.map +1 -0
  368. package/dist/utils/section_helpers.js +37 -0
  369. package/dist/utils/section_helpers.js.map +1 -0
  370. package/package.json +53 -23
@@ -0,0 +1,1435 @@
1
+ /**
2
+ * LLM "Run" pipeline hook - handles per-file classification + file routing to back-office.
3
+ *
4
+ * After classification, files are routed to matching back-office groups by tag_id,
5
+ * then autofill is triggered automatically on each group.
6
+ */
7
+ 'use client';
8
+ import { useCallback, useRef } from 'react';
9
+ import { use_logger } from '../../../logger/index.js';
10
+ import { rule_to_fb_execution } from '../../../utils/rule_to_execution.js';
11
+ /** Extract file info from a FileTextboxValue */
12
+ function get_files_from_value(value) {
13
+ if (!Array.isArray(value))
14
+ return [];
15
+ return value
16
+ .filter((b) => b.type === 'file' && b.attachment?.file_id)
17
+ .map((b) => ({ file_id: b.attachment.file_id, file_name: b.attachment.file_name, attachment: b.attachment }));
18
+ }
19
+ /** Extract text content from a FileTextboxValue */
20
+ function get_text_from_value(value) {
21
+ if (typeof value === 'string')
22
+ return value;
23
+ if (!Array.isArray(value))
24
+ return '';
25
+ return value
26
+ .filter((b) => b.type === 'text' && b.content)
27
+ .map((b) => b.content)
28
+ .join('\n');
29
+ }
30
+ /** Build a map of tag_id -> group configs from back_sections */
31
+ function build_tag_group_map(back_sections) {
32
+ const map = new Map();
33
+ for (const section of back_sections) {
34
+ for (const group of section.groups ?? []) {
35
+ const tag_id = group.tag_id;
36
+ if (tag_id) {
37
+ const existing = map.get(tag_id) ?? [];
38
+ existing.push(group);
39
+ map.set(tag_id, existing);
40
+ }
41
+ }
42
+ }
43
+ return map;
44
+ }
45
+ /** Route classified files to back-office groups and trigger autofill */
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 } = options;
48
+ const tag_group_map = build_tag_group_map(back_sections);
49
+ const errors = [];
50
+ const unassigned = [];
51
+ // Collect all classified files across all fields
52
+ const all_files = [];
53
+ for (const cls of classifications) {
54
+ const field_files = get_files_from_value(front_form_data[cls.field_id]);
55
+ for (const fc of cls.file_classifications) {
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 });
58
+ }
59
+ }
60
+ // Track accumulated files per group locally to avoid stale state reads.
61
+ // React state updates from on_back_change are async, so reading back_form_data
62
+ // for the same key multiple times would miss prior additions within this loop.
63
+ const accumulated_files = new Map();
64
+ // Track which files are NEWLY routed (not already in back_form_data) — only these need autofill
65
+ const newly_routed_files = new Map();
66
+ const groups_to_autofill = new Set();
67
+ for (const file of all_files) {
68
+ let matched = false;
69
+ // Route each file to only ONE group — the first matching tag wins.
70
+ // Tags are ordered by priority (LLM returns best-match first), so
71
+ // iterating in order and breaking after the first match prevents a
72
+ // multi-tagged file from being duplicated across every matching group.
73
+ for (const tag of file.tags) {
74
+ const groups = tag_group_map.get(tag);
75
+ if (groups) {
76
+ matched = true;
77
+ for (const group of groups) {
78
+ if (on_back_change && file.attachment) {
79
+ const file_key = `__files_${group.id}`;
80
+ // Seed from back_form_data on first access, then use local accumulator
81
+ if (!accumulated_files.has(file_key)) {
82
+ accumulated_files.set(file_key, [...(back_form_data[file_key] ?? [])]);
83
+ }
84
+ const current = accumulated_files.get(file_key);
85
+ const is_new = !current.some((f) => f.file_id === file.file_id);
86
+ if (is_new) {
87
+ current.push(file.attachment);
88
+ // Track as newly routed for autofill
89
+ if (!newly_routed_files.has(file_key)) {
90
+ newly_routed_files.set(file_key, []);
91
+ }
92
+ newly_routed_files.get(file_key).push(file.attachment);
93
+ groups_to_autofill.add(group.id);
94
+ }
95
+ }
96
+ }
97
+ break; // First matching tag wins — don't duplicate to other groups
98
+ }
99
+ }
100
+ // Persist tags to hazo_files
101
+ if (file.tags.length > 0 && file_manager?.callbacks?.update_tags) {
102
+ try {
103
+ await file_manager.callbacks.update_tags(file.file_id, file.tags);
104
+ }
105
+ catch (err) {
106
+ errors.push({ step: `update_tags:${file.file_id}`, error: err instanceof Error ? err.message : 'Failed to update tags' });
107
+ }
108
+ }
109
+ if (!matched) {
110
+ unassigned.push({
111
+ file_id: file.file_id,
112
+ file_name: file.file_name,
113
+ tags: file.tags,
114
+ document_date: file.document_date,
115
+ document_nature: file.document_nature,
116
+ source_field_id: file.source_field_id,
117
+ });
118
+ }
119
+ }
120
+ // Flush accumulated files to state in one call per group
121
+ if (on_back_change) {
122
+ for (const [file_key, files] of accumulated_files) {
123
+ on_back_change(file_key, files);
124
+ }
125
+ }
126
+ // Trigger autofill only for NEWLY routed files (not files already in the group)
127
+ if (autofill_api_endpoint && groups_to_autofill.size > 0) {
128
+ let autofill_done = 0;
129
+ // Count total NEW files across all groups for progress
130
+ let total_autofills = 0;
131
+ for (const gid of groups_to_autofill) {
132
+ const fk = `__files_${gid}`;
133
+ const new_files = newly_routed_files.get(fk) ?? [];
134
+ total_autofills += Math.max(new_files.length, 1);
135
+ }
136
+ // Add autofill actions to the pipeline tracker (now that we know the count)
137
+ if (tracker) {
138
+ tracker.total += total_autofills;
139
+ }
140
+ for (const group_id of groups_to_autofill) {
141
+ // Find the group config to get prompt_area/prompt_key
142
+ let group_config;
143
+ for (const section of back_sections) {
144
+ group_config = section.groups?.find((g) => g.id === group_id);
145
+ if (group_config)
146
+ break;
147
+ }
148
+ if (group_config?.prompt_area && group_config?.prompt_key) {
149
+ const file_key = `__files_${group_id}`;
150
+ // Only autofill newly routed files, not files already processed in prior runs
151
+ const group_files = newly_routed_files.get(file_key) ?? [];
152
+ const fields_schema = group_config.fields?.map((f) => ({
153
+ field_id: f.id,
154
+ label: f.label,
155
+ field_type: f.field_type,
156
+ component_type: f.component_type,
157
+ table_config: f.table_config,
158
+ })) ?? [];
159
+ let group_fields_populated = 0;
160
+ let group_last_file_name = '';
161
+ let group_error = '';
162
+ const details = [];
163
+ // Track accumulated values per field to merge array data (tables) across files
164
+ const accumulated_field_values = new Map();
165
+ // Autofill API expects one file per request (matches AutofillRequest shape)
166
+ for (let fi = 0; fi < group_files.length; fi++) {
167
+ const f = group_files[fi];
168
+ group_last_file_name = f.file_name;
169
+ autofill_done++;
170
+ if (tracker)
171
+ tracker.completed++;
172
+ const pct = tracker ? `${Math.round((tracker.completed / tracker.total) * 100)}% ` : '';
173
+ update_progress({
174
+ current_step: `${pct}Auto-filling ${group_id} — file ${fi + 1}/${group_files.length} (${autofill_done}/${total_autofills})`,
175
+ overall_percent: tracker ? Math.round((tracker.completed / tracker.total) * 100) : undefined,
176
+ });
177
+ on_autofill_file?.(f.file_id, true);
178
+ try {
179
+ const download_url = file_manager?.callbacks?.get_download_url?.(f.file_id, 'public') ?? '';
180
+ const autofill_body = {
181
+ file_id: f.file_id,
182
+ file_name: f.file_name,
183
+ mime_type: f.mime_type ?? 'application/octet-stream',
184
+ download_url,
185
+ group_id,
186
+ prompt_area: group_config.prompt_area,
187
+ prompt_key: group_config.prompt_key,
188
+ fields: fields_schema,
189
+ };
190
+ const autofill_start = Date.now();
191
+ const response = await fetch(autofill_api_endpoint, {
192
+ method: 'POST',
193
+ headers: { 'Content-Type': 'application/json' },
194
+ body: JSON.stringify(autofill_body),
195
+ });
196
+ const result = await response.json();
197
+ run_log.push({
198
+ step: 'autofill',
199
+ label: `Autofill: ${group_id} — ${f.file_name}`,
200
+ prompt_area: group_config.prompt_area,
201
+ prompt_key: group_config.prompt_key,
202
+ request: autofill_body,
203
+ response: result,
204
+ timestamp: autofill_start,
205
+ duration_ms: Date.now() - autofill_start,
206
+ });
207
+ if (result.success && result.data && on_back_change) {
208
+ const field_count = Object.keys(result.data).length;
209
+ group_fields_populated += field_count;
210
+ for (const [field_id, value] of Object.entries(result.data)) {
211
+ // Capture detail entry
212
+ const field_cfg = group_config.fields?.find((fld) => fld.id === field_id);
213
+ const value_summary = Array.isArray(value)
214
+ ? `${value.length} row(s)`
215
+ : String(value ?? '').slice(0, 50);
216
+ details.push({
217
+ field_id,
218
+ field_label: field_cfg?.label ?? field_id,
219
+ file_name: f.file_name,
220
+ value_summary,
221
+ });
222
+ // For arrays (data tables), append rows across files with source file metadata
223
+ if (Array.isArray(value)) {
224
+ const tagged_rows = value.map((row) => ({
225
+ ...row,
226
+ _source_file_id: f.file_id,
227
+ _source_file_name: f.file_name,
228
+ }));
229
+ // Seed from existing back_form_data on first access so prior instance data is preserved
230
+ const existing = accumulated_field_values.get(field_id)
231
+ ?? (Array.isArray(back_form_data[field_id]) ? back_form_data[field_id] : undefined);
232
+ const merged = Array.isArray(existing) ? [...existing, ...tagged_rows] : tagged_rows;
233
+ accumulated_field_values.set(field_id, merged);
234
+ on_back_change(field_id, merged);
235
+ }
236
+ else {
237
+ // Scalar: last file wins
238
+ accumulated_field_values.set(field_id, value);
239
+ on_back_change(field_id, value);
240
+ }
241
+ }
242
+ }
243
+ else if (!result.success) {
244
+ group_error = result.error || 'Autofill failed';
245
+ errors.push({ step: `autofill:${group_id}`, error: group_error });
246
+ }
247
+ else if (result.success && (!result.data || Object.keys(result.data).length === 0)) {
248
+ // Success but no data extracted
249
+ if (!group_error && group_fields_populated === 0) {
250
+ group_error = result.message || 'No matching data found in document';
251
+ }
252
+ }
253
+ }
254
+ catch (err) {
255
+ group_error = err instanceof Error ? err.message : 'Autofill request failed';
256
+ errors.push({ step: `autofill:${group_id}:${f.file_id}`, error: group_error });
257
+ }
258
+ finally {
259
+ on_autofill_file?.(f.file_id, false);
260
+ }
261
+ }
262
+ // Set autofill log for this group
263
+ if (group_error && group_fields_populated === 0) {
264
+ // If there was an error and no fields were populated, check if it's a real error or just empty
265
+ const is_empty = !errors.some((e) => e.step.startsWith(`autofill:${group_id}`));
266
+ set_group_autofill_log((prev) => ({
267
+ ...prev,
268
+ [group_id]: {
269
+ status: is_empty ? 'empty' : 'error',
270
+ message: group_error,
271
+ timestamp: Date.now(),
272
+ },
273
+ }));
274
+ }
275
+ else if (group_fields_populated > 0) {
276
+ const file_label = group_files.length === 1 ? group_last_file_name : `${group_files.length} file(s)`;
277
+ set_group_autofill_log((prev) => ({
278
+ ...prev,
279
+ [group_id]: {
280
+ status: 'success',
281
+ message: `Populated ${group_fields_populated} field(s) from ${file_label}`,
282
+ timestamp: Date.now(),
283
+ details,
284
+ },
285
+ }));
286
+ }
287
+ else {
288
+ set_group_autofill_log((prev) => ({
289
+ ...prev,
290
+ [group_id]: {
291
+ status: 'empty',
292
+ message: 'No matching data found in document',
293
+ timestamp: Date.now(),
294
+ },
295
+ }));
296
+ }
297
+ }
298
+ else {
299
+ autofill_done++;
300
+ }
301
+ }
302
+ }
303
+ return { unassigned, errors };
304
+ }
305
+ /** Build a lookup of user-resolved rule+file combinations from responded clarifications */
306
+ function build_resolved_rules_map(clarifications) {
307
+ const map = new Map();
308
+ for (const item of clarifications) {
309
+ if (!item.rule_id)
310
+ continue;
311
+ if (item.status !== 'responded' && item.status !== 'resolved')
312
+ continue;
313
+ for (const ref of item.doc_references ?? []) {
314
+ if (!ref.file_id)
315
+ continue;
316
+ const key = `${item.rule_id}::${ref.file_id}`;
317
+ map.set(key, {
318
+ response_choice: item.response_choice,
319
+ user_comment: item.user_comment,
320
+ });
321
+ }
322
+ }
323
+ return map;
324
+ }
325
+ /** Validate classified files against matching validation rules */
326
+ async function validate_classified_files(options) {
327
+ const { classifications, validation_api_endpoint, validation_rules, file_manager, front_form_data, update_progress, set_file_validation_results, run_log, tracker } = options;
328
+ const errors = [];
329
+ const all_validation_results = [];
330
+ const failed_file_ids = new Set();
331
+ // Collect all files with their document types
332
+ const files_to_validate = [];
333
+ for (const cls of classifications) {
334
+ // Look up file attachments from form data to get actual mime_type
335
+ const field_files = get_files_from_value(front_form_data[cls.field_id]);
336
+ const file_lookup = new Map(field_files.map(f => [f.file_id, f]));
337
+ for (const fc of cls.file_classifications) {
338
+ const source_file = file_lookup.get(fc.file_id);
339
+ files_to_validate.push({
340
+ file_id: fc.file_id,
341
+ file_name: fc.file_name,
342
+ mime_type: source_file?.attachment?.mime_type ?? 'application/octet-stream',
343
+ document_types: fc.document_type ?? [],
344
+ source_field_id: cls.field_id,
345
+ });
346
+ }
347
+ }
348
+ // Validation logging moved to hazo_logs (consumer configures via LoggerProvider)
349
+ const manual_review = options.manual_review_file_ids;
350
+ // Validate each file
351
+ for (let i = 0; i < files_to_validate.length; i++) {
352
+ const file = files_to_validate[i];
353
+ if (tracker)
354
+ tracker.completed++;
355
+ const pct = tracker ? `${Math.round((tracker.completed / tracker.total) * 100)}% ` : '';
356
+ update_progress({
357
+ current_step: `${pct}Validating ${file.file_name} (${i + 1}/${files_to_validate.length})`,
358
+ overall_percent: tracker ? Math.round((tracker.completed / tracker.total) * 100) : undefined,
359
+ });
360
+ // Skip validation for files marked for manual review by client
361
+ if (manual_review?.has(file.file_id)) {
362
+ const result = {
363
+ file_id: file.file_id,
364
+ file_name: file.file_name,
365
+ status: 'manual_review',
366
+ errors: [],
367
+ document_types: file.document_types,
368
+ };
369
+ all_validation_results.push(result);
370
+ set_file_validation_results((prev) => ({ ...prev, [file.file_id]: result }));
371
+ continue;
372
+ }
373
+ // Find matching rules by document_type.
374
+ // A rule matches if:
375
+ // 1. Its document_type is in the file's document_types, OR
376
+ // 2. Its document_type is 'general' (catch-all — always matches), OR
377
+ // 3. The file has no document_types (match all enabled rules)
378
+ const check_type_filter = options.check_type_filter;
379
+ // check_type filter: rules without check_type default to 'immediate' context.
380
+ // When filtering for 'backoffice', only explicitly backoffice rules run.
381
+ const matching_rules = validation_rules.filter((r) => r.enabled && (file.document_types.includes(r.document_type) ||
382
+ r.document_type === 'general' ||
383
+ file.document_types.length === 0) && (!check_type_filter || r.check_type === check_type_filter || (!r.check_type && check_type_filter === 'immediate')));
384
+ if (matching_rules.length === 0) {
385
+ // No rules match — preserve existing result if available (e.g. immediate passed),
386
+ // otherwise mark as skipped
387
+ set_file_validation_results((prev) => {
388
+ if (prev[file.file_id])
389
+ return prev; // keep existing result
390
+ const result = {
391
+ file_id: file.file_id,
392
+ file_name: file.file_name,
393
+ status: 'skipped',
394
+ errors: [],
395
+ document_types: file.document_types,
396
+ };
397
+ return { ...prev, [file.file_id]: result };
398
+ });
399
+ continue;
400
+ }
401
+ // Separate rules into user-resolved (skip LLM) and rules to execute
402
+ const resolved_map = options.resolved_rules;
403
+ const resolved_rule_results = [];
404
+ const rules_needing_execution = [];
405
+ for (const rule of matching_rules) {
406
+ const key = `${rule.rule_id}::${file.file_id}`;
407
+ const resolution = resolved_map?.get(key);
408
+ if (resolution) {
409
+ // User already resolved this rule for this file — skip LLM call
410
+ resolved_rule_results.push({
411
+ rule_id: rule.rule_id,
412
+ rule_name: rule.name,
413
+ has_issue: true,
414
+ user_resolved: true,
415
+ user_resolution: {
416
+ response_choice: resolution.response_choice,
417
+ user_comment: resolution.user_comment,
418
+ },
419
+ });
420
+ }
421
+ else {
422
+ rules_needing_execution.push(rule);
423
+ }
424
+ }
425
+ // If all rules are user-resolved, no need for LLM call
426
+ if (rules_needing_execution.length === 0) {
427
+ const validation_result = {
428
+ file_id: file.file_id,
429
+ file_name: file.file_name,
430
+ status: 'passed',
431
+ errors: [],
432
+ document_types: file.document_types,
433
+ rule_results: resolved_rule_results,
434
+ };
435
+ all_validation_results.push(validation_result);
436
+ set_file_validation_results((prev) => ({ ...prev, [file.file_id]: validation_result }));
437
+ continue;
438
+ }
439
+ // Mark as validating
440
+ set_file_validation_results((prev) => ({
441
+ ...prev,
442
+ [file.file_id]: {
443
+ file_id: file.file_id,
444
+ file_name: file.file_name,
445
+ status: 'validating',
446
+ errors: [],
447
+ document_types: file.document_types,
448
+ },
449
+ }));
450
+ try {
451
+ const download_url = file_manager?.callbacks?.get_download_url?.(file.file_id, 'public') ?? '';
452
+ const rules_to_execute = rules_needing_execution.map(rule_to_fb_execution);
453
+ const validate_body = {
454
+ file_name: file.file_name,
455
+ mime_type: file.mime_type,
456
+ download_url,
457
+ rules: rules_to_execute,
458
+ };
459
+ const validate_start = Date.now();
460
+ const response = await fetch(validation_api_endpoint, {
461
+ method: 'POST',
462
+ headers: { 'Content-Type': 'application/json' },
463
+ body: JSON.stringify(validate_body),
464
+ });
465
+ const result = await response.json();
466
+ run_log.push({
467
+ step: 'validate',
468
+ label: `Validate: ${file.file_name}`,
469
+ request: validate_body,
470
+ response: result,
471
+ timestamp: validate_start,
472
+ duration_ms: Date.now() - validate_start,
473
+ });
474
+ // Enrich rule_results with human-readable rule names from rules_needing_execution
475
+ const rule_name_lookup = new Map(rules_needing_execution.map(r => [r.rule_id, r.name]));
476
+ const enriched_rule_results = result.rule_results?.map(rr => ({
477
+ ...rr,
478
+ rule_name: rr.rule_name ?? rule_name_lookup.get(rr.rule_id),
479
+ })) ?? [];
480
+ // Merge user-resolved results with API results
481
+ const all_rule_results = [...resolved_rule_results, ...enriched_rule_results];
482
+ // Enrich clarification doc_references with actual file_id
483
+ const enriched_clarifications = (result.clarifications ?? []).map(c => ({
484
+ ...c,
485
+ doc_references: c.doc_references.map(ref => ({
486
+ ...ref,
487
+ file_id: ref.file_id || file.file_id,
488
+ })),
489
+ }));
490
+ const has_issues = enriched_clarifications.length > 0;
491
+ const validation_result = {
492
+ file_id: file.file_id,
493
+ file_name: file.file_name,
494
+ status: has_issues ? 'failed' : 'passed',
495
+ errors: enriched_clarifications,
496
+ document_types: file.document_types,
497
+ rule_results: all_rule_results,
498
+ };
499
+ all_validation_results.push(validation_result);
500
+ set_file_validation_results((prev) => ({ ...prev, [file.file_id]: validation_result }));
501
+ if (has_issues) {
502
+ failed_file_ids.add(file.file_id);
503
+ }
504
+ }
505
+ catch (err) {
506
+ errors.push({ step: `validate:${file.file_id}`, error: err instanceof Error ? err.message : 'Validation request failed' });
507
+ const error_result = {
508
+ file_id: file.file_id,
509
+ file_name: file.file_name,
510
+ status: 'failed',
511
+ errors: [],
512
+ document_types: file.document_types,
513
+ rule_results: resolved_rule_results.length > 0 ? resolved_rule_results : undefined,
514
+ };
515
+ all_validation_results.push(error_result);
516
+ set_file_validation_results((prev) => ({ ...prev, [file.file_id]: error_result }));
517
+ failed_file_ids.add(file.file_id);
518
+ }
519
+ }
520
+ // Build passed_classifications by filtering out failed files
521
+ const passed_classifications = classifications.map((cls) => {
522
+ const passed_file_cls = cls.file_classifications.filter((fc) => !failed_file_ids.has(fc.file_id));
523
+ const passed_tags = [...new Set(passed_file_cls.flatMap((fc) => fc.tags))];
524
+ return {
525
+ field_id: cls.field_id,
526
+ tags: passed_tags,
527
+ file_classifications: passed_file_cls,
528
+ };
529
+ }).filter((cls) => cls.file_classifications.length > 0);
530
+ return { passed_classifications, all_validation_results, errors };
531
+ }
532
+ export function use_llm_run({ props, update_progress, classification_results, set_classification_results, set_active_tab, add_classifying_files, remove_classifying_files, add_queued_files, remove_queued_files, set_unassigned_files, set_group_autofill_log, set_file_validation_results, manual_review_file_ids, sent_clarifications, pending_clarification_responses, draft_clarifications, file_validation_results, set_autofilling_file_ids, set_validating_file_ids }) {
533
+ const logger = use_logger();
534
+ // Refs to always read the latest values inside trigger_run,
535
+ // avoiding stale closures when submit flushes responses then immediately triggers a run.
536
+ const sent_clarifications_ref = useRef(sent_clarifications);
537
+ sent_clarifications_ref.current = sent_clarifications;
538
+ const pending_responses_ref = useRef(pending_clarification_responses);
539
+ pending_responses_ref.current = pending_clarification_responses;
540
+ const draft_clarifications_ref = useRef(draft_clarifications);
541
+ draft_clarifications_ref.current = draft_clarifications;
542
+ /** Classify a single file within a field and update results */
543
+ const trigger_classify_file = useCallback(async (field_id, file_id, file_name) => {
544
+ const { field_textbox_configs, llm_api_endpoint, front_form_data } = props;
545
+ logger.info('[use_llm_run] trigger_classify_file', { field_id, file_id, file_name, has_endpoint: !!llm_api_endpoint });
546
+ if (!field_textbox_configs || !llm_api_endpoint) {
547
+ logger.warn('[use_llm_run] classify_file_skipped: missing config', { has_configs: !!field_textbox_configs, has_endpoint: !!llm_api_endpoint });
548
+ return;
549
+ }
550
+ const config = field_textbox_configs[field_id];
551
+ if (!config)
552
+ return;
553
+ const client_text = get_text_from_value(front_form_data[field_id]);
554
+ add_classifying_files([file_id]);
555
+ update_progress({ status: 'classifying', total_steps: 1, completed_steps: 0, current_step: `Classifying ${file_name}`, error: undefined });
556
+ try {
557
+ const response = await fetch(llm_api_endpoint, {
558
+ method: 'POST',
559
+ headers: { 'Content-Type': 'application/json' },
560
+ body: JSON.stringify({
561
+ action: 'classify_files',
562
+ field_id,
563
+ files: [{ file_id, file_name }],
564
+ client_text,
565
+ prompt_area: config.classification.prompt_area,
566
+ prompt_key: config.classification.prompt_key,
567
+ available_tags: config.classification.available_tags ?? props.available_tags ?? [],
568
+ ...(props.available_document_types?.length ? { available_document_types: props.available_document_types } : {}),
569
+ }),
570
+ });
571
+ const result = await response.json();
572
+ logger.debug('[use_llm_run] classify_file_response', { file_id, success: result.success, classification_count: result.file_classifications?.length ?? 0 });
573
+ if (result.success && result.file_classifications) {
574
+ const new_file_cls = result.file_classifications;
575
+ // Merge: replace this file's classification, keep others.
576
+ // Use functional update to avoid stale closure when multiple files classify concurrently.
577
+ set_classification_results((prev_results) => {
578
+ const prev = prev_results.find((c) => c.field_id === field_id);
579
+ const existing_file_cls = prev?.file_classifications?.filter((fc) => fc.file_id !== file_id) ?? [];
580
+ const all_file_cls = [...existing_file_cls, ...new_file_cls];
581
+ const all_tags = [...new Set(all_file_cls.flatMap((fc) => fc.tags))];
582
+ const updated_result = { field_id, tags: all_tags, file_classifications: all_file_cls };
583
+ return [
584
+ ...prev_results.filter((c) => c.field_id !== field_id),
585
+ updated_result,
586
+ ];
587
+ });
588
+ }
589
+ // Validate the re-classified file (if configured)
590
+ if (result.success && result.file_classifications) {
591
+ const new_file_cls = result.file_classifications;
592
+ const single_classification = {
593
+ field_id,
594
+ tags: [...new Set(new_file_cls.flatMap((fc) => fc.tags))],
595
+ file_classifications: new_file_cls,
596
+ };
597
+ if (props.validation_api_endpoint && props.validation_rules?.length) {
598
+ // Classification done — switch from classifying to validating state
599
+ remove_classifying_files([file_id]);
600
+ set_validating_file_ids(prev => { const s = new Set(prev); s.add(file_id); return s; });
601
+ update_progress({ status: 'validating', total_steps: 2, completed_steps: 1, current_step: `Validating ${file_name}...` });
602
+ const run_log = [];
603
+ await validate_classified_files({
604
+ classifications: [single_classification],
605
+ validation_api_endpoint: props.validation_api_endpoint,
606
+ validation_rules: props.validation_rules,
607
+ file_manager: props.file_manager,
608
+ front_form_data: props.front_form_data,
609
+ update_progress,
610
+ set_file_validation_results,
611
+ run_log,
612
+ manual_review_file_ids,
613
+ check_type_filter: 'immediate',
614
+ });
615
+ }
616
+ }
617
+ // Phase 1 complete — routing deferred to trigger_complete
618
+ update_progress({ status: 'validated', total_steps: 2, completed_steps: 2, current_step: undefined, error: undefined });
619
+ }
620
+ catch (err) {
621
+ logger.error('[use_llm_run] classify_file_error', { file_id, file_name, error: err instanceof Error ? err.message : String(err) });
622
+ update_progress({
623
+ status: 'error',
624
+ total_steps: 1,
625
+ completed_steps: 0,
626
+ current_step: undefined,
627
+ error: err instanceof Error ? err.message : 'Classification failed',
628
+ });
629
+ }
630
+ finally {
631
+ remove_classifying_files([file_id]);
632
+ set_validating_file_ids(prev => { const s = new Set(prev); s.delete(file_id); return s; });
633
+ }
634
+ }, [props, classification_results, update_progress, set_classification_results, add_classifying_files, remove_classifying_files, set_unassigned_files, set_group_autofill_log, set_file_validation_results, manual_review_file_ids, set_validating_file_ids]);
635
+ /** Manually assign a file to a tag and route it to matching back-office group */
636
+ const trigger_assign_file = useCallback(async (file_id, tag_id) => {
637
+ const { back_sections, on_back_change, back_form_data, autofill_api_endpoint, file_manager, front_form_data } = props;
638
+ // Find the file attachment from front_form_data
639
+ let attachment;
640
+ let file_name = '';
641
+ for (const [_field_id, value] of Object.entries(front_form_data)) {
642
+ const files = get_files_from_value(value);
643
+ const found = files.find((f) => f.file_id === file_id);
644
+ if (found?.attachment) {
645
+ attachment = found.attachment;
646
+ file_name = found.file_name;
647
+ break;
648
+ }
649
+ }
650
+ if (!attachment)
651
+ return;
652
+ const tag_group_map = build_tag_group_map(back_sections);
653
+ const groups = tag_group_map.get(tag_id);
654
+ if (groups && on_back_change) {
655
+ for (const group of groups) {
656
+ const file_key = `__files_${group.id}`;
657
+ const existing_files = back_form_data[file_key] ?? [];
658
+ if (!existing_files.some((f) => f.file_id === file_id)) {
659
+ on_back_change(file_key, [...existing_files, attachment]);
660
+ }
661
+ // Trigger autofill for this group (single file, matches AutofillRequest shape)
662
+ if (autofill_api_endpoint && group.prompt_area && group.prompt_key) {
663
+ try {
664
+ const download_url = file_manager?.callbacks?.get_download_url?.(attachment.file_id, 'public') ?? '';
665
+ const response = await fetch(autofill_api_endpoint, {
666
+ method: 'POST',
667
+ headers: { 'Content-Type': 'application/json' },
668
+ body: JSON.stringify({
669
+ file_id: attachment.file_id,
670
+ file_name: attachment.file_name,
671
+ mime_type: attachment.mime_type ?? 'application/octet-stream',
672
+ download_url,
673
+ group_id: group.id,
674
+ prompt_area: group.prompt_area,
675
+ prompt_key: group.prompt_key,
676
+ fields: group.fields?.map((f) => ({
677
+ field_id: f.id,
678
+ label: f.label,
679
+ field_type: f.field_type,
680
+ component_type: f.component_type,
681
+ table_config: f.table_config,
682
+ })) ?? [],
683
+ }),
684
+ });
685
+ const result = await response.json();
686
+ if (result.success && result.data) {
687
+ const field_count = Object.keys(result.data).length;
688
+ for (const [field_id, value] of Object.entries(result.data)) {
689
+ on_back_change(field_id, value);
690
+ }
691
+ set_group_autofill_log((prev) => ({
692
+ ...prev,
693
+ [group.id]: {
694
+ status: field_count > 0 ? 'success' : 'empty',
695
+ message: field_count > 0
696
+ ? `Populated ${field_count} field(s) from ${file_name}`
697
+ : result.message || 'No matching data found in document',
698
+ timestamp: Date.now(),
699
+ },
700
+ }));
701
+ }
702
+ else if (!result.success) {
703
+ set_group_autofill_log((prev) => ({
704
+ ...prev,
705
+ [group.id]: {
706
+ status: 'error',
707
+ message: result.error || 'Autofill failed',
708
+ timestamp: Date.now(),
709
+ },
710
+ }));
711
+ }
712
+ }
713
+ catch (err) {
714
+ // Autofill error logged via group_autofill_log
715
+ set_group_autofill_log((prev) => ({
716
+ ...prev,
717
+ [group.id]: {
718
+ status: 'error',
719
+ message: err instanceof Error ? err.message : 'Autofill request failed',
720
+ timestamp: Date.now(),
721
+ },
722
+ }));
723
+ }
724
+ }
725
+ }
726
+ }
727
+ // Persist tags
728
+ if (file_manager?.callbacks?.update_tags) {
729
+ try {
730
+ await file_manager.callbacks.update_tags(file_id, [tag_id]);
731
+ }
732
+ catch (err) {
733
+ // Tag update error — non-critical, file_manager may not support update_tags
734
+ }
735
+ }
736
+ // Remove from unassigned files list (handled by caller via set_unassigned_files)
737
+ }, [props, set_group_autofill_log]);
738
+ const trigger_run = useCallback(async (mode = 'new_only') => {
739
+ const { field_textbox_configs, llm_api_endpoint, front_form_data, on_back_change, back_form_data, back_sections, autofill_api_endpoint, file_manager, on_run_complete } = props;
740
+ const run_start = Date.now();
741
+ logger.info('[use_llm_run] trigger_run_start', {
742
+ mode,
743
+ has_configs: !!field_textbox_configs,
744
+ has_llm_endpoint: !!llm_api_endpoint,
745
+ has_autofill_endpoint: !!autofill_api_endpoint,
746
+ has_validation_endpoint: !!props.validation_api_endpoint,
747
+ field_count: field_textbox_configs ? Object.keys(field_textbox_configs).length : 0,
748
+ validation_rule_count: props.validation_rules?.length ?? 0,
749
+ });
750
+ if (!field_textbox_configs || !llm_api_endpoint) {
751
+ logger.warn('[use_llm_run] trigger_run_aborted: missing config', { has_configs: !!field_textbox_configs, has_endpoint: !!llm_api_endpoint });
752
+ return;
753
+ }
754
+ set_group_autofill_log({});
755
+ // When running all, clear existing classification results (tags + metadata) upfront
756
+ if (mode === 'all') {
757
+ set_classification_results([]);
758
+ }
759
+ const all_field_ids = Object.keys(field_textbox_configs);
760
+ // Build list of fields that need classification and which files are new
761
+ const fields_to_process = [];
762
+ for (const field_id of all_field_ids) {
763
+ const current_files = get_files_from_value(front_form_data[field_id]);
764
+ if (current_files.length === 0)
765
+ continue;
766
+ const prev = classification_results.find((c) => c.field_id === field_id);
767
+ if (mode === 'all' || !prev) {
768
+ // Classify all files
769
+ fields_to_process.push({
770
+ field_id,
771
+ files_to_classify: current_files,
772
+ existing_file_classifications: [],
773
+ });
774
+ }
775
+ else {
776
+ // Only classify new files (not previously classified)
777
+ const prev_file_cls = prev.file_classifications ?? [];
778
+ const classified_ids = new Set(prev_file_cls.map((fc) => fc.file_id));
779
+ const new_files = current_files.filter((f) => !classified_ids.has(f.file_id));
780
+ if (new_files.length > 0) {
781
+ // Keep existing classifications for files still present
782
+ const still_present_ids = new Set(current_files.map((f) => f.file_id));
783
+ const kept = prev_file_cls.filter((fc) => still_present_ids.has(fc.file_id));
784
+ fields_to_process.push({
785
+ field_id,
786
+ files_to_classify: new_files,
787
+ existing_file_classifications: kept,
788
+ });
789
+ }
790
+ }
791
+ }
792
+ logger.info('[use_llm_run] fields_to_process', {
793
+ count: fields_to_process.length,
794
+ total_files: fields_to_process.reduce((sum, f) => sum + f.files_to_classify.length, 0),
795
+ fields: fields_to_process.map(f => ({ field_id: f.field_id, file_count: f.files_to_classify.length, existing_count: f.existing_file_classifications.length })),
796
+ });
797
+ if (fields_to_process.length === 0) {
798
+ // No new files to classify — but still run backoffice validation if configured
799
+ // (immediate-only validation may have skipped backoffice-only rules)
800
+ const has_backoffice_validation = !!(props.validation_api_endpoint && props.validation_rules?.length);
801
+ const has_existing_classifications = classification_results.length > 0;
802
+ if (has_backoffice_validation && has_existing_classifications) {
803
+ update_progress({ status: 'validating', current_step: 'Running back-office validation...' });
804
+ const run_log = [];
805
+ const current_sent = sent_clarifications_ref.current;
806
+ const current_pending = pending_responses_ref.current;
807
+ const current_drafts = draft_clarifications_ref.current;
808
+ let effective_clarifications = current_sent;
809
+ if (current_pending.size > 0) {
810
+ const pending_items = [];
811
+ for (const [clar_id, response] of current_pending) {
812
+ if (!response.response_choice)
813
+ continue;
814
+ if (current_sent.some(c => c.id === clar_id && (c.status === 'responded' || c.status === 'resolved')))
815
+ continue;
816
+ const original = current_drafts.find(c => c.id === clar_id) ?? current_sent.find(c => c.id === clar_id);
817
+ if (original) {
818
+ pending_items.push({ ...original, status: 'responded', response_choice: response.response_choice, user_comment: response.user_comment });
819
+ }
820
+ }
821
+ if (pending_items.length > 0) {
822
+ effective_clarifications = [...current_sent, ...pending_items];
823
+ }
824
+ }
825
+ const resolved_rules = build_resolved_rules_map(effective_clarifications);
826
+ const vr = await validate_classified_files({
827
+ classifications: classification_results,
828
+ validation_api_endpoint: props.validation_api_endpoint,
829
+ validation_rules: props.validation_rules,
830
+ file_manager: props.file_manager,
831
+ front_form_data,
832
+ update_progress,
833
+ set_file_validation_results,
834
+ run_log,
835
+ manual_review_file_ids,
836
+ check_type_filter: 'backoffice',
837
+ resolved_rules,
838
+ });
839
+ // Trigger completion with existing classifications + new validation results
840
+ const run_result = {
841
+ classifications: classification_results,
842
+ unassigned_files: [],
843
+ validation_results: vr.all_validation_results,
844
+ errors: vr.errors,
845
+ };
846
+ update_progress({ status: 'validated', total_steps: 0, completed_steps: 0, current_step: undefined, error: undefined });
847
+ return;
848
+ }
849
+ update_progress({ status: 'validated', total_steps: 0, completed_steps: 0, current_step: undefined, error: undefined });
850
+ return;
851
+ }
852
+ // Build overall pipeline tracker: classify + validate + autofill (autofill count added dynamically)
853
+ const total_files_for_validation = fields_to_process.flatMap((f) => f.files_to_classify).length;
854
+ const has_validation = !!(props.validation_api_endpoint && props.validation_rules?.length);
855
+ const tracker = {
856
+ completed: 0,
857
+ total: fields_to_process.length + (has_validation ? total_files_for_validation : 0),
858
+ // autofill actions are added to total in route_files_to_back_office once known
859
+ };
860
+ update_progress({ status: 'classifying', total_steps: fields_to_process.length, completed_steps: 0, overall_percent: 0, error: undefined });
861
+ const new_classifications = [];
862
+ const errors = [];
863
+ const run_log = [];
864
+ // Mark all files as queued initially
865
+ const all_file_ids = fields_to_process.flatMap((f) => f.files_to_classify.map((file) => file.file_id));
866
+ add_queued_files(all_file_ids);
867
+ // Step 1: Per-field classification with batch support
868
+ const batch_size = props.classification_batch_size ?? 5;
869
+ for (let i = 0; i < fields_to_process.length; i++) {
870
+ const { field_id, files_to_classify, existing_file_classifications } = fields_to_process[i];
871
+ const config = field_textbox_configs[field_id];
872
+ // Move this field's files from queued → classifying
873
+ const field_file_ids = files_to_classify.map((f) => f.file_id);
874
+ remove_queued_files(field_file_ids);
875
+ add_classifying_files(field_file_ids);
876
+ const client_text = get_text_from_value(front_form_data[field_id]);
877
+ tracker.completed++;
878
+ const pct = Math.round((tracker.completed / tracker.total) * 100);
879
+ update_progress({
880
+ current_step: `${pct}% Classifying ${files_to_classify.length} file(s) in ${field_id} (${i + 1}/${fields_to_process.length})`,
881
+ completed_steps: i,
882
+ overall_percent: pct,
883
+ });
884
+ // Split files into batches to reduce per-call overhead
885
+ const batches = [];
886
+ for (let b = 0; b < files_to_classify.length; b += batch_size) {
887
+ batches.push(files_to_classify.slice(b, b + batch_size));
888
+ }
889
+ const batch_file_cls = [];
890
+ let batch_failed = false;
891
+ for (let bi = 0; bi < batches.length; bi++) {
892
+ const batch = batches[bi];
893
+ try {
894
+ const classify_body = {
895
+ action: 'classify_files',
896
+ field_id,
897
+ files: batch,
898
+ client_text,
899
+ prompt_area: config.classification.prompt_area,
900
+ prompt_key: config.classification.prompt_key,
901
+ available_tags: config.classification.available_tags ?? props.available_tags ?? [],
902
+ ...(props.available_document_types?.length ? { available_document_types: props.available_document_types } : {}),
903
+ };
904
+ const classify_start = Date.now();
905
+ const response = await fetch(llm_api_endpoint, {
906
+ method: 'POST',
907
+ headers: { 'Content-Type': 'application/json' },
908
+ body: JSON.stringify(classify_body),
909
+ });
910
+ const result = await response.json();
911
+ run_log.push({
912
+ step: 'classify',
913
+ label: `Classification: ${field_id}${batches.length > 1 ? ` (batch ${bi + 1}/${batches.length})` : ''}`,
914
+ prompt_area: config.classification.prompt_area,
915
+ prompt_key: config.classification.prompt_key,
916
+ request: classify_body,
917
+ response: result,
918
+ timestamp: classify_start,
919
+ duration_ms: Date.now() - classify_start,
920
+ });
921
+ if (result.success && result.file_classifications) {
922
+ batch_file_cls.push(...result.file_classifications);
923
+ }
924
+ else {
925
+ errors.push({ step: `classify:${field_id}`, error: result.error || 'Classification failed' });
926
+ batch_failed = true;
927
+ }
928
+ }
929
+ catch (err) {
930
+ errors.push({ step: `classify:${field_id}`, error: err instanceof Error ? err.message : 'Unknown error' });
931
+ batch_failed = true;
932
+ }
933
+ }
934
+ if (!batch_failed || batch_file_cls.length > 0) {
935
+ const all_file_cls = [...existing_file_classifications, ...batch_file_cls];
936
+ const all_tags = [...new Set(all_file_cls.flatMap((fc) => fc.tags))];
937
+ new_classifications.push({
938
+ field_id,
939
+ tags: all_tags,
940
+ file_classifications: all_file_cls,
941
+ });
942
+ }
943
+ // Clear classifying state for this field's files
944
+ remove_classifying_files(field_file_ids);
945
+ }
946
+ logger.info('[use_llm_run] classification_phase_complete', {
947
+ new_classification_count: new_classifications.length,
948
+ total_file_classifications: new_classifications.reduce((sum, c) => sum + c.file_classifications.length, 0),
949
+ error_count: errors.length,
950
+ duration_ms: Date.now() - run_start,
951
+ });
952
+ // Merge with existing results for fields that weren't re-processed
953
+ const processed_field_ids = new Set(fields_to_process.map((f) => f.field_id));
954
+ const prior_results = mode === 'all' ? [] : classification_results;
955
+ const merged_classifications = [
956
+ ...prior_results.filter((c) => !processed_field_ids.has(c.field_id)),
957
+ ...new_classifications,
958
+ ];
959
+ set_classification_results(merged_classifications);
960
+ // Step 2: Validate (if configured)
961
+ let all_validation_results = [];
962
+ if (props.validation_api_endpoint && props.validation_rules?.length) {
963
+ update_progress({ status: 'validating', current_step: 'Validating documents...' });
964
+ // Build resolved rules map from sent clarifications + pending responses.
965
+ // Use refs to get latest values — avoids stale closure when submit flushes responses then runs pipeline.
966
+ // Merge pending responses with sent clarifications so we don't re-validate already-resolved issues
967
+ // even if the parent component hasn't re-rendered with updated props yet.
968
+ const current_sent = sent_clarifications_ref.current;
969
+ const current_pending = pending_responses_ref.current;
970
+ const current_drafts = draft_clarifications_ref.current;
971
+ let effective_clarifications = current_sent;
972
+ if (current_pending.size > 0) {
973
+ // Create synthetic responded items from pending responses
974
+ const pending_items = [];
975
+ for (const [clar_id, response] of current_pending) {
976
+ if (!response.response_choice)
977
+ continue;
978
+ // Skip if already in sent_clarifications as responded
979
+ if (current_sent.some(c => c.id === clar_id && (c.status === 'responded' || c.status === 'resolved')))
980
+ continue;
981
+ const original = current_drafts.find(c => c.id === clar_id) ?? current_sent.find(c => c.id === clar_id);
982
+ if (original) {
983
+ pending_items.push({
984
+ ...original,
985
+ status: 'responded',
986
+ response_choice: response.response_choice,
987
+ user_comment: response.user_comment,
988
+ });
989
+ }
990
+ }
991
+ if (pending_items.length > 0) {
992
+ effective_clarifications = [...current_sent, ...pending_items];
993
+ }
994
+ }
995
+ const resolved_rules = build_resolved_rules_map(effective_clarifications);
996
+ const vr = await validate_classified_files({
997
+ classifications: new_classifications,
998
+ validation_api_endpoint: props.validation_api_endpoint,
999
+ validation_rules: props.validation_rules,
1000
+ file_manager: props.file_manager,
1001
+ front_form_data,
1002
+ update_progress,
1003
+ set_file_validation_results,
1004
+ run_log,
1005
+ tracker,
1006
+ manual_review_file_ids,
1007
+ check_type_filter: 'backoffice',
1008
+ resolved_rules,
1009
+ });
1010
+ all_validation_results = vr.all_validation_results;
1011
+ errors.push(...vr.errors);
1012
+ logger.info('[use_llm_run] validation_phase_complete', {
1013
+ total_results: vr.all_validation_results.length,
1014
+ passed: vr.all_validation_results.filter(r => r.status === 'passed').length,
1015
+ failed: vr.all_validation_results.filter(r => r.status === 'failed').length,
1016
+ skipped: vr.all_validation_results.filter(r => r.status === 'skipped').length,
1017
+ validation_errors: vr.errors.length,
1018
+ });
1019
+ }
1020
+ // Phase 1 complete: classification + validation done.
1021
+ // Routing + autofill deferred to trigger_complete (Phase 2).
1022
+ const run_result = {
1023
+ classifications: new_classifications,
1024
+ unassigned_files: [],
1025
+ validation_results: all_validation_results,
1026
+ errors,
1027
+ };
1028
+ update_progress({
1029
+ status: errors.length > 0 ? 'error' : 'validated',
1030
+ completed_steps: fields_to_process.length,
1031
+ total_steps: fields_to_process.length,
1032
+ current_step: undefined,
1033
+ overall_percent: 100,
1034
+ error: errors.length > 0 ? `${errors.length} error(s) during processing` : undefined,
1035
+ error_details: errors.length > 0 ? errors : undefined,
1036
+ run_log,
1037
+ });
1038
+ logger.info('[use_llm_run] trigger_run_complete', {
1039
+ mode,
1040
+ total_duration_ms: Date.now() - run_start,
1041
+ classification_count: new_classifications.length,
1042
+ validation_count: all_validation_results.length,
1043
+ error_count: errors.length,
1044
+ run_log_entries: run_log.length,
1045
+ });
1046
+ on_run_complete?.(run_result);
1047
+ // Switch to clarifications tab so user can review validation results
1048
+ set_active_tab('clarifications');
1049
+ // Note: sent_clarifications is read via ref (sent_clarifications_ref.current) to avoid stale closures
1050
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1051
+ }, [props, classification_results, update_progress, set_classification_results, set_active_tab, add_classifying_files, remove_classifying_files, add_queued_files, remove_queued_files, set_unassigned_files, set_group_autofill_log, set_file_validation_results, manual_review_file_ids]);
1052
+ /** Route specific files (by file_id) to back-office groups using their classification tags + trigger autofill.
1053
+ * Used by "skip validation and process" flow in clarifications view. */
1054
+ const route_skipped_files = useCallback(async (file_ids) => {
1055
+ if (file_ids.length === 0)
1056
+ return;
1057
+ const { back_sections, front_form_data, on_back_change, back_form_data, autofill_api_endpoint, file_manager } = props;
1058
+ // Build synthetic passed_classifications containing only the specified files
1059
+ const file_id_set = new Set(file_ids);
1060
+ const synthetic_classifications = classification_results
1061
+ .map((cls) => ({
1062
+ field_id: cls.field_id,
1063
+ tags: cls.tags,
1064
+ file_classifications: cls.file_classifications.filter((fc) => file_id_set.has(fc.file_id)),
1065
+ }))
1066
+ .filter((cls) => cls.file_classifications.length > 0);
1067
+ if (synthetic_classifications.length === 0)
1068
+ return;
1069
+ const run_log = [];
1070
+ await route_files_to_back_office({
1071
+ classifications: synthetic_classifications,
1072
+ back_sections,
1073
+ front_form_data,
1074
+ on_back_change,
1075
+ back_form_data,
1076
+ autofill_api_endpoint,
1077
+ file_manager,
1078
+ update_progress,
1079
+ set_group_autofill_log,
1080
+ run_log,
1081
+ });
1082
+ }, [props, classification_results, update_progress, set_group_autofill_log]);
1083
+ /** Phase 2: Route eligible files to back-office groups and trigger autofill.
1084
+ * Called when user clicks "Complete" after classification + validation (Phase 1).
1085
+ * Includes files that passed validation AND files whose validation issues were resolved
1086
+ * via clarifications. For resolved clarifications with response files, both the original
1087
+ * file and response files are routed using the original file's tags. */
1088
+ const trigger_complete = useCallback(async () => {
1089
+ const { on_back_change, back_form_data, back_sections, front_form_data, autofill_api_endpoint, file_manager, on_run_complete } = props;
1090
+ const complete_start = Date.now();
1091
+ logger.info('[use_llm_run] trigger_complete_start', {
1092
+ classification_count: classification_results.length,
1093
+ total_file_classifications: classification_results.reduce((sum, c) => sum + c.file_classifications.length, 0),
1094
+ validation_result_count: Object.keys(file_validation_results).length,
1095
+ has_on_back_change: !!on_back_change,
1096
+ has_autofill_endpoint: !!autofill_api_endpoint,
1097
+ });
1098
+ if (!on_back_change)
1099
+ return;
1100
+ set_group_autofill_log({});
1101
+ update_progress({ status: 'routing', total_steps: 1, completed_steps: 0, current_step: 'Routing files to back-office...', error: undefined });
1102
+ const current_sent = sent_clarifications_ref.current;
1103
+ const current_pending = pending_responses_ref.current;
1104
+ const current_drafts = draft_clarifications_ref.current;
1105
+ // Build a unified view of all clarification items with their latest status.
1106
+ // Clarifications may be in drafts (auto-populated from validation), sent (approved by agent),
1107
+ // or have pending responses (client responded but not yet flushed). Merge them all.
1108
+ const all_clarification_items = new Map();
1109
+ for (const item of current_drafts)
1110
+ all_clarification_items.set(item.id, item);
1111
+ for (const item of current_sent)
1112
+ all_clarification_items.set(item.id, item);
1113
+ // Apply pending responses on top
1114
+ for (const [clar_id, response] of current_pending) {
1115
+ const existing = all_clarification_items.get(clar_id);
1116
+ if (existing && response.response_choice) {
1117
+ all_clarification_items.set(clar_id, {
1118
+ ...existing,
1119
+ status: 'responded',
1120
+ response_choice: response.response_choice,
1121
+ user_comment: response.user_comment,
1122
+ response_files: response.response_files ?? existing.response_files ?? [],
1123
+ });
1124
+ }
1125
+ }
1126
+ // Build a lookup of all file attachments from front_form_data
1127
+ const all_attachments = new Map();
1128
+ for (const value of Object.values(front_form_data)) {
1129
+ if (!Array.isArray(value))
1130
+ continue;
1131
+ for (const block of value) {
1132
+ if (block?.type === 'file' && block?.attachment?.file_id) {
1133
+ all_attachments.set(block.attachment.file_id, block.attachment);
1134
+ }
1135
+ }
1136
+ }
1137
+ // Determine eligible files: passed/skipped validation, OR failed but all clarifications resolved
1138
+ const eligible_classifications = [];
1139
+ for (const cls of classification_results) {
1140
+ const eligible_file_cls = [];
1141
+ for (const fc of cls.file_classifications) {
1142
+ const vr = file_validation_results[fc.file_id];
1143
+ if (!vr || vr.status === 'passed' || vr.status === 'skipped') {
1144
+ eligible_file_cls.push(fc);
1145
+ }
1146
+ else if (vr.status === 'failed') {
1147
+ // Failed — check if ALL its clarification errors are resolved (in sent, drafts, or pending).
1148
+ // 'ignore' response means the user wants to skip this file entirely — treat as NOT resolved.
1149
+ const all_resolved = vr.errors.length === 0 || vr.errors.every(err => {
1150
+ const item = all_clarification_items.get(err.id);
1151
+ if (!item || (item.status !== 'responded' && item.status !== 'resolved'))
1152
+ return false;
1153
+ return item.response_choice !== 'ignore';
1154
+ });
1155
+ if (all_resolved) {
1156
+ eligible_file_cls.push(fc);
1157
+ }
1158
+ }
1159
+ // manual_review and other statuses: not eligible unless explicitly skipped
1160
+ }
1161
+ if (eligible_file_cls.length > 0) {
1162
+ eligible_classifications.push({
1163
+ field_id: cls.field_id,
1164
+ tags: [...new Set(eligible_file_cls.flatMap(fc => fc.tags))],
1165
+ file_classifications: eligible_file_cls,
1166
+ });
1167
+ }
1168
+ }
1169
+ // Also include response files from resolved clarifications.
1170
+ // These inherit the original file's tags and get routed to the same groups.
1171
+ const routed_file_ids = new Set(eligible_classifications.flatMap(c => c.file_classifications.map(f => f.file_id)));
1172
+ for (const item of all_clarification_items.values()) {
1173
+ if ((item.status !== 'responded' && item.status !== 'resolved') || !item.response_files?.length)
1174
+ continue;
1175
+ const source_file_id = item.doc_references?.[0]?.file_id;
1176
+ if (!source_file_id)
1177
+ continue;
1178
+ // Find the original file's tags
1179
+ let source_tags = [];
1180
+ let source_field_id = '';
1181
+ for (const cls of classification_results) {
1182
+ const fc = cls.file_classifications.find(f => f.file_id === source_file_id);
1183
+ if (fc && fc.tags.length > 0) {
1184
+ source_tags = fc.tags;
1185
+ source_field_id = cls.field_id;
1186
+ break;
1187
+ }
1188
+ }
1189
+ if (source_tags.length === 0)
1190
+ continue;
1191
+ const new_file_cls = [];
1192
+ for (const att of item.response_files) {
1193
+ if (!routed_file_ids.has(att.file_id)) {
1194
+ new_file_cls.push({ file_id: att.file_id, file_name: att.file_name, tags: source_tags });
1195
+ routed_file_ids.add(att.file_id);
1196
+ }
1197
+ }
1198
+ if (new_file_cls.length > 0) {
1199
+ eligible_classifications.push({
1200
+ field_id: source_field_id,
1201
+ tags: source_tags,
1202
+ file_classifications: new_file_cls,
1203
+ });
1204
+ }
1205
+ }
1206
+ // Route + autofill
1207
+ const handle_autofill_file = (file_id, active) => {
1208
+ set_autofilling_file_ids(prev => {
1209
+ const next = new Set(prev);
1210
+ if (active)
1211
+ next.add(file_id);
1212
+ else
1213
+ next.delete(file_id);
1214
+ return next;
1215
+ });
1216
+ };
1217
+ const run_log = [];
1218
+ const routing_result = await route_files_to_back_office({
1219
+ classifications: eligible_classifications,
1220
+ back_sections,
1221
+ front_form_data,
1222
+ on_back_change,
1223
+ back_form_data,
1224
+ autofill_api_endpoint,
1225
+ file_manager,
1226
+ update_progress,
1227
+ set_group_autofill_log,
1228
+ run_log,
1229
+ on_autofill_file: handle_autofill_file,
1230
+ });
1231
+ set_unassigned_files(routing_result.unassigned);
1232
+ logger.info('[use_llm_run] trigger_complete_done', {
1233
+ duration_ms: Date.now() - complete_start,
1234
+ eligible_file_count: eligible_classifications.reduce((sum, c) => sum + c.file_classifications.length, 0),
1235
+ unassigned_count: routing_result.unassigned.length,
1236
+ routing_errors: routing_result.errors.length,
1237
+ });
1238
+ update_progress({
1239
+ status: routing_result.errors.length > 0 ? 'error' : 'done',
1240
+ completed_steps: 1,
1241
+ total_steps: 1,
1242
+ current_step: undefined,
1243
+ overall_percent: 100,
1244
+ error: routing_result.errors.length > 0 ? `${routing_result.errors.length} error(s) during routing` : undefined,
1245
+ run_log,
1246
+ });
1247
+ set_active_tab('back');
1248
+ }, [props, classification_results, file_validation_results, update_progress, set_active_tab, set_unassigned_files, set_group_autofill_log]);
1249
+ /** Run the back-office pipeline: backoffice validation → routing → autofill.
1250
+ * Classification is NOT re-run — it's already done per-file in the front office.
1251
+ * This is triggered manually by the "Run on Backoffice" button. */
1252
+ const trigger_backoffice_run = useCallback(async () => {
1253
+ const { on_back_change, back_form_data, back_sections, front_form_data, autofill_api_endpoint, file_manager, on_run_complete, validation_api_endpoint, validation_rules } = props;
1254
+ const bo_start = Date.now();
1255
+ logger.info('[use_llm_run] trigger_backoffice_run_start', {
1256
+ classification_count: classification_results.length,
1257
+ has_validation: !!(validation_api_endpoint && validation_rules?.length),
1258
+ rule_count: validation_rules?.length ?? 0,
1259
+ });
1260
+ if (!on_back_change)
1261
+ return;
1262
+ if (classification_results.length === 0)
1263
+ return;
1264
+ set_group_autofill_log({});
1265
+ const run_log = [];
1266
+ const errors = [];
1267
+ // ── Step 1: Backoffice validation ──
1268
+ const has_backoffice_validation = !!(validation_api_endpoint && validation_rules?.length);
1269
+ if (has_backoffice_validation) {
1270
+ update_progress({ status: 'validating', total_steps: 1, completed_steps: 0, current_step: 'Running back-office validation...', error: undefined });
1271
+ const current_sent = sent_clarifications_ref.current;
1272
+ const current_pending = pending_responses_ref.current;
1273
+ const current_drafts = draft_clarifications_ref.current;
1274
+ let effective_clarifications = current_sent;
1275
+ if (current_pending.size > 0) {
1276
+ const pending_items = [];
1277
+ for (const [clar_id, response] of current_pending) {
1278
+ if (!response.response_choice)
1279
+ continue;
1280
+ if (current_sent.some(c => c.id === clar_id && (c.status === 'responded' || c.status === 'resolved')))
1281
+ continue;
1282
+ const original = current_drafts.find(c => c.id === clar_id) ?? current_sent.find(c => c.id === clar_id);
1283
+ if (original) {
1284
+ pending_items.push({ ...original, status: 'responded', response_choice: response.response_choice, user_comment: response.user_comment });
1285
+ }
1286
+ }
1287
+ if (pending_items.length > 0) {
1288
+ effective_clarifications = [...current_sent, ...pending_items];
1289
+ }
1290
+ }
1291
+ const resolved_rules = build_resolved_rules_map(effective_clarifications);
1292
+ const vr = await validate_classified_files({
1293
+ classifications: classification_results,
1294
+ validation_api_endpoint: validation_api_endpoint,
1295
+ validation_rules: validation_rules,
1296
+ file_manager,
1297
+ front_form_data,
1298
+ update_progress,
1299
+ set_file_validation_results,
1300
+ run_log,
1301
+ manual_review_file_ids,
1302
+ check_type_filter: 'backoffice',
1303
+ resolved_rules,
1304
+ });
1305
+ errors.push(...vr.errors);
1306
+ }
1307
+ // ── Step 2: Determine eligible files and route + autofill ──
1308
+ update_progress({ status: 'routing', total_steps: 1, completed_steps: 0, current_step: 'Routing files to back-office...', error: undefined });
1309
+ const current_sent = sent_clarifications_ref.current;
1310
+ const current_pending = pending_responses_ref.current;
1311
+ const current_drafts = draft_clarifications_ref.current;
1312
+ // Build unified clarification items view
1313
+ const all_clarification_items = new Map();
1314
+ for (const item of current_drafts)
1315
+ all_clarification_items.set(item.id, item);
1316
+ for (const item of current_sent)
1317
+ all_clarification_items.set(item.id, item);
1318
+ for (const [clar_id, response] of current_pending) {
1319
+ const existing = all_clarification_items.get(clar_id);
1320
+ if (existing && response.response_choice) {
1321
+ all_clarification_items.set(clar_id, {
1322
+ ...existing,
1323
+ status: 'responded',
1324
+ response_choice: response.response_choice,
1325
+ user_comment: response.user_comment,
1326
+ response_files: response.response_files ?? existing.response_files ?? [],
1327
+ });
1328
+ }
1329
+ }
1330
+ // Determine eligible files
1331
+ const eligible_classifications = [];
1332
+ for (const cls of classification_results) {
1333
+ const eligible_file_cls = [];
1334
+ for (const fc of cls.file_classifications) {
1335
+ const vr = file_validation_results[fc.file_id];
1336
+ if (!vr || vr.status === 'passed' || vr.status === 'skipped') {
1337
+ eligible_file_cls.push(fc);
1338
+ }
1339
+ else if (vr.status === 'failed') {
1340
+ const all_resolved = vr.errors.length === 0 || vr.errors.every(err => {
1341
+ const item = all_clarification_items.get(err.id);
1342
+ if (!item || (item.status !== 'responded' && item.status !== 'resolved'))
1343
+ return false;
1344
+ return item.response_choice !== 'ignore';
1345
+ });
1346
+ if (all_resolved)
1347
+ eligible_file_cls.push(fc);
1348
+ }
1349
+ }
1350
+ if (eligible_file_cls.length > 0) {
1351
+ eligible_classifications.push({
1352
+ field_id: cls.field_id,
1353
+ tags: [...new Set(eligible_file_cls.flatMap(fc => fc.tags))],
1354
+ file_classifications: eligible_file_cls,
1355
+ });
1356
+ }
1357
+ }
1358
+ // Include response files from resolved clarifications
1359
+ const routed_file_ids = new Set(eligible_classifications.flatMap(c => c.file_classifications.map(f => f.file_id)));
1360
+ for (const item of all_clarification_items.values()) {
1361
+ if ((item.status !== 'responded' && item.status !== 'resolved') || !item.response_files?.length)
1362
+ continue;
1363
+ const source_file_id = item.doc_references?.[0]?.file_id;
1364
+ if (!source_file_id)
1365
+ continue;
1366
+ let source_tags = [];
1367
+ let source_field_id = '';
1368
+ for (const cls of classification_results) {
1369
+ const fc = cls.file_classifications.find(f => f.file_id === source_file_id);
1370
+ if (fc && fc.tags.length > 0) {
1371
+ source_tags = fc.tags;
1372
+ source_field_id = cls.field_id;
1373
+ break;
1374
+ }
1375
+ }
1376
+ if (source_tags.length === 0)
1377
+ continue;
1378
+ const new_file_cls = [];
1379
+ for (const att of item.response_files) {
1380
+ if (!routed_file_ids.has(att.file_id)) {
1381
+ new_file_cls.push({ file_id: att.file_id, file_name: att.file_name, tags: source_tags });
1382
+ routed_file_ids.add(att.file_id);
1383
+ }
1384
+ }
1385
+ if (new_file_cls.length > 0) {
1386
+ eligible_classifications.push({ field_id: source_field_id, tags: source_tags, file_classifications: new_file_cls });
1387
+ }
1388
+ }
1389
+ // Route + autofill
1390
+ const handle_autofill_file = (file_id, active) => {
1391
+ set_autofilling_file_ids(prev => {
1392
+ const next = new Set(prev);
1393
+ if (active)
1394
+ next.add(file_id);
1395
+ else
1396
+ next.delete(file_id);
1397
+ return next;
1398
+ });
1399
+ };
1400
+ const routing_result = await route_files_to_back_office({
1401
+ classifications: eligible_classifications,
1402
+ back_sections,
1403
+ front_form_data,
1404
+ on_back_change,
1405
+ back_form_data,
1406
+ autofill_api_endpoint,
1407
+ file_manager,
1408
+ update_progress,
1409
+ set_group_autofill_log,
1410
+ run_log,
1411
+ on_autofill_file: handle_autofill_file,
1412
+ });
1413
+ set_unassigned_files(routing_result.unassigned);
1414
+ errors.push(...routing_result.errors);
1415
+ logger.info('[use_llm_run] trigger_backoffice_run_complete', {
1416
+ duration_ms: Date.now() - bo_start,
1417
+ eligible_file_count: eligible_classifications.reduce((sum, c) => sum + c.file_classifications.length, 0),
1418
+ unassigned_count: routing_result.unassigned.length,
1419
+ error_count: errors.length,
1420
+ });
1421
+ update_progress({
1422
+ status: errors.length > 0 ? 'error' : 'done',
1423
+ completed_steps: 1,
1424
+ total_steps: 1,
1425
+ current_step: undefined,
1426
+ overall_percent: 100,
1427
+ error: errors.length > 0 ? `${errors.length} error(s) during processing` : undefined,
1428
+ error_details: errors.length > 0 ? errors : undefined,
1429
+ run_log,
1430
+ });
1431
+ set_active_tab('back');
1432
+ }, [props, classification_results, file_validation_results, update_progress, set_active_tab, set_unassigned_files, set_group_autofill_log, set_file_validation_results, manual_review_file_ids]);
1433
+ return { trigger_run, trigger_complete, trigger_classify_file, trigger_assign_file, route_skipped_files, trigger_backoffice_run };
1434
+ }
1435
+ //# sourceMappingURL=use_llm_run.js.map