@ixo/editor 6.33.0 → 6.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BidConfig-YLGSEIOS.js +154 -0
- package/dist/BidConfig-YLGSEIOS.js.map +1 -0
- package/dist/BidFlowDetail-U2SX5M7F.js +365 -0
- package/dist/BidFlowDetail-U2SX5M7F.js.map +1 -0
- package/dist/CalendarEventCreateConfig-J2LU6LJO.js +155 -0
- package/dist/CalendarEventCreateConfig-J2LU6LJO.js.map +1 -0
- package/dist/CalendarEventCreateFlowDetail-ZWHZWDOK.js +174 -0
- package/dist/CalendarEventCreateFlowDetail-ZWHZWDOK.js.map +1 -0
- package/dist/CalendarEventListConfig-GQNNF4ZB.js +116 -0
- package/dist/CalendarEventListConfig-GQNNF4ZB.js.map +1 -0
- package/dist/CalendarEventListFlowDetail-SSRTOXIX.js +58 -0
- package/dist/CalendarEventListFlowDetail-SSRTOXIX.js.map +1 -0
- package/dist/CalendarEventUpdateConfig-KACZA6GH.js +133 -0
- package/dist/CalendarEventUpdateConfig-KACZA6GH.js.map +1 -0
- package/dist/CalendarEventUpdateFlowDetail-CD6NHHOU.js +155 -0
- package/dist/CalendarEventUpdateFlowDetail-CD6NHHOU.js.map +1 -0
- package/dist/ClaimConfig-3HKPGK55.js +200 -0
- package/dist/ClaimConfig-3HKPGK55.js.map +1 -0
- package/dist/ClaimFlowDetail-I4OQSHAB.js +641 -0
- package/dist/ClaimFlowDetail-I4OQSHAB.js.map +1 -0
- package/dist/CollectionLifecycleConfig-LCCGZ3CJ.js +149 -0
- package/dist/CollectionLifecycleConfig-LCCGZ3CJ.js.map +1 -0
- package/dist/CollectionLifecycleFlowDetail-LQHKB227.js +647 -0
- package/dist/CollectionLifecycleFlowDetail-LQHKB227.js.map +1 -0
- package/dist/CollectionUsersConfig-RI2X3YY2.js +271 -0
- package/dist/CollectionUsersConfig-RI2X3YY2.js.map +1 -0
- package/dist/CollectionUsersFlowDetail-LHXVCCPQ.js +1276 -0
- package/dist/CollectionUsersFlowDetail-LHXVCCPQ.js.map +1 -0
- package/dist/CredentialStoreConfig-G66AQDGZ.js +75 -0
- package/dist/CredentialStoreConfig-G66AQDGZ.js.map +1 -0
- package/dist/CredentialStoreFlowDetail-FL7QAM3S.js +99 -0
- package/dist/CredentialStoreFlowDetail-FL7QAM3S.js.map +1 -0
- package/dist/DesignPodConfig-LJ7H2CZ6.js +8 -0
- package/dist/DesignPodConfig-LJ7H2CZ6.js.map +1 -0
- package/dist/DesignPodFlowDetail-JHJWGNVY.js +309 -0
- package/dist/DesignPodFlowDetail-JHJWGNVY.js.map +1 -0
- package/dist/DomainCardPreviewConfig-Q3X5M4WF.js +37 -0
- package/dist/DomainCardPreviewConfig-Q3X5M4WF.js.map +1 -0
- package/dist/DomainCardPreviewFlowDetail-Y2FE7O5Z.js +131 -0
- package/dist/DomainCardPreviewFlowDetail-Y2FE7O5Z.js.map +1 -0
- package/dist/DomainIndexerLookupConfig-N4VONQUY.js +41 -0
- package/dist/DomainIndexerLookupConfig-N4VONQUY.js.map +1 -0
- package/dist/DomainIndexerLookupFlowDetail-GJ7WWK7I.js +70 -0
- package/dist/DomainIndexerLookupFlowDetail-GJ7WWK7I.js.map +1 -0
- package/dist/DomainSignConfig-XFIXROVK.js +47 -0
- package/dist/DomainSignConfig-XFIXROVK.js.map +1 -0
- package/dist/DomainSignFlowDetail-BXZ6JBPR.js +206 -0
- package/dist/DomainSignFlowDetail-BXZ6JBPR.js.map +1 -0
- package/dist/DomainSingleSelectionConfig-ZNKIXB4P.js +101 -0
- package/dist/DomainSingleSelectionConfig-ZNKIXB4P.js.map +1 -0
- package/dist/DomainSingleSelectionFlowDetail-HWTQXSLA.js +147 -0
- package/dist/DomainSingleSelectionFlowDetail-HWTQXSLA.js.map +1 -0
- package/dist/EmailSendConfig-YEGZ5Q77.js +215 -0
- package/dist/EmailSendConfig-YEGZ5Q77.js.map +1 -0
- package/dist/EntityCreateOracleConfig-UZ6ZIMP5.js +150 -0
- package/dist/EntityCreateOracleConfig-UZ6ZIMP5.js.map +1 -0
- package/dist/EntityCreateOracleFlowDetail-H2X7CPYU.js +120 -0
- package/dist/EntityCreateOracleFlowDetail-H2X7CPYU.js.map +1 -0
- package/dist/EntitySingleSelectionConfig-DL57KNKM.js +30 -0
- package/dist/EntitySingleSelectionConfig-DL57KNKM.js.map +1 -0
- package/dist/EntitySingleSelectionFlowDetail-GFQGX3GA.js +118 -0
- package/dist/EntitySingleSelectionFlowDetail-GFQGX3GA.js.map +1 -0
- package/dist/EntityTransferConfig-JVZ3H35B.js +62 -0
- package/dist/EntityTransferConfig-JVZ3H35B.js.map +1 -0
- package/dist/EntityTransferFlowDetail-XEXM34XB.js +132 -0
- package/dist/EntityTransferFlowDetail-XEXM34XB.js.map +1 -0
- package/dist/EvalEngineConfig-CHC4IRN3.js +464 -0
- package/dist/EvalEngineConfig-CHC4IRN3.js.map +1 -0
- package/dist/EvalEngineFlowDetail-PUFRNO4U.js +4291 -0
- package/dist/EvalEngineFlowDetail-PUFRNO4U.js.map +1 -0
- package/dist/EvaluateBidConfig-OE4CM43G.js +151 -0
- package/dist/EvaluateBidConfig-OE4CM43G.js.map +1 -0
- package/dist/EvaluateBidFlowDetail-2EXGNBYC.js +564 -0
- package/dist/EvaluateBidFlowDetail-2EXGNBYC.js.map +1 -0
- package/dist/EvaluateClaimConfig-3D4F5LHU.js +297 -0
- package/dist/EvaluateClaimConfig-3D4F5LHU.js.map +1 -0
- package/dist/EvaluateClaimFlowDetail-QKHJVEDE.js +2237 -0
- package/dist/EvaluateClaimFlowDetail-QKHJVEDE.js.map +1 -0
- package/dist/FormSubmitConfig-UFZYMLUN.js +59 -0
- package/dist/FormSubmitConfig-UFZYMLUN.js.map +1 -0
- package/dist/FormSubmitFlowDetail-IAHDJS6P.js +169 -0
- package/dist/FormSubmitFlowDetail-IAHDJS6P.js.map +1 -0
- package/dist/GovernanceAuthzExecFlowDetail-SQBGU5BX.js +166 -0
- package/dist/GovernanceAuthzExecFlowDetail-SQBGU5BX.js.map +1 -0
- package/dist/GovernanceAuthzGrantFlowDetail-DZPNLANT.js +88 -0
- package/dist/GovernanceAuthzGrantFlowDetail-DZPNLANT.js.map +1 -0
- package/dist/GovernanceAuthzRevokeFlowDetail-3JZ4IUJG.js +88 -0
- package/dist/GovernanceAuthzRevokeFlowDetail-3JZ4IUJG.js.map +1 -0
- package/dist/GovernanceChainVoteFlowDetail-475UDNG6.js +83 -0
- package/dist/GovernanceChainVoteFlowDetail-475UDNG6.js.map +1 -0
- package/dist/GovernanceConfigConfig-V3WM4WBP.js +30 -0
- package/dist/GovernanceConfigConfig-V3WM4WBP.js.map +1 -0
- package/dist/GovernanceConfigFlowDetail-SQ5PPDRX.js +267 -0
- package/dist/GovernanceConfigFlowDetail-SQ5PPDRX.js.map +1 -0
- package/dist/GovernanceContractExecuteFlowDetail-H2ZV6W2E.js +113 -0
- package/dist/GovernanceContractExecuteFlowDetail-H2ZV6W2E.js.map +1 -0
- package/dist/GovernanceContractInstantiateFlowDetail-YJJMGM54.js +144 -0
- package/dist/GovernanceContractInstantiateFlowDetail-YJJMGM54.js.map +1 -0
- package/dist/GovernanceContractManageCw20FlowDetail-OCBMW3YJ.js +72 -0
- package/dist/GovernanceContractManageCw20FlowDetail-OCBMW3YJ.js.map +1 -0
- package/dist/GovernanceContractMigrateFlowDetail-JDNIKTT3.js +104 -0
- package/dist/GovernanceContractMigrateFlowDetail-JDNIKTT3.js.map +1 -0
- package/dist/GovernanceContractUpdateAdminFlowDetail-SZBOAOCC.js +68 -0
- package/dist/GovernanceContractUpdateAdminFlowDetail-SZBOAOCC.js.map +1 -0
- package/dist/GovernanceCustomMessageFlowDetail-CXD4ZATO.js +78 -0
- package/dist/GovernanceCustomMessageFlowDetail-CXD4ZATO.js.map +1 -0
- package/dist/GovernanceDaoAcceptToMarketplaceFlowDetail-Q6ZBNY7Z.js +98 -0
- package/dist/GovernanceDaoAcceptToMarketplaceFlowDetail-Q6ZBNY7Z.js.map +1 -0
- package/dist/GovernanceDaoAdminExecFlowDetail-JAEAY2US.js +94 -0
- package/dist/GovernanceDaoAdminExecFlowDetail-JAEAY2US.js.map +1 -0
- package/dist/GovernanceDaoCreateEntityFlowDetail-7RVNTVAB.js +96 -0
- package/dist/GovernanceDaoCreateEntityFlowDetail-7RVNTVAB.js.map +1 -0
- package/dist/GovernanceDaoJoinFlowDetail-XYCTCNHO.js +68 -0
- package/dist/GovernanceDaoJoinFlowDetail-XYCTCNHO.js.map +1 -0
- package/dist/GovernanceDaoManageStorageFlowDetail-LMLYLYYC.js +93 -0
- package/dist/GovernanceDaoManageStorageFlowDetail-LMLYLYYC.js.map +1 -0
- package/dist/GovernanceDaoManageSubDaosFlowDetail-YUFQ67JN.js +84 -0
- package/dist/GovernanceDaoManageSubDaosFlowDetail-YUFQ67JN.js.map +1 -0
- package/dist/GovernanceDaoUpdateInfoFlowDetail-G5FIBY4L.js +104 -0
- package/dist/GovernanceDaoUpdateInfoFlowDetail-G5FIBY4L.js.map +1 -0
- package/dist/GovernanceMemberProposalConfig-IGPXEFDB.js +42 -0
- package/dist/GovernanceMemberProposalConfig-IGPXEFDB.js.map +1 -0
- package/dist/GovernanceMemberProposalFlowDetail-T3VURONF.js +240 -0
- package/dist/GovernanceMemberProposalFlowDetail-T3VURONF.js.map +1 -0
- package/dist/GovernanceNftBurnFlowDetail-AG4I2O7H.js +69 -0
- package/dist/GovernanceNftBurnFlowDetail-AG4I2O7H.js.map +1 -0
- package/dist/GovernanceNftManageCollectionsFlowDetail-UAAGO3Q6.js +71 -0
- package/dist/GovernanceNftManageCollectionsFlowDetail-UAAGO3Q6.js.map +1 -0
- package/dist/GovernanceNftTransferFlowDetail-KXM7DTOV.js +130 -0
- package/dist/GovernanceNftTransferFlowDetail-KXM7DTOV.js.map +1 -0
- package/dist/GovernanceSettingsProposalConfig-4SLJTBLW.js +42 -0
- package/dist/GovernanceSettingsProposalConfig-4SLJTBLW.js.map +1 -0
- package/dist/GovernanceSettingsProposalFlowDetail-VFZPSCLR.js +165 -0
- package/dist/GovernanceSettingsProposalFlowDetail-VFZPSCLR.js.map +1 -0
- package/dist/GovernanceStakingStakeFlowDetail-ULLJ7FLM.js +123 -0
- package/dist/GovernanceStakingStakeFlowDetail-ULLJ7FLM.js.map +1 -0
- package/dist/GovernanceStakingStakeToGroupFlowDetail-CSPKJA3H.js +84 -0
- package/dist/GovernanceStakingStakeToGroupFlowDetail-CSPKJA3H.js.map +1 -0
- package/dist/GovernanceSubmissionConfigProposalFlowDetail-MZCPSZF2.js +111 -0
- package/dist/GovernanceSubmissionConfigProposalFlowDetail-MZCPSZF2.js.map +1 -0
- package/dist/GovernanceTransactionMintFlowDetail-Y2WRAODH.js +66 -0
- package/dist/GovernanceTransactionMintFlowDetail-Y2WRAODH.js.map +1 -0
- package/dist/GovernanceTransactionPerformTokenSwapFlowDetail-GEK6PMMI.js +133 -0
- package/dist/GovernanceTransactionPerformTokenSwapFlowDetail-GEK6PMMI.js.map +1 -0
- package/dist/GovernanceTransactionSendFundsConfig-VBKMLSF3.js +42 -0
- package/dist/GovernanceTransactionSendFundsConfig-VBKMLSF3.js.map +1 -0
- package/dist/GovernanceTransactionSendFundsFlowDetail-5TZB64VL.js +182 -0
- package/dist/GovernanceTransactionSendFundsFlowDetail-5TZB64VL.js.map +1 -0
- package/dist/GovernanceTransactionSendGroupTokenFlowDetail-EGOWVZM2.js +155 -0
- package/dist/GovernanceTransactionSendGroupTokenFlowDetail-EGOWVZM2.js.map +1 -0
- package/dist/GovernanceTransactionWithdrawTokenSwapFlowDetail-6XWVO5O6.js +56 -0
- package/dist/GovernanceTransactionWithdrawTokenSwapFlowDetail-6XWVO5O6.js.map +1 -0
- package/dist/GovernanceValidatorActionsFlowDetail-ZMLPAFUB.js +117 -0
- package/dist/GovernanceValidatorActionsFlowDetail-ZMLPAFUB.js.map +1 -0
- package/dist/HarvestConfig-AIGOGBFR.js +56 -0
- package/dist/HarvestConfig-AIGOGBFR.js.map +1 -0
- package/dist/HarvestFlowDetail-X3RUH3X3.js +131 -0
- package/dist/HarvestFlowDetail-X3RUH3X3.js.map +1 -0
- package/dist/HttpRequestConfig-OQEJIIJY.js +194 -0
- package/dist/HttpRequestConfig-OQEJIIJY.js.map +1 -0
- package/dist/HttpRequestFlowDetail-JVLV533Z.js +116 -0
- package/dist/HttpRequestFlowDetail-JVLV533Z.js.map +1 -0
- package/dist/IdentityCreateConfig-ZBUOJ6AL.js +94 -0
- package/dist/IdentityCreateConfig-ZBUOJ6AL.js.map +1 -0
- package/dist/IdentityCreateFlowDetail-4TE6UCEB.js +83 -0
- package/dist/IdentityCreateFlowDetail-4TE6UCEB.js.map +1 -0
- package/dist/IidCreateConfig-RRV2DYXI.js +75 -0
- package/dist/IidCreateConfig-RRV2DYXI.js.map +1 -0
- package/dist/IidCreateFlowDetail-HPVZAHVY.js +57 -0
- package/dist/IidCreateFlowDetail-HPVZAHVY.js.map +1 -0
- package/dist/KycVerifyConfig-VCG65K5K.js +65 -0
- package/dist/KycVerifyConfig-VCG65K5K.js.map +1 -0
- package/dist/KycVerifyFlowDetail-FEU43HML.js +421 -0
- package/dist/KycVerifyFlowDetail-FEU43HML.js.map +1 -0
- package/dist/ListDomainFlowsConfig-3JZEPTOJ.js +37 -0
- package/dist/ListDomainFlowsConfig-3JZEPTOJ.js.map +1 -0
- package/dist/ListDomainFlowsFlowDetail-XX5AXDZZ.js +257 -0
- package/dist/ListDomainFlowsFlowDetail-XX5AXDZZ.js.map +1 -0
- package/dist/LoadBatchesConfig-IMODIWRT.js +38 -0
- package/dist/LoadBatchesConfig-IMODIWRT.js.map +1 -0
- package/dist/LoadBatchesFlowDetail-GB24T76O.js +101 -0
- package/dist/LoadBatchesFlowDetail-GB24T76O.js.map +1 -0
- package/dist/MatrixDmConfig-NBII27CS.js +120 -0
- package/dist/MatrixDmConfig-NBII27CS.js.map +1 -0
- package/dist/MatrixRegisterConfig-N2U4FBYF.js +62 -0
- package/dist/MatrixRegisterConfig-N2U4FBYF.js.map +1 -0
- package/dist/MatrixRegisterFlowDetail-D5YNL5QX.js +86 -0
- package/dist/MatrixRegisterFlowDetail-D5YNL5QX.js.map +1 -0
- package/dist/MemberMultiSelectConfig-E7WUB2WD.js +30 -0
- package/dist/MemberMultiSelectConfig-E7WUB2WD.js.map +1 -0
- package/dist/MemberMultiSelectFlowDetail-6C4ZMSDR.js +392 -0
- package/dist/MemberMultiSelectFlowDetail-6C4ZMSDR.js.map +1 -0
- package/dist/OracleConfig-R7TDTTKN.js +56 -0
- package/dist/OracleConfig-R7TDTTKN.js.map +1 -0
- package/dist/OracleConfigureOracleConfig-QFF74X3N.js +198 -0
- package/dist/OracleConfigureOracleConfig-QFF74X3N.js.map +1 -0
- package/dist/OracleConfigureOracleFlowDetail-MIBXUEKC.js +255 -0
- package/dist/OracleConfigureOracleFlowDetail-MIBXUEKC.js.map +1 -0
- package/dist/OracleContractConfig-PHY5L7W2.js +38 -0
- package/dist/OracleContractConfig-PHY5L7W2.js.map +1 -0
- package/dist/OracleContractFlowDetail-M3PM4AU4.js +78 -0
- package/dist/OracleContractFlowDetail-M3PM4AU4.js.map +1 -0
- package/dist/OracleDeployConfig-QBG76ATZ.js +10 -0
- package/dist/OracleDeployConfig-QBG76ATZ.js.map +1 -0
- package/dist/OracleDeployFlowDetail-WKTMD2AD.js +231 -0
- package/dist/OracleDeployFlowDetail-WKTMD2AD.js.map +1 -0
- package/dist/OracleDeploySetupConfig-75XPGFJQ.js +10 -0
- package/dist/OracleDeploySetupConfig-75XPGFJQ.js.map +1 -0
- package/dist/OracleDeploySetupFlowDetail-R3BQ7EAU.js +165 -0
- package/dist/OracleDeploySetupFlowDetail-R3BQ7EAU.js.map +1 -0
- package/dist/OracleDeployStartConfig-NA6GZ4VQ.js +10 -0
- package/dist/OracleDeployStartConfig-NA6GZ4VQ.js.map +1 -0
- package/dist/OracleDeployStartFlowDetail-EL3CMDMG.js +89 -0
- package/dist/OracleDeployStartFlowDetail-EL3CMDMG.js.map +1 -0
- package/dist/OracleFlowDetail-5JCJK3GH.js +76 -0
- package/dist/OracleFlowDetail-5JCJK3GH.js.map +1 -0
- package/dist/OraclePromptConfig-YBRESISH.js +39 -0
- package/dist/OraclePromptConfig-YBRESISH.js.map +1 -0
- package/dist/OraclePromptFlowDetail-O3XNK7HY.js +112 -0
- package/dist/OraclePromptFlowDetail-O3XNK7HY.js.map +1 -0
- package/dist/OracleStoreConfigConfig-TUZWTJMA.js +134 -0
- package/dist/OracleStoreConfigConfig-TUZWTJMA.js.map +1 -0
- package/dist/OracleStoreConfigFlowDetail-Q6AUUSUL.js +99 -0
- package/dist/OracleStoreConfigFlowDetail-Q6AUUSUL.js.map +1 -0
- package/dist/OracleStoreSecretsAndConfigConfig-SZPYEK7U.js +198 -0
- package/dist/OracleStoreSecretsAndConfigConfig-SZPYEK7U.js.map +1 -0
- package/dist/OracleStoreSecretsAndConfigFlowDetail-HQU3UKEB.js +220 -0
- package/dist/OracleStoreSecretsAndConfigFlowDetail-HQU3UKEB.js.map +1 -0
- package/dist/OracleStoreSecretsConfig-PQMF7LFT.js +191 -0
- package/dist/OracleStoreSecretsConfig-PQMF7LFT.js.map +1 -0
- package/dist/OracleStoreSecretsFlowDetail-5RXWQ3KA.js +197 -0
- package/dist/OracleStoreSecretsFlowDetail-5RXWQ3KA.js.map +1 -0
- package/dist/ProposalCreateConfig-U5IHZNT7.js +161 -0
- package/dist/ProposalCreateConfig-U5IHZNT7.js.map +1 -0
- package/dist/ProposalCreateFlowDetail-2FCRCNYV.js +235 -0
- package/dist/ProposalCreateFlowDetail-2FCRCNYV.js.map +1 -0
- package/dist/ProposalVoteConfig-MH4MU7SL.js +150 -0
- package/dist/ProposalVoteConfig-MH4MU7SL.js.map +1 -0
- package/dist/ProposalVoteFlowDetail-IJJ3OHAB.js +280 -0
- package/dist/ProposalVoteFlowDetail-IJJ3OHAB.js.map +1 -0
- package/dist/ProtocolSelectConfig-S5U3P6K7.js +48 -0
- package/dist/ProtocolSelectConfig-S5U3P6K7.js.map +1 -0
- package/dist/ProtocolSelectFlowDetail-XBSJIEIH.js +116 -0
- package/dist/ProtocolSelectFlowDetail-XBSJIEIH.js.map +1 -0
- package/dist/RetireConfig-MV37WWZ6.js +59 -0
- package/dist/RetireConfig-MV37WWZ6.js.map +1 -0
- package/dist/RetireFlowDetail-VZ5O4SXI.js +147 -0
- package/dist/RetireFlowDetail-VZ5O4SXI.js.map +1 -0
- package/dist/SandboxProvisionConfig-ZGG4IRHM.js +46 -0
- package/dist/SandboxProvisionConfig-ZGG4IRHM.js.map +1 -0
- package/dist/SandboxProvisionFlowDetail-RUY64INV.js +79 -0
- package/dist/SandboxProvisionFlowDetail-RUY64INV.js.map +1 -0
- package/dist/WalletFundConfig-CMM3EP3B.js +117 -0
- package/dist/WalletFundConfig-CMM3EP3B.js.map +1 -0
- package/dist/WalletFundFlowDetail-TXWCZA4K.js +279 -0
- package/dist/WalletFundFlowDetail-TXWCZA4K.js.map +1 -0
- package/dist/WalletGenerateAndFundConfig-364RSEND.js +10 -0
- package/dist/WalletGenerateAndFundConfig-364RSEND.js.map +1 -0
- package/dist/WalletGenerateAndFundFlowDetail-C2LZW3NO.js +54 -0
- package/dist/WalletGenerateAndFundFlowDetail-C2LZW3NO.js.map +1 -0
- package/dist/WalletGenerateConfig-YYLCS65G.js +10 -0
- package/dist/WalletGenerateConfig-YYLCS65G.js.map +1 -0
- package/dist/WalletGenerateFlowDetail-CQCNDGJA.js +53 -0
- package/dist/WalletGenerateFlowDetail-CQCNDGJA.js.map +1 -0
- package/dist/XeroContactCreateConfig-OWG5PA3B.js +102 -0
- package/dist/XeroContactCreateConfig-OWG5PA3B.js.map +1 -0
- package/dist/XeroContactCreateFlowDetail-6MAINCF6.js +127 -0
- package/dist/XeroContactCreateFlowDetail-6MAINCF6.js.map +1 -0
- package/dist/XeroInvoiceCreateConfig-O74RUSZM.js +403 -0
- package/dist/XeroInvoiceCreateConfig-O74RUSZM.js.map +1 -0
- package/dist/XeroInvoiceCreateFlowDetail-UL7KS6CZ.js +631 -0
- package/dist/XeroInvoiceCreateFlowDetail-UL7KS6CZ.js.map +1 -0
- package/dist/XeroInvoiceListConfig-UR3GONBJ.js +113 -0
- package/dist/XeroInvoiceListConfig-UR3GONBJ.js.map +1 -0
- package/dist/XeroInvoiceListFlowDetail-LYHODWH6.js +58 -0
- package/dist/XeroInvoiceListFlowDetail-LYHODWH6.js.map +1 -0
- package/dist/XeroPaymentCreateConfig-RPMAIK6E.js +116 -0
- package/dist/XeroPaymentCreateConfig-RPMAIK6E.js.map +1 -0
- package/dist/XeroPaymentCreateFlowDetail-CF35MVIM.js +573 -0
- package/dist/XeroPaymentCreateFlowDetail-CF35MVIM.js.map +1 -0
- package/dist/action-manifest.json +2091 -393
- package/dist/chunk-2GAYKKTL.js +9 -0
- package/dist/chunk-2GAYKKTL.js.map +1 -0
- package/dist/chunk-3TUYY4HK.js +20 -0
- package/dist/chunk-3TUYY4HK.js.map +1 -0
- package/dist/chunk-4DGCCXD6.js +710 -0
- package/dist/chunk-4DGCCXD6.js.map +1 -0
- package/dist/chunk-4NAFQF6V.js +11 -0
- package/dist/chunk-4NAFQF6V.js.map +1 -0
- package/dist/chunk-4PONO4US.js +799 -0
- package/dist/chunk-4PONO4US.js.map +1 -0
- package/dist/chunk-4TMOIK5M.js +21 -0
- package/dist/chunk-4TMOIK5M.js.map +1 -0
- package/dist/chunk-5E6Z6JJN.js +4456 -0
- package/dist/chunk-5E6Z6JJN.js.map +1 -0
- package/dist/chunk-5IH3VC5I.js +102 -0
- package/dist/chunk-5IH3VC5I.js.map +1 -0
- package/dist/chunk-5NTKDOP6.js +89 -0
- package/dist/chunk-5NTKDOP6.js.map +1 -0
- package/dist/chunk-5O2SFRGU.js +18 -0
- package/dist/chunk-5O2SFRGU.js.map +1 -0
- package/dist/chunk-632JLECF.js +55 -0
- package/dist/chunk-632JLECF.js.map +1 -0
- package/dist/chunk-676HDB7K.js +95 -0
- package/dist/chunk-676HDB7K.js.map +1 -0
- package/dist/chunk-6ESTNTSH.js +223 -0
- package/dist/chunk-6ESTNTSH.js.map +1 -0
- package/dist/chunk-6GKJR52N.js +15 -0
- package/dist/chunk-6GKJR52N.js.map +1 -0
- package/dist/chunk-6L4ZIKYU.js +23 -0
- package/dist/chunk-6L4ZIKYU.js.map +1 -0
- package/dist/chunk-76YX5R4L.js +11 -0
- package/dist/chunk-76YX5R4L.js.map +1 -0
- package/dist/chunk-7J3GFMN3.js +32 -0
- package/dist/chunk-7J3GFMN3.js.map +1 -0
- package/dist/chunk-7LSARC2G.js +102 -0
- package/dist/chunk-7LSARC2G.js.map +1 -0
- package/dist/chunk-7W7UUUIE.js +609 -0
- package/dist/chunk-7W7UUUIE.js.map +1 -0
- package/dist/chunk-7ZLW7H37.js +21 -0
- package/dist/chunk-7ZLW7H37.js.map +1 -0
- package/dist/chunk-AAZHI22G.js +18 -0
- package/dist/chunk-AAZHI22G.js.map +1 -0
- package/dist/chunk-ACNE455I.js +76 -0
- package/dist/chunk-ACNE455I.js.map +1 -0
- package/dist/chunk-BKLSSLPZ.js +27 -0
- package/dist/chunk-BKLSSLPZ.js.map +1 -0
- package/dist/chunk-BZ7UPUTY.js +417 -0
- package/dist/chunk-BZ7UPUTY.js.map +1 -0
- package/dist/chunk-CGFAICUE.js +28 -0
- package/dist/chunk-CGFAICUE.js.map +1 -0
- package/dist/chunk-CMCEWQ3C.js +19 -0
- package/dist/chunk-CMCEWQ3C.js.map +1 -0
- package/dist/chunk-CP4UV436.js +41 -0
- package/dist/chunk-CP4UV436.js.map +1 -0
- package/dist/chunk-DIQRZP7Z.js +51 -0
- package/dist/chunk-DIQRZP7Z.js.map +1 -0
- package/dist/chunk-EBFFDDAS.js +13 -0
- package/dist/chunk-EBFFDDAS.js.map +1 -0
- package/dist/chunk-EGVS23DR.js +85 -0
- package/dist/chunk-EGVS23DR.js.map +1 -0
- package/dist/chunk-EKBQ7K4N.js +15 -0
- package/dist/chunk-EKBQ7K4N.js.map +1 -0
- package/dist/chunk-EKQCZRAN.js +24 -0
- package/dist/chunk-EKQCZRAN.js.map +1 -0
- package/dist/chunk-EYKZIZ5J.js +48 -0
- package/dist/chunk-EYKZIZ5J.js.map +1 -0
- package/dist/{chunk-NEOPTPDF.js → chunk-F4PTFXZH.js} +11554 -22046
- package/dist/chunk-F4PTFXZH.js.map +1 -0
- package/dist/chunk-F5GTFCYV.js +3333 -0
- package/dist/chunk-F5GTFCYV.js.map +1 -0
- package/dist/chunk-FFYZXA75.js +58 -0
- package/dist/chunk-FFYZXA75.js.map +1 -0
- package/dist/chunk-GFGDHPZ2.js +22 -0
- package/dist/chunk-GFGDHPZ2.js.map +1 -0
- package/dist/chunk-GR3CDFID.js +18 -0
- package/dist/chunk-GR3CDFID.js.map +1 -0
- package/dist/chunk-GR4SIU32.js +104 -0
- package/dist/chunk-GR4SIU32.js.map +1 -0
- package/dist/chunk-H5E46IIS.js +18 -0
- package/dist/chunk-H5E46IIS.js.map +1 -0
- package/dist/chunk-HFHVAMZL.js +22 -0
- package/dist/chunk-HFHVAMZL.js.map +1 -0
- package/dist/chunk-HT6L7A5M.js +12 -0
- package/dist/chunk-HT6L7A5M.js.map +1 -0
- package/dist/chunk-I2FPFISM.js +301 -0
- package/dist/chunk-I2FPFISM.js.map +1 -0
- package/dist/chunk-IADAQJDH.js +28 -0
- package/dist/chunk-IADAQJDH.js.map +1 -0
- package/dist/chunk-IBOBCMQQ.js +99 -0
- package/dist/chunk-IBOBCMQQ.js.map +1 -0
- package/dist/chunk-IFWKRCFF.js +56 -0
- package/dist/chunk-IFWKRCFF.js.map +1 -0
- package/dist/chunk-IH4T7V4C.js +40 -0
- package/dist/chunk-IH4T7V4C.js.map +1 -0
- package/dist/chunk-IIIZZ4E4.js +1679 -0
- package/dist/chunk-IIIZZ4E4.js.map +1 -0
- package/dist/chunk-ILSFU3WD.js +83 -0
- package/dist/chunk-ILSFU3WD.js.map +1 -0
- package/dist/chunk-IX3VBV3L.js +43 -0
- package/dist/chunk-IX3VBV3L.js.map +1 -0
- package/dist/chunk-JB6ASLUJ.js +315 -0
- package/dist/chunk-JB6ASLUJ.js.map +1 -0
- package/dist/chunk-JBBFSORW.js +846 -0
- package/dist/chunk-JBBFSORW.js.map +1 -0
- package/dist/chunk-JIDTNKKL.js +1183 -0
- package/dist/chunk-JIDTNKKL.js.map +1 -0
- package/dist/chunk-JSPE6NGZ.js +22 -0
- package/dist/chunk-JSPE6NGZ.js.map +1 -0
- package/dist/chunk-JVROGQ7Q.js +150 -0
- package/dist/chunk-JVROGQ7Q.js.map +1 -0
- package/dist/chunk-K4WUMWNJ.js +32 -0
- package/dist/chunk-K4WUMWNJ.js.map +1 -0
- package/dist/chunk-K74XQ4Q5.js +11 -0
- package/dist/chunk-K74XQ4Q5.js.map +1 -0
- package/dist/chunk-KLLMJ4U3.js +22 -0
- package/dist/chunk-KLLMJ4U3.js.map +1 -0
- package/dist/chunk-LTKVCJ2E.js +27 -0
- package/dist/chunk-LTKVCJ2E.js.map +1 -0
- package/dist/chunk-M36DJTFZ.js +34 -0
- package/dist/chunk-M36DJTFZ.js.map +1 -0
- package/dist/chunk-MHM52LCJ.js +154 -0
- package/dist/chunk-MHM52LCJ.js.map +1 -0
- package/dist/chunk-MQCDMHRZ.js +59 -0
- package/dist/chunk-MQCDMHRZ.js.map +1 -0
- package/dist/chunk-MZIBZ5FY.js +554 -0
- package/dist/chunk-MZIBZ5FY.js.map +1 -0
- package/dist/chunk-N3ORMGAI.js +348 -0
- package/dist/chunk-N3ORMGAI.js.map +1 -0
- package/dist/chunk-NCSGIRRQ.js +28 -0
- package/dist/chunk-NCSGIRRQ.js.map +1 -0
- package/dist/chunk-NIXT2SBH.js +1423 -0
- package/dist/chunk-NIXT2SBH.js.map +1 -0
- package/dist/chunk-NJAII6CO.js +148 -0
- package/dist/chunk-NJAII6CO.js.map +1 -0
- package/dist/chunk-NKE4TL43.js +92 -0
- package/dist/chunk-NKE4TL43.js.map +1 -0
- package/dist/chunk-NZ7AXA7B.js +1 -0
- package/dist/chunk-NZ7AXA7B.js.map +1 -0
- package/dist/chunk-OCAYEGI2.js +26 -0
- package/dist/chunk-OCAYEGI2.js.map +1 -0
- package/dist/chunk-OMLI45ER.js +90 -0
- package/dist/chunk-OMLI45ER.js.map +1 -0
- package/dist/chunk-P7OBLJAG.js +88 -0
- package/dist/chunk-P7OBLJAG.js.map +1 -0
- package/dist/chunk-PAY3CBWL.js +2321 -0
- package/dist/chunk-PAY3CBWL.js.map +1 -0
- package/dist/chunk-PFF6VY35.js +257 -0
- package/dist/chunk-PFF6VY35.js.map +1 -0
- package/dist/chunk-PYNDJDWZ.js +1758 -0
- package/dist/chunk-PYNDJDWZ.js.map +1 -0
- package/dist/chunk-QDQ6UMMN.js +53 -0
- package/dist/chunk-QDQ6UMMN.js.map +1 -0
- package/dist/chunk-QGT3G2XG.js +24 -0
- package/dist/chunk-QGT3G2XG.js.map +1 -0
- package/dist/chunk-QZYCJ2GZ.js +55 -0
- package/dist/chunk-QZYCJ2GZ.js.map +1 -0
- package/dist/chunk-REZ7RFSA.js +124 -0
- package/dist/chunk-REZ7RFSA.js.map +1 -0
- package/dist/chunk-RRYBAE6E.js +2449 -0
- package/dist/chunk-RRYBAE6E.js.map +1 -0
- package/dist/chunk-RWUZOB62.js +34 -0
- package/dist/chunk-RWUZOB62.js.map +1 -0
- package/dist/chunk-SB5UCPZH.js +116 -0
- package/dist/chunk-SB5UCPZH.js.map +1 -0
- package/dist/chunk-SEJJ7CRI.js +99 -0
- package/dist/chunk-SEJJ7CRI.js.map +1 -0
- package/dist/chunk-SHBFGRPV.js +45 -0
- package/dist/chunk-SHBFGRPV.js.map +1 -0
- package/dist/chunk-SOFJ4GC5.js +45 -0
- package/dist/chunk-SOFJ4GC5.js.map +1 -0
- package/dist/chunk-STH2HPZK.js +44 -0
- package/dist/chunk-STH2HPZK.js.map +1 -0
- package/dist/chunk-TK3JNKME.js +28 -0
- package/dist/chunk-TK3JNKME.js.map +1 -0
- package/dist/chunk-TMONHFE3.js +46 -0
- package/dist/chunk-TMONHFE3.js.map +1 -0
- package/dist/chunk-TR4KO3AG.js +44 -0
- package/dist/chunk-TR4KO3AG.js.map +1 -0
- package/dist/chunk-TS2LNBF2.js +68 -0
- package/dist/chunk-TS2LNBF2.js.map +1 -0
- package/dist/chunk-UZUMBFPP.js +26 -0
- package/dist/chunk-UZUMBFPP.js.map +1 -0
- package/dist/chunk-VRZ5LMOW.js +176 -0
- package/dist/chunk-VRZ5LMOW.js.map +1 -0
- package/dist/chunk-WLGGY6Z6.js +75 -0
- package/dist/chunk-WLGGY6Z6.js.map +1 -0
- package/dist/chunk-WO7V6ZNC.js +11 -0
- package/dist/chunk-WO7V6ZNC.js.map +1 -0
- package/dist/chunk-WXSENSRP.js +64 -0
- package/dist/chunk-WXSENSRP.js.map +1 -0
- package/dist/chunk-XEKBZ2SA.js +19 -0
- package/dist/chunk-XEKBZ2SA.js.map +1 -0
- package/dist/chunk-XPBKU3JX.js +61 -0
- package/dist/chunk-XPBKU3JX.js.map +1 -0
- package/dist/chunk-XRYXBXWH.js +24 -0
- package/dist/chunk-XRYXBXWH.js.map +1 -0
- package/dist/chunk-XVA7QRVH.js +22 -0
- package/dist/chunk-XVA7QRVH.js.map +1 -0
- package/dist/chunk-XYXDY72K.js +27508 -0
- package/dist/chunk-XYXDY72K.js.map +1 -0
- package/dist/chunk-YCTHHBSS.js +37 -0
- package/dist/chunk-YCTHHBSS.js.map +1 -0
- package/dist/chunk-YHPWD5NE.js +70 -0
- package/dist/chunk-YHPWD5NE.js.map +1 -0
- package/dist/chunk-YLNEKJQ5.js +126 -0
- package/dist/chunk-YLNEKJQ5.js.map +1 -0
- package/dist/chunk-YN4CEKCF.js +73 -0
- package/dist/chunk-YN4CEKCF.js.map +1 -0
- package/dist/chunk-YYNMESIF.js +17 -0
- package/dist/chunk-YYNMESIF.js.map +1 -0
- package/dist/chunk-Z6PRMCV6.js +1 -0
- package/dist/chunk-Z6PRMCV6.js.map +1 -0
- package/dist/chunk-ZDNW6VQG.js +11 -0
- package/dist/chunk-ZDNW6VQG.js.map +1 -0
- package/dist/chunk-ZP4D73DO.js +36 -0
- package/dist/chunk-ZP4D73DO.js.map +1 -0
- package/dist/chunk-ZUATADUV.js +26 -0
- package/dist/chunk-ZUATADUV.js.map +1 -0
- package/dist/{connections-1NtNQrsY.d.ts → connections-CEcurtWI.d.ts} +74 -9
- package/dist/core/index.cjs +14041 -6973
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.ts +422 -138
- package/dist/core/index.js +425 -183
- package/dist/core/index.js.map +1 -1
- package/dist/flow-vnext/index.cjs +2842 -0
- package/dist/flow-vnext/index.cjs.map +1 -0
- package/dist/flow-vnext/index.d.ts +3 -0
- package/dist/flow-vnext/index.js +119 -0
- package/dist/flow-vnext/index.js.map +1 -0
- package/dist/{graphql-client-Bn0tN049.d.ts → graphql-client-B2tREsx6.d.ts} +3 -1
- package/dist/index-b9xxXGFs.d.ts +1230 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +202 -37
- package/dist/index.js.map +1 -1
- package/dist/legacy-BpgIqaQ5.d.ts +14 -0
- package/dist/mantine/index.d.ts +7 -4
- package/dist/mantine/index.js +84 -30
- package/dist/mantine/index.js.map +1 -1
- package/dist/{runtime-BFwvCQDF.d.ts → types-Dqk5MTfW.d.ts} +6096 -5386
- package/package.json +12 -2
- package/dist/chunk-A4CWC4AG.js +0 -374
- package/dist/chunk-A4CWC4AG.js.map +0 -1
- package/dist/chunk-NEOPTPDF.js.map +0 -1
- package/dist/chunk-VRCRDKN5.js +0 -59694
- package/dist/chunk-VRCRDKN5.js.map +0 -1
|
@@ -0,0 +1,4291 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatCapCoins,
|
|
3
|
+
parseEvalEngineInputs
|
|
4
|
+
} from "./chunk-NJAII6CO.js";
|
|
5
|
+
import {
|
|
6
|
+
billingNote,
|
|
7
|
+
ownerPaysLine,
|
|
8
|
+
usdLabel,
|
|
9
|
+
useEnginePricing
|
|
10
|
+
} from "./chunk-ILSFU3WD.js";
|
|
11
|
+
import {
|
|
12
|
+
CollapsibleSection
|
|
13
|
+
} from "./chunk-CGFAICUE.js";
|
|
14
|
+
import {
|
|
15
|
+
getCollectionDisplayName
|
|
16
|
+
} from "./chunk-EKBQ7K4N.js";
|
|
17
|
+
import {
|
|
18
|
+
UserSearch
|
|
19
|
+
} from "./chunk-GR4SIU32.js";
|
|
20
|
+
import {
|
|
21
|
+
BaseAlert,
|
|
22
|
+
BaseMultiSelect
|
|
23
|
+
} from "./chunk-7ZLW7H37.js";
|
|
24
|
+
import {
|
|
25
|
+
icon
|
|
26
|
+
} from "./chunk-YN4CEKCF.js";
|
|
27
|
+
import {
|
|
28
|
+
BaseNumberInput
|
|
29
|
+
} from "./chunk-76YX5R4L.js";
|
|
30
|
+
import {
|
|
31
|
+
BasePrimaryButton
|
|
32
|
+
} from "./chunk-K74XQ4Q5.js";
|
|
33
|
+
import {
|
|
34
|
+
BaseSelect
|
|
35
|
+
} from "./chunk-4NAFQF6V.js";
|
|
36
|
+
import "./chunk-NZ7AXA7B.js";
|
|
37
|
+
import "./chunk-XVA7QRVH.js";
|
|
38
|
+
import {
|
|
39
|
+
FORM_SEGMENT,
|
|
40
|
+
SCHEMA_UNKNOWN_KEY_PREFIX,
|
|
41
|
+
buildRubricEnvelope,
|
|
42
|
+
extractRubricFieldCatalog,
|
|
43
|
+
operatorsForKind,
|
|
44
|
+
parseEvalRubricInputs,
|
|
45
|
+
parsePublishedRubric,
|
|
46
|
+
serializeEvalRubricInputs,
|
|
47
|
+
validateRubric,
|
|
48
|
+
validateRubricShape
|
|
49
|
+
} from "./chunk-NIXT2SBH.js";
|
|
50
|
+
import {
|
|
51
|
+
resolveReferences
|
|
52
|
+
} from "./chunk-JVROGQ7Q.js";
|
|
53
|
+
import "./chunk-HT6L7A5M.js";
|
|
54
|
+
import {
|
|
55
|
+
BaseTextArea
|
|
56
|
+
} from "./chunk-ZDNW6VQG.js";
|
|
57
|
+
import {
|
|
58
|
+
actionAlertStyles
|
|
59
|
+
} from "./chunk-6GKJR52N.js";
|
|
60
|
+
import {
|
|
61
|
+
BaseTextInput
|
|
62
|
+
} from "./chunk-WO7V6ZNC.js";
|
|
63
|
+
import {
|
|
64
|
+
useBlocknoteHandlers
|
|
65
|
+
} from "./chunk-4DGCCXD6.js";
|
|
66
|
+
import "./chunk-7W7UUUIE.js";
|
|
67
|
+
import {
|
|
68
|
+
matrixUserIdToDid
|
|
69
|
+
} from "./chunk-TS2LNBF2.js";
|
|
70
|
+
|
|
71
|
+
// src/mantine/blocks/action/actionTypes/evalEngine/EvalEngineFlowDetail.tsx
|
|
72
|
+
import React14, { useCallback as useCallback2, useEffect as useEffect6, useMemo as useMemo7, useRef as useRef5, useState as useState9 } from "react";
|
|
73
|
+
import { ActionIcon as ActionIcon6, Alert as Alert6, Badge as Badge2, Box as Box5, Button as Button8, Group as Group13, Loader as Loader3, Paper as Paper10, Stack as Stack12, Text as Text12 } from "@mantine/core";
|
|
74
|
+
import { IconAlertCircle, IconAlertTriangle as IconAlertTriangle5, IconArrowRight, IconCheck as IconCheck2, IconInfoCircle, IconTrash as IconTrash4 } from "@tabler/icons-react";
|
|
75
|
+
|
|
76
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/AdvancedPanel.tsx
|
|
77
|
+
import React from "react";
|
|
78
|
+
import { Button, Code, CopyButton, Group, ScrollArea, Stack } from "@mantine/core";
|
|
79
|
+
var AdvancedPanel = ({ body }) => /* @__PURE__ */ React.createElement(CollapsibleSection, { title: "Your rules, as the machine reads them" }, /* @__PURE__ */ React.createElement(MachineView, { body }));
|
|
80
|
+
var MachineView = ({ body }) => {
|
|
81
|
+
const text = JSON.stringify(buildRubricEnvelope(body), null, 2);
|
|
82
|
+
return /* @__PURE__ */ React.createElement(Stack, { gap: "xs" }, /* @__PURE__ */ React.createElement(Group, { justify: "flex-end" }, /* @__PURE__ */ React.createElement(CopyButton, { value: text }, ({ copied, copy }) => /* @__PURE__ */ React.createElement(Button, { size: "compact-xs", variant: "subtle", onClick: copy }, copied ? "Copied" : "Copy"))), /* @__PURE__ */ React.createElement(ScrollArea.Autosize, { mah: 280 }, /* @__PURE__ */ React.createElement(Code, { block: true, style: { fontSize: 11 } }, text)));
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/ApprovalPolicy.tsx
|
|
86
|
+
import React3, { useEffect, useRef, useState } from "react";
|
|
87
|
+
import { Box, Button as Button2, Checkbox, Group as Group2, Paper, SegmentedControl, Stack as Stack2, Text } from "@mantine/core";
|
|
88
|
+
import { IconPin } from "@tabler/icons-react";
|
|
89
|
+
|
|
90
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/phrases.ts
|
|
91
|
+
import { IconBan, IconUserSearch } from "@tabler/icons-react";
|
|
92
|
+
|
|
93
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/ruleModel.ts
|
|
94
|
+
function indexCatalog(catalog) {
|
|
95
|
+
const index = /* @__PURE__ */ new Map();
|
|
96
|
+
const add = (field) => {
|
|
97
|
+
index.set(field.path, field);
|
|
98
|
+
field.columns?.forEach(add);
|
|
99
|
+
};
|
|
100
|
+
for (const field of catalog?.fields ?? []) add(field);
|
|
101
|
+
return index;
|
|
102
|
+
}
|
|
103
|
+
function rootPath(path, cards) {
|
|
104
|
+
if (cards) {
|
|
105
|
+
for (let prefix = path; ; ) {
|
|
106
|
+
if (cards.has(prefix)) return prefix;
|
|
107
|
+
const cut = Math.max(prefix.lastIndexOf("."), prefix.lastIndexOf("["));
|
|
108
|
+
if (cut <= 0) break;
|
|
109
|
+
prefix = prefix.slice(0, cut);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return FIELD_ROOT.exec(path)?.[0] ?? path;
|
|
113
|
+
}
|
|
114
|
+
var FIELD_ROOT = new RegExp(`^\\$${FORM_SEGMENT}`);
|
|
115
|
+
function targetableFields(field) {
|
|
116
|
+
return [field, ...field.columns ?? []];
|
|
117
|
+
}
|
|
118
|
+
var EXT_REF = /^ext\.([A-Za-z0-9_-]+)\.(valid|score|reason)$/;
|
|
119
|
+
function extFieldKind(fieldName) {
|
|
120
|
+
return fieldName === "score" ? "number" : fieldName === "valid" ? "boolean" : "text";
|
|
121
|
+
}
|
|
122
|
+
function extRefField(ref, sources) {
|
|
123
|
+
const match = EXT_REF.exec(ref);
|
|
124
|
+
if (!match) return void 0;
|
|
125
|
+
const source = sources?.find((entry) => entry.name === match[1]);
|
|
126
|
+
if (!source) return void 0;
|
|
127
|
+
const fieldName = match[2];
|
|
128
|
+
if (fieldName === "valid" && source.expect !== "boolean") return void 0;
|
|
129
|
+
if (fieldName === "score" && source.expect !== "score") return void 0;
|
|
130
|
+
const noun = fieldName === "valid" ? "passed?" : fieldName;
|
|
131
|
+
return { path: ref, kind: extFieldKind(fieldName), title: `${source.name} check \u2014 ${noun}`, isRequired: false };
|
|
132
|
+
}
|
|
133
|
+
function extRefQuestion(ref, body) {
|
|
134
|
+
const name = EXT_REF.exec(ref)?.[1];
|
|
135
|
+
const source = name ? body?.sources?.find((entry) => entry.name === name) : void 0;
|
|
136
|
+
if (!source) return void 0;
|
|
137
|
+
return Object.values(source.send).find((value) => typeof value === "string" && value.startsWith("$"));
|
|
138
|
+
}
|
|
139
|
+
var AI_REF = /^ai\.([A-Za-z0-9_-]+)\.(valid|reason)$/;
|
|
140
|
+
function aiRefField(ref, aiChecks) {
|
|
141
|
+
const match = AI_REF.exec(ref);
|
|
142
|
+
if (!match) return void 0;
|
|
143
|
+
const check = aiChecks?.find((entry) => entry.name === match[1]);
|
|
144
|
+
if (!check) return void 0;
|
|
145
|
+
const fieldName = match[2];
|
|
146
|
+
return { path: ref, kind: fieldName === "valid" ? "boolean" : "text", title: `${check.name} AI check \u2014 ${fieldName === "valid" ? "passed?" : "reason"}`, isRequired: false };
|
|
147
|
+
}
|
|
148
|
+
function aiRefQuestion(ref, body) {
|
|
149
|
+
const name = AI_REF.exec(ref)?.[1];
|
|
150
|
+
const check = name ? body?.aiChecks?.find((entry) => entry.name === name) : void 0;
|
|
151
|
+
if (!check) return void 0;
|
|
152
|
+
return Object.values(check.send).find((value) => typeof value === "string" && value.startsWith("$"));
|
|
153
|
+
}
|
|
154
|
+
var AI_VALID_REF = /^ai\.([A-Za-z0-9_-]+)\.valid$/;
|
|
155
|
+
function aiMemberName(member) {
|
|
156
|
+
return member.op === "isTrue" ? AI_VALID_REF.exec(member.field)?.[1] : void 0;
|
|
157
|
+
}
|
|
158
|
+
function aiSendFields(check) {
|
|
159
|
+
const values = Object.values(check.send ?? {});
|
|
160
|
+
if (values.length === 0) return void 0;
|
|
161
|
+
return values.every((value) => typeof value === "string" && value.startsWith("$")) ? values : void 0;
|
|
162
|
+
}
|
|
163
|
+
var DERIVED_VALUE_REF = /^~([A-Za-z0-9_-]+)$/;
|
|
164
|
+
var DERIVED_EXPR_REFS = /~([A-Za-z0-9_-]+)/g;
|
|
165
|
+
function pruneDanglingSources(body) {
|
|
166
|
+
const sources = body.sources ?? [];
|
|
167
|
+
const aiChecks = body.aiChecks ?? [];
|
|
168
|
+
const derived = body.derived ?? [];
|
|
169
|
+
if (sources.length === 0 && aiChecks.length === 0 && derived.length === 0) return body;
|
|
170
|
+
const used = /* @__PURE__ */ new Set();
|
|
171
|
+
const usedAi = /* @__PURE__ */ new Set();
|
|
172
|
+
const usedDerived = /* @__PURE__ */ new Set();
|
|
173
|
+
const note = (value) => {
|
|
174
|
+
if (typeof value === "string") {
|
|
175
|
+
const name = EXT_REF.exec(value)?.[1];
|
|
176
|
+
if (name) used.add(name);
|
|
177
|
+
const aiName = AI_REF.exec(value)?.[1];
|
|
178
|
+
if (aiName) usedAi.add(aiName);
|
|
179
|
+
const derivedName = DERIVED_VALUE_REF.exec(value)?.[1];
|
|
180
|
+
if (derivedName) usedDerived.add(derivedName);
|
|
181
|
+
} else if (Array.isArray(value)) {
|
|
182
|
+
value.forEach(note);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const walkCondition = (cond) => {
|
|
186
|
+
if (!cond || typeof cond !== "object") return;
|
|
187
|
+
if ("forEach" in cond) {
|
|
188
|
+
walkCondition(cond.every);
|
|
189
|
+
walkCondition(cond.some);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if ("all" in cond) {
|
|
193
|
+
cond.all.forEach(walkCondition);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if ("any" in cond) {
|
|
197
|
+
cond.any.forEach(walkCondition);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if ("not" in cond) {
|
|
201
|
+
walkCondition(cond.not);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if ("atLeast" in cond) {
|
|
205
|
+
cond.of.forEach(walkCondition);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if ("field" in cond) {
|
|
209
|
+
note(cond.field);
|
|
210
|
+
note(cond.value);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
const walkScore = (score) => {
|
|
214
|
+
if (!score) return;
|
|
215
|
+
switch (score["@type"]) {
|
|
216
|
+
case "BooleanScore":
|
|
217
|
+
walkCondition(score.when);
|
|
218
|
+
break;
|
|
219
|
+
case "BandsScore":
|
|
220
|
+
note(score.value);
|
|
221
|
+
(score.bands ?? []).forEach((band) => "op" in band && note(band.value));
|
|
222
|
+
break;
|
|
223
|
+
case "LinearScore":
|
|
224
|
+
note(score.value);
|
|
225
|
+
note(score.zeroAt);
|
|
226
|
+
note(score.fullAt);
|
|
227
|
+
break;
|
|
228
|
+
case "MapScore":
|
|
229
|
+
note(score.value);
|
|
230
|
+
break;
|
|
231
|
+
case "LevelsScore":
|
|
232
|
+
(score.levels ?? []).forEach((level) => "when" in level && walkCondition(level.when));
|
|
233
|
+
break;
|
|
234
|
+
case "DeductionsScore":
|
|
235
|
+
(score.deduct ?? []).forEach((deduction) => walkCondition(deduction.when));
|
|
236
|
+
break;
|
|
237
|
+
default:
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
for (const gate of body.gates ?? []) {
|
|
242
|
+
walkCondition(gate.when);
|
|
243
|
+
walkCondition(gate.appliesWhen);
|
|
244
|
+
}
|
|
245
|
+
for (const criterion of body.scoring?.criteria ?? []) {
|
|
246
|
+
walkCondition(criterion.appliesWhen);
|
|
247
|
+
walkScore(criterion.score);
|
|
248
|
+
}
|
|
249
|
+
note(body.settlement?.onPartial?.prorateBy);
|
|
250
|
+
const kept = sources.filter((source) => used.has(source.name));
|
|
251
|
+
const keptAi = aiChecks.filter((check) => usedAi.has(check.name));
|
|
252
|
+
for (const source of kept) Object.values(source.send ?? {}).forEach(note);
|
|
253
|
+
for (const check of keptAi) Object.values(check.send ?? {}).forEach(note);
|
|
254
|
+
let keptDerived = derived;
|
|
255
|
+
for (; ; ) {
|
|
256
|
+
const live = new Set(usedDerived);
|
|
257
|
+
for (const entry of keptDerived) {
|
|
258
|
+
for (const match of entry.expr.matchAll(DERIVED_EXPR_REFS)) live.add(match[1]);
|
|
259
|
+
}
|
|
260
|
+
const next = keptDerived.filter((entry) => live.has(entry.name));
|
|
261
|
+
if (next.length === keptDerived.length) break;
|
|
262
|
+
keptDerived = next;
|
|
263
|
+
}
|
|
264
|
+
if (kept.length === sources.length && keptAi.length === aiChecks.length && keptDerived.length === derived.length) return body;
|
|
265
|
+
return {
|
|
266
|
+
...body,
|
|
267
|
+
sources: kept.length > 0 ? kept : void 0,
|
|
268
|
+
aiChecks: keptAi.length > 0 ? keptAi : void 0,
|
|
269
|
+
derived: keptDerived.length > 0 ? keptDerived : void 0
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function toCondition(rule) {
|
|
273
|
+
const inner = {
|
|
274
|
+
"@type": "Condition",
|
|
275
|
+
field: rule.field,
|
|
276
|
+
op: rule.op,
|
|
277
|
+
...rule.value !== void 0 ? { value: rule.value } : {},
|
|
278
|
+
...rule.tolerance !== void 0 ? { tolerance: rule.tolerance } : {}
|
|
279
|
+
};
|
|
280
|
+
const rowIndex = rule.field.indexOf("[*]");
|
|
281
|
+
if (rowIndex < 0) return inner;
|
|
282
|
+
return { "@type": "Condition", forEach: rule.field.slice(0, rowIndex), every: { ...inner, field: rule.field.slice(rowIndex + 3) } };
|
|
283
|
+
}
|
|
284
|
+
function toSimpleRule(cond) {
|
|
285
|
+
if (!cond) return void 0;
|
|
286
|
+
if ("forEach" in cond && typeof cond.forEach === "string") {
|
|
287
|
+
const every = cond.every;
|
|
288
|
+
if (!every || !("field" in every) || typeof every.field !== "string" || !every.field.startsWith(".")) return void 0;
|
|
289
|
+
return { field: `${cond.forEach}[*]${every.field}`, op: every.op, value: every.value, tolerance: every.tolerance };
|
|
290
|
+
}
|
|
291
|
+
if ("field" in cond && typeof cond.field === "string") return { field: cond.field, op: cond.op, value: cond.value, tolerance: cond.tolerance };
|
|
292
|
+
return void 0;
|
|
293
|
+
}
|
|
294
|
+
function groupShapeOf(when, aiChecks) {
|
|
295
|
+
if (!when || typeof when !== "object") return void 0;
|
|
296
|
+
if ("not" in when) return { editable: false };
|
|
297
|
+
if ("any" in when || "atLeast" in when || "of" in when) return { editable: false };
|
|
298
|
+
if (!("all" in when)) return void 0;
|
|
299
|
+
const raw = when.all;
|
|
300
|
+
if (!Array.isArray(raw) || raw.length === 0) return { editable: false };
|
|
301
|
+
const members = [];
|
|
302
|
+
for (const member of raw) {
|
|
303
|
+
const simple = toSimpleRule(member);
|
|
304
|
+
if (!simple) return { editable: false };
|
|
305
|
+
const aiName = aiMemberName(simple);
|
|
306
|
+
if (aiName && aiChecks !== void 0) {
|
|
307
|
+
const check = aiChecks.find((entry) => entry.name === aiName);
|
|
308
|
+
if (!check || !aiSendFields(check)) return { editable: false };
|
|
309
|
+
}
|
|
310
|
+
members.push(simple);
|
|
311
|
+
}
|
|
312
|
+
return { editable: true, members };
|
|
313
|
+
}
|
|
314
|
+
function policyMemberRenderable(cond) {
|
|
315
|
+
if (!cond || typeof cond !== "object") return false;
|
|
316
|
+
if (toSimpleRule(cond)) return true;
|
|
317
|
+
if ("all" in cond && Array.isArray(cond.all) && cond.all.length > 0) return cond.all.every((member) => toSimpleRule(member) !== void 0);
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
function policyShapeOf(body) {
|
|
321
|
+
const gates = body?.gates ?? [];
|
|
322
|
+
const candidates = [];
|
|
323
|
+
gates.forEach((gate2, index2) => {
|
|
324
|
+
const when2 = gate2.when;
|
|
325
|
+
if (when2 && typeof when2 === "object" && ("any" in when2 || "atLeast" in when2 || "of" in when2)) candidates.push(index2);
|
|
326
|
+
});
|
|
327
|
+
if (candidates.length === 0) return { kind: "none" };
|
|
328
|
+
if (candidates.length > 1) return { kind: "foreign" };
|
|
329
|
+
const index = candidates[0];
|
|
330
|
+
const gate = gates[index];
|
|
331
|
+
const when = gate.when;
|
|
332
|
+
const members = Array.isArray(when.any) ? when.any : Array.isArray(when.of) ? when.of : void 0;
|
|
333
|
+
if (!members || members.length === 0) return { kind: "foreign" };
|
|
334
|
+
if (!members.every(policyMemberRenderable)) return { kind: "foreign" };
|
|
335
|
+
if (gate.appliesWhen && !toSimpleRule(gate.appliesWhen)) return { kind: "foreign" };
|
|
336
|
+
if (Array.isArray(when.any)) return { kind: "policy", index, gate, tab: "any", members };
|
|
337
|
+
const n = when.atLeast;
|
|
338
|
+
if (!Number.isInteger(n) || n < 1 || n > members.length) return { kind: "foreign" };
|
|
339
|
+
return { kind: "policy", index, gate, tab: "atLeast", nRest: n, members };
|
|
340
|
+
}
|
|
341
|
+
function valueKey(value) {
|
|
342
|
+
return JSON.stringify(value) ?? "undefined";
|
|
343
|
+
}
|
|
344
|
+
function listLinkedRules(body) {
|
|
345
|
+
const gates = body?.gates ?? [];
|
|
346
|
+
const aiChecksByName = new Map((body?.aiChecks ?? []).map((check) => [check.name, check]));
|
|
347
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
348
|
+
const push = (key, candidate) => {
|
|
349
|
+
const bucket = byKey.get(key);
|
|
350
|
+
if (bucket) bucket.push(candidate);
|
|
351
|
+
else byKey.set(key, [candidate]);
|
|
352
|
+
};
|
|
353
|
+
gates.forEach((gate, index) => {
|
|
354
|
+
if (groupShapeOf(gate.when)) return;
|
|
355
|
+
const when = toSimpleRule(gate.when);
|
|
356
|
+
const guard = toSimpleRule(gate.appliesWhen);
|
|
357
|
+
if (!when || !guard) return;
|
|
358
|
+
if (guard.op !== "in" && guard.op !== "notIn") return;
|
|
359
|
+
if (!Array.isArray(guard.value) || guard.value.length === 0) return;
|
|
360
|
+
if (guard.op === "in" && guard.value.length !== 1) return;
|
|
361
|
+
if (guard.field.includes("[*]")) return;
|
|
362
|
+
const aiName = aiMemberName(when);
|
|
363
|
+
if (aiName) {
|
|
364
|
+
const check = aiChecksByName.get(aiName);
|
|
365
|
+
if (!check) return;
|
|
366
|
+
const sendFields = aiSendFields(check);
|
|
367
|
+
if (!sendFields) return;
|
|
368
|
+
if (check.instructions.length !== 1 || typeof check.instructions[0] !== "string") return;
|
|
369
|
+
if (guard.field === sendFields[0]) return;
|
|
370
|
+
push(JSON.stringify(["ai", guard.field, sendFields]), { index, gate, when, guard, ai: { name: aiName, instructions: check.instructions[0], sendFields } });
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (guard.field === when.field) return;
|
|
374
|
+
push(JSON.stringify([when.field, when.op, guard.field]), { index, gate, when, guard });
|
|
375
|
+
});
|
|
376
|
+
const out = [];
|
|
377
|
+
for (const bucket of byKey.values()) {
|
|
378
|
+
const named = bucket.filter((entry) => entry.guard.op === "in");
|
|
379
|
+
const others = bucket.filter((entry) => entry.guard.op === "notIn");
|
|
380
|
+
if (named.length < 1 || others.length !== 1) continue;
|
|
381
|
+
const elseEntry = others[0];
|
|
382
|
+
const unionKeys = named.map((entry) => valueKey(entry.guard.value[0]));
|
|
383
|
+
if (new Set(unionKeys).size !== unionKeys.length) continue;
|
|
384
|
+
const elseKeys = elseEntry.guard.value.map(valueKey);
|
|
385
|
+
if (elseKeys.length !== unionKeys.length) continue;
|
|
386
|
+
if ([...unionKeys].sort().join("\0") !== [...elseKeys].sort().join("\0")) continue;
|
|
387
|
+
const onFail = bucket[0].gate.onFail ?? "reject";
|
|
388
|
+
const gateClass = bucket[0].gate.class;
|
|
389
|
+
if (!bucket.every((entry) => (entry.gate.onFail ?? "reject") === onFail && entry.gate.class === gateClass)) continue;
|
|
390
|
+
const ai = bucket[0].ai;
|
|
391
|
+
if (ai && new Set(bucket.map((entry) => entry.ai?.name)).size !== bucket.length) continue;
|
|
392
|
+
const caseExtras = (entry) => entry.ai ? { aiName: entry.ai.name, instructions: entry.ai.instructions } : {};
|
|
393
|
+
out.push({
|
|
394
|
+
checkedField: ai ? ai.sendFields[0] : bucket[0].when.field,
|
|
395
|
+
op: bucket[0].when.op,
|
|
396
|
+
guardField: bucket[0].guard.field,
|
|
397
|
+
cases: named.map((entry) => ({
|
|
398
|
+
code: entry.gate.code,
|
|
399
|
+
index: entry.index,
|
|
400
|
+
gate: entry.gate,
|
|
401
|
+
guardValue: entry.guard.value[0],
|
|
402
|
+
when: entry.when,
|
|
403
|
+
...caseExtras(entry)
|
|
404
|
+
})),
|
|
405
|
+
elseCase: { code: elseEntry.gate.code, index: elseEntry.index, gate: elseEntry.gate, when: elseEntry.when, ...caseExtras(elseEntry) },
|
|
406
|
+
onFail,
|
|
407
|
+
gateClass,
|
|
408
|
+
indices: bucket.map((entry) => entry.index).sort((a, b) => a - b),
|
|
409
|
+
...ai ? { ai: { alsoSend: ai.sendFields.slice(1) } } : {}
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
return out.sort((a, b) => a.indices[0] - b.indices[0]);
|
|
413
|
+
}
|
|
414
|
+
function removeGatesAt(body, indices) {
|
|
415
|
+
const drop = new Set(indices);
|
|
416
|
+
return { ...body, gates: (body.gates ?? []).filter((_, i) => !drop.has(i)) };
|
|
417
|
+
}
|
|
418
|
+
var SCORE_SUBJECT = /^ext\.([A-Za-z0-9_-]+)\.score$/;
|
|
419
|
+
function scoreGateOf(gate) {
|
|
420
|
+
const simple = toSimpleRule(gate.when);
|
|
421
|
+
if (!simple || simple.op !== ">=") return void 0;
|
|
422
|
+
const name = SCORE_SUBJECT.exec(simple.field)?.[1];
|
|
423
|
+
if (!name) return void 0;
|
|
424
|
+
const value = simple.value;
|
|
425
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > 1e4) return void 0;
|
|
426
|
+
return { name, value };
|
|
427
|
+
}
|
|
428
|
+
function scoreBandsByHold(gates, skip, claimedFloors) {
|
|
429
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
430
|
+
gates.forEach((gate, index) => {
|
|
431
|
+
if (skip.has(index)) return;
|
|
432
|
+
const score = scoreGateOf(gate);
|
|
433
|
+
if (!score) return;
|
|
434
|
+
const entry = bySource.get(score.name) ?? { holds: [], floors: [] };
|
|
435
|
+
((gate.onFail ?? "reject") === "review" ? entry.holds : entry.floors).push(index);
|
|
436
|
+
bySource.set(score.name, entry);
|
|
437
|
+
});
|
|
438
|
+
const out = /* @__PURE__ */ new Map();
|
|
439
|
+
for (const { holds, floors } of bySource.values()) {
|
|
440
|
+
if (holds.length !== 1) continue;
|
|
441
|
+
const hold = holds[0];
|
|
442
|
+
const approveAt = scoreGateOf(gates[hold]).value;
|
|
443
|
+
const lone = { approveAt, rejectBelow: 0, indices: [hold] };
|
|
444
|
+
if (floors.length === 0) {
|
|
445
|
+
out.set(hold, lone);
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (floors.length > 1) continue;
|
|
449
|
+
const floorIndex = floors[0];
|
|
450
|
+
const floorGate = gates[floorIndex];
|
|
451
|
+
const rejectBelow = scoreGateOf(floorGate).value;
|
|
452
|
+
const sameGuard = JSON.stringify(gates[hold].appliesWhen ?? null) === JSON.stringify(floorGate.appliesWhen ?? null);
|
|
453
|
+
if (floorIndex > hold || rejectBelow >= approveAt || !sameGuard) {
|
|
454
|
+
out.set(hold, lone);
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
claimedFloors.add(floorIndex);
|
|
458
|
+
out.set(hold, { approveAt, rejectBelow, floor: { index: floorIndex, code: floorGate.code, gate: floorGate }, indices: [floorIndex, hold] });
|
|
459
|
+
}
|
|
460
|
+
return out;
|
|
461
|
+
}
|
|
462
|
+
function primaryFieldOf(cond) {
|
|
463
|
+
if (!cond || typeof cond !== "object") return "";
|
|
464
|
+
if ("forEach" in cond && typeof cond.forEach === "string") return cond.forEach;
|
|
465
|
+
if ("field" in cond && typeof cond.field === "string") return cond.field;
|
|
466
|
+
const members = "not" in cond ? [cond.not] : "all" in cond && Array.isArray(cond.all) ? cond.all : "any" in cond && Array.isArray(cond.any) ? cond.any : "of" in cond && Array.isArray(cond.of) ? cond.of : [];
|
|
467
|
+
for (const member of members) {
|
|
468
|
+
const found = primaryFieldOf(member);
|
|
469
|
+
if (found) return found;
|
|
470
|
+
}
|
|
471
|
+
return "";
|
|
472
|
+
}
|
|
473
|
+
function manualScoreField(score) {
|
|
474
|
+
return score?.["@type"] === "ManualScore" ? score.field : "";
|
|
475
|
+
}
|
|
476
|
+
var FIELD_IN_EXPR = new RegExp(`\\$${FORM_SEGMENT}(?:\\.${FORM_SEGMENT}|\\[\\*\\])*`);
|
|
477
|
+
function resolveRef(ref, body) {
|
|
478
|
+
if (ref.startsWith("ext.")) return extRefQuestion(ref, body) ?? ref;
|
|
479
|
+
if (ref.startsWith("ai.")) return aiRefQuestion(ref, body) ?? ref;
|
|
480
|
+
if (!ref.startsWith("~")) return ref;
|
|
481
|
+
const derived = body.derived?.find((entry) => entry.name === ref.slice(1));
|
|
482
|
+
return derived && FIELD_IN_EXPR.exec(derived.expr)?.[0] || ref;
|
|
483
|
+
}
|
|
484
|
+
function listRules(body) {
|
|
485
|
+
if (!body) return [];
|
|
486
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
487
|
+
for (const linked of listLinkedRules(body)) for (const index of linked.indices) claimed.add(index);
|
|
488
|
+
const policy = policyShapeOf(body);
|
|
489
|
+
if (policy.kind === "policy") claimed.add(policy.index);
|
|
490
|
+
const bandsByHold = scoreBandsByHold(body.gates ?? [], claimed, claimed);
|
|
491
|
+
const rules = [];
|
|
492
|
+
(body.gates ?? []).forEach((gate, index) => {
|
|
493
|
+
if (claimed.has(index)) return;
|
|
494
|
+
const shape = groupShapeOf(gate.when, body.aiChecks ?? []);
|
|
495
|
+
const grown = shape && shape.editable && gate.appliesWhen && !toSimpleRule(gate.appliesWhen) ? { editable: false } : shape;
|
|
496
|
+
const bands = bandsByHold.get(index);
|
|
497
|
+
rules.push({
|
|
498
|
+
consequence: "reject",
|
|
499
|
+
code: gate.code,
|
|
500
|
+
index,
|
|
501
|
+
fieldPath: resolveRef(primaryFieldOf(gate.when), body),
|
|
502
|
+
gate,
|
|
503
|
+
...grown ? { grown } : {},
|
|
504
|
+
...bands ? { bands } : {}
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
(body.scoring?.criteria ?? []).forEach((criterion, index) => {
|
|
508
|
+
if (criterion.score?.["@type"] !== "ManualScore") return;
|
|
509
|
+
const fieldPath = resolveRef(manualScoreField(criterion.score), body);
|
|
510
|
+
const task = (body.review?.tasks ?? []).find((t) => t.id === criterion.score.reviewTask);
|
|
511
|
+
rules.push({ consequence: "human", code: criterion.code, index, fieldPath, criterion, task });
|
|
512
|
+
});
|
|
513
|
+
return rules;
|
|
514
|
+
}
|
|
515
|
+
function rulesByCard(body, cards) {
|
|
516
|
+
const byCard = /* @__PURE__ */ new Map();
|
|
517
|
+
for (const rule of listRules(body)) {
|
|
518
|
+
const key = rule.fieldPath ? rootPath(rule.fieldPath, cards) : "";
|
|
519
|
+
const bucket = byCard.get(key);
|
|
520
|
+
if (bucket) bucket.push(rule);
|
|
521
|
+
else byCard.set(key, [rule]);
|
|
522
|
+
}
|
|
523
|
+
return byCard;
|
|
524
|
+
}
|
|
525
|
+
var CODE_SEQUENCE = /^[A-Z0-9]+-(\d+)-/;
|
|
526
|
+
function usedCodes(body) {
|
|
527
|
+
if (!body) return [];
|
|
528
|
+
const codes = [];
|
|
529
|
+
for (const gate of body.gates ?? []) codes.push(gate.code);
|
|
530
|
+
for (const criterion of body.scoring?.criteria ?? []) {
|
|
531
|
+
codes.push(criterion.code);
|
|
532
|
+
if (criterion.score?.["@type"] === "DeductionsScore") for (const deduction of criterion.score.deduct ?? []) codes.push(deduction.code);
|
|
533
|
+
}
|
|
534
|
+
for (const task of body.review?.tasks ?? []) codes.push(task.id);
|
|
535
|
+
for (const derived of body.derived ?? []) codes.push(derived.name);
|
|
536
|
+
return codes;
|
|
537
|
+
}
|
|
538
|
+
function codeToken(fieldPath) {
|
|
539
|
+
const token = fieldPath.replace(/^\$/, "").replace(/\[\*\]/g, "").replace(/[^A-Za-z0-9]+/g, "-").replace(/^-+|-+$/g, "").toUpperCase();
|
|
540
|
+
return token.slice(0, 24) || "ANSWER";
|
|
541
|
+
}
|
|
542
|
+
function nextCode(body, prefix, fieldPath, alsoUsed = []) {
|
|
543
|
+
const reserved = [...usedCodes(body), ...alsoUsed];
|
|
544
|
+
const taken = new Set(reserved);
|
|
545
|
+
const highest = reserved.reduce((max, code) => {
|
|
546
|
+
const seq = CODE_SEQUENCE.exec(code)?.[1];
|
|
547
|
+
return seq ? Math.max(max, Number(seq)) : max;
|
|
548
|
+
}, 100);
|
|
549
|
+
const token = codeToken(fieldPath);
|
|
550
|
+
for (let seq = highest + 1; ; seq++) {
|
|
551
|
+
const code = `${prefix}-${seq}-${token}`;
|
|
552
|
+
if (!taken.has(code)) return code;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
var APPROVAL_METHOD = "all_pass";
|
|
556
|
+
var APPROVE_AT = 8500;
|
|
557
|
+
function emptyRubric(protocolDid, proof, title, resource = "#vct") {
|
|
558
|
+
return {
|
|
559
|
+
"@type": "Rubric",
|
|
560
|
+
title,
|
|
561
|
+
claimSchema: { "@type": "ClaimSchemaBinding", protocol: protocolDid, resource, proof },
|
|
562
|
+
gates: [],
|
|
563
|
+
scoring: { "@type": "Scoring", method: APPROVAL_METHOD, approveAt: APPROVE_AT, criteria: [] },
|
|
564
|
+
settlement: { "@type": "Settlement", onApprove: { pay: "approval" } }
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
function bindForm(current, protocol, resource, proof, title) {
|
|
568
|
+
if (current) {
|
|
569
|
+
return {
|
|
570
|
+
...current,
|
|
571
|
+
claimSchema: { ...current.claimSchema, protocol, resource, proof },
|
|
572
|
+
// Re-pin the approval rule. A document written before the method picker
|
|
573
|
+
// was removed still says `weighted_average`, which would make the screen
|
|
574
|
+
// ("when all N checks pass") lie about what the engine will actually do.
|
|
575
|
+
...current.scoring ? { scoring: { ...current.scoring, method: APPROVAL_METHOD } } : {}
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
return emptyRubric(protocol, proof, title, resource);
|
|
579
|
+
}
|
|
580
|
+
function putGate(body, gate, at) {
|
|
581
|
+
const gates = [...body.gates ?? []];
|
|
582
|
+
if (at === void 0) gates.push(gate);
|
|
583
|
+
else gates[at] = gate;
|
|
584
|
+
return { ...body, gates };
|
|
585
|
+
}
|
|
586
|
+
function putCriterion(body, criterion, at) {
|
|
587
|
+
const scoring = body.scoring ?? { "@type": "Scoring", method: APPROVAL_METHOD, approveAt: APPROVE_AT, criteria: [] };
|
|
588
|
+
const criteria = [...scoring.criteria ?? []];
|
|
589
|
+
if (at === void 0) criteria.push(criterion);
|
|
590
|
+
else criteria[at] = criterion;
|
|
591
|
+
return { ...body, scoring: { ...scoring, criteria } };
|
|
592
|
+
}
|
|
593
|
+
function putReviewTask(body, task) {
|
|
594
|
+
const review = body.review ?? {
|
|
595
|
+
"@type": "HumanReview",
|
|
596
|
+
assignTo: { role: "reviewer" },
|
|
597
|
+
slaHours: 48,
|
|
598
|
+
onTimeout: "reject",
|
|
599
|
+
tasks: []
|
|
600
|
+
};
|
|
601
|
+
const tasks = [...review.tasks ?? []];
|
|
602
|
+
const at = tasks.findIndex((t) => t.id === task.id);
|
|
603
|
+
if (at < 0) tasks.push(task);
|
|
604
|
+
else tasks[at] = task;
|
|
605
|
+
return { ...body, review: { ...review, tasks } };
|
|
606
|
+
}
|
|
607
|
+
function removeRule(body, rule) {
|
|
608
|
+
if (rule.consequence === "reject") {
|
|
609
|
+
const drop = new Set(rule.bands?.indices ?? [rule.index]);
|
|
610
|
+
return { ...body, gates: (body.gates ?? []).filter((_, i) => !drop.has(i)) };
|
|
611
|
+
}
|
|
612
|
+
const scoring = body.scoring;
|
|
613
|
+
const next = { ...body, ...scoring ? { scoring: { ...scoring, criteria: (scoring.criteria ?? []).filter((_, i) => i !== rule.index) } } : {} };
|
|
614
|
+
if (!rule.task) return next;
|
|
615
|
+
const tasks = (next.review?.tasks ?? []).filter((t) => t.id !== rule.task?.id);
|
|
616
|
+
if (tasks.length === 0) {
|
|
617
|
+
const { review: _dropped, ...withoutReview } = next;
|
|
618
|
+
return withoutReview;
|
|
619
|
+
}
|
|
620
|
+
return { ...next, review: { ...next.review, tasks } };
|
|
621
|
+
}
|
|
622
|
+
function ruleForDiagnosticPath(body, path) {
|
|
623
|
+
if (!body || !path) return void 0;
|
|
624
|
+
const rules = listRules(body);
|
|
625
|
+
const gate = /^\/gates\/(\d+)/.exec(path);
|
|
626
|
+
if (gate) {
|
|
627
|
+
const index = Number(gate[1]);
|
|
628
|
+
return rules.find((r) => r.consequence === "reject" && (r.index === index || r.bands?.indices.includes(index) === true));
|
|
629
|
+
}
|
|
630
|
+
const criterion = /^\/scoring\/criteria\/(\d+)/.exec(path);
|
|
631
|
+
if (criterion) return rules.find((r) => r.consequence !== "reject" && r.index === Number(criterion[1]));
|
|
632
|
+
const task = /^\/review\/tasks\/(\d+)/.exec(path);
|
|
633
|
+
if (task) {
|
|
634
|
+
const forCriterion = body.review?.tasks?.[Number(task[1])]?.forCriterion;
|
|
635
|
+
return rules.find((r) => r.code === forCriterion);
|
|
636
|
+
}
|
|
637
|
+
return void 0;
|
|
638
|
+
}
|
|
639
|
+
function cardForDiagnosticPath(body, path, cards) {
|
|
640
|
+
const rule = ruleForDiagnosticPath(body, path);
|
|
641
|
+
if (rule?.fieldPath) return rootPath(rule.fieldPath, cards);
|
|
642
|
+
const gate = /^\/gates\/(\d+)/.exec(path);
|
|
643
|
+
if (gate && body) {
|
|
644
|
+
const index = Number(gate[1]);
|
|
645
|
+
const linked = listLinkedRules(body).find((entry) => entry.indices.includes(index));
|
|
646
|
+
if (linked) return rootPath(linked.checkedField, cards);
|
|
647
|
+
}
|
|
648
|
+
return void 0;
|
|
649
|
+
}
|
|
650
|
+
var PASSED = 1e4;
|
|
651
|
+
var FAILED = 0;
|
|
652
|
+
|
|
653
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/phrases.ts
|
|
654
|
+
var CONSEQUENCES = [
|
|
655
|
+
{ value: "reject", Icon: IconBan, color: "var(--mantine-color-red-6)", label: "Reject the claim", hint: "The claim is turned down straight away." },
|
|
656
|
+
{ value: "human", Icon: IconUserSearch, color: "var(--mantine-color-grape-6)", label: "A person checks", hint: "Someone looks at it and answers a question." }
|
|
657
|
+
];
|
|
658
|
+
function consequenceIcon(consequence, size = 18) {
|
|
659
|
+
const found = CONSEQUENCES.find((c) => c.value === consequence);
|
|
660
|
+
return found ? icon(found.Icon, size, found.color) : null;
|
|
661
|
+
}
|
|
662
|
+
var KIND_LABEL = {
|
|
663
|
+
text: "writing",
|
|
664
|
+
number: "number",
|
|
665
|
+
date: "date",
|
|
666
|
+
choice: "choice",
|
|
667
|
+
multichoice: "tick list",
|
|
668
|
+
boolean: "yes / no",
|
|
669
|
+
file: "file",
|
|
670
|
+
signature: "signature",
|
|
671
|
+
keyed_choice: "grid",
|
|
672
|
+
cells: "table",
|
|
673
|
+
rows: "table",
|
|
674
|
+
keyed_text: "group of answers",
|
|
675
|
+
geo: "place"
|
|
676
|
+
};
|
|
677
|
+
var OPERATOR_PHRASE = {
|
|
678
|
+
present: { label: "must be filled in" },
|
|
679
|
+
empty: { label: "must be empty" },
|
|
680
|
+
"==": { label: "must be exactly" },
|
|
681
|
+
"!=": { label: "must not be" },
|
|
682
|
+
">": { label: "must be more than" },
|
|
683
|
+
">=": { label: "must be at least" },
|
|
684
|
+
"<": { label: "must be less than" },
|
|
685
|
+
"<=": { label: "must be at most" },
|
|
686
|
+
between: { label: "must be between" },
|
|
687
|
+
approx: { label: "must be about" },
|
|
688
|
+
in: { label: "must be one of" },
|
|
689
|
+
notIn: { label: "must not be one of" },
|
|
690
|
+
matches: { label: "must look like" },
|
|
691
|
+
startsWith: { label: "must start with" },
|
|
692
|
+
endsWith: { label: "must end with" },
|
|
693
|
+
lengthBetween: { label: "must be between", suffix: "characters long" },
|
|
694
|
+
contains: { label: "must include" },
|
|
695
|
+
containsAll: { label: "must include all of" },
|
|
696
|
+
containsAny: { label: "must include any of" },
|
|
697
|
+
subsetOf: { label: "may only include" },
|
|
698
|
+
countBetween: { label: "must have between", suffix: "entries" },
|
|
699
|
+
isTrue: { label: "must be ticked" },
|
|
700
|
+
isFalse: { label: "must be left unticked" },
|
|
701
|
+
before: { label: "must be before" },
|
|
702
|
+
after: { label: "must be after" },
|
|
703
|
+
withinWindow: { label: "must be during the collection period" },
|
|
704
|
+
withinDays: { label: "must be within the last", suffix: "days" },
|
|
705
|
+
notInFuture: { label: "cannot be in the future" },
|
|
706
|
+
mediaTypeIn: { label: "must be" },
|
|
707
|
+
maxSizeMB: { label: "must be smaller than", suffix: "MB" },
|
|
708
|
+
withinBoundary: { label: "must be inside the project area" },
|
|
709
|
+
withinRadius: { label: "must be near" }
|
|
710
|
+
};
|
|
711
|
+
function takesNoValue(op) {
|
|
712
|
+
return op === "present" || op === "empty" || op === "isTrue" || op === "isFalse" || op === "withinWindow" || op === "notInFuture" || op === "withinBoundary";
|
|
713
|
+
}
|
|
714
|
+
var CTX_LABEL = {
|
|
715
|
+
"ctx.now": "the moment it is judged",
|
|
716
|
+
"ctx.collection.startDate": "the day the collection opened",
|
|
717
|
+
"ctx.collection.endDate": "the day the collection closes",
|
|
718
|
+
"ctx.claim.submitter": "the person who sent the claim",
|
|
719
|
+
"ctx.claim.submitterAddress": "the wallet address of the person who sent the claim",
|
|
720
|
+
"ctx.claim.submissionDate": "the day the claim was sent",
|
|
721
|
+
"ctx.submitter.priorClaimCount": "how many claims this person sent before",
|
|
722
|
+
"ctx.submitter.priorApprovedCount": "how many of their claims were approved",
|
|
723
|
+
"ctx.collection.projectBoundary": "the project area"
|
|
724
|
+
};
|
|
725
|
+
var MEDIA_TYPES = [
|
|
726
|
+
{ value: "application/pdf", label: "a PDF" },
|
|
727
|
+
{ value: "image/*", label: "a picture" },
|
|
728
|
+
{ value: "video/*", label: "a video" },
|
|
729
|
+
{ value: "audio/*", label: "a sound recording" },
|
|
730
|
+
{ value: "text/csv", label: "a spreadsheet (CSV)" }
|
|
731
|
+
];
|
|
732
|
+
var DIAGNOSTIC_TEXT = {
|
|
733
|
+
RUB_SCHEMA: "This rule has a setting the rules service will not accept \u2014 open it and check its options.",
|
|
734
|
+
RUB_FIELD_NOT_IN_SCHEMA: "This rule is about a question that is not on the form.",
|
|
735
|
+
RUB_FIELD_PATH: "This rule points at something the form does not have.",
|
|
736
|
+
RUB_OPERATOR_TYPE_MISMATCH: "This check does not work on this kind of answer.",
|
|
737
|
+
RUB_VALUE_TYPE: "The value here does not suit the check \u2014 fix it and the rule will work.",
|
|
738
|
+
RUB_UNKNOWN_REF: "This rule uses a value that no longer exists.",
|
|
739
|
+
RUB_EXPR_SYNTAX: "This calculated value could not be worked out.",
|
|
740
|
+
RUB_EXPR_UNKNOWN_FUNC: "This calculated value uses a way of counting we do not support.",
|
|
741
|
+
RUB_REGEX_UNSAFE: "This pattern is too complicated to be safe \u2014 please simplify it.",
|
|
742
|
+
RUB_THRESHOLD_ORDER: "The approve, part-pay and person-checks scores are in the wrong order.",
|
|
743
|
+
RUB_WEIGHT_INVALID: "How much this question counts must be between one and five stars.",
|
|
744
|
+
RUB_QUORUM_INVALID: "You are asking for more checks to pass than there are checks to pass.",
|
|
745
|
+
RUB_DUPLICATE_CODE: "Two rules are using the same name.",
|
|
746
|
+
RUB_REVIEW_TASK_UNMAPPED: "A person is meant to check this, but there is no question to ask them.",
|
|
747
|
+
RUB_REVIEW_ANSWER_UNMAPPED: "One of the answers a person can give does not decide anything yet.",
|
|
748
|
+
RUB_UNREACHABLE_BAND: "A step below can never be reached, because an earlier one always wins.",
|
|
749
|
+
RUB_WEIGHT_IGNORED: "The stars on your scored rules are being ignored \u2014 the way this rubric adds up scores does not use them.",
|
|
750
|
+
RUB_NO_RULES: "No rules yet \u2014 as it stands, every claim would be approved and paid without any checking. Add at least one rule before publishing.",
|
|
751
|
+
RUB_SCHEMA_DRIFT: "The form changed: this rule is about a question that was removed or altered.",
|
|
752
|
+
RUB_UNIQUE_KEY_DERIVED: "Calculated values cannot be part of the unique key \u2014 pick answers from the form instead.",
|
|
753
|
+
RUB_EXT_SOURCE_UNKNOWN: "This rule uses an external check that is not set up \u2014 add it under External checks, or fix the name.",
|
|
754
|
+
RUB_EXT_FIELD_INVALID: "This rule reads the wrong kind of answer from an external check \u2014 a yes/no check has no score, and a score has no yes/no.",
|
|
755
|
+
RUB_AI_CHECK_UNKNOWN: "This rule uses an AI check that is not set up \u2014 re-open the rule and pick \u201CAI checks it\u201D again.",
|
|
756
|
+
RUB_AI_FIELD_INVALID: "This rule reads the wrong kind of answer from an AI check \u2014 it gives a yes/no and a reason, nothing else."
|
|
757
|
+
};
|
|
758
|
+
function problemText(problem) {
|
|
759
|
+
return DIAGNOSTIC_TEXT[problem.code] ?? problem.message;
|
|
760
|
+
}
|
|
761
|
+
function schemaProblemText(path, hasCard) {
|
|
762
|
+
if (hasCard) return DIAGNOSTIC_TEXT.RUB_SCHEMA;
|
|
763
|
+
if (path === "/claimSchema/proof") return "This form has no fingerprint yet \u2014 reopen the collection so its form is pinned to these rules before publishing.";
|
|
764
|
+
if (path.startsWith("/claimSchema")) return "The form link needs refreshing \u2014 reopen the collection to reconnect these rules to its questions.";
|
|
765
|
+
if (path.startsWith("/review/assignTo")) return "This review\u2019s assigned person uses an old setting \u2014 re-pick the reviewer.";
|
|
766
|
+
if (path.startsWith("/review")) return "The \u201Ca person checks\u201D settings have something the rules service will not accept \u2014 re-open that rule and re-pick its options.";
|
|
767
|
+
if (path.startsWith("/scoring")) return "The approval scoring has a setting the rules service will not accept \u2014 check the approve score and how the rules add up.";
|
|
768
|
+
if (path.startsWith("/settlement")) return "The payout settings have a value the rules service will not accept.";
|
|
769
|
+
if (path.startsWith("/derived")) return "One of your calculated values has a setting the rules service will not accept.";
|
|
770
|
+
if (path.startsWith("/sources")) return "One of your external checks has a setting that isn\u2019t valid \u2014 check its web address, its audience, and that it sends at least one value.";
|
|
771
|
+
if (path.startsWith("/aiChecks")) return "One of your AI checks has a setting that isn\u2019t valid \u2014 re-open its rule and make sure it has at least one written rule to check.";
|
|
772
|
+
if (path.startsWith("/unique")) return "The \u201Cmust be unique\u201D rule has a setting the rules service will not accept \u2014 re-pick its answers and thresholds.";
|
|
773
|
+
if (path.startsWith("/frequency")) return "The \u201Chow often\u201D limit has a setting the rules service will not accept \u2014 re-pick its number and period.";
|
|
774
|
+
return "These rules are missing something the rules service needs \u2014 add at least one reject rule or scored question.";
|
|
775
|
+
}
|
|
776
|
+
var EXT_REF2 = /^ext\.([A-Za-z0-9_-]+)\.(valid|score|reason)$/;
|
|
777
|
+
function describeExtRef(ref, quoted) {
|
|
778
|
+
const match = EXT_REF2.exec(ref);
|
|
779
|
+
if (!match) return ref;
|
|
780
|
+
const [, name, fieldName] = match;
|
|
781
|
+
const label = quoted ? `the \u201C${name}\u201D check` : `${name} check`;
|
|
782
|
+
if (fieldName === "score") return `${label} score`;
|
|
783
|
+
if (fieldName === "reason") return `${label} reason`;
|
|
784
|
+
return label;
|
|
785
|
+
}
|
|
786
|
+
var AI_REF2 = /^ai\.([A-Za-z0-9_-]+)\.(valid|reason)$/;
|
|
787
|
+
function describeAiRef(ref) {
|
|
788
|
+
const match = AI_REF2.exec(ref);
|
|
789
|
+
if (!match) return ref;
|
|
790
|
+
const [, name, fieldName] = match;
|
|
791
|
+
const label = `${name.replace(/_ai(_\d+)?$/, "")} AI check`;
|
|
792
|
+
return fieldName === "reason" ? `${label} reason` : label;
|
|
793
|
+
}
|
|
794
|
+
function fieldTitle(path, ctx) {
|
|
795
|
+
if (path.startsWith("ai.")) return describeAiRef(path);
|
|
796
|
+
if (path.startsWith("ext.")) return describeExtRef(path, false);
|
|
797
|
+
if (path.startsWith("~")) return "a calculated value";
|
|
798
|
+
const field = ctx.fields.get(path);
|
|
799
|
+
if (field) return field.title;
|
|
800
|
+
const rowPath = path.replace("[*]", "");
|
|
801
|
+
return ctx.fields.get(rowPath)?.title ?? path.replace(/^\$/, "");
|
|
802
|
+
}
|
|
803
|
+
function choiceText(value, field) {
|
|
804
|
+
const match = field?.choices?.find((choice) => choice.value === value);
|
|
805
|
+
return match ? match.text : String(value);
|
|
806
|
+
}
|
|
807
|
+
function valueText(value, field, ctx) {
|
|
808
|
+
if (value === void 0 || value === null) return "\u2026";
|
|
809
|
+
if (Array.isArray(value)) return value.map((entry) => valueText(entry, field, ctx)).join(", ") || "\u2026";
|
|
810
|
+
if (typeof value === "object" && "@value" in value) return String(value["@value"]);
|
|
811
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
812
|
+
if (typeof value === "string") {
|
|
813
|
+
if (value.startsWith("$")) return `the answer to \u201C${fieldTitle(value, ctx)}\u201D`;
|
|
814
|
+
if (value.startsWith("ext.")) return describeExtRef(value, true);
|
|
815
|
+
if (value in CTX_LABEL) return CTX_LABEL[value];
|
|
816
|
+
if (value.startsWith("~")) return fieldTitle(value, ctx);
|
|
817
|
+
}
|
|
818
|
+
return choiceText(value, field);
|
|
819
|
+
}
|
|
820
|
+
function describeValue(op, value, field, ctx) {
|
|
821
|
+
if (takesNoValue(op)) return "";
|
|
822
|
+
if (op === "mediaTypeIn") {
|
|
823
|
+
const globs = Array.isArray(value) ? value : [];
|
|
824
|
+
return globs.map((glob) => MEDIA_TYPES.find((m) => m.value === glob)?.label ?? String(glob)).join(" or ") || "\u2026";
|
|
825
|
+
}
|
|
826
|
+
if (op === "between" || op === "lengthBetween" || op === "countBetween") {
|
|
827
|
+
const pair = Array.isArray(value) ? value : [];
|
|
828
|
+
return `${valueText(pair[0], field, ctx)} and ${valueText(pair[1], field, ctx)}`;
|
|
829
|
+
}
|
|
830
|
+
if (op === "withinRadius") {
|
|
831
|
+
const spec = value ?? {};
|
|
832
|
+
return `${valueText(spec.center, field, ctx)}, within ${String(spec.meters ?? "\u2026")} m`;
|
|
833
|
+
}
|
|
834
|
+
return valueText(value, field, ctx);
|
|
835
|
+
}
|
|
836
|
+
function describeCondition(cond, ctx, rowOf) {
|
|
837
|
+
if (!cond) return "nothing to check";
|
|
838
|
+
if ("forEach" in cond && typeof cond.forEach === "string") {
|
|
839
|
+
const inner = cond.every ?? cond.some;
|
|
840
|
+
const lead = cond.every ? "every row" : "at least one row";
|
|
841
|
+
return `${lead} of \u201C${fieldTitle(cond.forEach, ctx)}\u201D: ${describeCondition(inner, ctx, cond.forEach)}`;
|
|
842
|
+
}
|
|
843
|
+
if ("all" in cond && Array.isArray(cond.all)) return cond.all.map((member) => describeCondition(member, ctx, rowOf)).join(", and ");
|
|
844
|
+
if ("any" in cond && Array.isArray(cond.any)) return cond.any.map((member) => describeCondition(member, ctx, rowOf)).join(", or ");
|
|
845
|
+
if ("not" in cond) return `it is not the case that ${describeCondition(cond.not, ctx, rowOf)}`;
|
|
846
|
+
if ("atLeast" in cond && Array.isArray(cond.of)) return `at least ${cond.atLeast} of: ${cond.of.map((member) => describeCondition(member, ctx, rowOf)).join("; ")}`;
|
|
847
|
+
if (!("field" in cond)) return "nothing to check";
|
|
848
|
+
const path = cond.field.startsWith(".") && rowOf ? `${rowOf}[*]${cond.field}` : cond.field;
|
|
849
|
+
const field = ctx.fields.get(path);
|
|
850
|
+
const phrase = OPERATOR_PHRASE[cond.op];
|
|
851
|
+
if (!phrase) return `\u201C${fieldTitle(path, ctx)}\u201D has a check we no longer recognise`;
|
|
852
|
+
const value = describeValue(cond.op, cond.value, field, ctx);
|
|
853
|
+
const tolerance = cond.op === "approx" && typeof cond.tolerance === "number" ? `(give or take ${cond.tolerance})` : "";
|
|
854
|
+
return [`\u201C${fieldTitle(path, ctx)}\u201D`, phrase.label, value, phrase.suffix, tolerance].filter(Boolean).join(" ");
|
|
855
|
+
}
|
|
856
|
+
function describeReviewTask(rule, ctx) {
|
|
857
|
+
const task = rule.task;
|
|
858
|
+
if (!task) return `a person checks \u201C${fieldTitle(rule.fieldPath, ctx)}\u201D \u2014 but there is no question to ask them yet`;
|
|
859
|
+
const outcomes = Object.entries(task.map ?? {}).map(([answer, verdict]) => `${answerLabel(answer)} \u2192 ${VERDICT_LABEL[verdict]}`);
|
|
860
|
+
const score = rule.criterion.score;
|
|
861
|
+
const missing = score?.["@type"] === "ManualScore" ? ` If nothing was attached, nobody is asked and the check ${score.whenMissing === FAILED ? "fails" : "passes"}.` : "";
|
|
862
|
+
return `A person answers: \u201C${task.prompt}\u201D ${outcomes.join(" \xB7 ")}${missing}`;
|
|
863
|
+
}
|
|
864
|
+
var VERDICT_LABEL = {
|
|
865
|
+
approve: "approve",
|
|
866
|
+
partial: "part-approve",
|
|
867
|
+
reject: "reject"
|
|
868
|
+
};
|
|
869
|
+
function answerLabel(answer) {
|
|
870
|
+
if (answer === "true") return "Yes";
|
|
871
|
+
if (answer === "false") return "No";
|
|
872
|
+
return answer;
|
|
873
|
+
}
|
|
874
|
+
function appliesWhenLead(cond, ctx) {
|
|
875
|
+
return cond ? `If ${describeCondition(cond, ctx)} \u2014 ` : "";
|
|
876
|
+
}
|
|
877
|
+
function describeRule(rule, ctx) {
|
|
878
|
+
switch (rule.consequence) {
|
|
879
|
+
case "reject": {
|
|
880
|
+
if (rule.bands) {
|
|
881
|
+
return `${appliesWhenLead(rule.gate.appliesWhen, ctx)}${describeScoreBands(rule.fieldPath, rule.bands, ctx)}`;
|
|
882
|
+
}
|
|
883
|
+
if (rule.grown?.editable) {
|
|
884
|
+
return describeGrown({ members: rule.grown.members, onFail: rule.gate.onFail, reason: rule.gate.reason, appliesWhen: rule.gate.appliesWhen }, ctx);
|
|
885
|
+
}
|
|
886
|
+
const check = describeCondition(rule.gate.when, ctx);
|
|
887
|
+
const outcome = rule.gate.onFail === "review" ? "else a person checks it" : `else reject: \u201C${rule.gate.reason}\u201D`;
|
|
888
|
+
return `${appliesWhenLead(rule.gate.appliesWhen, ctx)}${check} \u2014 ${outcome}`;
|
|
889
|
+
}
|
|
890
|
+
case "human":
|
|
891
|
+
return `${appliesWhenLead(rule.criterion.appliesWhen, ctx)}${describeReviewTask(rule, ctx)}`;
|
|
892
|
+
default:
|
|
893
|
+
return "";
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
var EXT_ANSWER_TYPE_LABEL = "What does it answer?";
|
|
897
|
+
var EXT_ANSWERS_BOOLEAN_LABEL = "Yes or no";
|
|
898
|
+
var EXT_ANSWERS_SCORE_LABEL = "A score out of 100";
|
|
899
|
+
var SCORE_HOLD_REASON = "The check\u2019s score is in the middle band \u2014 a person decides";
|
|
900
|
+
var SCORE_FLOOR_REASON = "The check\u2019s score is too low";
|
|
901
|
+
var SCORE_MESSAGE_LABEL = "What should the claimant be told when the score is too low?";
|
|
902
|
+
function describeScoreBands(fieldPath, bands, ctx) {
|
|
903
|
+
const question = `\u201C${fieldTitle(fieldPath, ctx)}\u201D`;
|
|
904
|
+
const approveAt = Math.round(bands.approveAt / 100);
|
|
905
|
+
const rejectBelow = Math.round(bands.rejectBelow / 100);
|
|
906
|
+
if (rejectBelow <= 0) {
|
|
907
|
+
return `The outside service scores ${question} out of 100 \u2014 below ${approveAt} a person checks it; at ${approveAt} or more it passes`;
|
|
908
|
+
}
|
|
909
|
+
return `The outside service scores ${question} out of 100 \u2014 below ${rejectBelow} the claim is turned down; from ${rejectBelow} to ${approveAt - 1} a person checks it; at ${approveAt} or more it passes`;
|
|
910
|
+
}
|
|
911
|
+
var GROW_RULE_LABEL = "Add another check to this rule";
|
|
912
|
+
var GROWN_CHECKS_LABEL = "The checks";
|
|
913
|
+
var LINKED_ADD_LABEL = "Depends on another answer";
|
|
914
|
+
var AI_CHECK_LANE_LABEL = "AI checks it";
|
|
915
|
+
var GROUP_AI_MEMBER_LEAD = "The AI checks";
|
|
916
|
+
var LINKED_AI_ROW_LEAD = "the AI checks:";
|
|
917
|
+
var LINKED_AI_ELSE_LEAD = "For anyone else, the AI checks:";
|
|
918
|
+
var LINKED_AI_PLACEHOLDER = "e.g. The invoice must show a salary of 3000";
|
|
919
|
+
var LINKED_MISSING_AI_TEXT = "Every case needs what the AI should check, written in plain words.";
|
|
920
|
+
var AI_SEES_LABEL = "What can the AI see?";
|
|
921
|
+
var AI_SEES_PLACEHOLDER = "Add other answers the AI should see";
|
|
922
|
+
function aiSeesHint(questionTitle) {
|
|
923
|
+
return `The answer it checks \u2014 \u201C${questionTitle}\u201D \u2014 is always sent. Add any other answers the AI should read; photos and files are sent as the files themselves.`;
|
|
924
|
+
}
|
|
925
|
+
var AI_SETTING_HINT = "Needs \u201CAI checks\u201D switched on for this collection in the Evaluation Engine block \u2014 without it these rules can\u2019t run and claims wait for a person.";
|
|
926
|
+
var GROUP_OUTSIDE_BUILDER_TEXT = "This rule was written outside the builder. It is applied exactly as written \u2014 the advanced view below shows it in full.";
|
|
927
|
+
var LINKED_DUPLICATE_CASE_TEXT = "Two of your cases are about the same value \u2014 every case must be different.";
|
|
928
|
+
var LINKED_MISSING_CASE_TEXT = "Every case needs a value to check against.";
|
|
929
|
+
function describeGrown(rule, ctx) {
|
|
930
|
+
const lead = rule.members.map((member) => describeGroupMember(member, ctx)).join(", and ");
|
|
931
|
+
const outcome = rule.onFail === "review" ? "otherwise a person checks it" : `otherwise reject: \u201C${rule.reason}\u201D`;
|
|
932
|
+
return `${appliesWhenLead(rule.appliesWhen, ctx)}${lead} \u2014 ${outcome}`;
|
|
933
|
+
}
|
|
934
|
+
function linkedCaseText(guardField, guardValue, ctx) {
|
|
935
|
+
return `when \u201C${fieldTitle(guardField, ctx)}\u201D is \u201C${describeValue("==", guardValue, ctx.fields.get(guardField), ctx)}\u201D`;
|
|
936
|
+
}
|
|
937
|
+
function linkedElseText() {
|
|
938
|
+
return "for anyone else";
|
|
939
|
+
}
|
|
940
|
+
function linkedSentenceOf(entry) {
|
|
941
|
+
return {
|
|
942
|
+
checkedField: entry.checkedField,
|
|
943
|
+
op: entry.op,
|
|
944
|
+
guardField: entry.guardField,
|
|
945
|
+
cases: entry.cases.map((c) => ({ guardValue: c.guardValue, value: c.when.value, instructions: c.instructions })),
|
|
946
|
+
elseValue: entry.elseCase.when.value,
|
|
947
|
+
...entry.ai ? { ai: true, elseInstructions: entry.elseCase.instructions } : {}
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
function describeLinked(linked, ctx) {
|
|
951
|
+
const guard = ctx.fields.get(linked.guardField);
|
|
952
|
+
if (linked.ai) {
|
|
953
|
+
const rules = (instructions) => `\u201C${(instructions ?? "").trim() || "\u2026"}\u201D`;
|
|
954
|
+
const parts2 = linked.cases.map((entry, at) => {
|
|
955
|
+
const guardText = `\u201C${describeValue("==", entry.guardValue, guard, ctx)}\u201D`;
|
|
956
|
+
return at === 0 ? `If \u201C${fieldTitle(linked.guardField, ctx)}\u201D is ${guardText}, the AI checks \u201C${fieldTitle(linked.checkedField, ctx)}\u201D: ${rules(entry.instructions)}.` : `If it is ${guardText}: ${rules(entry.instructions)}.`;
|
|
957
|
+
});
|
|
958
|
+
parts2.push(`For anyone else: ${rules(linked.elseInstructions)}.`);
|
|
959
|
+
return parts2.join(" ");
|
|
960
|
+
}
|
|
961
|
+
const phrase = OPERATOR_PHRASE[linked.op];
|
|
962
|
+
const checked = ctx.fields.get(linked.checkedField);
|
|
963
|
+
const check = (value) => [phrase.label, describeValue(linked.op, value, checked, ctx), phrase.suffix].filter(Boolean).join(" ");
|
|
964
|
+
const parts = linked.cases.map((entry, at) => {
|
|
965
|
+
const guardText = `\u201C${describeValue("==", entry.guardValue, guard, ctx)}\u201D`;
|
|
966
|
+
return at === 0 ? `If \u201C${fieldTitle(linked.guardField, ctx)}\u201D is ${guardText}, \u201C${fieldTitle(linked.checkedField, ctx)}\u201D ${check(entry.value)}.` : `If it is ${guardText}, it ${check(entry.value)}.`;
|
|
967
|
+
});
|
|
968
|
+
parts.push(`For anyone else it ${check(linked.elseValue)}.`);
|
|
969
|
+
return parts.join(" ");
|
|
970
|
+
}
|
|
971
|
+
function describeGroupMember(member, ctx) {
|
|
972
|
+
const name = aiMemberName(member);
|
|
973
|
+
const check = name ? ctx.aiChecks?.find((entry) => entry.name === name) : void 0;
|
|
974
|
+
if (check) {
|
|
975
|
+
const anchor = Object.values(check.send).find((value) => typeof value === "string" && value.startsWith("$"));
|
|
976
|
+
const rules = check.instructions.filter((rule) => rule.trim().length > 0).join("; ");
|
|
977
|
+
return anchor ? `the AI checks \u201C${fieldTitle(anchor, ctx)}\u201D: \u201C${rules}\u201D` : `the AI checks it: \u201C${rules}\u201D`;
|
|
978
|
+
}
|
|
979
|
+
return describeCondition(toCondition(member), ctx);
|
|
980
|
+
}
|
|
981
|
+
var POLICY_TAB_LABEL = {
|
|
982
|
+
all: "All of them",
|
|
983
|
+
any: "Any of them",
|
|
984
|
+
atLeast: "At least\u2026"
|
|
985
|
+
};
|
|
986
|
+
var POLICY_PIN_LABEL = "Must always pass";
|
|
987
|
+
var POLICY_DEFAULT_REASON = "Not enough of the checks passed";
|
|
988
|
+
var POLICY_EMPTY_TALLY_TEXT = "Tick the rules that should count \u2014 until then, every check must still pass.";
|
|
989
|
+
var POLICY_NO_RULES_TEXT = "No rules yet \u2014 add a rule to a question above, then choose how many must pass.";
|
|
990
|
+
var POLICY_OUTSIDE_BUILDER_TEXT = "The approval rule was written outside the builder. It is applied exactly as written \u2014 the advanced view below shows it in full.";
|
|
991
|
+
function policyStepperText(selectedCount) {
|
|
992
|
+
return `of the ${plural(selectedCount, "selected rule")} must pass`;
|
|
993
|
+
}
|
|
994
|
+
function listOf(parts) {
|
|
995
|
+
if (parts.length <= 1) return parts[0] ?? "";
|
|
996
|
+
return `${parts.slice(0, -1).join(", ")} and ${parts[parts.length - 1]}`;
|
|
997
|
+
}
|
|
998
|
+
function describePolicyMember(member, ctx) {
|
|
999
|
+
const simple = toSimpleRule(member);
|
|
1000
|
+
if (simple) return describeGroupMember(simple, ctx);
|
|
1001
|
+
if ("all" in member && Array.isArray(member.all)) {
|
|
1002
|
+
const parts = member.all.map((inner) => {
|
|
1003
|
+
const innerSimple = toSimpleRule(inner);
|
|
1004
|
+
return innerSimple ? describeGroupMember(innerSimple, ctx) : describeCondition(inner, ctx);
|
|
1005
|
+
});
|
|
1006
|
+
return parts.join(", and ");
|
|
1007
|
+
}
|
|
1008
|
+
return describeCondition(member, ctx);
|
|
1009
|
+
}
|
|
1010
|
+
function describePolicy(input) {
|
|
1011
|
+
const { tab, pinned, restCount, n, reason } = input;
|
|
1012
|
+
const outcome = `\u2014 otherwise reject: \u201C${reason}\u201D`;
|
|
1013
|
+
const rules = plural(restCount, "rule");
|
|
1014
|
+
if (pinned.length === 0) {
|
|
1015
|
+
return tab === "any" ? `Any one of the ${rules} must pass ${outcome}` : `At least ${n} of the ${rules} must pass ${outcome}`;
|
|
1016
|
+
}
|
|
1017
|
+
const names = listOf(pinned.map((title) => `\u201C${title}\u201D`));
|
|
1018
|
+
const beyond = pinned.length === 1 ? "Beyond that" : "Beyond those";
|
|
1019
|
+
const rest = tab === "any" ? `any one of the other ${rules} must also pass` : `at least ${n - pinned.length} of the other ${rules} must also pass`;
|
|
1020
|
+
return `${names} must always pass. ${beyond}, ${rest} ${outcome}`;
|
|
1021
|
+
}
|
|
1022
|
+
var UNIQUE_TITLE = "This claim must be unique";
|
|
1023
|
+
var UNIQUE_EXPLAINER = "Claims already in this collection are compared with each new one \u2014 exact copies, re-saved files and near-identical wording are caught. A claim that was rejected never blocks a fixed resubmission.";
|
|
1024
|
+
var UNIQUE_BY_LABEL = "Unique by\u2026";
|
|
1025
|
+
var UNIQUE_BY_PLACEHOLDER = "Pick the answers that identify a claim";
|
|
1026
|
+
var UNIQUE_BY_HINT = "Two claims matching on ALL of these count as the same claim. Leave empty to compare whole claims.";
|
|
1027
|
+
var UNIQUE_FILE_BADGE = "matched by file fingerprint";
|
|
1028
|
+
var UNIQUE_GRANULARITY_OPTIONS = [
|
|
1029
|
+
{ value: "exact", label: "exactly" },
|
|
1030
|
+
{ value: "day", label: "on the same day" },
|
|
1031
|
+
{ value: "month", label: "in the same month" },
|
|
1032
|
+
{ value: "year", label: "in the same year" }
|
|
1033
|
+
];
|
|
1034
|
+
function uniqueGranularityLead(title) {
|
|
1035
|
+
return `Dates in \u201C${title}\u201D match`;
|
|
1036
|
+
}
|
|
1037
|
+
var UNIQUE_AI_LABEL = "Use AI to catch reworded duplicates";
|
|
1038
|
+
var UNIQUE_AI_ON_HINT = "Similar-looking claims are checked by AI; matches go to human review.";
|
|
1039
|
+
var UNIQUE_AI_OFF_WARNING = "Without AI checking, similar-looking claims pass with only a recorded score.";
|
|
1040
|
+
var UNIQUE_PRICING_NOTE = "Duplicate screening \u2014 fingerprints, similarity and AI checks \u2014 is included in this collection\u2019s per-claim price.";
|
|
1041
|
+
var UNIQUE_REJECT_LABEL = "Auto-reject at";
|
|
1042
|
+
var UNIQUE_REVIEW_LABEL = "Send to review at";
|
|
1043
|
+
var FREQUENCY_TITLE = "Limit how often each person can claim";
|
|
1044
|
+
var FREQUENCY_EXPLAINER = "Counts how many claims each person has already sent this calendar period \u2014 however different their content. A claim that was rejected never uses up a turn, so a fixed resubmission always fits.";
|
|
1045
|
+
var FREQUENCY_MAX_LEAD = "At most";
|
|
1046
|
+
function frequencyMaxNoun(max) {
|
|
1047
|
+
return max === 1 ? "claim" : "claims";
|
|
1048
|
+
}
|
|
1049
|
+
var FREQUENCY_PERIOD_ARIA = "The period the limit counts over";
|
|
1050
|
+
var FREQUENCY_PERIOD_OPTIONS = [
|
|
1051
|
+
{ value: "day", label: "per day" },
|
|
1052
|
+
{ value: "week", label: "per week" },
|
|
1053
|
+
{ value: "month", label: "per month" },
|
|
1054
|
+
{ value: "year", label: "per year" }
|
|
1055
|
+
];
|
|
1056
|
+
var FREQUENCY_BREACH_LABEL = "When someone goes over the limit";
|
|
1057
|
+
var FREQUENCY_BREACH_OPTIONS = [
|
|
1058
|
+
{ value: "review", label: "A person checks" },
|
|
1059
|
+
{ value: "reject", label: "Reject the claim" }
|
|
1060
|
+
];
|
|
1061
|
+
var FREQUENCY_BREACH_REVIEW_HINT = "A claim over the limit is sent to a person to look at \u2014 nothing is turned down automatically.";
|
|
1062
|
+
function plural(count, noun) {
|
|
1063
|
+
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
1064
|
+
}
|
|
1065
|
+
function describeTally(rules, extra) {
|
|
1066
|
+
const parts = [
|
|
1067
|
+
plural(rules.filter((r) => r.consequence === "reject").length + (extra?.policyMembers ?? 0), "reject rule"),
|
|
1068
|
+
plural(rules.filter((r) => r.consequence === "human").length, "human check")
|
|
1069
|
+
];
|
|
1070
|
+
if (extra?.linked) parts.push(plural(extra.linked, "linked rule"));
|
|
1071
|
+
return parts.join(" \xB7 ");
|
|
1072
|
+
}
|
|
1073
|
+
function describeApproval(checkCount) {
|
|
1074
|
+
if (checkCount === 0) return "When every check passes. No checks yet \u2014 add a rule to a question above.";
|
|
1075
|
+
if (checkCount === 1) return "When the one check passes.";
|
|
1076
|
+
return `When all ${checkCount} checks pass.`;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/policyModel.ts
|
|
1080
|
+
function clampPolicyN(n, pinnedSelected, selectedCount) {
|
|
1081
|
+
const min = pinnedSelected + 1;
|
|
1082
|
+
const max = Math.max(selectedCount, min);
|
|
1083
|
+
return Math.min(Math.max(Math.round(n) || min, min), max);
|
|
1084
|
+
}
|
|
1085
|
+
function foldableRule(rule) {
|
|
1086
|
+
if (rule.gate.appliesWhen) return false;
|
|
1087
|
+
if ((rule.gate.onFail ?? "reject") !== "reject") return false;
|
|
1088
|
+
if (rule.grown && !rule.grown.editable) return false;
|
|
1089
|
+
return policyMemberRenderable(rule.gate.when);
|
|
1090
|
+
}
|
|
1091
|
+
function derivePolicy(body) {
|
|
1092
|
+
const shape = policyShapeOf(body);
|
|
1093
|
+
if (shape.kind === "foreign") return { base: body, foreign: true, tab: "all", rows: [], n: 1, reason: "" };
|
|
1094
|
+
const rejects = listRules(body).filter((rule) => rule.consequence === "reject");
|
|
1095
|
+
if (shape.kind === "none") {
|
|
1096
|
+
const rows = rejects.map((rule) => {
|
|
1097
|
+
const locked = !foldableRule(rule);
|
|
1098
|
+
return { key: rule.code, rule, locked, selected: true, pinned: locked };
|
|
1099
|
+
});
|
|
1100
|
+
const pinned = rows.filter((row) => row.pinned).length;
|
|
1101
|
+
return { base: body, foreign: false, tab: "all", rows, n: pinned + 1, reason: "" };
|
|
1102
|
+
}
|
|
1103
|
+
const memberRows = shape.members.map((member, at) => ({ key: `member-${at}`, member, locked: false, selected: true, pinned: false }));
|
|
1104
|
+
const gateRows = rejects.map((rule) => ({ key: rule.code, rule, locked: !foldableRule(rule), selected: true, pinned: true }));
|
|
1105
|
+
return {
|
|
1106
|
+
base: body,
|
|
1107
|
+
foreign: false,
|
|
1108
|
+
tab: shape.tab,
|
|
1109
|
+
rows: [...memberRows, ...gateRows],
|
|
1110
|
+
n: (shape.nRest ?? 1) + gateRows.length,
|
|
1111
|
+
reason: shape.gate.reason,
|
|
1112
|
+
gate: shape.gate,
|
|
1113
|
+
gateAt: shape.index
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
function compilePolicy(state) {
|
|
1117
|
+
if (state.foreign) return state.base;
|
|
1118
|
+
const baseGates = state.base.gates ?? [];
|
|
1119
|
+
const folded = state.tab === "all" ? [] : state.rows.filter((row) => row.selected && !row.pinned && !row.locked);
|
|
1120
|
+
const members = folded.map((row) => row.rule ? row.rule.gate.when : row.member);
|
|
1121
|
+
const foldedIndexes = new Set(folded.flatMap((row) => row.rule ? [row.rule.index] : []));
|
|
1122
|
+
const pinnedSelected = state.rows.filter((row) => row.selected && row.pinned).length;
|
|
1123
|
+
const nRest = Math.min(Math.max(state.n - pinnedSelected, 1), Math.max(members.length, 1));
|
|
1124
|
+
const reason = state.reason.trim() || POLICY_DEFAULT_REASON;
|
|
1125
|
+
const minted = [];
|
|
1126
|
+
const extractedGates = state.rows.filter((row) => row.member && !folded.includes(row)).map((row) => {
|
|
1127
|
+
const code = nextCode(state.base, "RULE", primaryFieldOf(row.member), minted);
|
|
1128
|
+
minted.push(code);
|
|
1129
|
+
return { "@type": "Gate", code, reason: state.gate?.reason ?? reason, when: row.member, onFail: "reject", class: state.gate?.class ?? "insufficient_evidence" };
|
|
1130
|
+
});
|
|
1131
|
+
let policyGate;
|
|
1132
|
+
if (state.tab !== "all" && members.length > 0) {
|
|
1133
|
+
const when = state.tab === "any" ? { "@type": "Condition", any: members } : { "@type": "Condition", atLeast: nRest, of: members };
|
|
1134
|
+
const old = state.gate;
|
|
1135
|
+
policyGate = old && old.reason === reason && JSON.stringify(old.when) === JSON.stringify(when) ? old : {
|
|
1136
|
+
"@type": "Gate",
|
|
1137
|
+
code: old?.code ?? nextCode(state.base, "RULE", "approval", minted),
|
|
1138
|
+
reason,
|
|
1139
|
+
...old?.appliesWhen ? { appliesWhen: old.appliesWhen } : {},
|
|
1140
|
+
when,
|
|
1141
|
+
onFail: old?.onFail ?? "reject",
|
|
1142
|
+
class: old?.class ?? "insufficient_evidence"
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
const gates = [];
|
|
1146
|
+
baseGates.forEach((gate, at) => {
|
|
1147
|
+
if (at === state.gateAt) {
|
|
1148
|
+
if (policyGate) gates.push(policyGate);
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
if (foldedIndexes.has(at)) return;
|
|
1152
|
+
gates.push(gate);
|
|
1153
|
+
});
|
|
1154
|
+
gates.push(...extractedGates);
|
|
1155
|
+
if (policyGate && state.gateAt === void 0) gates.push(policyGate);
|
|
1156
|
+
return { ...state.base, gates };
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/rubricStyles.tsx
|
|
1160
|
+
import React2 from "react";
|
|
1161
|
+
var ACCENT = "var(--mantine-color-accent-5)";
|
|
1162
|
+
var ACCENT_SOFT = `color-mix(in srgb, ${ACCENT} 10%, transparent)`;
|
|
1163
|
+
var INK = "var(--mantine-color-text)";
|
|
1164
|
+
var SLATE = "var(--mantine-color-dimmed)";
|
|
1165
|
+
var HAIRLINE = "var(--mantine-color-neutralColor-4)";
|
|
1166
|
+
var SURFACE = "var(--mantine-color-body)";
|
|
1167
|
+
var PANEL_GROUND = "var(--mantine-color-neutralColor-1)";
|
|
1168
|
+
var SHADOW_TINT = "color-mix(in srgb, var(--mantine-color-black) 5%, transparent)";
|
|
1169
|
+
var cardStyle = {
|
|
1170
|
+
background: SURFACE,
|
|
1171
|
+
border: `1px solid ${HAIRLINE}`,
|
|
1172
|
+
borderRadius: 12,
|
|
1173
|
+
boxShadow: `0 1px 2px ${SHADOW_TINT}, 0 8px 28px ${SHADOW_TINT}`
|
|
1174
|
+
};
|
|
1175
|
+
var panelStyle = {
|
|
1176
|
+
background: PANEL_GROUND,
|
|
1177
|
+
border: `1px solid ${HAIRLINE}`,
|
|
1178
|
+
borderRadius: 10
|
|
1179
|
+
};
|
|
1180
|
+
var microLabelStyle = {
|
|
1181
|
+
fontSize: 11.5,
|
|
1182
|
+
fontWeight: 600,
|
|
1183
|
+
letterSpacing: "0.07em",
|
|
1184
|
+
textTransform: "uppercase",
|
|
1185
|
+
color: SLATE
|
|
1186
|
+
};
|
|
1187
|
+
var leadWordStyle = { fontSize: 14, fontWeight: 600, color: INK };
|
|
1188
|
+
var chipInput = {
|
|
1189
|
+
wrapper: { "--input-bd": HAIRLINE, "--input-bg": SURFACE },
|
|
1190
|
+
input: {
|
|
1191
|
+
borderRadius: 8,
|
|
1192
|
+
boxShadow: `0 1px 1px ${SHADOW_TINT}`,
|
|
1193
|
+
fontSize: 14,
|
|
1194
|
+
fontWeight: 500,
|
|
1195
|
+
paddingInline: 12
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
function chipInputStyles(hasError) {
|
|
1199
|
+
return hasError ? { input: chipInput.input } : chipInput;
|
|
1200
|
+
}
|
|
1201
|
+
var segmentedPill = {
|
|
1202
|
+
root: {
|
|
1203
|
+
background: SURFACE,
|
|
1204
|
+
border: `1px solid ${HAIRLINE}`,
|
|
1205
|
+
borderRadius: 999,
|
|
1206
|
+
padding: 3,
|
|
1207
|
+
"--sc-radius": "999px",
|
|
1208
|
+
"--sc-color": ACCENT,
|
|
1209
|
+
"--sc-label-color": "var(--mantine-color-white)"
|
|
1210
|
+
},
|
|
1211
|
+
label: { fontSize: 13.5, fontWeight: 500 }
|
|
1212
|
+
};
|
|
1213
|
+
var chipButton = {
|
|
1214
|
+
root: {
|
|
1215
|
+
fontWeight: 500,
|
|
1216
|
+
boxShadow: `0 1px 1px ${SHADOW_TINT}`,
|
|
1217
|
+
"--button-radius": "8px",
|
|
1218
|
+
"--button-bd": `1px solid ${HAIRLINE}`,
|
|
1219
|
+
"--button-bg": SURFACE,
|
|
1220
|
+
"--button-color": INK,
|
|
1221
|
+
"--button-hover": ACCENT_SOFT,
|
|
1222
|
+
"--button-hover-color": INK
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
var quietButton = {
|
|
1226
|
+
root: {
|
|
1227
|
+
fontWeight: 500,
|
|
1228
|
+
"--button-radius": "8px",
|
|
1229
|
+
"--button-bg": "transparent",
|
|
1230
|
+
"--button-color": SLATE,
|
|
1231
|
+
"--button-hover": ACCENT_SOFT,
|
|
1232
|
+
"--button-hover-color": INK
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1235
|
+
function consequencePillStyle(color) {
|
|
1236
|
+
return {
|
|
1237
|
+
display: "inline-flex",
|
|
1238
|
+
alignItems: "center",
|
|
1239
|
+
justifyContent: "center",
|
|
1240
|
+
padding: 4,
|
|
1241
|
+
borderRadius: 8,
|
|
1242
|
+
color,
|
|
1243
|
+
background: `color-mix(in srgb, ${color} 10%, transparent)`
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
var QUOTED = /(“[^”]*”)/g;
|
|
1247
|
+
var RuleSentence = ({ text }) => /* @__PURE__ */ React2.createElement(
|
|
1248
|
+
"div",
|
|
1249
|
+
{
|
|
1250
|
+
style: {
|
|
1251
|
+
borderLeft: `3px solid ${ACCENT}`,
|
|
1252
|
+
background: ACCENT_SOFT,
|
|
1253
|
+
borderRadius: "0 8px 8px 0",
|
|
1254
|
+
padding: "8px 12px",
|
|
1255
|
+
fontSize: 14,
|
|
1256
|
+
fontStyle: "italic",
|
|
1257
|
+
lineHeight: 1.55,
|
|
1258
|
+
color: INK
|
|
1259
|
+
}
|
|
1260
|
+
},
|
|
1261
|
+
text.split(QUOTED).map(
|
|
1262
|
+
(part, at) => part.startsWith("\u201C") ? (
|
|
1263
|
+
// Index keys are safe: the segments are a pure projection of `text`.
|
|
1264
|
+
/* @__PURE__ */ React2.createElement("span", { key: `${part}${at}`, style: { fontStyle: "normal", fontWeight: 600 } }, part)
|
|
1265
|
+
) : /* @__PURE__ */ React2.createElement(React2.Fragment, { key: `${part}${at}` }, part)
|
|
1266
|
+
)
|
|
1267
|
+
);
|
|
1268
|
+
|
|
1269
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/ApprovalPolicy.tsx
|
|
1270
|
+
var TABS = ["all", "any", "atLeast"];
|
|
1271
|
+
function rowTitle(row, ctx) {
|
|
1272
|
+
return fieldTitle(row.rule ? row.rule.fieldPath : primaryFieldOf(row.member), ctx);
|
|
1273
|
+
}
|
|
1274
|
+
function rowSentence(row, ctx) {
|
|
1275
|
+
if (row.rule) return describeRule(row.rule, ctx);
|
|
1276
|
+
return row.member ? describePolicyMember(row.member, ctx) : "";
|
|
1277
|
+
}
|
|
1278
|
+
function pinChipStyles(active) {
|
|
1279
|
+
return { root: { ...quietButton.root, ...active ? { "--button-bg": ACCENT_SOFT, "--button-color": INK } : {} } };
|
|
1280
|
+
}
|
|
1281
|
+
var ApprovalPolicy = ({ body, index, checkCount, onChange, disabled }) => {
|
|
1282
|
+
const [state, setState] = useState(() => derivePolicy(body));
|
|
1283
|
+
const lastWrittenRef = useRef(null);
|
|
1284
|
+
const bodyJson = JSON.stringify(body);
|
|
1285
|
+
useEffect(() => {
|
|
1286
|
+
if (bodyJson === lastWrittenRef.current) return;
|
|
1287
|
+
setState(derivePolicy(body));
|
|
1288
|
+
}, [bodyJson]);
|
|
1289
|
+
const commit = (next) => {
|
|
1290
|
+
setState(next);
|
|
1291
|
+
const compiled = compilePolicy(next);
|
|
1292
|
+
const json = JSON.stringify(compiled);
|
|
1293
|
+
lastWrittenRef.current = json;
|
|
1294
|
+
if (json !== bodyJson) onChange(compiled);
|
|
1295
|
+
};
|
|
1296
|
+
const selectedCount = state.rows.filter((row) => row.selected).length;
|
|
1297
|
+
const pinnedSelected = state.rows.filter((row) => row.selected && row.pinned).length;
|
|
1298
|
+
const restCount = state.rows.filter((row) => row.selected && !row.pinned && !row.locked).length;
|
|
1299
|
+
const patchRow = (key, patch) => {
|
|
1300
|
+
const rows = state.rows.map((row) => row.key === key ? { ...row, ...patch } : row);
|
|
1301
|
+
const pinned = rows.filter((row) => row.selected && row.pinned).length;
|
|
1302
|
+
const selected = rows.filter((row) => row.selected).length;
|
|
1303
|
+
commit({ ...state, rows, n: clampPolicyN(state.n, pinned, selected) });
|
|
1304
|
+
};
|
|
1305
|
+
const pickTab = (tab) => commit({ ...state, tab, n: clampPolicyN(state.n, pinnedSelected, selectedCount) });
|
|
1306
|
+
const setN = (n) => commit({ ...state, n: clampPolicyN(n, pinnedSelected, selectedCount) });
|
|
1307
|
+
const setReason = (reason) => commit({ ...state, reason });
|
|
1308
|
+
const ctx = { fields: index, aiChecks: body.aiChecks };
|
|
1309
|
+
const pinnedTitles = state.rows.filter((row) => row.selected && row.pinned).map((row) => rowTitle(row, ctx));
|
|
1310
|
+
const sentence = state.tab === "all" ? describeApproval(checkCount) : restCount === 0 ? "" : describePolicy({ tab: state.tab, pinned: pinnedTitles, restCount, n: state.n, reason: state.reason.trim() || POLICY_DEFAULT_REASON });
|
|
1311
|
+
return /* @__PURE__ */ React3.createElement(Paper, { p: 18, style: cardStyle }, /* @__PURE__ */ React3.createElement(Stack2, { gap: "sm" }, /* @__PURE__ */ React3.createElement(Group2, { gap: "sm", justify: "space-between", align: "center", wrap: "wrap" }, /* @__PURE__ */ React3.createElement(Text, { size: "sm", fw: 600 }, "When is a claim approved?"), /* @__PURE__ */ React3.createElement(
|
|
1312
|
+
SegmentedControl,
|
|
1313
|
+
{
|
|
1314
|
+
styles: segmentedPill,
|
|
1315
|
+
value: state.foreign ? "" : state.tab,
|
|
1316
|
+
onChange: (tab) => pickTab(tab),
|
|
1317
|
+
disabled: disabled || state.foreign,
|
|
1318
|
+
data: TABS.map((tab) => ({ value: tab, label: POLICY_TAB_LABEL[tab] }))
|
|
1319
|
+
}
|
|
1320
|
+
)), state.foreign ? (
|
|
1321
|
+
// Hand-authored territory the tabs cannot edit — applied as written,
|
|
1322
|
+
// shown read-only; the stray gates stay deletable on their cards.
|
|
1323
|
+
/* @__PURE__ */ React3.createElement(Text, { size: "xs", c: "dimmed" }, POLICY_OUTSIDE_BUILDER_TEXT)
|
|
1324
|
+
) : state.tab === "all" ? /* @__PURE__ */ React3.createElement(RuleSentence, { text: sentence }) : /* @__PURE__ */ React3.createElement(Stack2, { gap: "xs" }, state.rows.length === 0 ? /* @__PURE__ */ React3.createElement(Text, { size: "xs", c: "dimmed" }, POLICY_NO_RULES_TEXT) : state.rows.map((row) => /* @__PURE__ */ React3.createElement(Group2, { key: row.key, gap: "sm", align: "flex-start", wrap: "nowrap" }, /* @__PURE__ */ React3.createElement(
|
|
1325
|
+
Checkbox,
|
|
1326
|
+
{
|
|
1327
|
+
size: "xs",
|
|
1328
|
+
mt: 8,
|
|
1329
|
+
checked: row.selected,
|
|
1330
|
+
disabled: disabled || row.locked,
|
|
1331
|
+
onChange: (event) => patchRow(row.key, { selected: event.currentTarget.checked }),
|
|
1332
|
+
"aria-label": `Count the rule on ${rowTitle(row, ctx)} towards approval`
|
|
1333
|
+
}
|
|
1334
|
+
), /* @__PURE__ */ React3.createElement(Box, { style: { flex: 1 } }, row.rule?.grown && !row.rule.grown.editable ? /* @__PURE__ */ React3.createElement(Text, { size: "xs", c: "dimmed" }, GROUP_OUTSIDE_BUILDER_TEXT) : /* @__PURE__ */ React3.createElement(RuleSentence, { text: rowSentence(row, ctx) })), row.selected && /* @__PURE__ */ React3.createElement(
|
|
1335
|
+
Button2,
|
|
1336
|
+
{
|
|
1337
|
+
size: "compact-xs",
|
|
1338
|
+
variant: "subtle",
|
|
1339
|
+
styles: pinChipStyles(row.pinned),
|
|
1340
|
+
leftSection: icon(IconPin, 12, row.pinned ? ACCENT : void 0),
|
|
1341
|
+
onClick: () => patchRow(row.key, { pinned: !row.pinned }),
|
|
1342
|
+
disabled: disabled || row.locked
|
|
1343
|
+
},
|
|
1344
|
+
POLICY_PIN_LABEL
|
|
1345
|
+
))), state.tab === "atLeast" && state.rows.length > 0 && /* @__PURE__ */ React3.createElement(Group2, { gap: 8, align: "center", wrap: "nowrap" }, /* @__PURE__ */ React3.createElement(
|
|
1346
|
+
BaseNumberInput,
|
|
1347
|
+
{
|
|
1348
|
+
w: 80,
|
|
1349
|
+
styles: chipInput,
|
|
1350
|
+
min: pinnedSelected + 1,
|
|
1351
|
+
max: Math.max(selectedCount, pinnedSelected + 1),
|
|
1352
|
+
value: state.n,
|
|
1353
|
+
onChange: (next) => setN(typeof next === "number" ? next : pinnedSelected + 1),
|
|
1354
|
+
disabled
|
|
1355
|
+
}
|
|
1356
|
+
), /* @__PURE__ */ React3.createElement(Text, { size: "sm", c: "dimmed" }, policyStepperText(selectedCount))), state.rows.length > 0 && /* @__PURE__ */ React3.createElement(
|
|
1357
|
+
BaseTextInput,
|
|
1358
|
+
{
|
|
1359
|
+
label: "What should the claimant be told?",
|
|
1360
|
+
styles: chipInput,
|
|
1361
|
+
placeholder: POLICY_DEFAULT_REASON,
|
|
1362
|
+
description: "They will read this when too few of the rules pass.",
|
|
1363
|
+
value: state.reason,
|
|
1364
|
+
onChange: (event) => setReason(event.currentTarget.value),
|
|
1365
|
+
disabled
|
|
1366
|
+
}
|
|
1367
|
+
), sentence ? /* @__PURE__ */ React3.createElement(RuleSentence, { text: sentence }) : state.rows.length > 0 && /* @__PURE__ */ React3.createElement(Text, { size: "xs", c: "dimmed" }, POLICY_EMPTY_TALLY_TEXT))));
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/LinkedRuleEditor.tsx
|
|
1371
|
+
import React5, { useMemo as useMemo2, useState as useState2 } from "react";
|
|
1372
|
+
import { ActionIcon, Alert, Button as Button3, Group as Group4, Modal, Paper as Paper2, ScrollArea as ScrollArea2, Stack as Stack4, Text as Text3 } from "@mantine/core";
|
|
1373
|
+
import { IconAlertTriangle, IconPlus, IconRobot, IconTrash } from "@tabler/icons-react";
|
|
1374
|
+
|
|
1375
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/ValueInput.tsx
|
|
1376
|
+
import React4, { useMemo } from "react";
|
|
1377
|
+
import { Group as Group3, SegmentedControl as SegmentedControl2, Stack as Stack3, TagsInput, Text as Text2 } from "@mantine/core";
|
|
1378
|
+
import { DateTimePicker } from "@mantine/dates";
|
|
1379
|
+
var CTX_DATES = ["ctx.now", "ctx.collection.startDate", "ctx.collection.endDate", "ctx.claim.submissionDate"];
|
|
1380
|
+
var ValueInput = ({ op, field, index, value, tolerance, onChange, disabled }) => {
|
|
1381
|
+
const kind = field?.kind ?? "text";
|
|
1382
|
+
const setValue = (next) => onChange({ value: next });
|
|
1383
|
+
const refOptions = useMemo(() => {
|
|
1384
|
+
const needed = comparableKind(op, kind);
|
|
1385
|
+
const options = [...index.values()].filter((entry) => entry.kind === needed && entry.path !== field?.path && !entry.path.includes("[*]")).map((entry) => ({ value: entry.path, label: entry.title }));
|
|
1386
|
+
if (needed === "date") options.push(...CTX_DATES.map((token) => ({ value: token, label: CTX_LABEL[token] })));
|
|
1387
|
+
return options;
|
|
1388
|
+
}, [index, op, kind, field?.path]);
|
|
1389
|
+
if (takesNoValue(op)) return null;
|
|
1390
|
+
switch (op) {
|
|
1391
|
+
case "between":
|
|
1392
|
+
return /* @__PURE__ */ React4.createElement(PairInput, { kind: kind === "date" ? "date" : "number", value, onChange: setValue, disabled, labels: ["from", "to"] });
|
|
1393
|
+
case "lengthBetween":
|
|
1394
|
+
return /* @__PURE__ */ React4.createElement(PairInput, { kind: "number", value, onChange: setValue, disabled, labels: ["at least", "at most"] });
|
|
1395
|
+
case "countBetween":
|
|
1396
|
+
return /* @__PURE__ */ React4.createElement(PairInput, { kind: "number", value, onChange: setValue, disabled, labels: ["at least", "at most"] });
|
|
1397
|
+
case "withinDays":
|
|
1398
|
+
case "maxSizeMB":
|
|
1399
|
+
return /* @__PURE__ */ React4.createElement(BaseNumberInput, { w: 140, styles: chipInput, min: 0, value: typeof value === "number" ? value : "", onChange: (next) => setValue(toNumber(next)), disabled });
|
|
1400
|
+
case "approx":
|
|
1401
|
+
return /* @__PURE__ */ React4.createElement(Group3, { gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React4.createElement(LiteralOrRef, { kind: "number", field, index, refOptions, value, onChange: setValue, disabled }), /* @__PURE__ */ React4.createElement(Text2, { size: "xs", c: "dimmed", fw: 500 }, "give or take"), /* @__PURE__ */ React4.createElement(
|
|
1402
|
+
BaseNumberInput,
|
|
1403
|
+
{
|
|
1404
|
+
w: 100,
|
|
1405
|
+
styles: chipInput,
|
|
1406
|
+
min: 0,
|
|
1407
|
+
value: typeof tolerance === "number" ? tolerance : "",
|
|
1408
|
+
onChange: (next) => onChange({ tolerance: toNumber(next) ?? 0 }),
|
|
1409
|
+
disabled
|
|
1410
|
+
}
|
|
1411
|
+
));
|
|
1412
|
+
case "in":
|
|
1413
|
+
case "notIn":
|
|
1414
|
+
case "containsAll":
|
|
1415
|
+
case "containsAny":
|
|
1416
|
+
case "subsetOf":
|
|
1417
|
+
return /* @__PURE__ */ React4.createElement(ListInput, { field, value, onChange: setValue, disabled });
|
|
1418
|
+
case "contains":
|
|
1419
|
+
return /* @__PURE__ */ React4.createElement(LiteralOrRef, { kind: kind === "multichoice" ? "choice" : kind, field, index, refOptions: [], value, onChange: setValue, disabled });
|
|
1420
|
+
case "mediaTypeIn":
|
|
1421
|
+
return /* @__PURE__ */ React4.createElement(
|
|
1422
|
+
BaseMultiSelect,
|
|
1423
|
+
{
|
|
1424
|
+
w: 280,
|
|
1425
|
+
styles: chipInput,
|
|
1426
|
+
placeholder: "Pick the file types you accept",
|
|
1427
|
+
data: MEDIA_TYPES.map((type) => ({ value: type.value, label: type.label })),
|
|
1428
|
+
value: Array.isArray(value) ? value.map(String) : [],
|
|
1429
|
+
onChange: (next) => setValue(next),
|
|
1430
|
+
disabled
|
|
1431
|
+
}
|
|
1432
|
+
);
|
|
1433
|
+
case "matches":
|
|
1434
|
+
return /* @__PURE__ */ React4.createElement(
|
|
1435
|
+
BaseTextInput,
|
|
1436
|
+
{
|
|
1437
|
+
w: 260,
|
|
1438
|
+
styles: chipInput,
|
|
1439
|
+
placeholder: "e.g. ^INV-[0-9]{6}$",
|
|
1440
|
+
description: "A simple pattern the answer has to match.",
|
|
1441
|
+
value: typeof value === "string" ? value : "",
|
|
1442
|
+
onChange: (event) => setValue(event.currentTarget.value),
|
|
1443
|
+
disabled
|
|
1444
|
+
}
|
|
1445
|
+
);
|
|
1446
|
+
case "withinRadius": {
|
|
1447
|
+
const spec = value ?? {};
|
|
1448
|
+
return /* @__PURE__ */ React4.createElement(Group3, { gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React4.createElement(
|
|
1449
|
+
BaseSelect,
|
|
1450
|
+
{
|
|
1451
|
+
w: 200,
|
|
1452
|
+
styles: chipInput,
|
|
1453
|
+
placeholder: "Near which answer?",
|
|
1454
|
+
data: refOptions,
|
|
1455
|
+
value: typeof spec.center === "string" ? spec.center : null,
|
|
1456
|
+
onChange: (next) => setValue({ ...spec, center: next || void 0 }),
|
|
1457
|
+
disabled
|
|
1458
|
+
}
|
|
1459
|
+
), /* @__PURE__ */ React4.createElement(
|
|
1460
|
+
BaseNumberInput,
|
|
1461
|
+
{
|
|
1462
|
+
w: 120,
|
|
1463
|
+
styles: chipInput,
|
|
1464
|
+
min: 1,
|
|
1465
|
+
suffix: " m",
|
|
1466
|
+
value: typeof spec.meters === "number" ? spec.meters : "",
|
|
1467
|
+
onChange: (next) => setValue({ ...spec, meters: toNumber(next) }),
|
|
1468
|
+
disabled
|
|
1469
|
+
}
|
|
1470
|
+
));
|
|
1471
|
+
}
|
|
1472
|
+
case "startsWith":
|
|
1473
|
+
case "endsWith":
|
|
1474
|
+
return /* @__PURE__ */ React4.createElement(BaseTextInput, { w: 220, styles: chipInput, value: typeof value === "string" ? value : "", onChange: (event) => setValue(event.currentTarget.value), disabled });
|
|
1475
|
+
default:
|
|
1476
|
+
return /* @__PURE__ */ React4.createElement(LiteralOrRef, { kind: comparableKind(op, kind), field, index, refOptions, value, onChange: setValue, disabled });
|
|
1477
|
+
}
|
|
1478
|
+
};
|
|
1479
|
+
function comparableKind(op, kind) {
|
|
1480
|
+
if (op === "before" || op === "after") return "date";
|
|
1481
|
+
if (op === ">" || op === ">=" || op === "<" || op === "<=" || op === "approx") return kind === "date" ? "date" : "number";
|
|
1482
|
+
if (op === "withinRadius") return "geo";
|
|
1483
|
+
return kind;
|
|
1484
|
+
}
|
|
1485
|
+
var LiteralOrRef = ({ kind, field, refOptions, value, onChange, disabled }) => {
|
|
1486
|
+
const isRef = typeof value === "string" && (value.startsWith("$") || value.startsWith("ctx.") || value.startsWith("ext."));
|
|
1487
|
+
const mode = isRef ? "ref" : "literal";
|
|
1488
|
+
return /* @__PURE__ */ React4.createElement(Stack3, { gap: 6 }, refOptions.length > 0 && /* @__PURE__ */ React4.createElement(
|
|
1489
|
+
SegmentedControl2,
|
|
1490
|
+
{
|
|
1491
|
+
size: "xs",
|
|
1492
|
+
styles: segmentedPill,
|
|
1493
|
+
value: mode,
|
|
1494
|
+
onChange: (next) => onChange(next === "ref" ? refOptions[0]?.value : void 0),
|
|
1495
|
+
data: [
|
|
1496
|
+
{ value: "literal", label: "A value" },
|
|
1497
|
+
{ value: "ref", label: "Another answer" }
|
|
1498
|
+
],
|
|
1499
|
+
disabled
|
|
1500
|
+
}
|
|
1501
|
+
), mode === "ref" ? /* @__PURE__ */ React4.createElement(
|
|
1502
|
+
BaseSelect,
|
|
1503
|
+
{
|
|
1504
|
+
w: 240,
|
|
1505
|
+
styles: chipInput,
|
|
1506
|
+
placeholder: "Which answer?",
|
|
1507
|
+
data: refOptions,
|
|
1508
|
+
value: typeof value === "string" ? value : null,
|
|
1509
|
+
onChange: (next) => onChange(next || void 0),
|
|
1510
|
+
disabled,
|
|
1511
|
+
searchable: true
|
|
1512
|
+
}
|
|
1513
|
+
) : /* @__PURE__ */ React4.createElement(LiteralInput, { kind, field, value, onChange, disabled }));
|
|
1514
|
+
};
|
|
1515
|
+
var LiteralInput = ({
|
|
1516
|
+
kind,
|
|
1517
|
+
field,
|
|
1518
|
+
value,
|
|
1519
|
+
onChange,
|
|
1520
|
+
disabled
|
|
1521
|
+
}) => {
|
|
1522
|
+
switch (kind) {
|
|
1523
|
+
case "number":
|
|
1524
|
+
return /* @__PURE__ */ React4.createElement(
|
|
1525
|
+
BaseNumberInput,
|
|
1526
|
+
{
|
|
1527
|
+
w: 160,
|
|
1528
|
+
styles: chipInput,
|
|
1529
|
+
placeholder: "0",
|
|
1530
|
+
value: typeof value === "number" ? value : "",
|
|
1531
|
+
onChange: (next) => onChange(toNumber(next)),
|
|
1532
|
+
disabled
|
|
1533
|
+
}
|
|
1534
|
+
);
|
|
1535
|
+
case "date":
|
|
1536
|
+
return /* @__PURE__ */ React4.createElement(
|
|
1537
|
+
DateTimePicker,
|
|
1538
|
+
{
|
|
1539
|
+
w: 240,
|
|
1540
|
+
styles: chipInput,
|
|
1541
|
+
placeholder: "Pick a date",
|
|
1542
|
+
valueFormat: "DD MMM YYYY, HH:mm",
|
|
1543
|
+
clearable: true,
|
|
1544
|
+
value: isoToDate(value),
|
|
1545
|
+
onChange: (next) => onChange(dateToIso(next)),
|
|
1546
|
+
disabled
|
|
1547
|
+
}
|
|
1548
|
+
);
|
|
1549
|
+
case "boolean":
|
|
1550
|
+
return /* @__PURE__ */ React4.createElement(
|
|
1551
|
+
BaseSelect,
|
|
1552
|
+
{
|
|
1553
|
+
w: 140,
|
|
1554
|
+
styles: chipInput,
|
|
1555
|
+
data: [
|
|
1556
|
+
{ value: "true", label: "Yes" },
|
|
1557
|
+
{ value: "false", label: "No" }
|
|
1558
|
+
],
|
|
1559
|
+
value: typeof value === "boolean" ? String(value) : null,
|
|
1560
|
+
onChange: (next) => onChange(next === null ? void 0 : next === "true"),
|
|
1561
|
+
disabled
|
|
1562
|
+
}
|
|
1563
|
+
);
|
|
1564
|
+
case "choice":
|
|
1565
|
+
case "multichoice":
|
|
1566
|
+
if (field?.choices?.length) {
|
|
1567
|
+
return /* @__PURE__ */ React4.createElement(
|
|
1568
|
+
BaseSelect,
|
|
1569
|
+
{
|
|
1570
|
+
w: 240,
|
|
1571
|
+
styles: chipInput,
|
|
1572
|
+
placeholder: "Pick an option",
|
|
1573
|
+
data: field.choices.map((choice) => ({ value: choice.value, label: choice.text })),
|
|
1574
|
+
value: typeof value === "string" ? value : null,
|
|
1575
|
+
onChange: (next) => onChange(next ?? void 0),
|
|
1576
|
+
disabled,
|
|
1577
|
+
searchable: true
|
|
1578
|
+
}
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
return /* @__PURE__ */ React4.createElement(BaseTextInput, { w: 240, styles: chipInput, value: typeof value === "string" ? value : "", onChange: (event) => onChange(event.currentTarget.value), disabled });
|
|
1582
|
+
default:
|
|
1583
|
+
return /* @__PURE__ */ React4.createElement(
|
|
1584
|
+
BaseTextInput,
|
|
1585
|
+
{
|
|
1586
|
+
w: 240,
|
|
1587
|
+
styles: chipInput,
|
|
1588
|
+
placeholder: "Type the value",
|
|
1589
|
+
value: typeof value === "string" ? value : "",
|
|
1590
|
+
onChange: (event) => onChange(event.currentTarget.value),
|
|
1591
|
+
disabled
|
|
1592
|
+
}
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
};
|
|
1596
|
+
var ListInput = ({ field, value, onChange, disabled }) => {
|
|
1597
|
+
const current = Array.isArray(value) ? value.map(String) : [];
|
|
1598
|
+
if (field?.choices?.length) {
|
|
1599
|
+
return /* @__PURE__ */ React4.createElement(
|
|
1600
|
+
BaseMultiSelect,
|
|
1601
|
+
{
|
|
1602
|
+
w: 300,
|
|
1603
|
+
styles: chipInput,
|
|
1604
|
+
placeholder: "Pick the options you allow",
|
|
1605
|
+
data: field.choices.map((choice) => ({ value: choice.value, label: choice.text })),
|
|
1606
|
+
value: current,
|
|
1607
|
+
onChange: (next) => onChange(next),
|
|
1608
|
+
disabled,
|
|
1609
|
+
searchable: true
|
|
1610
|
+
}
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
const numeric = field?.kind === "number";
|
|
1614
|
+
return /* @__PURE__ */ React4.createElement(
|
|
1615
|
+
TagsInput,
|
|
1616
|
+
{
|
|
1617
|
+
w: 300,
|
|
1618
|
+
styles: chipInput,
|
|
1619
|
+
placeholder: "Type a value, press Enter",
|
|
1620
|
+
value: current,
|
|
1621
|
+
onChange: (next) => onChange(numeric ? next.map(Number).filter((entry) => !Number.isNaN(entry)) : next),
|
|
1622
|
+
disabled
|
|
1623
|
+
}
|
|
1624
|
+
);
|
|
1625
|
+
};
|
|
1626
|
+
var PairInput = ({
|
|
1627
|
+
kind,
|
|
1628
|
+
value,
|
|
1629
|
+
onChange,
|
|
1630
|
+
disabled,
|
|
1631
|
+
labels
|
|
1632
|
+
}) => {
|
|
1633
|
+
const pair = Array.isArray(value) ? value : [void 0, void 0];
|
|
1634
|
+
const setAt = (at, next) => {
|
|
1635
|
+
const updated = [pair[0], pair[1]];
|
|
1636
|
+
updated[at] = next;
|
|
1637
|
+
onChange(updated);
|
|
1638
|
+
};
|
|
1639
|
+
return /* @__PURE__ */ React4.createElement(Group3, { gap: "xs", wrap: "nowrap", align: "flex-end" }, [0, 1].map((at) => /* @__PURE__ */ React4.createElement(Stack3, { key: at, gap: 2 }, /* @__PURE__ */ React4.createElement(Text2, { size: "xs", c: "dimmed", fw: 500 }, labels[at]), kind === "date" ? /* @__PURE__ */ React4.createElement(
|
|
1640
|
+
DateTimePicker,
|
|
1641
|
+
{
|
|
1642
|
+
w: 190,
|
|
1643
|
+
styles: chipInput,
|
|
1644
|
+
valueFormat: "DD MMM YYYY, HH:mm",
|
|
1645
|
+
clearable: true,
|
|
1646
|
+
value: isoToDate(pair[at]),
|
|
1647
|
+
onChange: (next) => setAt(at, dateToIso(next)),
|
|
1648
|
+
disabled
|
|
1649
|
+
}
|
|
1650
|
+
) : /* @__PURE__ */ React4.createElement(
|
|
1651
|
+
BaseNumberInput,
|
|
1652
|
+
{
|
|
1653
|
+
w: 110,
|
|
1654
|
+
styles: chipInput,
|
|
1655
|
+
value: typeof pair[at] === "number" ? pair[at] : "",
|
|
1656
|
+
onChange: (next) => setAt(at, toNumber(next)),
|
|
1657
|
+
disabled
|
|
1658
|
+
}
|
|
1659
|
+
))));
|
|
1660
|
+
};
|
|
1661
|
+
function isoToDate(value) {
|
|
1662
|
+
if (typeof value !== "string" || !value) return null;
|
|
1663
|
+
const date = new Date(value);
|
|
1664
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
1665
|
+
}
|
|
1666
|
+
function dateToIso(value) {
|
|
1667
|
+
if (!value) return void 0;
|
|
1668
|
+
try {
|
|
1669
|
+
return value.toISOString();
|
|
1670
|
+
} catch {
|
|
1671
|
+
return void 0;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
function toNumber(value) {
|
|
1675
|
+
if (value === "" || value === null || value === void 0) return void 0;
|
|
1676
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
1677
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/ruleDraft.ts
|
|
1681
|
+
var SCORE_BANDS_DEFAULT = { rejectBelow: 30, approveAt: 80 };
|
|
1682
|
+
function clampBands(bands) {
|
|
1683
|
+
const approveAt = Math.min(100, Math.max(1, Math.round(bands.approveAt)));
|
|
1684
|
+
const rejectBelow = Math.min(approveAt - 1, Math.max(0, Math.round(bands.rejectBelow)));
|
|
1685
|
+
return { rejectBelow, approveAt };
|
|
1686
|
+
}
|
|
1687
|
+
function extSubjectRef(ext) {
|
|
1688
|
+
return `ext.${ext.name}.${ext.expect === "score" ? "score" : "valid"}`;
|
|
1689
|
+
}
|
|
1690
|
+
function sendKey(fieldPath) {
|
|
1691
|
+
return fieldPath.replace(/^\$/, "").replace(/\[\*\]/g, "").replace(/[^A-Za-z0-9_-]+/g, "_").replace(/_{2,}/g, "_").replace(/^[_-]+|[_-]+$/g, "") || "answer";
|
|
1692
|
+
}
|
|
1693
|
+
function autoExtName(sendField, sources) {
|
|
1694
|
+
const base = `${sendKey(sendField)}_check`;
|
|
1695
|
+
const taken = new Set((sources ?? []).map((source) => source.name));
|
|
1696
|
+
if (!taken.has(base)) return base;
|
|
1697
|
+
for (let n = 2; ; n++) {
|
|
1698
|
+
const name = `${base}_${n}`;
|
|
1699
|
+
if (!taken.has(name)) return name;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
function extSourceFromDraft(ext) {
|
|
1703
|
+
return { "@type": "ExternalSource", name: ext.name, endpoint: ext.endpoint, audience: ext.audience, send: { [sendKey(ext.sendField)]: ext.sendField }, expect: ext.expect };
|
|
1704
|
+
}
|
|
1705
|
+
function upsertExtSource(body, ext) {
|
|
1706
|
+
const source = extSourceFromDraft(ext);
|
|
1707
|
+
const sources = [...body.sources ?? []];
|
|
1708
|
+
const at = sources.findIndex((entry) => entry.name === source.name);
|
|
1709
|
+
if (at < 0) sources.push(source);
|
|
1710
|
+
else sources[at] = source;
|
|
1711
|
+
return { ...body, sources };
|
|
1712
|
+
}
|
|
1713
|
+
function withExtSubject(draft, ext) {
|
|
1714
|
+
const ref = extSubjectRef(ext);
|
|
1715
|
+
if (ext.expect === "score") {
|
|
1716
|
+
const bands = clampBands(ext.bands ?? SCORE_BANDS_DEFAULT);
|
|
1717
|
+
return {
|
|
1718
|
+
...draft,
|
|
1719
|
+
ext: { ...ext, bands },
|
|
1720
|
+
ai: void 0,
|
|
1721
|
+
field: ref,
|
|
1722
|
+
consequence: "reject",
|
|
1723
|
+
reject: { ...draft.reject, when: { field: ref, op: ">=", value: bands.approveAt * 100 }, onFail: "review", verdictClass: "requires_human_review" }
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
if (draft.ext && draft.field === ref) return { ...draft, ext };
|
|
1727
|
+
return {
|
|
1728
|
+
...draft,
|
|
1729
|
+
ext,
|
|
1730
|
+
field: ref,
|
|
1731
|
+
consequence: "reject",
|
|
1732
|
+
// Fresh lane entry, a rename, or a score flipped back to yes/no — the fixed
|
|
1733
|
+
// check with the lane's default ending, never a leftover score ending.
|
|
1734
|
+
reject: { ...draft.reject, when: { field: ref, op: "isTrue" }, onFail: "reject", verdictClass: "refuted" }
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
function aiSubjectRef(ai) {
|
|
1738
|
+
return `ai.${ai.name}.valid`;
|
|
1739
|
+
}
|
|
1740
|
+
function autoAiName(sendField, aiChecks, alsoTaken = []) {
|
|
1741
|
+
const base = `${sendKey(sendField)}_ai`;
|
|
1742
|
+
const taken = /* @__PURE__ */ new Set([...(aiChecks ?? []).map((check) => check.name), ...alsoTaken]);
|
|
1743
|
+
if (!taken.has(base)) return base;
|
|
1744
|
+
for (let n = 2; ; n++) {
|
|
1745
|
+
const name = `${base}_${n}`;
|
|
1746
|
+
if (!taken.has(name)) return name;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
function aiSendOf(sendField, alsoSend) {
|
|
1750
|
+
const send = {};
|
|
1751
|
+
const sent = /* @__PURE__ */ new Set();
|
|
1752
|
+
for (const field of [sendField, ...alsoSend ?? []]) {
|
|
1753
|
+
if (!field || sent.has(field)) continue;
|
|
1754
|
+
sent.add(field);
|
|
1755
|
+
const base = sendKey(field);
|
|
1756
|
+
let key = base;
|
|
1757
|
+
for (let n = 2; key in send; n++) key = `${base}_${n}`;
|
|
1758
|
+
send[key] = field;
|
|
1759
|
+
}
|
|
1760
|
+
return send;
|
|
1761
|
+
}
|
|
1762
|
+
function aiCheckFromDraft(ai) {
|
|
1763
|
+
return {
|
|
1764
|
+
"@type": "AiCheck",
|
|
1765
|
+
name: ai.name,
|
|
1766
|
+
send: aiSendOf(ai.sendField, ai.alsoSend),
|
|
1767
|
+
instructions: ai.instructions.map((rule) => rule.trim()).filter((rule) => rule.length > 0),
|
|
1768
|
+
expect: "boolean"
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
function putAiCheckNode(body, check) {
|
|
1772
|
+
const aiChecks = [...body.aiChecks ?? []];
|
|
1773
|
+
const at = aiChecks.findIndex((entry) => entry.name === check.name);
|
|
1774
|
+
if (at < 0) aiChecks.push(check);
|
|
1775
|
+
else aiChecks[at] = check;
|
|
1776
|
+
return { ...body, aiChecks };
|
|
1777
|
+
}
|
|
1778
|
+
function upsertAiCheck(body, ai) {
|
|
1779
|
+
return putAiCheckNode(body, aiCheckFromDraft(ai));
|
|
1780
|
+
}
|
|
1781
|
+
function aiDraftFromCheck(check) {
|
|
1782
|
+
const sendFields = aiSendFields(check);
|
|
1783
|
+
if (!sendFields) return void 0;
|
|
1784
|
+
return {
|
|
1785
|
+
name: check.name,
|
|
1786
|
+
instructions: check.instructions,
|
|
1787
|
+
sendField: sendFields[0],
|
|
1788
|
+
...sendFields.length > 1 ? { alsoSend: sendFields.slice(1) } : {}
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
function withAiSubject(draft, ai) {
|
|
1792
|
+
const ref = aiSubjectRef(ai);
|
|
1793
|
+
if (draft.ai && draft.field === ref) return { ...draft, ai };
|
|
1794
|
+
return {
|
|
1795
|
+
...draft,
|
|
1796
|
+
ai,
|
|
1797
|
+
ext: void 0,
|
|
1798
|
+
field: ref,
|
|
1799
|
+
consequence: "reject",
|
|
1800
|
+
reject: { ...draft.reject, when: { field: ref, op: "isTrue" } }
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
var AI_SUBJECT_REF = /^ai\.([A-Za-z0-9_-]+)\.(valid|reason)$/;
|
|
1804
|
+
var EXT_SUBJECT_REF = /^ext\.([A-Za-z0-9_-]+)\.(valid|score|reason)$/;
|
|
1805
|
+
function checkRefOfRule(rule, pattern) {
|
|
1806
|
+
if (rule.consequence !== "reject") return void 0;
|
|
1807
|
+
const candidate = toSimpleRule(rule.gate.when)?.field;
|
|
1808
|
+
return candidate && pattern.test(candidate) ? candidate : void 0;
|
|
1809
|
+
}
|
|
1810
|
+
function newDraft(body, field, op = "present") {
|
|
1811
|
+
return {
|
|
1812
|
+
code: nextCode(body, "RULE", field),
|
|
1813
|
+
field,
|
|
1814
|
+
consequence: "reject",
|
|
1815
|
+
reject: { when: { field, op }, reason: "", onFail: "reject", verdictClass: "refuted" },
|
|
1816
|
+
human: {
|
|
1817
|
+
whenMissing: FAILED,
|
|
1818
|
+
taskId: nextCode(body, "ASK", field),
|
|
1819
|
+
prompt: "",
|
|
1820
|
+
show: [field],
|
|
1821
|
+
answer: { type: "boolean" },
|
|
1822
|
+
map: { true: "approve", false: "reject" },
|
|
1823
|
+
// The shared reviewer routing, as it stands — so saving without touching
|
|
1824
|
+
// the picker writes back exactly what was there.
|
|
1825
|
+
assigneeDid: body?.review?.assignTo?.assigneeDid,
|
|
1826
|
+
channelRoomId: body?.review?.route?.matrixRoom
|
|
1827
|
+
}
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
function draftFromRule(rule, body) {
|
|
1831
|
+
if (rule.consequence === "reject" && rule.grown?.editable) {
|
|
1832
|
+
const base = plainDraftFromRule(rule, body);
|
|
1833
|
+
return {
|
|
1834
|
+
...base,
|
|
1835
|
+
grown: {
|
|
1836
|
+
members: rule.grown.members.map((member) => {
|
|
1837
|
+
const name2 = aiMemberName(member);
|
|
1838
|
+
const check = name2 ? body?.aiChecks?.find((entry) => entry.name === name2) : void 0;
|
|
1839
|
+
const ai = check ? aiDraftFromCheck(check) : void 0;
|
|
1840
|
+
return ai ? { kind: "ai", ai } : { kind: "check", rule: member };
|
|
1841
|
+
}),
|
|
1842
|
+
verdictClass: gateClassOf(rule.gate.class)
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
const draft = plainDraftFromRule(rule, body);
|
|
1847
|
+
const aiRef = checkRefOfRule(rule, AI_SUBJECT_REF);
|
|
1848
|
+
const aiName = aiRef ? AI_SUBJECT_REF.exec(aiRef)?.[1] : void 0;
|
|
1849
|
+
const aiCheck = aiName ? body?.aiChecks?.find((entry) => entry.name === aiName) : void 0;
|
|
1850
|
+
if (aiRef && aiCheck) {
|
|
1851
|
+
const sendField2 = Object.values(aiCheck.send).find((value) => typeof value === "string" && value.startsWith("$"));
|
|
1852
|
+
const when = draft.reject.when.field === aiRef ? draft.reject.when : { field: aiRef, op: "isTrue" };
|
|
1853
|
+
return {
|
|
1854
|
+
...draft,
|
|
1855
|
+
field: aiRef,
|
|
1856
|
+
consequence: "reject",
|
|
1857
|
+
reject: { ...draft.reject, when },
|
|
1858
|
+
// Every sent answer loads back — anchor first, the rest into the picker.
|
|
1859
|
+
// A hand-authored send the pickers can't represent falls back to the
|
|
1860
|
+
// anchor-only read it always had.
|
|
1861
|
+
ai: aiDraftFromCheck(aiCheck) ?? { name: aiCheck.name, instructions: aiCheck.instructions, sendField: sendField2 ?? rule.fieldPath }
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
const extRef = checkRefOfRule(rule, EXT_SUBJECT_REF);
|
|
1865
|
+
const name = extRef ? EXT_SUBJECT_REF.exec(extRef)?.[1] : void 0;
|
|
1866
|
+
const source = name ? body?.sources?.find((entry) => entry.name === name) : void 0;
|
|
1867
|
+
if (!extRef || !source) return draft;
|
|
1868
|
+
const sendField = Object.values(source.send).find((value) => typeof value === "string" && value.startsWith("$"));
|
|
1869
|
+
if (source.expect === "score") {
|
|
1870
|
+
const bands = rule.consequence === "reject" ? rule.bands : void 0;
|
|
1871
|
+
if (!bands) return draft;
|
|
1872
|
+
return {
|
|
1873
|
+
...draft,
|
|
1874
|
+
field: extRef,
|
|
1875
|
+
consequence: "reject",
|
|
1876
|
+
reject: {
|
|
1877
|
+
...draft.reject,
|
|
1878
|
+
when: { field: extRef, op: ">=", value: bands.approveAt },
|
|
1879
|
+
onFail: "review",
|
|
1880
|
+
// The author's message lives on the FLOOR gate — the hold gate's
|
|
1881
|
+
// reason is generated and never shown as theirs.
|
|
1882
|
+
reason: bands.floor?.gate.reason ?? ""
|
|
1883
|
+
},
|
|
1884
|
+
ext: {
|
|
1885
|
+
name: source.name,
|
|
1886
|
+
endpoint: source.endpoint,
|
|
1887
|
+
audience: source.audience,
|
|
1888
|
+
expect: "score",
|
|
1889
|
+
sendField: sendField ?? rule.fieldPath,
|
|
1890
|
+
// Document bps → the author's 0–100 scale (hand-authored odd values
|
|
1891
|
+
// snap to whole points on the next save).
|
|
1892
|
+
bands: { rejectBelow: Math.round(bands.rejectBelow / 100), approveAt: Math.round(bands.approveAt / 100) },
|
|
1893
|
+
...bands.floor ? { floorCode: bands.floor.code } : {}
|
|
1894
|
+
}
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
return {
|
|
1898
|
+
...draft,
|
|
1899
|
+
field: extRef,
|
|
1900
|
+
ext: { name: source.name, endpoint: source.endpoint, audience: source.audience, expect: "boolean", sendField: sendField ?? rule.fieldPath }
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
function plainDraftFromRule(rule, body) {
|
|
1904
|
+
const base = newDraft(body, rule.fieldPath);
|
|
1905
|
+
base.at = rule.index;
|
|
1906
|
+
base.code = rule.code;
|
|
1907
|
+
switch (rule.consequence) {
|
|
1908
|
+
case "reject":
|
|
1909
|
+
return {
|
|
1910
|
+
...base,
|
|
1911
|
+
consequence: "reject",
|
|
1912
|
+
appliesWhen: toSimpleRule(rule.gate.appliesWhen),
|
|
1913
|
+
reject: {
|
|
1914
|
+
when: toSimpleRule(rule.gate.when) ?? base.reject.when,
|
|
1915
|
+
reason: rule.gate.reason,
|
|
1916
|
+
onFail: rule.gate.onFail ?? "reject",
|
|
1917
|
+
verdictClass: gateClassOf(rule.gate.class)
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
case "human": {
|
|
1921
|
+
const manual = rule.criterion.score;
|
|
1922
|
+
return {
|
|
1923
|
+
...base,
|
|
1924
|
+
consequence: "human",
|
|
1925
|
+
appliesWhen: toSimpleRule(rule.criterion.appliesWhen),
|
|
1926
|
+
human: {
|
|
1927
|
+
...base.human,
|
|
1928
|
+
// carries the shared reviewer (assigneeDid)
|
|
1929
|
+
// Anything that isn't a clean pass reads as a fail — the control only
|
|
1930
|
+
// offers the two, so a legacy in-between value settles on one.
|
|
1931
|
+
whenMissing: manual.whenMissing === PASSED ? PASSED : FAILED,
|
|
1932
|
+
taskId: rule.task?.id ?? base.human.taskId,
|
|
1933
|
+
prompt: rule.task?.prompt ?? "",
|
|
1934
|
+
show: rule.task?.show ?? [rule.fieldPath],
|
|
1935
|
+
answer: rule.task?.answer ?? base.human.answer,
|
|
1936
|
+
map: rule.task?.map ?? base.human.map
|
|
1937
|
+
}
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
default:
|
|
1941
|
+
return base;
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
function applyDraft(body, draft, editing, fieldTitle2) {
|
|
1945
|
+
const withSource = draft.ext ? upsertExtSource(body, draft.ext) : draft.ai ? upsertAiCheck(body, draft.ai) : body;
|
|
1946
|
+
const moved = editing !== void 0 && editing.consequence !== draft.consequence;
|
|
1947
|
+
const target = moved ? removeRule(withSource, editing) : withSource;
|
|
1948
|
+
const at = moved ? void 0 : draft.at;
|
|
1949
|
+
const appliesWhen = draft.appliesWhen ? { appliesWhen: toCondition(draft.appliesWhen) } : {};
|
|
1950
|
+
switch (draft.consequence) {
|
|
1951
|
+
case "reject": {
|
|
1952
|
+
const ownedBands = !moved && editing?.consequence === "reject" && editing.bands ? editing.bands.indices : void 0;
|
|
1953
|
+
const base = ownedBands ? removeGatesAt(target, ownedBands) : target;
|
|
1954
|
+
const putRejectGates = (b, add) => {
|
|
1955
|
+
if (ownedBands) {
|
|
1956
|
+
const gates2 = [...b.gates ?? []];
|
|
1957
|
+
gates2.splice(Math.min(...ownedBands), 0, ...add);
|
|
1958
|
+
return { ...b, gates: gates2 };
|
|
1959
|
+
}
|
|
1960
|
+
if (add.length === 1) return putGate(b, add[0], at);
|
|
1961
|
+
const gates = [...b.gates ?? []];
|
|
1962
|
+
if (at === void 0) gates.push(...add);
|
|
1963
|
+
else gates.splice(at, 1, ...add);
|
|
1964
|
+
return { ...b, gates };
|
|
1965
|
+
};
|
|
1966
|
+
if (draft.ext?.expect === "score") {
|
|
1967
|
+
const bands = clampBands(draft.ext.bands ?? SCORE_BANDS_DEFAULT);
|
|
1968
|
+
const ref = extSubjectRef(draft.ext);
|
|
1969
|
+
const hold = {
|
|
1970
|
+
"@type": "Gate",
|
|
1971
|
+
code: draft.code,
|
|
1972
|
+
reason: SCORE_HOLD_REASON,
|
|
1973
|
+
...appliesWhen,
|
|
1974
|
+
when: { "@type": "Condition", field: ref, op: ">=", value: bands.approveAt * 100 },
|
|
1975
|
+
onFail: "review",
|
|
1976
|
+
class: "requires_human_review"
|
|
1977
|
+
};
|
|
1978
|
+
if (bands.rejectBelow <= 0) return putRejectGates(base, [hold]);
|
|
1979
|
+
const floor = {
|
|
1980
|
+
"@type": "Gate",
|
|
1981
|
+
code: draft.ext.floorCode ?? nextCode(base, "RULE", draft.ext.sendField, [draft.code]),
|
|
1982
|
+
reason: draft.reject.reason.trim() || SCORE_FLOOR_REASON,
|
|
1983
|
+
...appliesWhen,
|
|
1984
|
+
when: { "@type": "Condition", field: ref, op: ">=", value: bands.rejectBelow * 100 },
|
|
1985
|
+
onFail: "reject",
|
|
1986
|
+
class: "refuted"
|
|
1987
|
+
};
|
|
1988
|
+
return putRejectGates(base, [floor, hold]);
|
|
1989
|
+
}
|
|
1990
|
+
const grown = draft.grown;
|
|
1991
|
+
if (grown && grown.members.length >= 2) {
|
|
1992
|
+
const withMemberChecks = grown.members.reduce((acc, member) => member.kind === "ai" ? upsertAiCheck(acc, member.ai) : acc, base);
|
|
1993
|
+
return putRejectGates(withMemberChecks, [
|
|
1994
|
+
{
|
|
1995
|
+
"@type": "Gate",
|
|
1996
|
+
code: draft.code,
|
|
1997
|
+
reason: draft.reject.reason.trim() || "This claim does not meet the rules",
|
|
1998
|
+
...appliesWhen,
|
|
1999
|
+
when: grownWhen(grown),
|
|
2000
|
+
onFail: draft.reject.onFail,
|
|
2001
|
+
class: grown.verdictClass ?? "refuted"
|
|
2002
|
+
}
|
|
2003
|
+
]);
|
|
2004
|
+
}
|
|
2005
|
+
return putRejectGates(base, [
|
|
2006
|
+
{
|
|
2007
|
+
"@type": "Gate",
|
|
2008
|
+
code: draft.code,
|
|
2009
|
+
reason: draft.reject.reason.trim() || "This claim does not meet the rules",
|
|
2010
|
+
...appliesWhen,
|
|
2011
|
+
when: toCondition(draft.reject.when),
|
|
2012
|
+
onFail: draft.reject.onFail,
|
|
2013
|
+
class: draft.reject.verdictClass
|
|
2014
|
+
}
|
|
2015
|
+
]);
|
|
2016
|
+
}
|
|
2017
|
+
case "human": {
|
|
2018
|
+
const task = {
|
|
2019
|
+
"@type": "ReviewTask",
|
|
2020
|
+
id: draft.human.taskId,
|
|
2021
|
+
forCriterion: draft.code,
|
|
2022
|
+
prompt: draft.human.prompt.trim() || "Does this look right?",
|
|
2023
|
+
show: draft.human.show,
|
|
2024
|
+
answer: draft.human.answer,
|
|
2025
|
+
map: draft.human.map
|
|
2026
|
+
};
|
|
2027
|
+
const withCriterion = putCriterion(
|
|
2028
|
+
target,
|
|
2029
|
+
{
|
|
2030
|
+
"@type": "Criterion",
|
|
2031
|
+
code: draft.code,
|
|
2032
|
+
reason: reasonFor(draft, fieldTitle2),
|
|
2033
|
+
// `weight` is REQUIRED by the format and read by `weighted_average`
|
|
2034
|
+
// alone — inert under `all_pass`, which is the only method v1 writes.
|
|
2035
|
+
// A constant keeps it out of the author's way without breaking shape.
|
|
2036
|
+
weight: 1,
|
|
2037
|
+
...appliesWhen,
|
|
2038
|
+
score: { "@type": "ManualScore", field: draft.field, whenMissing: draft.human.whenMissing, reviewTask: task.id }
|
|
2039
|
+
},
|
|
2040
|
+
at
|
|
2041
|
+
);
|
|
2042
|
+
const withTask = putReviewTask(withCriterion, task);
|
|
2043
|
+
const { route: _route, ...review } = withTask.review;
|
|
2044
|
+
const assignTo = { role: review.assignTo?.role || "reviewer", ...draft.human.assigneeDid ? { assigneeDid: draft.human.assigneeDid } : {} };
|
|
2045
|
+
return {
|
|
2046
|
+
...withTask,
|
|
2047
|
+
review: { ...review, assignTo, ...draft.human.channelRoomId ? { route: { matrixRoom: draft.human.channelRoomId } } : {} }
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
default:
|
|
2051
|
+
return body;
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
function reasonFor(draft, fieldTitle2) {
|
|
2055
|
+
const question = fieldTitle2?.trim() || (draft.ext ? `the ${draft.ext.name} check` : draft.ai ? "the AI check" : plainField(draft.field));
|
|
2056
|
+
return `A person checked ${question}`;
|
|
2057
|
+
}
|
|
2058
|
+
function plainField(field) {
|
|
2059
|
+
return field.replace(/^\$/, "").replace(/\[\*\]/g, "").split(".").map((seg) => seg.includes(":") ? seg.split(":").pop() || seg : seg).join(" ");
|
|
2060
|
+
}
|
|
2061
|
+
function gateClassOf(value) {
|
|
2062
|
+
return value ?? "refuted";
|
|
2063
|
+
}
|
|
2064
|
+
function growDraft(draft, seedField) {
|
|
2065
|
+
if (draft.grown || draft.ext || draft.consequence !== "reject") return draft;
|
|
2066
|
+
const first = draft.ai ? { kind: "ai", ai: draft.ai } : { kind: "check", rule: draft.reject.when };
|
|
2067
|
+
return { ...draft, ai: void 0, grown: { members: [first, { kind: "check", rule: { field: seedField, op: "present" } }] } };
|
|
2068
|
+
}
|
|
2069
|
+
function addGrownMember(draft, seedField) {
|
|
2070
|
+
if (!draft.grown) return draft;
|
|
2071
|
+
return { ...draft, grown: { ...draft.grown, members: [...draft.grown.members, { kind: "check", rule: { field: seedField, op: "present" } }] } };
|
|
2072
|
+
}
|
|
2073
|
+
function removeGrownMember(draft, at) {
|
|
2074
|
+
const grown = draft.grown;
|
|
2075
|
+
if (!grown) return draft;
|
|
2076
|
+
const members = grown.members.filter((_, i) => i !== at);
|
|
2077
|
+
if (members.length >= 2) return { ...draft, grown: { ...grown, members } };
|
|
2078
|
+
const [last] = members;
|
|
2079
|
+
const base = { ...draft, grown: void 0 };
|
|
2080
|
+
if (!last) return base;
|
|
2081
|
+
if (last.kind === "ai") return withAiSubject(base, last.ai);
|
|
2082
|
+
const show = last.rule.field.startsWith("$") && !base.human.show.includes(last.rule.field) ? [last.rule.field] : base.human.show;
|
|
2083
|
+
return { ...base, ai: void 0, field: last.rule.field, reject: { ...base.reject, when: last.rule }, human: { ...base.human, show } };
|
|
2084
|
+
}
|
|
2085
|
+
function memberCondition(member) {
|
|
2086
|
+
return member.kind === "ai" ? { "@type": "Condition", field: aiSubjectRef(member.ai), op: "isTrue" } : toCondition(member.rule);
|
|
2087
|
+
}
|
|
2088
|
+
function grownWhen(grown) {
|
|
2089
|
+
return { "@type": "Condition", all: grown.members.map(memberCondition) };
|
|
2090
|
+
}
|
|
2091
|
+
function newLinkedDraft(checkedField, op, guardField) {
|
|
2092
|
+
return { checkedField, op, guardField, reason: "", cases: [{}], elseCase: {}, onFail: "reject", verdictClass: "refuted" };
|
|
2093
|
+
}
|
|
2094
|
+
function linkedReason(prefix, caseText) {
|
|
2095
|
+
return `${prefix} \u2014 ${caseText}`;
|
|
2096
|
+
}
|
|
2097
|
+
function draftFromLinked(linked, fields) {
|
|
2098
|
+
const first = linked.cases[0].gate.reason;
|
|
2099
|
+
const suffix = ` \u2014 ${linkedCaseText(linked.guardField, linked.cases[0].guardValue, { fields })}`;
|
|
2100
|
+
const reason = first.endsWith(suffix) ? first.slice(0, -suffix.length) : first;
|
|
2101
|
+
return {
|
|
2102
|
+
checkedField: linked.checkedField,
|
|
2103
|
+
// An AI table's gates carry no value check, so `op` holds nothing to keep —
|
|
2104
|
+
// '==' is the neutral seat the editor re-legalises if the author switches
|
|
2105
|
+
// the table back to values.
|
|
2106
|
+
op: linked.ai ? "==" : linked.op,
|
|
2107
|
+
guardField: linked.guardField,
|
|
2108
|
+
reason,
|
|
2109
|
+
cases: linked.cases.map((entry) => ({
|
|
2110
|
+
guardValue: entry.guardValue,
|
|
2111
|
+
value: entry.when.value,
|
|
2112
|
+
tolerance: entry.when.tolerance,
|
|
2113
|
+
...linked.ai ? { instructions: entry.instructions, aiName: entry.aiName } : {},
|
|
2114
|
+
code: entry.code
|
|
2115
|
+
})),
|
|
2116
|
+
elseCase: {
|
|
2117
|
+
value: linked.elseCase.when.value,
|
|
2118
|
+
tolerance: linked.elseCase.when.tolerance,
|
|
2119
|
+
...linked.ai ? { instructions: linked.elseCase.instructions, aiName: linked.elseCase.aiName } : {},
|
|
2120
|
+
code: linked.elseCase.code
|
|
2121
|
+
},
|
|
2122
|
+
onFail: linked.onFail,
|
|
2123
|
+
verdictClass: linked.gateClass,
|
|
2124
|
+
editing: linked.indices,
|
|
2125
|
+
...linked.ai ? { aiMode: true, ...linked.ai.alsoSend.length > 0 ? { alsoSend: linked.ai.alsoSend } : {} } : {}
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
function applyLinkedDraft(body, draft, fields) {
|
|
2129
|
+
const at = draft.editing?.length ? Math.min(...draft.editing) : void 0;
|
|
2130
|
+
const base = draft.editing?.length ? removeGatesAt(body, draft.editing) : body;
|
|
2131
|
+
const prefix = draft.reason.trim() || "This claim does not meet the rules";
|
|
2132
|
+
const minted = [];
|
|
2133
|
+
const codeOf = (row) => {
|
|
2134
|
+
const code = row.code ?? nextCode(base, "RULE", draft.checkedField, minted);
|
|
2135
|
+
minted.push(code);
|
|
2136
|
+
return code;
|
|
2137
|
+
};
|
|
2138
|
+
const inGuard = (row) => ({ "@type": "Condition", field: draft.guardField, op: "in", value: [row.guardValue] });
|
|
2139
|
+
const elseGuard = { "@type": "Condition", field: draft.guardField, op: "notIn", value: draft.cases.map((row) => row.guardValue) };
|
|
2140
|
+
if (draft.aiMode) {
|
|
2141
|
+
const mintedNames = [];
|
|
2142
|
+
const nameOf = (row) => {
|
|
2143
|
+
const name = row.aiName ?? autoAiName(draft.checkedField, base.aiChecks, mintedNames);
|
|
2144
|
+
mintedNames.push(name);
|
|
2145
|
+
return name;
|
|
2146
|
+
};
|
|
2147
|
+
const send = aiSendOf(draft.checkedField, draft.alsoSend);
|
|
2148
|
+
const rows = [
|
|
2149
|
+
...draft.cases.map((row) => ({ row, appliesWhen: inGuard(row), caseText: linkedCaseText(draft.guardField, row.guardValue, { fields }) })),
|
|
2150
|
+
{ row: draft.elseCase, appliesWhen: elseGuard, caseText: linkedElseText() }
|
|
2151
|
+
];
|
|
2152
|
+
const checks = [];
|
|
2153
|
+
const aiGates = rows.map(({ row, appliesWhen, caseText }) => {
|
|
2154
|
+
const name = nameOf(row);
|
|
2155
|
+
checks.push({
|
|
2156
|
+
"@type": "AiCheck",
|
|
2157
|
+
name,
|
|
2158
|
+
send,
|
|
2159
|
+
instructions: [(row.instructions ?? "").trim()].filter((rule) => rule.length > 0),
|
|
2160
|
+
expect: "boolean"
|
|
2161
|
+
});
|
|
2162
|
+
return {
|
|
2163
|
+
"@type": "Gate",
|
|
2164
|
+
code: codeOf(row),
|
|
2165
|
+
reason: linkedReason(prefix, caseText),
|
|
2166
|
+
appliesWhen,
|
|
2167
|
+
when: { "@type": "Condition", field: `ai.${name}.valid`, op: "isTrue" },
|
|
2168
|
+
onFail: draft.onFail,
|
|
2169
|
+
class: draft.verdictClass
|
|
2170
|
+
};
|
|
2171
|
+
});
|
|
2172
|
+
const withChecks = checks.reduce(putAiCheckNode, base);
|
|
2173
|
+
const gates2 = [...withChecks.gates ?? []];
|
|
2174
|
+
gates2.splice(at ?? gates2.length, 0, ...aiGates);
|
|
2175
|
+
return { ...withChecks, gates: gates2 };
|
|
2176
|
+
}
|
|
2177
|
+
const whenOf = (row) => toCondition({ field: draft.checkedField, op: draft.op, value: row.value, tolerance: row.tolerance });
|
|
2178
|
+
const caseGates = draft.cases.map((row) => ({
|
|
2179
|
+
"@type": "Gate",
|
|
2180
|
+
code: codeOf(row),
|
|
2181
|
+
reason: linkedReason(prefix, linkedCaseText(draft.guardField, row.guardValue, { fields })),
|
|
2182
|
+
appliesWhen: inGuard(row),
|
|
2183
|
+
when: whenOf(row),
|
|
2184
|
+
onFail: draft.onFail,
|
|
2185
|
+
class: draft.verdictClass
|
|
2186
|
+
}));
|
|
2187
|
+
const elseGate = {
|
|
2188
|
+
"@type": "Gate",
|
|
2189
|
+
code: codeOf(draft.elseCase),
|
|
2190
|
+
reason: linkedReason(prefix, linkedElseText()),
|
|
2191
|
+
appliesWhen: elseGuard,
|
|
2192
|
+
when: whenOf(draft.elseCase),
|
|
2193
|
+
onFail: draft.onFail,
|
|
2194
|
+
class: draft.verdictClass
|
|
2195
|
+
};
|
|
2196
|
+
const gates = [...base.gates ?? []];
|
|
2197
|
+
gates.splice(at ?? gates.length, 0, ...caseGates, elseGate);
|
|
2198
|
+
return { ...base, gates };
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/LinkedRuleEditor.tsx
|
|
2202
|
+
var AI_MODE = "__ai-check__";
|
|
2203
|
+
var GUARD_KINDS = /* @__PURE__ */ new Set(["text", "number", "choice"]);
|
|
2204
|
+
function legalOps(index, path) {
|
|
2205
|
+
return operatorsForKind(index.get(path)?.kind ?? "text").filter((op) => !takesNoValue(op));
|
|
2206
|
+
}
|
|
2207
|
+
var LinkedRuleEditor = ({ opened, onClose, body, catalog, index, initialChecked, editing, onSave }) => {
|
|
2208
|
+
const [draft, setDraft] = useState2(() => {
|
|
2209
|
+
if (editing) return draftFromLinked(editing, index);
|
|
2210
|
+
const guard = [...index.values()].find((entry) => GUARD_KINDS.has(entry.kind) && entry.path !== initialChecked && !entry.path.includes("[*]"));
|
|
2211
|
+
return newLinkedDraft(initialChecked, legalOps(index, initialChecked)[0] ?? "==", guard?.path ?? "");
|
|
2212
|
+
});
|
|
2213
|
+
const checkedField = index.get(draft.checkedField);
|
|
2214
|
+
const guardField = index.get(draft.guardField);
|
|
2215
|
+
const ctx = useMemo2(() => ({ fields: index }), [index]);
|
|
2216
|
+
const checkedOptions = useMemo2(
|
|
2217
|
+
() => [...index.values()].filter((entry) => entry.path !== draft.guardField).map((entry) => ({ value: entry.path, label: entry.title })),
|
|
2218
|
+
[index, draft.guardField]
|
|
2219
|
+
);
|
|
2220
|
+
const guardOptions = useMemo2(
|
|
2221
|
+
() => [...index.values()].filter((entry) => GUARD_KINDS.has(entry.kind) && entry.path !== draft.checkedField && !entry.path.includes("[*]")).map((entry) => ({ value: entry.path, label: entry.title })),
|
|
2222
|
+
[index, draft.checkedField]
|
|
2223
|
+
);
|
|
2224
|
+
const opOptions = useMemo2(
|
|
2225
|
+
() => [
|
|
2226
|
+
...legalOps(index, draft.checkedField).map((op) => {
|
|
2227
|
+
const phrase = OPERATOR_PHRASE[op];
|
|
2228
|
+
return { value: op, label: phrase.suffix ? `${phrase.label} \u2026 ${phrase.suffix}` : phrase.label };
|
|
2229
|
+
}),
|
|
2230
|
+
// The AI lane, entered from the check box like everywhere else (§A.15).
|
|
2231
|
+
{ value: AI_MODE, label: AI_CHECK_LANE_LABEL }
|
|
2232
|
+
],
|
|
2233
|
+
[index, draft.checkedField]
|
|
2234
|
+
);
|
|
2235
|
+
const pickChecked = (path) => {
|
|
2236
|
+
if (!path) return;
|
|
2237
|
+
setDraft((current) => {
|
|
2238
|
+
const legal = legalOps(index, path);
|
|
2239
|
+
const op = legal.includes(current.op) ? current.op : legal[0];
|
|
2240
|
+
return {
|
|
2241
|
+
...current,
|
|
2242
|
+
checkedField: path,
|
|
2243
|
+
op,
|
|
2244
|
+
cases: current.cases.map((row) => ({ ...row, value: void 0, tolerance: void 0 })),
|
|
2245
|
+
elseCase: { ...current.elseCase, value: void 0, tolerance: void 0 }
|
|
2246
|
+
};
|
|
2247
|
+
});
|
|
2248
|
+
};
|
|
2249
|
+
const pickOp = (op) => {
|
|
2250
|
+
if (!op) return;
|
|
2251
|
+
if (op === AI_MODE) {
|
|
2252
|
+
setDraft((current) => ({ ...current, aiMode: true }));
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
setDraft((current) => {
|
|
2256
|
+
if (current.aiMode && op === current.op) return { ...current, aiMode: false };
|
|
2257
|
+
return {
|
|
2258
|
+
...current,
|
|
2259
|
+
aiMode: false,
|
|
2260
|
+
op,
|
|
2261
|
+
cases: current.cases.map((row) => ({ ...row, value: void 0, tolerance: void 0 })),
|
|
2262
|
+
elseCase: { ...current.elseCase, value: void 0, tolerance: void 0 }
|
|
2263
|
+
};
|
|
2264
|
+
});
|
|
2265
|
+
};
|
|
2266
|
+
const pickGuard = (path) => {
|
|
2267
|
+
if (!path) return;
|
|
2268
|
+
setDraft((current) => ({ ...current, guardField: path, cases: current.cases.map((row) => ({ ...row, guardValue: void 0 })) }));
|
|
2269
|
+
};
|
|
2270
|
+
const setCase = (at, patch) => setDraft((current) => ({ ...current, cases: current.cases.map((row, i) => i === at ? { ...row, ...patch } : row) }));
|
|
2271
|
+
const addCase = () => setDraft((current) => ({ ...current, cases: [...current.cases, {}] }));
|
|
2272
|
+
const removeCase = (at) => setDraft((current) => current.cases.length <= 1 ? current : { ...current, cases: current.cases.filter((_, i) => i !== at) });
|
|
2273
|
+
const preview = useMemo2(() => {
|
|
2274
|
+
const base = editing ? removeGatesAt(body, editing.indices) : body;
|
|
2275
|
+
const applied = applyLinkedDraft(base, { ...draft, editing: void 0 }, index);
|
|
2276
|
+
const first = base.gates?.length ?? 0;
|
|
2277
|
+
const count = draft.cases.length + 1;
|
|
2278
|
+
const problems = validateRubric(applied, catalog).filter((diagnostic) => {
|
|
2279
|
+
if (diagnostic.severity !== "error") return false;
|
|
2280
|
+
const at = /^\/gates\/(\d+)/.exec(diagnostic.path);
|
|
2281
|
+
return !!at && Number(at[1]) >= first && Number(at[1]) < first + count;
|
|
2282
|
+
}).map(problemText);
|
|
2283
|
+
const missingCase = draft.cases.some((row) => row.guardValue === void 0 || row.guardValue === "");
|
|
2284
|
+
if (missingCase) problems.push(LINKED_MISSING_CASE_TEXT);
|
|
2285
|
+
const keys = draft.cases.map((row) => JSON.stringify(row.guardValue ?? null));
|
|
2286
|
+
if (new Set(keys).size !== keys.length) problems.push(LINKED_DUPLICATE_CASE_TEXT);
|
|
2287
|
+
if (draft.aiMode && [...draft.cases, draft.elseCase].some((row) => !(row.instructions ?? "").trim())) problems.push(LINKED_MISSING_AI_TEXT);
|
|
2288
|
+
const sentence = describeLinked(
|
|
2289
|
+
{
|
|
2290
|
+
checkedField: draft.checkedField,
|
|
2291
|
+
op: draft.op,
|
|
2292
|
+
guardField: draft.guardField,
|
|
2293
|
+
cases: draft.cases,
|
|
2294
|
+
elseValue: draft.elseCase.value,
|
|
2295
|
+
...draft.aiMode ? { ai: true, elseInstructions: draft.elseCase.instructions } : {}
|
|
2296
|
+
},
|
|
2297
|
+
ctx
|
|
2298
|
+
);
|
|
2299
|
+
return { sentence, problems: [...new Set(problems)] };
|
|
2300
|
+
}, [body, catalog, ctx, draft, editing, index]);
|
|
2301
|
+
const missingReason = !draft.reason.trim();
|
|
2302
|
+
const canSave = preview.problems.length === 0 && !missingReason && !!draft.guardField;
|
|
2303
|
+
const opLabel = OPERATOR_PHRASE[draft.op]?.label ?? "";
|
|
2304
|
+
const opSuffix = OPERATOR_PHRASE[draft.op]?.suffix;
|
|
2305
|
+
return /* @__PURE__ */ React5.createElement(
|
|
2306
|
+
Modal,
|
|
2307
|
+
{
|
|
2308
|
+
opened,
|
|
2309
|
+
onClose,
|
|
2310
|
+
title: editing ? "Change this rule" : `\u201C${fieldTitle(draft.checkedField, ctx)}\u201D depends on another answer`,
|
|
2311
|
+
size: "xl",
|
|
2312
|
+
radius: 14,
|
|
2313
|
+
styles: { title: { fontWeight: 600 } },
|
|
2314
|
+
scrollAreaComponent: ScrollArea2.Autosize
|
|
2315
|
+
},
|
|
2316
|
+
/* @__PURE__ */ React5.createElement(Stack4, { gap: "md" }, /* @__PURE__ */ React5.createElement(Group4, { gap: 8, align: "center", wrap: "wrap" }, /* @__PURE__ */ React5.createElement(BaseSelect, { w: 230, styles: chipInput, data: checkedOptions, value: draft.checkedField, onChange: pickChecked, searchable: checkedOptions.length > 6 }), /* @__PURE__ */ React5.createElement(
|
|
2317
|
+
BaseSelect,
|
|
2318
|
+
{
|
|
2319
|
+
w: 210,
|
|
2320
|
+
styles: chipInput,
|
|
2321
|
+
data: opOptions,
|
|
2322
|
+
value: draft.aiMode ? AI_MODE : draft.op,
|
|
2323
|
+
onChange: pickOp,
|
|
2324
|
+
searchable: opOptions.length > 8,
|
|
2325
|
+
renderOption: ({ option }) => /* @__PURE__ */ React5.createElement(Group4, { gap: 8, wrap: "nowrap" }, option.value === AI_MODE && icon(IconRobot, 16), /* @__PURE__ */ React5.createElement(Text3, { size: "sm" }, option.label))
|
|
2326
|
+
}
|
|
2327
|
+
), /* @__PURE__ */ React5.createElement(Text3, { style: leadWordStyle }, "\u2026 depending on"), /* @__PURE__ */ React5.createElement(
|
|
2328
|
+
BaseSelect,
|
|
2329
|
+
{
|
|
2330
|
+
w: 230,
|
|
2331
|
+
styles: chipInput,
|
|
2332
|
+
data: guardOptions,
|
|
2333
|
+
value: draft.guardField || null,
|
|
2334
|
+
onChange: pickGuard,
|
|
2335
|
+
searchable: guardOptions.length > 6,
|
|
2336
|
+
placeholder: "Which answer?"
|
|
2337
|
+
}
|
|
2338
|
+
)), /* @__PURE__ */ React5.createElement(Paper2, { p: "md", style: panelStyle }, /* @__PURE__ */ React5.createElement(Stack4, { gap: 8 }, /* @__PURE__ */ React5.createElement(Text3, { style: microLabelStyle }, "The cases"), draft.cases.map((row, at) => /* @__PURE__ */ React5.createElement(Group4, { key: at, gap: 8, align: "flex-start", wrap: "wrap" }, /* @__PURE__ */ React5.createElement(Text3, { mt: 6, style: leadWordStyle }, "If it is"), /* @__PURE__ */ React5.createElement(LiteralInput, { kind: guardField?.kind ?? "text", field: guardField, value: row.guardValue, onChange: (next) => setCase(at, { guardValue: next }) }), draft.aiMode ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text3, { mt: 6, style: leadWordStyle }, LINKED_AI_ROW_LEAD), /* @__PURE__ */ React5.createElement(
|
|
2339
|
+
BaseTextInput,
|
|
2340
|
+
{
|
|
2341
|
+
style: { flex: 1, minWidth: 260 },
|
|
2342
|
+
styles: chipInput,
|
|
2343
|
+
placeholder: LINKED_AI_PLACEHOLDER,
|
|
2344
|
+
value: row.instructions ?? "",
|
|
2345
|
+
onChange: (event) => setCase(at, { instructions: event.currentTarget.value })
|
|
2346
|
+
}
|
|
2347
|
+
)) : /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text3, { mt: 6, style: leadWordStyle }, "it ", opLabel), /* @__PURE__ */ React5.createElement(ValueInput, { op: draft.op, field: checkedField, index, value: row.value, tolerance: row.tolerance, onChange: (patch) => setCase(at, patch) }), opSuffix && /* @__PURE__ */ React5.createElement(Text3, { mt: 6, size: "sm", c: "dimmed" }, opSuffix)), /* @__PURE__ */ React5.createElement(ActionIcon, { mt: 4, variant: "subtle", color: "red", onClick: () => removeCase(at), disabled: draft.cases.length <= 1, "aria-label": "Remove this case" }, /* @__PURE__ */ React5.createElement(IconTrash, { size: 16 })))), /* @__PURE__ */ React5.createElement(Group4, { gap: 8, align: "flex-start", wrap: "wrap" }, draft.aiMode ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text3, { mt: 6, style: leadWordStyle }, LINKED_AI_ELSE_LEAD), /* @__PURE__ */ React5.createElement(
|
|
2348
|
+
BaseTextInput,
|
|
2349
|
+
{
|
|
2350
|
+
style: { flex: 1, minWidth: 260 },
|
|
2351
|
+
styles: chipInput,
|
|
2352
|
+
placeholder: LINKED_AI_PLACEHOLDER,
|
|
2353
|
+
value: draft.elseCase.instructions ?? "",
|
|
2354
|
+
onChange: (event) => setDraft((current) => ({ ...current, elseCase: { ...current.elseCase, instructions: event.currentTarget.value } }))
|
|
2355
|
+
}
|
|
2356
|
+
)) : /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text3, { mt: 6, style: leadWordStyle }, "For anyone else, it ", opLabel), /* @__PURE__ */ React5.createElement(
|
|
2357
|
+
ValueInput,
|
|
2358
|
+
{
|
|
2359
|
+
op: draft.op,
|
|
2360
|
+
field: checkedField,
|
|
2361
|
+
index,
|
|
2362
|
+
value: draft.elseCase.value,
|
|
2363
|
+
tolerance: draft.elseCase.tolerance,
|
|
2364
|
+
onChange: (patch) => setDraft((current) => ({ ...current, elseCase: { ...current.elseCase, ...patch } }))
|
|
2365
|
+
}
|
|
2366
|
+
), opSuffix && /* @__PURE__ */ React5.createElement(Text3, { mt: 6, size: "sm", c: "dimmed" }, opSuffix))), /* @__PURE__ */ React5.createElement(Button3, { variant: "subtle", size: "compact-sm", styles: quietButton, style: { alignSelf: "flex-start" }, leftSection: /* @__PURE__ */ React5.createElement(IconPlus, { size: 14 }), onClick: addCase }, "Add another case"))), draft.aiMode && /* @__PURE__ */ React5.createElement(Paper2, { p: "md", style: panelStyle }, /* @__PURE__ */ React5.createElement(Stack4, { gap: "xs" }, /* @__PURE__ */ React5.createElement(Group4, { gap: 6, wrap: "nowrap" }, icon(IconRobot, 16), /* @__PURE__ */ React5.createElement(Text3, { size: "sm", fw: 600 }, AI_SEES_LABEL)), /* @__PURE__ */ React5.createElement(Text3, { size: "xs", c: "dimmed" }, aiSeesHint(fieldTitle(draft.checkedField, ctx))), /* @__PURE__ */ React5.createElement(Text3, { size: "xs", c: "dimmed" }, AI_SETTING_HINT), /* @__PURE__ */ React5.createElement(
|
|
2367
|
+
BaseMultiSelect,
|
|
2368
|
+
{
|
|
2369
|
+
styles: chipInput,
|
|
2370
|
+
placeholder: AI_SEES_PLACEHOLDER,
|
|
2371
|
+
data: [...index.values()].filter((entry) => entry.path !== draft.checkedField).map((entry) => ({ value: entry.path, label: entry.title })),
|
|
2372
|
+
value: (draft.alsoSend ?? []).filter((path) => path !== draft.checkedField),
|
|
2373
|
+
onChange: (alsoSend) => setDraft((current) => ({ ...current, alsoSend: alsoSend.length > 0 ? alsoSend : void 0 })),
|
|
2374
|
+
searchable: true
|
|
2375
|
+
}
|
|
2376
|
+
))), /* @__PURE__ */ React5.createElement(
|
|
2377
|
+
BaseTextInput,
|
|
2378
|
+
{
|
|
2379
|
+
label: "What should the claimant be told?",
|
|
2380
|
+
styles: chipInputStyles(missingReason),
|
|
2381
|
+
placeholder: "e.g. The amount does not match our records",
|
|
2382
|
+
description: "One message for the whole table \u2014 each case adds which answer it was checked against.",
|
|
2383
|
+
error: missingReason ? "Please write the message." : void 0,
|
|
2384
|
+
value: draft.reason,
|
|
2385
|
+
onChange: (event) => setDraft((current) => ({ ...current, reason: event.currentTarget.value }))
|
|
2386
|
+
}
|
|
2387
|
+
), preview.problems.length > 0 && /* @__PURE__ */ React5.createElement(Alert, { color: "red", icon: /* @__PURE__ */ React5.createElement(IconAlertTriangle, { size: 16 }), styles: actionAlertStyles }, /* @__PURE__ */ React5.createElement(Stack4, { gap: 2 }, preview.problems.map((problem) => /* @__PURE__ */ React5.createElement(Text3, { key: problem, size: "xs" }, problem)))), /* @__PURE__ */ React5.createElement(Stack4, { gap: 6 }, /* @__PURE__ */ React5.createElement(Text3, { style: microLabelStyle }, "This rule reads:"), /* @__PURE__ */ React5.createElement(RuleSentence, { text: preview.sentence })), /* @__PURE__ */ React5.createElement(Group4, { justify: "flex-end", gap: "xs" }, /* @__PURE__ */ React5.createElement(Button3, { variant: "default", onClick: onClose }, "Cancel"), /* @__PURE__ */ React5.createElement(BasePrimaryButton, { disabled: !canSave, onClick: () => onSave(draft) }, editing ? "Save the change" : "Add this rule")))
|
|
2388
|
+
);
|
|
2389
|
+
};
|
|
2390
|
+
|
|
2391
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/RuleCard.tsx
|
|
2392
|
+
import React6 from "react";
|
|
2393
|
+
import { ActionIcon as ActionIcon2, Alert as Alert2, Box as Box2, Button as Button4, Group as Group5, Paper as Paper3, Stack as Stack5, Text as Text4 } from "@mantine/core";
|
|
2394
|
+
import { IconAlertTriangle as IconAlertTriangle2, IconLink, IconPencil, IconPlus as IconPlus2, IconTrash as IconTrash2 } from "@tabler/icons-react";
|
|
2395
|
+
var RuleCard = ({
|
|
2396
|
+
field,
|
|
2397
|
+
rules,
|
|
2398
|
+
linked,
|
|
2399
|
+
problems,
|
|
2400
|
+
index,
|
|
2401
|
+
aiChecks,
|
|
2402
|
+
onAdd,
|
|
2403
|
+
onEdit,
|
|
2404
|
+
onDelete,
|
|
2405
|
+
onAddLinked,
|
|
2406
|
+
onEditLinked,
|
|
2407
|
+
onDeleteLinked,
|
|
2408
|
+
disabled
|
|
2409
|
+
}) => {
|
|
2410
|
+
const errors = problems.filter((problem) => problem.severity === "error");
|
|
2411
|
+
const warnings = problems.filter((problem) => problem.severity === "warning");
|
|
2412
|
+
const ctx = { fields: index, aiChecks };
|
|
2413
|
+
return /* @__PURE__ */ React6.createElement(Paper3, { p: 18, style: { ...cardStyle, ...errors.length > 0 ? { borderColor: "var(--mantine-color-red-6)" } : {} } }, /* @__PURE__ */ React6.createElement(Stack5, { gap: "sm" }, /* @__PURE__ */ React6.createElement(Group5, { gap: "xs", justify: "space-between", wrap: "nowrap" }, /* @__PURE__ */ React6.createElement(Group5, { gap: 8, wrap: "nowrap" }, /* @__PURE__ */ React6.createElement(Text4, { size: "sm", c: "dimmed" }, "\u25CF"), /* @__PURE__ */ React6.createElement(Text4, { size: "sm", fw: 600 }, field.title), field.isRequired && /* @__PURE__ */ React6.createElement(Text4, { size: "xs", c: "dimmed" }, "(always asked)")), /* @__PURE__ */ React6.createElement(Text4, { style: microLabelStyle }, KIND_LABEL[field.kind])), rules.length === 0 && (linked ?? []).length === 0 ? /* @__PURE__ */ React6.createElement(Text4, { size: "xs", c: "dimmed", pl: 20 }, "No rules yet \u2014 this answer won't be checked.") : rules.map((rule) => {
|
|
2414
|
+
const grownShape = rule.consequence === "reject" ? rule.grown : void 0;
|
|
2415
|
+
return /* @__PURE__ */ React6.createElement(Group5, { key: rule.code, gap: "sm", align: "flex-start", wrap: "nowrap", pl: 20 }, /* @__PURE__ */ React6.createElement(Box2, { mt: 2, style: consequencePillStyle(CONSEQUENCES.find((entry) => entry.value === rule.consequence)?.color ?? SLATE) }, consequenceIcon(rule.consequence, 15)), /* @__PURE__ */ React6.createElement(Box2, { style: { flex: 1 } }, grownShape && !grownShape.editable ? (
|
|
2416
|
+
// A compound the builder cannot represent — quiet, honest,
|
|
2417
|
+
// and deletable; never rewritten (§B.5 doctrine).
|
|
2418
|
+
/* @__PURE__ */ React6.createElement(Text4, { size: "xs", c: "dimmed" }, GROUP_OUTSIDE_BUILDER_TEXT)
|
|
2419
|
+
) : /* @__PURE__ */ React6.createElement(RuleSentence, { text: describeRule(rule, ctx) })), (!grownShape || grownShape.editable) && /* @__PURE__ */ React6.createElement(ActionIcon2, { variant: "subtle", onClick: () => onEdit(rule), disabled, "aria-label": `Change the rule on ${field.title}` }, /* @__PURE__ */ React6.createElement(IconPencil, { size: 16 })), /* @__PURE__ */ React6.createElement(ActionIcon2, { variant: "subtle", color: "red", onClick: () => onDelete(rule), disabled, "aria-label": `Delete the rule on ${field.title}` }, /* @__PURE__ */ React6.createElement(IconTrash2, { size: 16 })));
|
|
2420
|
+
}), (linked ?? []).map((entry) => /* @__PURE__ */ React6.createElement(Group5, { key: entry.elseCase.code, gap: "sm", align: "flex-start", wrap: "nowrap", pl: 20 }, /* @__PURE__ */ React6.createElement(Box2, { mt: 2, style: consequencePillStyle(entry.onFail === "review" ? "var(--mantine-color-grape-6)" : "var(--mantine-color-red-6)") }, consequenceIcon(entry.onFail === "review" ? "human" : "reject", 15)), /* @__PURE__ */ React6.createElement(Box2, { style: { flex: 1 } }, /* @__PURE__ */ React6.createElement(RuleSentence, { text: describeLinked(linkedSentenceOf(entry), ctx) })), /* @__PURE__ */ React6.createElement(ActionIcon2, { variant: "subtle", onClick: () => onEditLinked?.(entry), disabled: disabled || !onEditLinked, "aria-label": `Change the linked rule on ${field.title}` }, /* @__PURE__ */ React6.createElement(IconPencil, { size: 16 })), /* @__PURE__ */ React6.createElement(
|
|
2421
|
+
ActionIcon2,
|
|
2422
|
+
{
|
|
2423
|
+
variant: "subtle",
|
|
2424
|
+
color: "red",
|
|
2425
|
+
onClick: () => onDeleteLinked?.(entry),
|
|
2426
|
+
disabled: disabled || !onDeleteLinked,
|
|
2427
|
+
"aria-label": `Delete the linked rule on ${field.title}`
|
|
2428
|
+
},
|
|
2429
|
+
/* @__PURE__ */ React6.createElement(IconTrash2, { size: 16 })
|
|
2430
|
+
))), errors.length > 0 && /* @__PURE__ */ React6.createElement(Alert2, { color: "red", icon: /* @__PURE__ */ React6.createElement(IconAlertTriangle2, { size: 16 }), styles: actionAlertStyles, p: "xs" }, /* @__PURE__ */ React6.createElement(Stack5, { gap: 2 }, unique(errors).map((problem) => /* @__PURE__ */ React6.createElement(Text4, { key: problem, size: "xs" }, problem)))), warnings.length > 0 && /* @__PURE__ */ React6.createElement(Alert2, { color: "yellow", styles: actionAlertStyles, p: "xs" }, /* @__PURE__ */ React6.createElement(Stack5, { gap: 2 }, unique(warnings).map((problem) => /* @__PURE__ */ React6.createElement(Text4, { key: problem, size: "xs" }, problem)))), /* @__PURE__ */ React6.createElement(Group5, { pl: 20, gap: "xs" }, /* @__PURE__ */ React6.createElement(Button4, { size: "compact-xs", variant: "subtle", styles: quietButton, leftSection: /* @__PURE__ */ React6.createElement(IconPlus2, { size: 14 }), onClick: onAdd, disabled }, "Add a rule"), onAddLinked && /* @__PURE__ */ React6.createElement(Button4, { size: "compact-xs", variant: "subtle", styles: quietButton, leftSection: /* @__PURE__ */ React6.createElement(IconLink, { size: 14 }), onClick: onAddLinked, disabled }, LINKED_ADD_LABEL))));
|
|
2431
|
+
};
|
|
2432
|
+
function unique(problems) {
|
|
2433
|
+
return [...new Set(problems.map(problemText))];
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/RuleEditor.tsx
|
|
2437
|
+
import React11, { useMemo as useMemo4, useState as useState5 } from "react";
|
|
2438
|
+
import { ActionIcon as ActionIcon5, Alert as Alert4, Box as Box4, Button as Button7, Divider as Divider2, Group as Group10, Modal as Modal2, Paper as Paper7, ScrollArea as ScrollArea3, Stack as Stack9, Text as Text9 } from "@mantine/core";
|
|
2439
|
+
import { IconAlertTriangle as IconAlertTriangle4, IconPlus as IconPlus4, IconRobot as IconRobot3, IconTrash as IconTrash3, IconWand, IconWorld as IconWorld2, IconX as IconX3 } from "@tabler/icons-react";
|
|
2440
|
+
|
|
2441
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/AiCheckInline.tsx
|
|
2442
|
+
import React7 from "react";
|
|
2443
|
+
import { ActionIcon as ActionIcon3, Button as Button5, Group as Group6, Paper as Paper4, Stack as Stack6, Text as Text5 } from "@mantine/core";
|
|
2444
|
+
import { IconPlus as IconPlus3, IconRobot as IconRobot2, IconX } from "@tabler/icons-react";
|
|
2445
|
+
var AiCheckInline = ({ ai, questionTitle, onChange, disabled, index }) => {
|
|
2446
|
+
const setRule = (at, text) => onChange({ ...ai, instructions: ai.instructions.map((rule, i) => i === at ? text : rule) });
|
|
2447
|
+
const removeRule2 = (at) => onChange({ ...ai, instructions: ai.instructions.filter((_, i) => i !== at) });
|
|
2448
|
+
const addRule = () => onChange({ ...ai, instructions: [...ai.instructions, ""] });
|
|
2449
|
+
return /* @__PURE__ */ React7.createElement(Paper4, { p: "md", style: panelStyle }, /* @__PURE__ */ React7.createElement(Stack6, { gap: "xs" }, /* @__PURE__ */ React7.createElement(Group6, { gap: 6, wrap: "nowrap" }, icon(IconRobot2, 16), /* @__PURE__ */ React7.createElement(Text5, { size: "sm", fw: 600 }, "What should the AI check?")), /* @__PURE__ */ React7.createElement(Text5, { size: "xs", c: "dimmed" }, "While a claim is judged, the AI reads \u201C", questionTitle, "\u201D and passes it only when every rule below holds."), /* @__PURE__ */ React7.createElement(Text5, { size: "xs", c: "dimmed" }, AI_SETTING_HINT), /* @__PURE__ */ React7.createElement(Stack6, { gap: 6 }, ai.instructions.map((rule, at) => (
|
|
2450
|
+
// Index keys are safe here: rows are edited in place and only ever
|
|
2451
|
+
// removed by index — there is no identity to preserve across reorders.
|
|
2452
|
+
/* @__PURE__ */ React7.createElement(Group6, { key: at, gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React7.createElement(
|
|
2453
|
+
BaseTextInput,
|
|
2454
|
+
{
|
|
2455
|
+
style: { flex: 1 },
|
|
2456
|
+
styles: chipInput,
|
|
2457
|
+
placeholder: "e.g. Looks like a real batch code, not a placeholder",
|
|
2458
|
+
value: rule,
|
|
2459
|
+
onChange: (event) => setRule(at, event.currentTarget.value),
|
|
2460
|
+
disabled
|
|
2461
|
+
}
|
|
2462
|
+
), /* @__PURE__ */ React7.createElement(ActionIcon3, { variant: "subtle", color: "red", onClick: () => removeRule2(at), disabled: disabled || ai.instructions.length <= 1, "aria-label": "Remove this rule" }, /* @__PURE__ */ React7.createElement(IconX, { size: 16 })))
|
|
2463
|
+
)), /* @__PURE__ */ React7.createElement(Button5, { size: "xs", variant: "subtle", styles: quietButton, leftSection: /* @__PURE__ */ React7.createElement(IconPlus3, { size: 14 }), onClick: addRule, disabled, style: { alignSelf: "flex-start" } }, "Add a rule")), index && /* @__PURE__ */ React7.createElement(
|
|
2464
|
+
BaseMultiSelect,
|
|
2465
|
+
{
|
|
2466
|
+
label: AI_SEES_LABEL,
|
|
2467
|
+
styles: chipInput,
|
|
2468
|
+
placeholder: AI_SEES_PLACEHOLDER,
|
|
2469
|
+
description: aiSeesHint(questionTitle),
|
|
2470
|
+
data: [...index.values()].filter((entry) => entry.path !== ai.sendField).map((entry) => ({ value: entry.path, label: entry.title })),
|
|
2471
|
+
value: (ai.alsoSend ?? []).filter((path) => path !== ai.sendField),
|
|
2472
|
+
onChange: (alsoSend) => onChange({ ...ai, alsoSend: alsoSend.length > 0 ? alsoSend : void 0 }),
|
|
2473
|
+
searchable: true,
|
|
2474
|
+
disabled
|
|
2475
|
+
}
|
|
2476
|
+
)));
|
|
2477
|
+
};
|
|
2478
|
+
|
|
2479
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/ConditionRow.tsx
|
|
2480
|
+
import React8, { useMemo as useMemo3 } from "react";
|
|
2481
|
+
import { Group as Group7, Text as Text6 } from "@mantine/core";
|
|
2482
|
+
var ConditionRow = ({
|
|
2483
|
+
index,
|
|
2484
|
+
options,
|
|
2485
|
+
sources,
|
|
2486
|
+
aiChecks,
|
|
2487
|
+
rule,
|
|
2488
|
+
onChange,
|
|
2489
|
+
lead = "If",
|
|
2490
|
+
showField = true,
|
|
2491
|
+
disabled,
|
|
2492
|
+
extraOperators,
|
|
2493
|
+
onPickExtra
|
|
2494
|
+
}) => {
|
|
2495
|
+
const field = index.get(rule.field) ?? extRefField(rule.field, sources) ?? aiRefField(rule.field, aiChecks);
|
|
2496
|
+
const kind = field?.kind ?? "text";
|
|
2497
|
+
const operatorOptions = useMemo3(
|
|
2498
|
+
() => [
|
|
2499
|
+
...operatorsForKind(kind).map((op) => {
|
|
2500
|
+
const phrase = OPERATOR_PHRASE[op];
|
|
2501
|
+
return { value: op, label: phrase.suffix ? `${phrase.label} \u2026 ${phrase.suffix}` : phrase.label };
|
|
2502
|
+
}),
|
|
2503
|
+
...extraOperators ?? []
|
|
2504
|
+
],
|
|
2505
|
+
[kind, extraOperators]
|
|
2506
|
+
);
|
|
2507
|
+
const pickField = (path) => {
|
|
2508
|
+
if (!path) return;
|
|
2509
|
+
const nextKind = (index.get(path) ?? extRefField(path, sources) ?? aiRefField(path, aiChecks))?.kind ?? "text";
|
|
2510
|
+
const legal = operatorsForKind(nextKind);
|
|
2511
|
+
const op = legal.includes(rule.op) ? rule.op : legal[0];
|
|
2512
|
+
const kept = op === rule.op;
|
|
2513
|
+
onChange({ field: path, op, value: kept ? rule.value : void 0, tolerance: kept ? rule.tolerance : void 0 });
|
|
2514
|
+
};
|
|
2515
|
+
const pickOperator = (op) => {
|
|
2516
|
+
if (!op) return;
|
|
2517
|
+
if (extraOperators?.some((entry) => entry.value === op)) {
|
|
2518
|
+
onPickExtra?.(op);
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
onChange({ field: rule.field, op, value: void 0, tolerance: void 0 });
|
|
2522
|
+
};
|
|
2523
|
+
return /* @__PURE__ */ React8.createElement(Group7, { gap: 8, align: "flex-start", wrap: "wrap" }, /* @__PURE__ */ React8.createElement(Text6, { mt: 6, style: leadWordStyle }, lead), showField && /* @__PURE__ */ React8.createElement(BaseSelect, { w: 220, styles: chipInput, data: options, value: rule.field, onChange: pickField, disabled: disabled || options.length <= 1, searchable: options.length > 6 }), /* @__PURE__ */ React8.createElement(
|
|
2524
|
+
BaseSelect,
|
|
2525
|
+
{
|
|
2526
|
+
w: 230,
|
|
2527
|
+
styles: chipInput,
|
|
2528
|
+
data: operatorOptions,
|
|
2529
|
+
value: rule.op,
|
|
2530
|
+
onChange: pickOperator,
|
|
2531
|
+
disabled,
|
|
2532
|
+
searchable: operatorOptions.length > 8,
|
|
2533
|
+
renderOption: ({ option }) => {
|
|
2534
|
+
const Icon = extraOperators?.find((entry) => entry.value === option.value)?.Icon;
|
|
2535
|
+
return /* @__PURE__ */ React8.createElement(Group7, { gap: 8, wrap: "nowrap" }, Icon && icon(Icon, 16), /* @__PURE__ */ React8.createElement(Text6, { size: "sm" }, option.label));
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
), /* @__PURE__ */ React8.createElement(
|
|
2539
|
+
ValueInput,
|
|
2540
|
+
{
|
|
2541
|
+
op: rule.op,
|
|
2542
|
+
field,
|
|
2543
|
+
index,
|
|
2544
|
+
value: rule.value,
|
|
2545
|
+
tolerance: rule.tolerance,
|
|
2546
|
+
onChange: (patch) => onChange({ ...rule, ...patch }),
|
|
2547
|
+
disabled
|
|
2548
|
+
}
|
|
2549
|
+
));
|
|
2550
|
+
};
|
|
2551
|
+
|
|
2552
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/ExternalCheckInline.tsx
|
|
2553
|
+
import React9, { useState as useState3 } from "react";
|
|
2554
|
+
import { Alert as Alert3, Box as Box3, Button as Button6, Code as Code2, Divider, Group as Group8, Loader, Paper as Paper5, SegmentedControl as SegmentedControl3, Stack as Stack7, Text as Text7 } from "@mantine/core";
|
|
2555
|
+
import { IconAlertTriangle as IconAlertTriangle3, IconCheck, IconWorld } from "@tabler/icons-react";
|
|
2556
|
+
var ExternalCheckInline = ({ ext, questionTitle, onChange, disabled }) => {
|
|
2557
|
+
const handlers = useBlocknoteHandlers();
|
|
2558
|
+
const canTest = typeof handlers.testExternalSource === "function";
|
|
2559
|
+
const [sample, setSample] = useState3("");
|
|
2560
|
+
const [result, setResult] = useState3(null);
|
|
2561
|
+
const [testing, setTesting] = useState3(false);
|
|
2562
|
+
const [detecting, setDetecting] = useState3(false);
|
|
2563
|
+
const [detectError, setDetectError] = useState3(null);
|
|
2564
|
+
const [manual, setManual] = useState3(false);
|
|
2565
|
+
const detect = async (endpoint) => {
|
|
2566
|
+
if (!endpoint.trim()) return;
|
|
2567
|
+
if (typeof handlers.resolveSourceDid !== "function") {
|
|
2568
|
+
setManual(true);
|
|
2569
|
+
setDetectError("Couldn\u2019t detect who this service is automatically \u2014 enter its did:web address.");
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2572
|
+
setDetecting(true);
|
|
2573
|
+
setDetectError(null);
|
|
2574
|
+
try {
|
|
2575
|
+
const outcome = await handlers.resolveSourceDid({ endpoint: endpoint.trim() });
|
|
2576
|
+
if (outcome.resolved && outcome.did) {
|
|
2577
|
+
onChange({ ...ext, audience: outcome.did });
|
|
2578
|
+
setManual(false);
|
|
2579
|
+
} else {
|
|
2580
|
+
setDetectError(outcome.error || "Couldn\u2019t detect who this service is \u2014 enter its did:web address.");
|
|
2581
|
+
setManual(true);
|
|
2582
|
+
}
|
|
2583
|
+
} catch (error) {
|
|
2584
|
+
setDetectError(error instanceof Error ? error.message : String(error));
|
|
2585
|
+
setManual(true);
|
|
2586
|
+
} finally {
|
|
2587
|
+
setDetecting(false);
|
|
2588
|
+
}
|
|
2589
|
+
};
|
|
2590
|
+
const runTest = async () => {
|
|
2591
|
+
if (!handlers.testExternalSource) return;
|
|
2592
|
+
const source = extSourceFromDraft(ext);
|
|
2593
|
+
setTesting(true);
|
|
2594
|
+
setResult(null);
|
|
2595
|
+
try {
|
|
2596
|
+
setResult(await handlers.testExternalSource({ source, sampleValues: { [Object.keys(source.send)[0]]: sample } }));
|
|
2597
|
+
} catch (error) {
|
|
2598
|
+
setResult({
|
|
2599
|
+
ok: false,
|
|
2600
|
+
reachable: false,
|
|
2601
|
+
httpStatus: null,
|
|
2602
|
+
contractOk: false,
|
|
2603
|
+
expect: ext.expect,
|
|
2604
|
+
value: null,
|
|
2605
|
+
reason: null,
|
|
2606
|
+
error: readableError(error),
|
|
2607
|
+
sent: {},
|
|
2608
|
+
audience: ext.audience,
|
|
2609
|
+
elapsedMs: 0
|
|
2610
|
+
});
|
|
2611
|
+
} finally {
|
|
2612
|
+
setTesting(false);
|
|
2613
|
+
}
|
|
2614
|
+
};
|
|
2615
|
+
const ready = /^https?:\/\//.test(ext.endpoint) && /^did:web:/.test(ext.audience);
|
|
2616
|
+
return /* @__PURE__ */ React9.createElement(Paper5, { p: "md", style: panelStyle }, /* @__PURE__ */ React9.createElement(Stack7, { gap: "xs" }, /* @__PURE__ */ React9.createElement(Group8, { gap: 6, wrap: "nowrap" }, icon(IconWorld, 16), /* @__PURE__ */ React9.createElement(Text7, { size: "sm", fw: 600 }, "The outside service")), /* @__PURE__ */ React9.createElement(Text7, { size: "xs", c: "dimmed" }, "While a claim is judged, the engine sends \u201C", questionTitle, "\u201D to this service and uses its answer in the rule."), /* @__PURE__ */ React9.createElement(Stack7, { gap: 4 }, /* @__PURE__ */ React9.createElement(
|
|
2617
|
+
BaseTextInput,
|
|
2618
|
+
{
|
|
2619
|
+
label: "Web address",
|
|
2620
|
+
styles: chipInput,
|
|
2621
|
+
placeholder: "https://\u2026",
|
|
2622
|
+
value: ext.endpoint,
|
|
2623
|
+
onChange: (event) => onChange({ ...ext, endpoint: event.currentTarget.value }),
|
|
2624
|
+
onBlur: (event) => detect(event.currentTarget.value),
|
|
2625
|
+
disabled
|
|
2626
|
+
}
|
|
2627
|
+
), detecting && /* @__PURE__ */ React9.createElement(Group8, { gap: 6, wrap: "nowrap" }, /* @__PURE__ */ React9.createElement(Loader, { size: 12 }), /* @__PURE__ */ React9.createElement(Text7, { size: "xs", c: "dimmed" }, "Finding who this service is\u2026")), !detecting && ext.audience && !manual && /* @__PURE__ */ React9.createElement(Group8, { gap: 6, wrap: "nowrap" }, icon(IconCheck, 14, "var(--mantine-color-green-6)"), /* @__PURE__ */ React9.createElement(Text7, { size: "xs", c: "dimmed", style: { wordBreak: "break-all" } }, "detected ", ext.audience), /* @__PURE__ */ React9.createElement(Button6, { size: "compact-xs", variant: "subtle", styles: quietButton, onClick: () => setManual(true), disabled }, "change")), !detecting && (manual || detectError) && /* @__PURE__ */ React9.createElement(Stack7, { gap: 2 }, detectError && /* @__PURE__ */ React9.createElement(Text7, { size: "xs", c: "red" }, detectError), /* @__PURE__ */ React9.createElement(
|
|
2628
|
+
BaseTextInput,
|
|
2629
|
+
{
|
|
2630
|
+
label: "Who it is (did:web)",
|
|
2631
|
+
styles: chipInput,
|
|
2632
|
+
placeholder: "did:web:example.com",
|
|
2633
|
+
value: ext.audience,
|
|
2634
|
+
onChange: (event) => onChange({ ...ext, audience: event.currentTarget.value }),
|
|
2635
|
+
disabled
|
|
2636
|
+
}
|
|
2637
|
+
))), /* @__PURE__ */ React9.createElement(Divider, { label: EXT_ANSWER_TYPE_LABEL, labelPosition: "left", color: HAIRLINE, styles: { label: microLabelStyle } }), /* @__PURE__ */ React9.createElement(
|
|
2638
|
+
SegmentedControl3,
|
|
2639
|
+
{
|
|
2640
|
+
styles: segmentedPill,
|
|
2641
|
+
value: ext.expect,
|
|
2642
|
+
onChange: (expect) => onChange(expect === "score" ? { ...ext, expect: "score", bands: ext.bands ?? { ...SCORE_BANDS_DEFAULT } } : { ...ext, expect: "boolean" }),
|
|
2643
|
+
disabled,
|
|
2644
|
+
data: [
|
|
2645
|
+
{ value: "boolean", label: EXT_ANSWERS_BOOLEAN_LABEL },
|
|
2646
|
+
{ value: "score", label: EXT_ANSWERS_SCORE_LABEL }
|
|
2647
|
+
],
|
|
2648
|
+
style: { alignSelf: "flex-start" }
|
|
2649
|
+
}
|
|
2650
|
+
), ext.expect === "score" && /* @__PURE__ */ React9.createElement(ScoreBandsEditor, { ext, onChange, disabled }), /* @__PURE__ */ React9.createElement(Divider, { label: "Try it", labelPosition: "left", color: HAIRLINE, styles: { label: microLabelStyle } }), /* @__PURE__ */ React9.createElement(Group8, { gap: "xs", align: "flex-end", wrap: "wrap" }, /* @__PURE__ */ React9.createElement(
|
|
2651
|
+
BaseTextInput,
|
|
2652
|
+
{
|
|
2653
|
+
w: 260,
|
|
2654
|
+
styles: chipInput,
|
|
2655
|
+
label: `Sample ${questionTitle}`,
|
|
2656
|
+
placeholder: "a value to try",
|
|
2657
|
+
value: sample,
|
|
2658
|
+
onChange: (event) => setSample(event.currentTarget.value),
|
|
2659
|
+
disabled
|
|
2660
|
+
}
|
|
2661
|
+
), /* @__PURE__ */ React9.createElement(
|
|
2662
|
+
Button6,
|
|
2663
|
+
{
|
|
2664
|
+
size: "xs",
|
|
2665
|
+
variant: "light",
|
|
2666
|
+
styles: chipButton,
|
|
2667
|
+
leftSection: testing ? /* @__PURE__ */ React9.createElement(Loader, { size: 12 }) : icon(IconWorld, 14),
|
|
2668
|
+
onClick: runTest,
|
|
2669
|
+
disabled: disabled || !canTest || !ready || testing
|
|
2670
|
+
},
|
|
2671
|
+
"Test this check"
|
|
2672
|
+
), !canTest ? /* @__PURE__ */ React9.createElement(Text7, { size: "xs", c: "dimmed" }, "Testing isn\u2019t available here.") : !ready && /* @__PURE__ */ React9.createElement(Text7, { size: "xs", c: "dimmed" }, "Enter the web address first.")), result && /* @__PURE__ */ React9.createElement(TestResult, { result, ext })));
|
|
2673
|
+
};
|
|
2674
|
+
var ScoreBandsEditor = ({ ext, onChange, disabled }) => {
|
|
2675
|
+
const bands = clampBands(ext.bands ?? SCORE_BANDS_DEFAULT);
|
|
2676
|
+
const commit = (next) => onChange({ ...ext, bands: clampBands(next) });
|
|
2677
|
+
const zone = (background, width, label) => /* @__PURE__ */ React9.createElement(
|
|
2678
|
+
Box3,
|
|
2679
|
+
{
|
|
2680
|
+
style: {
|
|
2681
|
+
width: `${width}%`,
|
|
2682
|
+
background,
|
|
2683
|
+
overflow: "hidden",
|
|
2684
|
+
whiteSpace: "nowrap",
|
|
2685
|
+
display: "flex",
|
|
2686
|
+
alignItems: "center",
|
|
2687
|
+
justifyContent: "center",
|
|
2688
|
+
fontSize: 10,
|
|
2689
|
+
fontWeight: 700,
|
|
2690
|
+
color: "var(--mantine-color-white)",
|
|
2691
|
+
opacity: 0.85
|
|
2692
|
+
}
|
|
2693
|
+
},
|
|
2694
|
+
width >= 14 ? label : ""
|
|
2695
|
+
);
|
|
2696
|
+
return /* @__PURE__ */ React9.createElement(Stack7, { gap: 8 }, /* @__PURE__ */ React9.createElement(Group8, { gap: 8, align: "center", wrap: "wrap" }, /* @__PURE__ */ React9.createElement(Text7, { size: "sm" }, "Below"), /* @__PURE__ */ React9.createElement(
|
|
2697
|
+
BaseNumberInput,
|
|
2698
|
+
{
|
|
2699
|
+
w: 72,
|
|
2700
|
+
styles: chipInput,
|
|
2701
|
+
min: 0,
|
|
2702
|
+
max: 99,
|
|
2703
|
+
allowDecimal: false,
|
|
2704
|
+
value: bands.rejectBelow,
|
|
2705
|
+
onChange: (next) => commit({ ...bands, rejectBelow: typeof next === "number" ? next : 0 }),
|
|
2706
|
+
disabled,
|
|
2707
|
+
"aria-label": "Reject below this score"
|
|
2708
|
+
}
|
|
2709
|
+
), /* @__PURE__ */ React9.createElement(Text7, { size: "sm" }, "the claim is turned down \xB7 at"), /* @__PURE__ */ React9.createElement(
|
|
2710
|
+
BaseNumberInput,
|
|
2711
|
+
{
|
|
2712
|
+
w: 72,
|
|
2713
|
+
styles: chipInput,
|
|
2714
|
+
min: 1,
|
|
2715
|
+
max: 100,
|
|
2716
|
+
allowDecimal: false,
|
|
2717
|
+
value: bands.approveAt,
|
|
2718
|
+
onChange: (next) => commit({ ...bands, approveAt: typeof next === "number" ? next : 100 }),
|
|
2719
|
+
disabled,
|
|
2720
|
+
"aria-label": "Passes at this score"
|
|
2721
|
+
}
|
|
2722
|
+
), /* @__PURE__ */ React9.createElement(Text7, { size: "sm" }, "or more it passes")), /* @__PURE__ */ React9.createElement(Box3, { style: { display: "flex", height: 22, borderRadius: 6, overflow: "hidden", border: `1px solid ${HAIRLINE}` }, "aria-hidden": true }, zone("var(--mantine-color-red-8)", bands.rejectBelow, "turned down"), zone("var(--mantine-color-yellow-8)", bands.approveAt - bands.rejectBelow, "a person decides"), zone("var(--mantine-color-green-8)", 100 - bands.approveAt, "passes")), /* @__PURE__ */ React9.createElement(Text7, { size: "xs", c: "dimmed" }, "In between \u2014 from ", bands.rejectBelow, " to ", bands.approveAt - 1, " \u2014 the claim waits for a person to decide."));
|
|
2723
|
+
};
|
|
2724
|
+
var TestResult = ({ result, ext }) => {
|
|
2725
|
+
const title = (() => {
|
|
2726
|
+
if (!result.ok) return "Did not pass";
|
|
2727
|
+
if (result.expect === "score" && typeof result.value === "number") {
|
|
2728
|
+
const score = Math.round(result.value / 100);
|
|
2729
|
+
const bands = clampBands(ext.bands ?? SCORE_BANDS_DEFAULT);
|
|
2730
|
+
const outcome = score >= bands.approveAt ? "it would pass" : score >= bands.rejectBelow ? "a person would decide" : "the claim would be turned down";
|
|
2731
|
+
return `Scored ${score} / 100 \u2014 ${outcome}`;
|
|
2732
|
+
}
|
|
2733
|
+
return `Passed \u2014 ${result.value ? "yes" : "no"}`;
|
|
2734
|
+
})();
|
|
2735
|
+
return /* @__PURE__ */ React9.createElement(Alert3, { color: result.ok ? "green" : "red", icon: result.ok ? /* @__PURE__ */ React9.createElement(IconCheck, { size: 16 }) : /* @__PURE__ */ React9.createElement(IconAlertTriangle3, { size: 16 }), title }, /* @__PURE__ */ React9.createElement(Stack7, { gap: 4 }, !result.ok && /* @__PURE__ */ React9.createElement(Text7, { size: "xs" }, [result.error, result.httpStatus != null ? `HTTP ${result.httpStatus}` : null].filter(Boolean).join(" \xB7 ") || "The check could not be completed."), result.reason && /* @__PURE__ */ React9.createElement(Text7, { size: "xs", c: "dimmed" }, result.reason), /* @__PURE__ */ React9.createElement(Text7, { size: "xs", c: "dimmed" }, "Sent: ", /* @__PURE__ */ React9.createElement(Code2, { style: { fontSize: 11 } }, JSON.stringify(result.sent)))));
|
|
2736
|
+
};
|
|
2737
|
+
function readableError(error) {
|
|
2738
|
+
return appErrorMessage(error) ?? (error instanceof Error ? error.message : String(error));
|
|
2739
|
+
}
|
|
2740
|
+
function appErrorMessage(error) {
|
|
2741
|
+
if (!error || typeof error !== "object") return null;
|
|
2742
|
+
const source = error;
|
|
2743
|
+
for (const candidate of [source.response?.data, source.data, source]) {
|
|
2744
|
+
const envelope = candidate && typeof candidate === "object" ? candidate.error : void 0;
|
|
2745
|
+
if (envelope && typeof envelope === "object" && typeof envelope.message === "string") {
|
|
2746
|
+
const { message, details } = envelope;
|
|
2747
|
+
const detail = typeof details === "string" ? details : Array.isArray(details) ? details.map((d) => typeof d === "string" ? d : JSON.stringify(d)).join("; ") : details ? JSON.stringify(details) : "";
|
|
2748
|
+
return detail ? `${message} \u2014 ${detail}` : message;
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
return null;
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/ReviewTaskEditor.tsx
|
|
2755
|
+
import React10, { useEffect as useEffect2, useState as useState4 } from "react";
|
|
2756
|
+
import { ActionIcon as ActionIcon4, Group as Group9, Loader as Loader2, Paper as Paper6, SegmentedControl as SegmentedControl4, Stack as Stack8, TagsInput as TagsInput2, Text as Text8 } from "@mantine/core";
|
|
2757
|
+
import { IconBan as IconBan2, IconCircleCheck, IconMessages, IconPercentage, IconUser, IconX as IconX2 } from "@tabler/icons-react";
|
|
2758
|
+
var VERDICT_OPTIONS = [
|
|
2759
|
+
{ value: "approve", label: "Approve the claim", Icon: IconCircleCheck, color: "var(--mantine-color-green-6)" },
|
|
2760
|
+
{ value: "partial", label: "Part-approve it", Icon: IconPercentage, color: "var(--mantine-color-blue-6)" },
|
|
2761
|
+
{ value: "reject", label: "Reject the claim", Icon: IconBan2, color: "var(--mantine-color-red-6)" }
|
|
2762
|
+
];
|
|
2763
|
+
var ReviewTaskEditor = ({ fieldOptions, draft, onChange }) => {
|
|
2764
|
+
const answers = possibleAnswers(draft.answer);
|
|
2765
|
+
const pickShape = (type) => {
|
|
2766
|
+
if (type === "boolean") {
|
|
2767
|
+
onChange({ ...draft, answer: { type: "boolean" }, map: { true: "approve", false: "reject" } });
|
|
2768
|
+
} else if (type === "enum") {
|
|
2769
|
+
const options = draft.answer.type === "enum" ? draft.answer.options : [];
|
|
2770
|
+
onChange({ ...draft, answer: { type: "enum", options }, map: mapFor(options, draft.map) });
|
|
2771
|
+
}
|
|
2772
|
+
};
|
|
2773
|
+
const setOptions = (options) => onChange({ ...draft, answer: { type: "enum", options }, map: mapFor(options, draft.map) });
|
|
2774
|
+
return /* @__PURE__ */ React10.createElement(Stack8, { gap: "sm" }, /* @__PURE__ */ React10.createElement(
|
|
2775
|
+
BaseTextArea,
|
|
2776
|
+
{
|
|
2777
|
+
label: "What should the person be asked?",
|
|
2778
|
+
styles: chipInput,
|
|
2779
|
+
placeholder: "e.g. Does the delivery photo clearly show the goods at the outlet?",
|
|
2780
|
+
description: "Ask one plain question they can answer by looking.",
|
|
2781
|
+
minRows: 2,
|
|
2782
|
+
autosize: true,
|
|
2783
|
+
value: draft.prompt,
|
|
2784
|
+
onChange: (event) => onChange({ ...draft, prompt: event.currentTarget.value })
|
|
2785
|
+
}
|
|
2786
|
+
), /* @__PURE__ */ React10.createElement(
|
|
2787
|
+
BaseMultiSelect,
|
|
2788
|
+
{
|
|
2789
|
+
label: "What should they see?",
|
|
2790
|
+
styles: chipInput,
|
|
2791
|
+
placeholder: "Pick the answers to show them",
|
|
2792
|
+
description: "They will only see what you pick here.",
|
|
2793
|
+
data: fieldOptions,
|
|
2794
|
+
value: draft.show,
|
|
2795
|
+
onChange: (show) => onChange({ ...draft, show }),
|
|
2796
|
+
searchable: true
|
|
2797
|
+
}
|
|
2798
|
+
), /* @__PURE__ */ React10.createElement(
|
|
2799
|
+
BaseSelect,
|
|
2800
|
+
{
|
|
2801
|
+
label: "How do they answer?",
|
|
2802
|
+
w: 240,
|
|
2803
|
+
styles: chipInput,
|
|
2804
|
+
data: [
|
|
2805
|
+
{ value: "boolean", label: "Yes or No" },
|
|
2806
|
+
{ value: "enum", label: "Pick one from a list" }
|
|
2807
|
+
],
|
|
2808
|
+
value: draft.answer.type,
|
|
2809
|
+
onChange: pickShape
|
|
2810
|
+
}
|
|
2811
|
+
), draft.answer.type === "enum" && /* @__PURE__ */ React10.createElement(TagsInput2, { label: "The answers they can pick", styles: chipInput, placeholder: "Type an answer, press Enter", value: draft.answer.options, onChange: setOptions }), /* @__PURE__ */ React10.createElement(Stack8, { gap: 6 }, /* @__PURE__ */ React10.createElement(Text8, { style: microLabelStyle }, "What does each answer do?"), answers.length === 0 ? /* @__PURE__ */ React10.createElement(Text8, { size: "xs", c: "dimmed" }, "Add at least one answer above.") : answers.map((answer) => /* @__PURE__ */ React10.createElement(Group9, { key: answer, gap: "xs", align: "center" }, /* @__PURE__ */ React10.createElement(Text8, { size: "sm", fw: 500, style: { width: 160 } }, answerLabel(answer)), /* @__PURE__ */ React10.createElement(Text8, { size: "xs", c: "dimmed" }, "\u2192"), /* @__PURE__ */ React10.createElement(
|
|
2812
|
+
BaseSelect,
|
|
2813
|
+
{
|
|
2814
|
+
w: 200,
|
|
2815
|
+
styles: chipInput,
|
|
2816
|
+
data: VERDICT_OPTIONS.map(({ value, label }) => ({ value, label })),
|
|
2817
|
+
renderOption: ({ option }) => {
|
|
2818
|
+
const verdict = VERDICT_OPTIONS.find((entry) => entry.value === option.value);
|
|
2819
|
+
return /* @__PURE__ */ React10.createElement(Group9, { gap: 8, wrap: "nowrap" }, verdict && icon(verdict.Icon, 16, verdict.color), /* @__PURE__ */ React10.createElement(Text8, { size: "sm" }, option.label));
|
|
2820
|
+
},
|
|
2821
|
+
value: draft.map[answer] ?? null,
|
|
2822
|
+
onChange: (verdict) => verdict && onChange({ ...draft, map: { ...draft.map, [answer]: verdict } })
|
|
2823
|
+
}
|
|
2824
|
+
)))), /* @__PURE__ */ React10.createElement(Stack8, { gap: 6 }, /* @__PURE__ */ React10.createElement(Text8, { style: microLabelStyle }, "If nothing was attached"), /* @__PURE__ */ React10.createElement(
|
|
2825
|
+
SegmentedControl4,
|
|
2826
|
+
{
|
|
2827
|
+
w: 280,
|
|
2828
|
+
styles: segmentedPill,
|
|
2829
|
+
data: [
|
|
2830
|
+
{ value: String(FAILED), label: "The check fails" },
|
|
2831
|
+
{ value: String(PASSED), label: "The check passes" }
|
|
2832
|
+
],
|
|
2833
|
+
value: draft.whenMissing === PASSED ? String(PASSED) : String(FAILED),
|
|
2834
|
+
onChange: (value) => onChange({ ...draft, whenMissing: Number(value) })
|
|
2835
|
+
}
|
|
2836
|
+
), /* @__PURE__ */ React10.createElement(Text8, { size: "xs", c: "dimmed", maw: 320 }, "Nobody is asked when there is nothing to look at \u2014 the claim is decided this way instead.")), /* @__PURE__ */ React10.createElement(Text8, { size: "xs", c: "dimmed" }, "When there IS something to look at, the claim waits for the person's answer \u2014 it cannot be approved before then."), /* @__PURE__ */ React10.createElement(ReviewerPicker, { draft, onChange }));
|
|
2837
|
+
};
|
|
2838
|
+
var ROOM_SHAPE = /^[!#][^\s:]+:.+$/;
|
|
2839
|
+
var ReviewerPicker = ({ draft, onChange }) => {
|
|
2840
|
+
const [mode, setMode] = useState4(draft.assigneeDid && !draft.channelRoomId ? "person" : "channel");
|
|
2841
|
+
const [pickedName, setPickedName] = useState4(null);
|
|
2842
|
+
const pickPerson = (did, displayName) => {
|
|
2843
|
+
setPickedName(did ? displayName ?? null : null);
|
|
2844
|
+
onChange({ ...draft, assigneeDid: did ? matrixUserIdToDid(did) : void 0, channelRoomId: void 0 });
|
|
2845
|
+
};
|
|
2846
|
+
const pickChannel = (roomId) => onChange({ ...draft, channelRoomId: roomId || void 0, assigneeDid: void 0 });
|
|
2847
|
+
return /* @__PURE__ */ React10.createElement(Stack8, { gap: 6 }, /* @__PURE__ */ React10.createElement(Text8, { style: microLabelStyle }, "Who checks it?"), /* @__PURE__ */ React10.createElement(
|
|
2848
|
+
SegmentedControl4,
|
|
2849
|
+
{
|
|
2850
|
+
w: 320,
|
|
2851
|
+
size: "xs",
|
|
2852
|
+
styles: segmentedPill,
|
|
2853
|
+
value: mode,
|
|
2854
|
+
onChange: (value) => setMode(value),
|
|
2855
|
+
data: [
|
|
2856
|
+
{
|
|
2857
|
+
value: "channel",
|
|
2858
|
+
label: /* @__PURE__ */ React10.createElement(Group9, { gap: 6, wrap: "nowrap", justify: "center" }, icon(IconMessages, 14), /* @__PURE__ */ React10.createElement("span", null, "A channel"))
|
|
2859
|
+
},
|
|
2860
|
+
{
|
|
2861
|
+
value: "person",
|
|
2862
|
+
label: /* @__PURE__ */ React10.createElement(Group9, { gap: 6, wrap: "nowrap", justify: "center" }, icon(IconUser, 14), /* @__PURE__ */ React10.createElement("span", null, "A person"))
|
|
2863
|
+
}
|
|
2864
|
+
]
|
|
2865
|
+
}
|
|
2866
|
+
), mode === "channel" ? /* @__PURE__ */ React10.createElement(Stack8, { gap: 6 }, /* @__PURE__ */ React10.createElement(Text8, { size: "xs", c: "dimmed" }, "Anyone in the channel can pick the review up. Leave it empty and anyone with the reviewer role can."), draft.channelRoomId ? /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(PickedValue, { title: "Review channel", value: draft.channelRoomId, onClear: () => pickChannel(void 0), clearLabel: "Remove this channel" }), /* @__PURE__ */ React10.createElement(EngineChannelAccess, { roomId: draft.channelRoomId })) : /* @__PURE__ */ React10.createElement(ChannelSearch, { onPick: pickChannel })) : /* @__PURE__ */ React10.createElement(Stack8, { gap: 6 }, /* @__PURE__ */ React10.createElement(Text8, { size: "xs", c: "dimmed" }, "Name the person who reviews these claims. Leave it open and anyone with the reviewer role can pick it up."), draft.assigneeDid ? /* @__PURE__ */ React10.createElement(PickedValue, { title: pickedName ?? "Assigned reviewer", value: draft.assigneeDid, onClear: () => pickPerson(void 0), clearLabel: "Remove this reviewer" }) : /* @__PURE__ */ React10.createElement(UserSearch, { onPick: (user) => pickPerson(user.did, user.displayName), placeholder: "Search for the person who checks" })));
|
|
2867
|
+
};
|
|
2868
|
+
var EngineChannelAccess = ({ roomId }) => {
|
|
2869
|
+
const handlers = useBlocknoteHandlers();
|
|
2870
|
+
const invite = handlers.inviteEvalEngineToChannel;
|
|
2871
|
+
const [state, setState] = useState4("working");
|
|
2872
|
+
useEffect2(() => {
|
|
2873
|
+
if (typeof invite !== "function") return;
|
|
2874
|
+
let alive = true;
|
|
2875
|
+
setState("working");
|
|
2876
|
+
invite({ roomId }).then((result) => alive && setState(result?.status === "not-configured" ? "unavailable" : "ready")).catch((error) => {
|
|
2877
|
+
console.warn("[eval.rubric] could not invite the eval engine to the review channel:", error);
|
|
2878
|
+
if (alive) setState("failed");
|
|
2879
|
+
});
|
|
2880
|
+
return () => {
|
|
2881
|
+
alive = false;
|
|
2882
|
+
};
|
|
2883
|
+
}, [roomId, invite]);
|
|
2884
|
+
if (typeof invite !== "function" || state === "unavailable") return null;
|
|
2885
|
+
if (state === "working") {
|
|
2886
|
+
return /* @__PURE__ */ React10.createElement(Group9, { gap: 6, wrap: "nowrap" }, /* @__PURE__ */ React10.createElement(Loader2, { size: 12 }), /* @__PURE__ */ React10.createElement(Text8, { size: "xs", c: "dimmed" }, "Giving the eval engine access to this channel\u2026"));
|
|
2887
|
+
}
|
|
2888
|
+
if (state === "failed") {
|
|
2889
|
+
return /* @__PURE__ */ React10.createElement(Text8, { size: "xs", c: "red" }, "Couldn\u2019t give the eval engine access to this channel. Open the channel and invite it yourself, or pick another one.");
|
|
2890
|
+
}
|
|
2891
|
+
return /* @__PURE__ */ React10.createElement(Group9, { gap: 6, wrap: "nowrap" }, icon(IconCircleCheck, 14, "var(--mantine-color-green-6)"), /* @__PURE__ */ React10.createElement(Text8, { size: "xs", c: "dimmed" }, "The eval engine can post review requests here."));
|
|
2892
|
+
};
|
|
2893
|
+
var PickedValue = ({ title, value, onClear, clearLabel }) => /* @__PURE__ */ React10.createElement(Paper6, { p: "xs", style: panelStyle }, /* @__PURE__ */ React10.createElement(Group9, { gap: "xs", justify: "space-between", wrap: "nowrap" }, /* @__PURE__ */ React10.createElement(Stack8, { gap: 0 }, /* @__PURE__ */ React10.createElement(Text8, { size: "sm", fw: 500 }, title), /* @__PURE__ */ React10.createElement(Text8, { size: "xs", c: "dimmed", style: { wordBreak: "break-all" } }, value)), /* @__PURE__ */ React10.createElement(ActionIcon4, { variant: "subtle", color: "red", onClick: onClear, "aria-label": clearLabel }, /* @__PURE__ */ React10.createElement(IconX2, { size: 16 }))));
|
|
2894
|
+
var ChannelSearch = ({ onPick }) => {
|
|
2895
|
+
const handlers = useBlocknoteHandlers();
|
|
2896
|
+
const canList = typeof handlers.listDomainChannels === "function" || typeof handlers.sourceDomainSpaces === "function";
|
|
2897
|
+
const [options, setOptions] = useState4([]);
|
|
2898
|
+
const [loading, setLoading] = useState4(canList);
|
|
2899
|
+
const [loadError, setLoadError] = useState4(null);
|
|
2900
|
+
const [pasted, setPasted] = useState4("");
|
|
2901
|
+
useEffect2(() => {
|
|
2902
|
+
if (!canList) return;
|
|
2903
|
+
let alive = true;
|
|
2904
|
+
const load = async () => {
|
|
2905
|
+
if (typeof handlers.listDomainChannels === "function") {
|
|
2906
|
+
const rooms = await handlers.listDomainChannels();
|
|
2907
|
+
return rooms.map((room) => ({ value: room.roomId, label: room.name || room.roomId }));
|
|
2908
|
+
}
|
|
2909
|
+
const tree = await handlers.sourceDomainSpaces();
|
|
2910
|
+
const label = (room) => room.roomAlias || room.roomId;
|
|
2911
|
+
const groups = [
|
|
2912
|
+
{ group: "This entity", items: (tree.rooms ?? []).map((room) => ({ value: room.roomId, label: label(room) })) },
|
|
2913
|
+
...Object.entries(tree.subspaces ?? {}).map(([name, subspace]) => ({
|
|
2914
|
+
group: name,
|
|
2915
|
+
items: (subspace?.rooms ?? []).map((room) => ({ value: room.roomId, label: label(room) }))
|
|
2916
|
+
}))
|
|
2917
|
+
];
|
|
2918
|
+
return groups.filter((entry) => entry.items.length > 0);
|
|
2919
|
+
};
|
|
2920
|
+
load().then((loaded) => alive && setOptions(loaded)).catch((error) => {
|
|
2921
|
+
console.warn("[eval.rubric] channel listing failed:", error);
|
|
2922
|
+
if (alive) setLoadError(error instanceof Error ? error.message : String(error));
|
|
2923
|
+
}).finally(() => alive && setLoading(false));
|
|
2924
|
+
return () => {
|
|
2925
|
+
alive = false;
|
|
2926
|
+
};
|
|
2927
|
+
}, []);
|
|
2928
|
+
const pastedValid = ROOM_SHAPE.test(pasted.trim());
|
|
2929
|
+
const empty = !loading && options.length === 0;
|
|
2930
|
+
return /* @__PURE__ */ React10.createElement(Stack8, { gap: 6 }, canList && !empty && /* @__PURE__ */ React10.createElement(
|
|
2931
|
+
BaseSelect,
|
|
2932
|
+
{
|
|
2933
|
+
w: 320,
|
|
2934
|
+
styles: chipInput,
|
|
2935
|
+
placeholder: loading ? "Finding this domain\u2019s channels\u2026" : "Pick one of this domain\u2019s channels",
|
|
2936
|
+
data: options,
|
|
2937
|
+
value: null,
|
|
2938
|
+
onChange: (roomId) => roomId && onPick(roomId),
|
|
2939
|
+
searchable: true,
|
|
2940
|
+
disabled: loading,
|
|
2941
|
+
rightSection: loading ? /* @__PURE__ */ React10.createElement(Loader2, { size: 12 }) : void 0
|
|
2942
|
+
}
|
|
2943
|
+
), (empty || !canList) && /* @__PURE__ */ React10.createElement(Text8, { size: "xs", c: loadError ? "red" : "dimmed" }, loadError ? `Couldn\u2019t list this domain\u2019s channels (${loadError}) \u2014 paste the channel id instead.` : canList ? "No channels found in this domain space \u2014 paste the channel id instead." : "Channels can\u2019t be listed here \u2014 paste the channel id instead."), /* @__PURE__ */ React10.createElement(
|
|
2944
|
+
BaseTextInput,
|
|
2945
|
+
{
|
|
2946
|
+
w: 320,
|
|
2947
|
+
styles: chipInputStyles(Boolean(pasted.trim() && !pastedValid)),
|
|
2948
|
+
placeholder: "\u2026or paste a channel id (!room:server)",
|
|
2949
|
+
value: pasted,
|
|
2950
|
+
error: pasted.trim() && !pastedValid ? "A channel id starts with ! or # and contains :server" : void 0,
|
|
2951
|
+
onChange: (event) => setPasted(event.currentTarget.value),
|
|
2952
|
+
onKeyDown: (event) => {
|
|
2953
|
+
if (event.key === "Enter" && pastedValid) onPick(pasted.trim());
|
|
2954
|
+
},
|
|
2955
|
+
onBlur: () => {
|
|
2956
|
+
if (pastedValid) onPick(pasted.trim());
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
));
|
|
2960
|
+
};
|
|
2961
|
+
function possibleAnswers(answer) {
|
|
2962
|
+
if (answer.type === "boolean") return ["true", "false"];
|
|
2963
|
+
if (answer.type === "enum") return answer.options;
|
|
2964
|
+
return [];
|
|
2965
|
+
}
|
|
2966
|
+
function mapFor(options, existing) {
|
|
2967
|
+
return Object.fromEntries(options.map((option) => [option, existing[option] ?? "reject"]));
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/templates.ts
|
|
2971
|
+
var mustBeFilledIn = {
|
|
2972
|
+
id: "present",
|
|
2973
|
+
label: "Must be filled in",
|
|
2974
|
+
build: (field, body) => reject(newDraft(body, field.path, "present"), `${field.title} is missing`, "insufficient_evidence")
|
|
2975
|
+
};
|
|
2976
|
+
var mustBeAttached = {
|
|
2977
|
+
id: "attached",
|
|
2978
|
+
label: "Must be attached",
|
|
2979
|
+
build: (field, body) => reject(newDraft(body, field.path, "present"), `${field.title} is missing`, "insufficient_evidence")
|
|
2980
|
+
};
|
|
2981
|
+
var mustBeDuringPeriod = {
|
|
2982
|
+
id: "withinWindow",
|
|
2983
|
+
label: "Must be during the collection period",
|
|
2984
|
+
build: (field, body) => reject(newDraft(body, field.path, "withinWindow"), `${field.title} is outside the collection period`, "refuted")
|
|
2985
|
+
};
|
|
2986
|
+
var cannotBeInFuture = {
|
|
2987
|
+
id: "notInFuture",
|
|
2988
|
+
label: "Cannot be in the future",
|
|
2989
|
+
build: (field, body) => reject(newDraft(body, field.path, "notInFuture"), `${field.title} is in the future`, "refuted")
|
|
2990
|
+
};
|
|
2991
|
+
var mustBeOneOf = {
|
|
2992
|
+
id: "in",
|
|
2993
|
+
label: "Must be one of\u2026",
|
|
2994
|
+
build: (field, body) => {
|
|
2995
|
+
if (!field.choices || field.choices.length === 0) return void 0;
|
|
2996
|
+
const draft = newDraft(body, field.path, "in");
|
|
2997
|
+
draft.reject.when = { field: field.path, op: "in", value: field.choices.map((choice) => choice.value) };
|
|
2998
|
+
return reject(draft, `${field.title} is not allowed`, "out_of_authority");
|
|
2999
|
+
}
|
|
3000
|
+
};
|
|
3001
|
+
var mustBeAtLeast = {
|
|
3002
|
+
id: "atLeast",
|
|
3003
|
+
label: "Must be at least\u2026",
|
|
3004
|
+
build: (field, body) => {
|
|
3005
|
+
const draft = newDraft(body, field.path, ">=");
|
|
3006
|
+
draft.reject.when = { field: field.path, op: ">=", value: 1 };
|
|
3007
|
+
return reject(draft, `${field.title} is too low`, "refuted");
|
|
3008
|
+
}
|
|
3009
|
+
};
|
|
3010
|
+
var mustBeAPdf = {
|
|
3011
|
+
id: "pdf",
|
|
3012
|
+
label: "Must be a PDF",
|
|
3013
|
+
build: (field, body) => {
|
|
3014
|
+
const draft = newDraft(body, field.path, "mediaTypeIn");
|
|
3015
|
+
draft.reject.when = { field: field.path, op: "mediaTypeIn", value: ["application/pdf"] };
|
|
3016
|
+
return reject(draft, `${field.title} must be a PDF`, "invalid_evidence");
|
|
3017
|
+
}
|
|
3018
|
+
};
|
|
3019
|
+
var aPersonChecks = {
|
|
3020
|
+
id: "human",
|
|
3021
|
+
label: "A person checks it",
|
|
3022
|
+
build: (field, body) => {
|
|
3023
|
+
const draft = newDraft(body, field.path);
|
|
3024
|
+
draft.consequence = "human";
|
|
3025
|
+
draft.human = { ...draft.human, prompt: `Does \u201C${field.title}\u201D show what it should?`, show: [field.path] };
|
|
3026
|
+
return draft;
|
|
3027
|
+
}
|
|
3028
|
+
};
|
|
3029
|
+
var mustBeTicked = {
|
|
3030
|
+
id: "isTrue",
|
|
3031
|
+
label: "Must be ticked",
|
|
3032
|
+
build: (field, body) => reject(newDraft(body, field.path, "isTrue"), `${field.title} was not confirmed`, "refuted")
|
|
3033
|
+
};
|
|
3034
|
+
var everyRowMust = {
|
|
3035
|
+
id: "everyRow",
|
|
3036
|
+
label: "Every row must be filled in",
|
|
3037
|
+
build: (field, body) => {
|
|
3038
|
+
const column = field.columns?.find((inner) => inner.kind === "number") ?? field.columns?.[0];
|
|
3039
|
+
if (!column) return void 0;
|
|
3040
|
+
return reject(newDraft(body, column.path, "present"), `Every row of ${field.title} must be complete`, "insufficient_evidence");
|
|
3041
|
+
}
|
|
3042
|
+
};
|
|
3043
|
+
var mustHaveEntries = {
|
|
3044
|
+
id: "countBetween",
|
|
3045
|
+
label: "Must have at least one entry",
|
|
3046
|
+
build: (field, body) => {
|
|
3047
|
+
const draft = newDraft(body, field.path, "countBetween");
|
|
3048
|
+
draft.reject.when = { field: field.path, op: "countBetween", value: [1, 100] };
|
|
3049
|
+
return reject(draft, `${field.title} is empty`, "insufficient_evidence");
|
|
3050
|
+
}
|
|
3051
|
+
};
|
|
3052
|
+
var mustBeInProjectArea = {
|
|
3053
|
+
id: "withinBoundary",
|
|
3054
|
+
label: "Must be inside the project area",
|
|
3055
|
+
build: (field, body) => {
|
|
3056
|
+
const draft = newDraft(body, field.path, "withinBoundary");
|
|
3057
|
+
draft.reject.when = { field: field.path, op: "withinBoundary", value: "ctx.collection.projectBoundary" };
|
|
3058
|
+
return reject(draft, `${field.title} is outside the project area`, "out_of_authority");
|
|
3059
|
+
}
|
|
3060
|
+
};
|
|
3061
|
+
var TEMPLATES = {
|
|
3062
|
+
text: [mustBeFilledIn],
|
|
3063
|
+
number: [mustBeAtLeast],
|
|
3064
|
+
date: [mustBeDuringPeriod, cannotBeInFuture],
|
|
3065
|
+
choice: [mustBeOneOf],
|
|
3066
|
+
multichoice: [mustBeFilledIn, mustHaveEntries],
|
|
3067
|
+
boolean: [mustBeTicked],
|
|
3068
|
+
file: [mustBeAttached, mustBeAPdf, aPersonChecks],
|
|
3069
|
+
signature: [mustBeAttached, aPersonChecks],
|
|
3070
|
+
geo: [mustBeInProjectArea],
|
|
3071
|
+
rows: [everyRowMust, mustHaveEntries],
|
|
3072
|
+
cells: [mustBeFilledIn],
|
|
3073
|
+
keyed_choice: [mustBeFilledIn],
|
|
3074
|
+
keyed_text: [mustBeFilledIn]
|
|
3075
|
+
};
|
|
3076
|
+
function templatesForField(field, body) {
|
|
3077
|
+
const templates = [];
|
|
3078
|
+
for (const def of TEMPLATES[field.kind] ?? []) {
|
|
3079
|
+
const draft = def.build(field, body);
|
|
3080
|
+
if (draft) templates.push({ id: def.id, label: def.label, draft });
|
|
3081
|
+
}
|
|
3082
|
+
return templates;
|
|
3083
|
+
}
|
|
3084
|
+
function reject(draft, reason, verdictClass) {
|
|
3085
|
+
draft.consequence = "reject";
|
|
3086
|
+
draft.reject = { ...draft.reject, reason, verdictClass };
|
|
3087
|
+
return draft;
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/RuleEditor.tsx
|
|
3091
|
+
var EXT_SUBJECT = "__external-check__";
|
|
3092
|
+
var AI_CHECK = "__ai-check__";
|
|
3093
|
+
var RuleEditor = ({ opened, onClose, body, catalog, index, field, editing, onSave }) => {
|
|
3094
|
+
const [draft, setDraft] = useState5(() => editing ? draftFromRule(editing, body) : newDraft(body, field.path));
|
|
3095
|
+
const [showTemplates, setShowTemplates] = useState5(!editing);
|
|
3096
|
+
const ext = draft.ext;
|
|
3097
|
+
const ai = draft.ai;
|
|
3098
|
+
const grown = draft.grown;
|
|
3099
|
+
const draftBody = useMemo4(() => {
|
|
3100
|
+
const withLane = ext ? upsertExtSource(body, ext) : ai ? upsertAiCheck(body, ai) : body;
|
|
3101
|
+
return grown ? grown.members.reduce((acc, member) => member.kind === "ai" ? upsertAiCheck(acc, member.ai) : acc, withLane) : withLane;
|
|
3102
|
+
}, [body, ext, ai, grown]);
|
|
3103
|
+
const fieldOptions = useMemo4(() => targetableFields(field).map((entry) => ({ value: entry.path, label: entry.title })), [field]);
|
|
3104
|
+
const subjectOptions = useMemo4(
|
|
3105
|
+
() => [
|
|
3106
|
+
...fieldOptions,
|
|
3107
|
+
{ value: EXT_SUBJECT, label: "An outside service checks this answer" },
|
|
3108
|
+
...ai ? [{ value: AI_CHECK, label: `The AI checks \u201C${index.get(ai.sendField)?.title ?? field.title}\u201D` }] : []
|
|
3109
|
+
],
|
|
3110
|
+
[fieldOptions, ai, index, field.title]
|
|
3111
|
+
);
|
|
3112
|
+
const showOptions = useMemo4(() => [...index.values()].map((entry) => ({ value: entry.path, label: entry.title })), [index]);
|
|
3113
|
+
const templates = useMemo4(() => templatesForField(field, body), [field, body]);
|
|
3114
|
+
const consequences = useMemo4(() => ai || ext ? CONSEQUENCES.filter((entry) => entry.value === "reject") : CONSEQUENCES, [ext, ai]);
|
|
3115
|
+
const preview = useMemo4(() => {
|
|
3116
|
+
const applied = applyDraft(
|
|
3117
|
+
editing ? removeRule(body, editing) : body,
|
|
3118
|
+
{ ...draft, at: void 0 },
|
|
3119
|
+
void 0,
|
|
3120
|
+
index.get(draft.ext?.sendField ?? draft.ai?.sendField ?? draft.field)?.title
|
|
3121
|
+
);
|
|
3122
|
+
const rule = listRules(applied).find((entry) => entry.code === draft.code);
|
|
3123
|
+
const problems = validateRubric(applied, catalog).filter((diagnostic) => diagnostic.severity === "error" && ruleForDiagnosticPath(applied, diagnostic.path)?.code === draft.code).map(problemText);
|
|
3124
|
+
return { sentence: rule ? describeRule(rule, { fields: index, aiChecks: applied.aiChecks }) : "", problems: [...new Set(problems)] };
|
|
3125
|
+
}, [body, catalog, draft, editing, index]);
|
|
3126
|
+
const pickTemplate = (next) => {
|
|
3127
|
+
setDraft({ ...next, at: draft.at, code: draft.code });
|
|
3128
|
+
setShowTemplates(false);
|
|
3129
|
+
};
|
|
3130
|
+
const pickSubject = (path) => {
|
|
3131
|
+
if (!path || path === AI_CHECK || path.startsWith("~")) return;
|
|
3132
|
+
if (path === EXT_SUBJECT) {
|
|
3133
|
+
setDraft((current) => {
|
|
3134
|
+
if (current.ext) return current;
|
|
3135
|
+
const sendField = current.field.startsWith("$") ? current.field : field.path;
|
|
3136
|
+
return withExtSubject({ ...current, ai: void 0 }, { name: autoExtName(sendField, body.sources), endpoint: "", audience: "", expect: "boolean", sendField });
|
|
3137
|
+
});
|
|
3138
|
+
return;
|
|
3139
|
+
}
|
|
3140
|
+
setDraft((current) => {
|
|
3141
|
+
const legal = operatorsForKind(index.get(path)?.kind ?? "text");
|
|
3142
|
+
const keepWhen = !current.ext && !current.ai && legal.includes(current.reject.when.op);
|
|
3143
|
+
return {
|
|
3144
|
+
...current,
|
|
3145
|
+
ext: void 0,
|
|
3146
|
+
ai: void 0,
|
|
3147
|
+
field: path,
|
|
3148
|
+
reject: { ...current.reject, when: keepWhen ? { ...current.reject.when, field: path } : { field: path, op: legal[0] } },
|
|
3149
|
+
human: { ...current.human, show: current.human.show.includes(path) ? current.human.show : [path] }
|
|
3150
|
+
};
|
|
3151
|
+
});
|
|
3152
|
+
};
|
|
3153
|
+
const enterAiCheck = () => {
|
|
3154
|
+
setDraft((current) => {
|
|
3155
|
+
if (current.ai) return current;
|
|
3156
|
+
const sendField = current.field.startsWith("$") ? current.field : field.path;
|
|
3157
|
+
return withAiSubject(current, { name: autoAiName(sendField, body.aiChecks), instructions: [""], sendField });
|
|
3158
|
+
});
|
|
3159
|
+
};
|
|
3160
|
+
const setExt = (next) => setDraft((current) => withExtSubject(current, next));
|
|
3161
|
+
const setAi = (next) => setDraft((current) => withAiSubject(current, next));
|
|
3162
|
+
const setRejectRule = (rule) => {
|
|
3163
|
+
if (rule.field === EXT_SUBJECT) {
|
|
3164
|
+
pickSubject(EXT_SUBJECT);
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
3167
|
+
setDraft((current) => ({ ...current, field: rule.field, reject: { ...current.reject, when: rule } }));
|
|
3168
|
+
};
|
|
3169
|
+
const setConsequence = (consequence) => setDraft((current) => {
|
|
3170
|
+
if (consequence !== "reject") return { ...current, consequence };
|
|
3171
|
+
if (current.ext?.expect === "score") return { ...current, consequence };
|
|
3172
|
+
if (current.ext || current.ai) return { ...current, consequence, reject: { ...current.reject, when: { field: current.field, op: "isTrue" } } };
|
|
3173
|
+
const legal = operatorsForKind(index.get(current.field)?.kind ?? "text");
|
|
3174
|
+
const when = legal.includes(current.reject.when.op) ? current.reject.when : { field: current.field, op: legal[0] };
|
|
3175
|
+
return { ...current, consequence, reject: { ...current.reject, when } };
|
|
3176
|
+
});
|
|
3177
|
+
const setOnFail = (onFail) => setDraft((current) => ({ ...current, reject: { ...current.reject, onFail } }));
|
|
3178
|
+
const grow = () => setDraft((current) => growDraft(current, field.path));
|
|
3179
|
+
const addCheck = () => setDraft((current) => addGrownMember(current, field.path));
|
|
3180
|
+
const removeCheck = (at) => setDraft((current) => removeGrownMember(current, at));
|
|
3181
|
+
const setMemberRule = (at, rule) => setDraft(
|
|
3182
|
+
(current) => current.grown ? { ...current, grown: { ...current.grown, members: current.grown.members.map((member, i) => i === at ? { kind: "check", rule } : member) } } : current
|
|
3183
|
+
);
|
|
3184
|
+
const setMemberAi = (at, memberAi) => setDraft(
|
|
3185
|
+
(current) => current.grown ? { ...current, grown: { ...current.grown, members: current.grown.members.map((member, i) => i === at ? { kind: "ai", ai: memberAi } : member) } } : current
|
|
3186
|
+
);
|
|
3187
|
+
const toAiMember = (at) => setDraft((current) => {
|
|
3188
|
+
const state = current.grown;
|
|
3189
|
+
const member = state?.members[at];
|
|
3190
|
+
if (!state || !member || member.kind === "ai") return current;
|
|
3191
|
+
const sendField = member.rule.field.startsWith("$") ? member.rule.field : field.path;
|
|
3192
|
+
const taken = state.members.flatMap((entry) => entry.kind === "ai" ? [entry.ai.name] : []);
|
|
3193
|
+
const memberAi = { name: autoAiName(sendField, body.aiChecks, taken), instructions: [""], sendField };
|
|
3194
|
+
return { ...current, grown: { ...state, members: state.members.map((entry, i) => i === at ? { kind: "ai", ai: memberAi } : entry) } };
|
|
3195
|
+
});
|
|
3196
|
+
const memberOptionsFor = (rule) => showOptions.some((option) => option.value === rule.field) ? showOptions : [...showOptions, { value: rule.field, label: fieldTitle(rule.field, { fields: index }) }];
|
|
3197
|
+
const scoreExt = ext?.expect === "score";
|
|
3198
|
+
const scoreFloorOn = scoreExt && (ext?.bands?.rejectBelow ?? 0) > 0;
|
|
3199
|
+
const holdsForReview = draft.reject.onFail === "review";
|
|
3200
|
+
const showOnFail = draft.consequence === "reject" && !scoreExt && (!!ai || !!grown || holdsForReview);
|
|
3201
|
+
const needsMessage = draft.consequence === "reject" && (scoreExt ? scoreFloorOn : !holdsForReview);
|
|
3202
|
+
const missingMessage = needsMessage && !draft.reject.reason.trim();
|
|
3203
|
+
const extIncomplete = !!ext && !(/^https?:\/\//.test(ext.endpoint) && /^did:web:/.test(ext.audience));
|
|
3204
|
+
const aiIncomplete = !!ai && ai.instructions.every((rule) => !rule.trim()) || !!grown?.members.some((member) => member.kind === "ai" && member.ai.instructions.every((rule) => !rule.trim()));
|
|
3205
|
+
const canSave = preview.problems.length === 0 && !missingMessage && !extIncomplete && !aiIncomplete;
|
|
3206
|
+
return /* @__PURE__ */ React11.createElement(
|
|
3207
|
+
Modal2,
|
|
3208
|
+
{
|
|
3209
|
+
opened,
|
|
3210
|
+
onClose,
|
|
3211
|
+
title: editing ? "Change this rule" : `Add a rule to \u201C${field.title}\u201D`,
|
|
3212
|
+
size: "xl",
|
|
3213
|
+
radius: 14,
|
|
3214
|
+
styles: { title: { fontWeight: 600 } },
|
|
3215
|
+
scrollAreaComponent: ScrollArea3.Autosize
|
|
3216
|
+
},
|
|
3217
|
+
showTemplates ? /* @__PURE__ */ React11.createElement(Stack9, { gap: "sm" }, /* @__PURE__ */ React11.createElement(Text9, { size: "sm", c: "dimmed" }, "Pick one \u2014 you can change it afterwards."), templates.map((template) => /* @__PURE__ */ React11.createElement(Button7, { key: template.id, variant: "default", justify: "flex-start", size: "md", styles: chipButton, onClick: () => pickTemplate(template.draft) }, template.label)), templates.length === 0 && /* @__PURE__ */ React11.createElement(Text9, { size: "sm", c: "dimmed" }, "There are no quick rules for this kind of answer \u2014 build your own below."), /* @__PURE__ */ React11.createElement(Divider2, { label: "or", labelPosition: "center", color: HAIRLINE }), /* @__PURE__ */ React11.createElement(Button7, { variant: "subtle", styles: quietButton, leftSection: /* @__PURE__ */ React11.createElement(IconWand, { size: 16 }), onClick: () => setShowTemplates(false) }, "Custom rule")) : /* @__PURE__ */ React11.createElement(Stack9, { gap: "md" }, grown ? (
|
|
3218
|
+
/* The checks box (owner 2026-08-03, v3): every check a sentence row
|
|
3219
|
+
(or the inline AI lane). ALL of them must pass — no knob; any/
|
|
3220
|
+
at-least belong to the rulebook's approval policy. Compiles to
|
|
3221
|
+
ONE gate whose `when` is `{all: […]}`. */
|
|
3222
|
+
/* @__PURE__ */ React11.createElement(Paper7, { p: "md", style: panelStyle }, /* @__PURE__ */ React11.createElement(Stack9, { gap: 8 }, /* @__PURE__ */ React11.createElement(Text9, { style: microLabelStyle }, GROWN_CHECKS_LABEL), grown.members.map((member, at) => /* @__PURE__ */ React11.createElement(Group10, { key: `${at}-${grown.members.length}`, gap: "xs", align: "flex-start", wrap: "nowrap" }, /* @__PURE__ */ React11.createElement(Box4, { style: { flex: 1 } }, member.kind === "check" ? /* @__PURE__ */ React11.createElement(
|
|
3223
|
+
ConditionRow,
|
|
3224
|
+
{
|
|
3225
|
+
index,
|
|
3226
|
+
options: memberOptionsFor(member.rule),
|
|
3227
|
+
sources: draftBody.sources,
|
|
3228
|
+
aiChecks: draftBody.aiChecks,
|
|
3229
|
+
rule: member.rule,
|
|
3230
|
+
onChange: (next) => setMemberRule(at, next),
|
|
3231
|
+
lead: "",
|
|
3232
|
+
extraOperators: [{ value: AI_CHECK, label: AI_CHECK_LANE_LABEL, Icon: IconRobot3 }],
|
|
3233
|
+
onPickExtra: () => toAiMember(at)
|
|
3234
|
+
}
|
|
3235
|
+
) : (
|
|
3236
|
+
// The AI member: the question the AI looks at, then the
|
|
3237
|
+
// same inline instructions (+ "what can the AI see")
|
|
3238
|
+
// every AI lane has. No operator, no value — the check
|
|
3239
|
+
// IS the sentence.
|
|
3240
|
+
/* @__PURE__ */ React11.createElement(Stack9, { gap: 8 }, /* @__PURE__ */ React11.createElement(Group10, { gap: 8, align: "center", wrap: "wrap" }, /* @__PURE__ */ React11.createElement(Text9, { style: leadWordStyle }, GROUP_AI_MEMBER_LEAD), /* @__PURE__ */ React11.createElement(
|
|
3241
|
+
BaseSelect,
|
|
3242
|
+
{
|
|
3243
|
+
w: 230,
|
|
3244
|
+
styles: chipInput,
|
|
3245
|
+
data: showOptions,
|
|
3246
|
+
value: member.ai.sendField,
|
|
3247
|
+
onChange: (path) => path && setMemberAi(at, { ...member.ai, sendField: path }),
|
|
3248
|
+
searchable: showOptions.length > 6
|
|
3249
|
+
}
|
|
3250
|
+
)), /* @__PURE__ */ React11.createElement(
|
|
3251
|
+
AiCheckInline,
|
|
3252
|
+
{
|
|
3253
|
+
ai: member.ai,
|
|
3254
|
+
questionTitle: fieldTitle(member.ai.sendField, { fields: index }),
|
|
3255
|
+
index,
|
|
3256
|
+
onChange: (next) => setMemberAi(at, next)
|
|
3257
|
+
}
|
|
3258
|
+
))
|
|
3259
|
+
)), /* @__PURE__ */ React11.createElement(ActionIcon5, { variant: "subtle", color: "red", onClick: () => removeCheck(at), "aria-label": "Remove this check" }, /* @__PURE__ */ React11.createElement(IconTrash3, { size: 16 })))), /* @__PURE__ */ React11.createElement(Button7, { variant: "subtle", size: "compact-sm", styles: quietButton, style: { alignSelf: "flex-start" }, leftSection: /* @__PURE__ */ React11.createElement(IconPlus4, { size: 14 }), onClick: addCheck }, GROW_RULE_LABEL)))
|
|
3260
|
+
) : draft.consequence === "reject" && !ext && !ai ? /* @__PURE__ */ React11.createElement(
|
|
3261
|
+
ConditionRow,
|
|
3262
|
+
{
|
|
3263
|
+
index,
|
|
3264
|
+
options: subjectOptions,
|
|
3265
|
+
rule: draft.reject.when,
|
|
3266
|
+
onChange: setRejectRule,
|
|
3267
|
+
lead: "If",
|
|
3268
|
+
extraOperators: [{ value: AI_CHECK, label: AI_CHECK_LANE_LABEL, Icon: IconRobot3 }],
|
|
3269
|
+
onPickExtra: enterAiCheck
|
|
3270
|
+
}
|
|
3271
|
+
) : /* @__PURE__ */ React11.createElement(
|
|
3272
|
+
BaseSelect,
|
|
3273
|
+
{
|
|
3274
|
+
label: "Which answer is this rule about?",
|
|
3275
|
+
w: 300,
|
|
3276
|
+
styles: chipInput,
|
|
3277
|
+
data: subjectOptions,
|
|
3278
|
+
value: ext ? EXT_SUBJECT : ai ? AI_CHECK : draft.field,
|
|
3279
|
+
onChange: pickSubject,
|
|
3280
|
+
searchable: subjectOptions.length > 6,
|
|
3281
|
+
renderOption: ({ option }) => {
|
|
3282
|
+
const OptionIcon = option.value === AI_CHECK ? IconRobot3 : option.value === EXT_SUBJECT ? IconWorld2 : void 0;
|
|
3283
|
+
return /* @__PURE__ */ React11.createElement(Group10, { gap: 8, wrap: "nowrap" }, OptionIcon && icon(OptionIcon, 16), /* @__PURE__ */ React11.createElement(Text9, { size: "sm" }, option.label));
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
), ext && /* @__PURE__ */ React11.createElement(ExternalCheckInline, { ext, questionTitle: index.get(ext.sendField)?.title ?? field.title, onChange: setExt }), ai && /* @__PURE__ */ React11.createElement(AiCheckInline, { ai, questionTitle: index.get(ai.sendField)?.title ?? field.title, index, onChange: setAi }), !ai && !grown && /* @__PURE__ */ React11.createElement(Stack9, { gap: 6 }, /* @__PURE__ */ React11.createElement(Text9, { style: leadWordStyle }, "Then\u2026"), /* @__PURE__ */ React11.createElement(Group10, { gap: "sm", align: "stretch", grow: true }, consequences.map((consequence) => /* @__PURE__ */ React11.createElement(
|
|
3287
|
+
ConsequenceCard,
|
|
3288
|
+
{
|
|
3289
|
+
key: consequence.value,
|
|
3290
|
+
active: draft.consequence === consequence.value,
|
|
3291
|
+
tint: consequence.color,
|
|
3292
|
+
iconNode: consequenceIcon(consequence.value, 22),
|
|
3293
|
+
title: consequence.label,
|
|
3294
|
+
description: consequence.hint,
|
|
3295
|
+
onClick: () => setConsequence(consequence.value)
|
|
3296
|
+
}
|
|
3297
|
+
)))), /* @__PURE__ */ React11.createElement(Paper7, { p: "md", style: panelStyle }, draft.consequence === "reject" && /* @__PURE__ */ React11.createElement(Stack9, { gap: "xs" }, ext && !scoreExt && /* @__PURE__ */ React11.createElement(Text9, { size: "xs", c: "dimmed" }, "The outside service must confirm this answer \u2014 when it doesn\u2019t, the claim is rejected with your message."), scoreExt && /* @__PURE__ */ React11.createElement(Text9, { size: "xs", c: "dimmed" }, scoreFloorOn ? "The bands above decide: too low is turned down with your message, the middle waits for a person, high enough passes." : "The bands above decide: below the passing score the claim waits for a person \u2014 nothing is turned down automatically."), ai && /* @__PURE__ */ React11.createElement(Text9, { size: "xs", c: "dimmed" }, "The AI reads this answer against your rules and gives a yes or a no. When it says no\u2026"), showOnFail && /* @__PURE__ */ React11.createElement(Group10, { gap: "sm", align: "stretch", grow: true }, /* @__PURE__ */ React11.createElement(
|
|
3298
|
+
ConsequenceCard,
|
|
3299
|
+
{
|
|
3300
|
+
active: !holdsForReview,
|
|
3301
|
+
tint: "var(--mantine-color-red-6)",
|
|
3302
|
+
iconNode: consequenceIcon("reject", 22),
|
|
3303
|
+
title: "Reject the claim",
|
|
3304
|
+
description: "The claim is turned down straight away, with your message.",
|
|
3305
|
+
onClick: () => setOnFail("reject")
|
|
3306
|
+
}
|
|
3307
|
+
), /* @__PURE__ */ React11.createElement(
|
|
3308
|
+
ConsequenceCard,
|
|
3309
|
+
{
|
|
3310
|
+
active: holdsForReview,
|
|
3311
|
+
tint: "var(--mantine-color-grape-6)",
|
|
3312
|
+
iconNode: consequenceIcon("human", 22),
|
|
3313
|
+
title: "A person checks it",
|
|
3314
|
+
description: "The claim is held for a reviewer instead of being turned down.",
|
|
3315
|
+
onClick: () => setOnFail("review")
|
|
3316
|
+
}
|
|
3317
|
+
)), needsMessage && /* @__PURE__ */ React11.createElement(
|
|
3318
|
+
BaseTextInput,
|
|
3319
|
+
{
|
|
3320
|
+
label: scoreExt ? SCORE_MESSAGE_LABEL : "What should the claimant be told?",
|
|
3321
|
+
styles: chipInputStyles(missingMessage),
|
|
3322
|
+
placeholder: "e.g. Delivery date outside the collection period",
|
|
3323
|
+
description: "They will read this, so say plainly what went wrong.",
|
|
3324
|
+
error: missingMessage ? "Please write the message." : void 0,
|
|
3325
|
+
value: draft.reject.reason,
|
|
3326
|
+
onChange: (event) => setDraft({ ...draft, reject: { ...draft.reject, reason: event.currentTarget.value } })
|
|
3327
|
+
}
|
|
3328
|
+
)), draft.consequence === "human" && /* @__PURE__ */ React11.createElement(ReviewTaskEditor, { fieldOptions: showOptions, draft: draft.human, onChange: (human) => setDraft((current) => ({ ...current, human })) })), draft.appliesWhen ? /* @__PURE__ */ React11.createElement(Paper7, { p: "sm", style: panelStyle }, /* @__PURE__ */ React11.createElement(Group10, { gap: "xs", align: "flex-start", wrap: "nowrap" }, /* @__PURE__ */ React11.createElement(Box4, { style: { flex: 1 } }, /* @__PURE__ */ React11.createElement(
|
|
3329
|
+
ConditionRow,
|
|
3330
|
+
{
|
|
3331
|
+
index,
|
|
3332
|
+
options: showOptions,
|
|
3333
|
+
sources: draftBody.sources,
|
|
3334
|
+
aiChecks: draftBody.aiChecks,
|
|
3335
|
+
rule: draft.appliesWhen,
|
|
3336
|
+
onChange: (rule) => setDraft((current) => ({ ...current, appliesWhen: rule })),
|
|
3337
|
+
lead: "Only check this if"
|
|
3338
|
+
}
|
|
3339
|
+
)), /* @__PURE__ */ React11.createElement(ActionIcon5, { variant: "subtle", color: "red", onClick: () => setDraft((current) => ({ ...current, appliesWhen: void 0 })), "aria-label": "Always check this rule" }, /* @__PURE__ */ React11.createElement(IconX3, { size: 16 })))) : /* @__PURE__ */ React11.createElement(
|
|
3340
|
+
Button7,
|
|
3341
|
+
{
|
|
3342
|
+
variant: "subtle",
|
|
3343
|
+
size: "compact-sm",
|
|
3344
|
+
styles: quietButton,
|
|
3345
|
+
style: { alignSelf: "flex-start" },
|
|
3346
|
+
leftSection: /* @__PURE__ */ React11.createElement(IconPlus4, { size: 14 }),
|
|
3347
|
+
onClick: () => setDraft((current) => ({ ...current, appliesWhen: { field: field.path, op: "present" } }))
|
|
3348
|
+
},
|
|
3349
|
+
"Only check this if\u2026"
|
|
3350
|
+
), draft.consequence === "reject" && !grown && !ext && /* @__PURE__ */ React11.createElement(Button7, { variant: "subtle", size: "compact-sm", styles: quietButton, style: { alignSelf: "flex-start" }, leftSection: /* @__PURE__ */ React11.createElement(IconPlus4, { size: 14 }), onClick: grow }, GROW_RULE_LABEL), preview.problems.length > 0 && /* @__PURE__ */ React11.createElement(Alert4, { color: "red", icon: /* @__PURE__ */ React11.createElement(IconAlertTriangle4, { size: 16 }), styles: actionAlertStyles }, /* @__PURE__ */ React11.createElement(Stack9, { gap: 2 }, preview.problems.map((problem) => /* @__PURE__ */ React11.createElement(Text9, { key: problem, size: "xs" }, problem)))), /* @__PURE__ */ React11.createElement(Stack9, { gap: 6 }, /* @__PURE__ */ React11.createElement(Text9, { style: microLabelStyle }, "This rule reads:"), /* @__PURE__ */ React11.createElement(RuleSentence, { text: preview.sentence })), /* @__PURE__ */ React11.createElement(Group10, { justify: "space-between" }, !editing ? /* @__PURE__ */ React11.createElement(Button7, { variant: "subtle", styles: quietButton, onClick: () => setShowTemplates(true) }, "Back to the quick rules") : /* @__PURE__ */ React11.createElement("span", null), /* @__PURE__ */ React11.createElement(Group10, { gap: "xs" }, /* @__PURE__ */ React11.createElement(Button7, { variant: "default", onClick: onClose }, "Cancel"), /* @__PURE__ */ React11.createElement(BasePrimaryButton, { disabled: !canSave, onClick: () => onSave(draft) }, editing ? "Save the change" : "Add this rule"))))
|
|
3351
|
+
);
|
|
3352
|
+
};
|
|
3353
|
+
var ConsequenceCard = ({
|
|
3354
|
+
active,
|
|
3355
|
+
tint,
|
|
3356
|
+
iconNode,
|
|
3357
|
+
title,
|
|
3358
|
+
description,
|
|
3359
|
+
onClick
|
|
3360
|
+
}) => /* @__PURE__ */ React11.createElement(
|
|
3361
|
+
Paper7,
|
|
3362
|
+
{
|
|
3363
|
+
p: "sm",
|
|
3364
|
+
onClick,
|
|
3365
|
+
role: "button",
|
|
3366
|
+
tabIndex: 0,
|
|
3367
|
+
onKeyDown: (event) => {
|
|
3368
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
3369
|
+
event.preventDefault();
|
|
3370
|
+
onClick();
|
|
3371
|
+
}
|
|
3372
|
+
},
|
|
3373
|
+
style: {
|
|
3374
|
+
flex: 1,
|
|
3375
|
+
cursor: "pointer",
|
|
3376
|
+
borderRadius: 10,
|
|
3377
|
+
border: `1px solid ${active ? `color-mix(in srgb, ${tint} 55%, transparent)` : HAIRLINE}`,
|
|
3378
|
+
background: active ? `color-mix(in srgb, ${tint} 10%, transparent)` : void 0,
|
|
3379
|
+
transition: "border-color 120ms ease, background 120ms ease"
|
|
3380
|
+
}
|
|
3381
|
+
},
|
|
3382
|
+
/* @__PURE__ */ React11.createElement(Stack9, { gap: 4 }, /* @__PURE__ */ React11.createElement(Group10, { gap: 6, align: "center", wrap: "nowrap" }, iconNode, /* @__PURE__ */ React11.createElement(Text9, { fw: 600, style: { fontSize: 13.5, color: active ? tint : void 0 } }, title)), /* @__PURE__ */ React11.createElement(Text9, { size: "xs", c: "dimmed" }, description))
|
|
3383
|
+
);
|
|
3384
|
+
|
|
3385
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/FrequencyRule.tsx
|
|
3386
|
+
import React12, { useEffect as useEffect3, useRef as useRef2, useState as useState6 } from "react";
|
|
3387
|
+
import { Group as Group11, Paper as Paper8, SegmentedControl as SegmentedControl5, Stack as Stack10, Switch, Text as Text10 } from "@mantine/core";
|
|
3388
|
+
|
|
3389
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/frequencyModel.ts
|
|
3390
|
+
var DEFAULT_ID = "claim-limit";
|
|
3391
|
+
var DEFAULT_MAX = 1;
|
|
3392
|
+
var DEFAULT_PER = "month";
|
|
3393
|
+
function deriveFrequency(body) {
|
|
3394
|
+
const frequency = body.frequency;
|
|
3395
|
+
return {
|
|
3396
|
+
on: !!frequency,
|
|
3397
|
+
max: frequency?.max ?? DEFAULT_MAX,
|
|
3398
|
+
per: frequency?.per ?? DEFAULT_PER,
|
|
3399
|
+
onBreach: frequency?.onBreach ?? "review"
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
function compileFrequency(body, state) {
|
|
3403
|
+
if (!state.on) {
|
|
3404
|
+
if (body.frequency === void 0) return body;
|
|
3405
|
+
const { frequency: _off, ...rest } = body;
|
|
3406
|
+
return rest;
|
|
3407
|
+
}
|
|
3408
|
+
const prior = body.frequency;
|
|
3409
|
+
const onBreach = state.onBreach === (prior?.onBreach ?? "review") ? prior?.onBreach : state.onBreach === "review" ? void 0 : "reject";
|
|
3410
|
+
return {
|
|
3411
|
+
...body,
|
|
3412
|
+
frequency: {
|
|
3413
|
+
"@type": "Frequency",
|
|
3414
|
+
id: prior?.id ?? DEFAULT_ID,
|
|
3415
|
+
...prior?.label !== void 0 ? { label: prior.label } : {},
|
|
3416
|
+
max: state.max,
|
|
3417
|
+
per: state.per,
|
|
3418
|
+
...onBreach !== void 0 ? { onBreach } : {}
|
|
3419
|
+
}
|
|
3420
|
+
};
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/FrequencyRule.tsx
|
|
3424
|
+
var FrequencyRule = ({ body, onChange, disabled }) => {
|
|
3425
|
+
const [state, setState] = useState6(() => deriveFrequency(body));
|
|
3426
|
+
const lastWrittenRef = useRef2(null);
|
|
3427
|
+
const bodyJson = JSON.stringify(body);
|
|
3428
|
+
useEffect3(() => {
|
|
3429
|
+
if (bodyJson === lastWrittenRef.current) return;
|
|
3430
|
+
setState(deriveFrequency(body));
|
|
3431
|
+
}, [bodyJson]);
|
|
3432
|
+
const commit = (next) => {
|
|
3433
|
+
setState(next);
|
|
3434
|
+
const compiled = compileFrequency(body, next);
|
|
3435
|
+
const json = JSON.stringify(compiled);
|
|
3436
|
+
lastWrittenRef.current = json;
|
|
3437
|
+
if (json !== bodyJson) onChange(compiled);
|
|
3438
|
+
};
|
|
3439
|
+
return /* @__PURE__ */ React12.createElement(Paper8, { p: 18, style: cardStyle }, /* @__PURE__ */ React12.createElement(Stack10, { gap: "sm" }, /* @__PURE__ */ React12.createElement(Group11, { gap: "sm", justify: "space-between", align: "center", wrap: "wrap" }, /* @__PURE__ */ React12.createElement(Text10, { size: "sm", fw: 600 }, FREQUENCY_TITLE), /* @__PURE__ */ React12.createElement(Switch, { size: "sm", checked: state.on, onChange: (event) => commit({ ...state, on: event.currentTarget.checked }), disabled, "aria-label": FREQUENCY_TITLE })), state.on && /* @__PURE__ */ React12.createElement(Stack10, { gap: "sm" }, /* @__PURE__ */ React12.createElement(Text10, { size: "xs", c: "dimmed" }, FREQUENCY_EXPLAINER), /* @__PURE__ */ React12.createElement(Group11, { gap: 8, align: "center", wrap: "nowrap" }, /* @__PURE__ */ React12.createElement(Text10, { size: "sm", fw: 500 }, FREQUENCY_MAX_LEAD), /* @__PURE__ */ React12.createElement(
|
|
3440
|
+
BaseNumberInput,
|
|
3441
|
+
{
|
|
3442
|
+
w: 80,
|
|
3443
|
+
styles: chipInput,
|
|
3444
|
+
min: 1,
|
|
3445
|
+
allowDecimal: false,
|
|
3446
|
+
value: state.max,
|
|
3447
|
+
onChange: (next) => commit({ ...state, max: typeof next === "number" && next >= 1 ? Math.round(next) : 1 }),
|
|
3448
|
+
disabled,
|
|
3449
|
+
"aria-label": FREQUENCY_MAX_LEAD
|
|
3450
|
+
}
|
|
3451
|
+
), /* @__PURE__ */ React12.createElement(Text10, { size: "sm", fw: 500 }, frequencyMaxNoun(state.max)), /* @__PURE__ */ React12.createElement(
|
|
3452
|
+
BaseSelect,
|
|
3453
|
+
{
|
|
3454
|
+
w: 130,
|
|
3455
|
+
styles: chipInput,
|
|
3456
|
+
data: FREQUENCY_PERIOD_OPTIONS,
|
|
3457
|
+
value: state.per,
|
|
3458
|
+
onChange: (per) => commit({ ...state, per: per ?? state.per }),
|
|
3459
|
+
disabled,
|
|
3460
|
+
"aria-label": FREQUENCY_PERIOD_ARIA
|
|
3461
|
+
}
|
|
3462
|
+
)), /* @__PURE__ */ React12.createElement(Stack10, { gap: 6 }, /* @__PURE__ */ React12.createElement(Text10, { size: "sm", fw: 500 }, FREQUENCY_BREACH_LABEL), /* @__PURE__ */ React12.createElement(
|
|
3463
|
+
SegmentedControl5,
|
|
3464
|
+
{
|
|
3465
|
+
styles: segmentedPill,
|
|
3466
|
+
value: state.onBreach,
|
|
3467
|
+
onChange: (onBreach) => commit({ ...state, onBreach }),
|
|
3468
|
+
disabled,
|
|
3469
|
+
data: FREQUENCY_BREACH_OPTIONS
|
|
3470
|
+
}
|
|
3471
|
+
), state.onBreach === "review" && /* @__PURE__ */ React12.createElement(Text10, { size: "xs", c: "dimmed" }, FREQUENCY_BREACH_REVIEW_HINT)))));
|
|
3472
|
+
};
|
|
3473
|
+
|
|
3474
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/UniqueRule.tsx
|
|
3475
|
+
import React13, { useEffect as useEffect4, useMemo as useMemo5, useRef as useRef3, useState as useState7 } from "react";
|
|
3476
|
+
import { Alert as Alert5, Badge, Group as Group12, Paper as Paper9, Slider, Stack as Stack11, Switch as Switch2, Text as Text11 } from "@mantine/core";
|
|
3477
|
+
|
|
3478
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/uniqueModel.ts
|
|
3479
|
+
var DEFAULT_COUNT_PRIOR = ["approved", "pending_review", "queued"];
|
|
3480
|
+
var DEFAULT_REJECT_AT_PCT = 90;
|
|
3481
|
+
var DEFAULT_REVIEW_AT_PCT = 50;
|
|
3482
|
+
var DEFAULT_ID2 = "no-duplicates";
|
|
3483
|
+
var refOf = (entry) => typeof entry === "string" ? entry : entry.ref;
|
|
3484
|
+
var clampPct = (pct) => Math.min(Math.max(Math.round(pct) || 0, 0), 100);
|
|
3485
|
+
function withRejectAt(state, pct) {
|
|
3486
|
+
const rejectAtPct = clampPct(pct);
|
|
3487
|
+
return { ...state, rejectAtPct, reviewAtPct: Math.min(state.reviewAtPct, rejectAtPct) };
|
|
3488
|
+
}
|
|
3489
|
+
function withReviewAt(state, pct) {
|
|
3490
|
+
const reviewAtPct = clampPct(pct);
|
|
3491
|
+
return { ...state, reviewAtPct, rejectAtPct: Math.max(state.rejectAtPct, reviewAtPct) };
|
|
3492
|
+
}
|
|
3493
|
+
function withGranularity(state, ref, granularity) {
|
|
3494
|
+
const { [ref]: _prior, ...rest } = state.granularity;
|
|
3495
|
+
return { ...state, granularity: granularity ? { ...rest, [ref]: granularity } : rest };
|
|
3496
|
+
}
|
|
3497
|
+
function deriveUnique(body) {
|
|
3498
|
+
const unique2 = body.unique;
|
|
3499
|
+
const granularity = {};
|
|
3500
|
+
for (const entry of unique2?.by ?? []) {
|
|
3501
|
+
if (typeof entry !== "string" && entry.granularity) granularity[entry.ref] = entry.granularity;
|
|
3502
|
+
}
|
|
3503
|
+
return {
|
|
3504
|
+
on: !!unique2,
|
|
3505
|
+
by: (unique2?.by ?? []).map(refOf),
|
|
3506
|
+
granularity,
|
|
3507
|
+
// Absent bytes mean the engine's default — ON. Only an explicit `enabled: false` reads back
|
|
3508
|
+
// as off, so a newly-enabled rule (no block yet) starts with the judge on, like the engine.
|
|
3509
|
+
ai: unique2?.ai?.enabled !== false,
|
|
3510
|
+
rejectAtPct: unique2?.thresholds ? Math.round(unique2.thresholds.rejectAtBps / 100) : DEFAULT_REJECT_AT_PCT,
|
|
3511
|
+
reviewAtPct: unique2?.thresholds ? Math.round(unique2.thresholds.reviewAtBps / 100) : DEFAULT_REVIEW_AT_PCT
|
|
3512
|
+
};
|
|
3513
|
+
}
|
|
3514
|
+
function compileUnique(body, state) {
|
|
3515
|
+
if (!state.on) {
|
|
3516
|
+
if (body.unique === void 0) return body;
|
|
3517
|
+
const { unique: _off, ...rest } = body;
|
|
3518
|
+
return rest;
|
|
3519
|
+
}
|
|
3520
|
+
const prior = body.unique;
|
|
3521
|
+
const priorByRef = new Map((prior?.by ?? []).map((entry) => [refOf(entry), entry]));
|
|
3522
|
+
const by = state.by.map((ref) => {
|
|
3523
|
+
const authored = priorByRef.get(ref);
|
|
3524
|
+
const authoredGranularity = authored !== void 0 && typeof authored !== "string" ? authored.granularity : void 0;
|
|
3525
|
+
const picked = state.granularity[ref];
|
|
3526
|
+
if (picked === authoredGranularity && authored !== void 0) return authored;
|
|
3527
|
+
return picked ? { ref, granularity: picked } : ref;
|
|
3528
|
+
});
|
|
3529
|
+
const priorPairs = prior?.ai?.maxPairs;
|
|
3530
|
+
const ai = state.ai === (prior?.ai?.enabled !== false) ? prior?.ai : state.ai ? priorPairs !== void 0 ? { enabled: true, maxPairs: priorPairs } : void 0 : { enabled: false, ...priorPairs !== void 0 ? { maxPairs: priorPairs } : {} };
|
|
3531
|
+
return {
|
|
3532
|
+
...body,
|
|
3533
|
+
unique: {
|
|
3534
|
+
"@type": "Unique",
|
|
3535
|
+
id: prior?.id ?? DEFAULT_ID2,
|
|
3536
|
+
...prior?.label !== void 0 ? { label: prior.label } : {},
|
|
3537
|
+
...by.length > 0 ? { by } : {},
|
|
3538
|
+
countPrior: prior?.countPrior ?? DEFAULT_COUNT_PRIOR,
|
|
3539
|
+
attachments: prior?.attachments ?? true,
|
|
3540
|
+
...ai !== void 0 ? { ai } : {},
|
|
3541
|
+
thresholds: { rejectAtBps: state.rejectAtPct * 100, reviewAtBps: state.reviewAtPct * 100 }
|
|
3542
|
+
}
|
|
3543
|
+
};
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/UniqueRule.tsx
|
|
3547
|
+
var CONTAINER_KINDS = /* @__PURE__ */ new Set(["keyed_choice", "cells", "rows", "keyed_text"]);
|
|
3548
|
+
var UniqueRule = ({ body, catalog, onChange, disabled }) => {
|
|
3549
|
+
const [state, setState] = useState7(() => deriveUnique(body));
|
|
3550
|
+
const lastWrittenRef = useRef3(null);
|
|
3551
|
+
const bodyJson = JSON.stringify(body);
|
|
3552
|
+
useEffect4(() => {
|
|
3553
|
+
if (bodyJson === lastWrittenRef.current) return;
|
|
3554
|
+
setState(deriveUnique(body));
|
|
3555
|
+
}, [bodyJson]);
|
|
3556
|
+
const commit = (next) => {
|
|
3557
|
+
setState(next);
|
|
3558
|
+
const compiled = compileUnique(body, next);
|
|
3559
|
+
const json = JSON.stringify(compiled);
|
|
3560
|
+
lastWrittenRef.current = json;
|
|
3561
|
+
if (json !== bodyJson) onChange(compiled);
|
|
3562
|
+
};
|
|
3563
|
+
const keyFields = useMemo5(() => catalog.fields.filter((field) => !CONTAINER_KINDS.has(field.kind)), [catalog]);
|
|
3564
|
+
const fieldByPath = useMemo5(() => new Map(keyFields.map((field) => [field.path, field])), [keyFields]);
|
|
3565
|
+
const options = useMemo5(
|
|
3566
|
+
() => [...keyFields.map((field) => ({ value: field.path, label: field.title })), ...state.by.filter((ref) => !fieldByPath.has(ref)).map((ref) => ({ value: ref, label: ref }))],
|
|
3567
|
+
[keyFields, fieldByPath, state.by]
|
|
3568
|
+
);
|
|
3569
|
+
const pickedFileTitles = state.by.filter((ref) => fieldByPath.get(ref)?.kind === "file").map((ref) => fieldByPath.get(ref).title);
|
|
3570
|
+
const pickedDateRefs = state.by.filter((ref) => fieldByPath.get(ref)?.kind === "date");
|
|
3571
|
+
return /* @__PURE__ */ React13.createElement(Paper9, { p: 18, style: cardStyle }, /* @__PURE__ */ React13.createElement(Stack11, { gap: "sm" }, /* @__PURE__ */ React13.createElement(Group12, { gap: "sm", justify: "space-between", align: "center", wrap: "wrap" }, /* @__PURE__ */ React13.createElement(Text11, { size: "sm", fw: 600 }, UNIQUE_TITLE), /* @__PURE__ */ React13.createElement(Switch2, { size: "sm", checked: state.on, onChange: (event) => commit({ ...state, on: event.currentTarget.checked }), disabled, "aria-label": UNIQUE_TITLE })), state.on && /* @__PURE__ */ React13.createElement(Stack11, { gap: "sm" }, /* @__PURE__ */ React13.createElement(Text11, { size: "xs", c: "dimmed" }, UNIQUE_EXPLAINER), /* @__PURE__ */ React13.createElement(
|
|
3572
|
+
BaseMultiSelect,
|
|
3573
|
+
{
|
|
3574
|
+
label: UNIQUE_BY_LABEL,
|
|
3575
|
+
description: UNIQUE_BY_HINT,
|
|
3576
|
+
placeholder: state.by.length === 0 ? UNIQUE_BY_PLACEHOLDER : void 0,
|
|
3577
|
+
data: options,
|
|
3578
|
+
value: state.by,
|
|
3579
|
+
onChange: (by) => commit({ ...state, by }),
|
|
3580
|
+
renderOption: ({ option }) => /* @__PURE__ */ React13.createElement(Group12, { gap: "xs", wrap: "nowrap", justify: "space-between", style: { flex: 1 } }, /* @__PURE__ */ React13.createElement(Text11, { size: "sm" }, option.label), fieldByPath.get(option.value)?.kind === "file" && /* @__PURE__ */ React13.createElement(Badge, { size: "xs", variant: "light", color: "neutralColor" }, UNIQUE_FILE_BADGE)),
|
|
3581
|
+
styles: chipInput,
|
|
3582
|
+
searchable: true,
|
|
3583
|
+
disabled
|
|
3584
|
+
}
|
|
3585
|
+
), pickedFileTitles.length > 0 && /* @__PURE__ */ React13.createElement(Text11, { size: "xs", c: "dimmed" }, pickedFileTitles.map((title) => `\u201C${title}\u201D`).join(", "), " \u2014 ", UNIQUE_FILE_BADGE, ": a re-saved or recompressed copy still matches."), pickedDateRefs.map((ref) => /* @__PURE__ */ React13.createElement(Group12, { key: ref, gap: 8, align: "center", wrap: "nowrap" }, /* @__PURE__ */ React13.createElement(Text11, { size: "xs", c: "dimmed" }, uniqueGranularityLead(fieldByPath.get(ref).title)), /* @__PURE__ */ React13.createElement(
|
|
3586
|
+
BaseSelect,
|
|
3587
|
+
{
|
|
3588
|
+
w: 180,
|
|
3589
|
+
styles: chipInput,
|
|
3590
|
+
data: UNIQUE_GRANULARITY_OPTIONS,
|
|
3591
|
+
value: state.granularity[ref] ?? "exact",
|
|
3592
|
+
onChange: (value) => commit(withGranularity(state, ref, value && value !== "exact" ? value : void 0)),
|
|
3593
|
+
disabled,
|
|
3594
|
+
"aria-label": uniqueGranularityLead(fieldByPath.get(ref).title)
|
|
3595
|
+
}
|
|
3596
|
+
))), /* @__PURE__ */ React13.createElement(
|
|
3597
|
+
Switch2,
|
|
3598
|
+
{
|
|
3599
|
+
size: "sm",
|
|
3600
|
+
label: UNIQUE_AI_LABEL,
|
|
3601
|
+
description: state.ai ? UNIQUE_AI_ON_HINT : void 0,
|
|
3602
|
+
checked: state.ai,
|
|
3603
|
+
onChange: (event) => commit({ ...state, ai: event.currentTarget.checked }),
|
|
3604
|
+
styles: { label: { fontWeight: 500 } },
|
|
3605
|
+
disabled
|
|
3606
|
+
}
|
|
3607
|
+
), !state.ai && /* @__PURE__ */ React13.createElement(Alert5, { color: "yellow", styles: actionAlertStyles, p: "xs" }, /* @__PURE__ */ React13.createElement(Text11, { size: "xs" }, UNIQUE_AI_OFF_WARNING)), /* @__PURE__ */ React13.createElement(Text11, { size: "xs", c: "dimmed" }, UNIQUE_PRICING_NOTE), /* @__PURE__ */ React13.createElement(Stack11, { gap: 4 }, /* @__PURE__ */ React13.createElement(Group12, { justify: "space-between" }, /* @__PURE__ */ React13.createElement(Text11, { size: "sm", fw: 500 }, UNIQUE_REJECT_LABEL), /* @__PURE__ */ React13.createElement(Text11, { size: "sm", c: "dimmed", ff: "monospace" }, "\u2265 ", state.rejectAtPct, "%")), /* @__PURE__ */ React13.createElement(
|
|
3608
|
+
Slider,
|
|
3609
|
+
{
|
|
3610
|
+
min: 0,
|
|
3611
|
+
max: 100,
|
|
3612
|
+
step: 1,
|
|
3613
|
+
value: state.rejectAtPct,
|
|
3614
|
+
onChange: (pct) => setState(withRejectAt(state, pct)),
|
|
3615
|
+
onChangeEnd: (pct) => commit(withRejectAt(state, pct)),
|
|
3616
|
+
label: (pct) => `\u2265 ${pct}%`,
|
|
3617
|
+
color: "accent.5",
|
|
3618
|
+
disabled
|
|
3619
|
+
}
|
|
3620
|
+
)), /* @__PURE__ */ React13.createElement(Stack11, { gap: 4 }, /* @__PURE__ */ React13.createElement(Group12, { justify: "space-between" }, /* @__PURE__ */ React13.createElement(Text11, { size: "sm", fw: 500 }, UNIQUE_REVIEW_LABEL), /* @__PURE__ */ React13.createElement(Text11, { size: "sm", c: "dimmed", ff: "monospace" }, "\u2265 ", state.reviewAtPct, "%")), /* @__PURE__ */ React13.createElement(
|
|
3621
|
+
Slider,
|
|
3622
|
+
{
|
|
3623
|
+
min: 0,
|
|
3624
|
+
max: 100,
|
|
3625
|
+
step: 1,
|
|
3626
|
+
value: state.reviewAtPct,
|
|
3627
|
+
onChange: (pct) => setState(withReviewAt(state, pct)),
|
|
3628
|
+
onChangeEnd: (pct) => commit(withReviewAt(state, pct)),
|
|
3629
|
+
label: (pct) => `\u2265 ${pct}%`,
|
|
3630
|
+
color: "accent.5",
|
|
3631
|
+
disabled
|
|
3632
|
+
}
|
|
3633
|
+
)))));
|
|
3634
|
+
};
|
|
3635
|
+
|
|
3636
|
+
// src/mantine/blocks/action/actionTypes/evalRubric/useRubricDraft.ts
|
|
3637
|
+
import { useCallback, useEffect as useEffect5, useMemo as useMemo6, useRef as useRef4, useState as useState8 } from "react";
|
|
3638
|
+
function protocolDidOf(collection) {
|
|
3639
|
+
const protocol = collection?.protocol;
|
|
3640
|
+
const did = typeof protocol === "string" ? protocol : protocol?.id;
|
|
3641
|
+
return String(did ?? "").trim();
|
|
3642
|
+
}
|
|
3643
|
+
function resourceFragment(resourceId) {
|
|
3644
|
+
const raw = String(resourceId ?? "");
|
|
3645
|
+
const hash = raw.indexOf("#");
|
|
3646
|
+
return hash >= 0 ? raw.slice(hash) : "#vct";
|
|
3647
|
+
}
|
|
3648
|
+
var ENGINE_CHECK_DEBOUNCE_MS = 600;
|
|
3649
|
+
var RULES_UNCHECKED_MESSAGE = "Couldn\u2019t reach the rules service to check your rules \u2014 try again.";
|
|
3650
|
+
function useRubricDraft(inputs, onInputsChange, opts) {
|
|
3651
|
+
const handlers = useBlocknoteHandlers();
|
|
3652
|
+
const handlersRef = useRef4(handlers);
|
|
3653
|
+
useEffect5(() => {
|
|
3654
|
+
handlersRef.current = handlers;
|
|
3655
|
+
}, [handlers]);
|
|
3656
|
+
const mountedRef = useRef4(true);
|
|
3657
|
+
useEffect5(
|
|
3658
|
+
() => () => {
|
|
3659
|
+
mountedRef.current = false;
|
|
3660
|
+
},
|
|
3661
|
+
[]
|
|
3662
|
+
);
|
|
3663
|
+
const [local, setLocal] = useState8(() => parseEvalRubricInputs(inputs));
|
|
3664
|
+
const localRef = useRef4(local);
|
|
3665
|
+
const lastWrittenRef = useRef4(null);
|
|
3666
|
+
useEffect5(() => {
|
|
3667
|
+
if (inputs === lastWrittenRef.current) return;
|
|
3668
|
+
const parsed = parseEvalRubricInputs(inputs);
|
|
3669
|
+
localRef.current = parsed;
|
|
3670
|
+
setLocal(parsed);
|
|
3671
|
+
}, [inputs]);
|
|
3672
|
+
const update = useCallback(
|
|
3673
|
+
(patch) => {
|
|
3674
|
+
const next = { ...localRef.current, ...patch };
|
|
3675
|
+
localRef.current = next;
|
|
3676
|
+
setLocal(next);
|
|
3677
|
+
const json = serializeEvalRubricInputs(next);
|
|
3678
|
+
lastWrittenRef.current = json;
|
|
3679
|
+
onInputsChange(json);
|
|
3680
|
+
},
|
|
3681
|
+
[onInputsChange]
|
|
3682
|
+
);
|
|
3683
|
+
const setRubric = useCallback((next) => update({ rubric: next }), [update]);
|
|
3684
|
+
const [collections, setCollections] = useState8([]);
|
|
3685
|
+
const [loadingCollections, setLoadingCollections] = useState8(false);
|
|
3686
|
+
const [collectionsError, setCollectionsError] = useState8(null);
|
|
3687
|
+
const fetchCollections = useCallback(async () => {
|
|
3688
|
+
const deedDid = localRef.current.deedDid.trim();
|
|
3689
|
+
if (!deedDid) {
|
|
3690
|
+
setCollectionsError("Please enter a DID first");
|
|
3691
|
+
return;
|
|
3692
|
+
}
|
|
3693
|
+
update({ deedDid });
|
|
3694
|
+
setLoadingCollections(true);
|
|
3695
|
+
setCollectionsError(null);
|
|
3696
|
+
try {
|
|
3697
|
+
const response = await handlers.getClaimCollections(deedDid);
|
|
3698
|
+
const fetched = response?.collections || [];
|
|
3699
|
+
setCollections(fetched);
|
|
3700
|
+
if (fetched.length === 0) setCollectionsError("No claim collections found for this DID");
|
|
3701
|
+
if (localRef.current.collectionId && !fetched.some((collection) => collection.id === localRef.current.collectionId)) {
|
|
3702
|
+
update({ collectionId: "" });
|
|
3703
|
+
}
|
|
3704
|
+
} catch (error) {
|
|
3705
|
+
setCollections([]);
|
|
3706
|
+
setCollectionsError(error instanceof Error ? error.message : "Failed to fetch claim collections");
|
|
3707
|
+
} finally {
|
|
3708
|
+
setLoadingCollections(false);
|
|
3709
|
+
}
|
|
3710
|
+
}, [handlers, update]);
|
|
3711
|
+
const [formLoading, setFormLoading] = useState8(false);
|
|
3712
|
+
const [formError, setFormError] = useState8(null);
|
|
3713
|
+
const fetchGenRef = useRef4(0);
|
|
3714
|
+
const [prefilledFromChain, setPrefilledFromChain] = useState8(false);
|
|
3715
|
+
const loadForm = useCallback(
|
|
3716
|
+
async (deedDid, collectionId, collection) => {
|
|
3717
|
+
if (!deedDid || !collectionId) return;
|
|
3718
|
+
const generation = ++fetchGenRef.current;
|
|
3719
|
+
setFormLoading(true);
|
|
3720
|
+
setFormError(null);
|
|
3721
|
+
try {
|
|
3722
|
+
const result = await handlers.getDeedSurveyTemplate(deedDid, collectionId);
|
|
3723
|
+
if (generation !== fetchGenRef.current) return;
|
|
3724
|
+
const surveyTemplate = result?.surveyTemplate;
|
|
3725
|
+
if (!surveyTemplate) {
|
|
3726
|
+
setFormError("This collection has no claim form yet. Add the form first \u2014 the rules are written against its questions.");
|
|
3727
|
+
update({ claimSchemaSnapshot: null });
|
|
3728
|
+
return;
|
|
3729
|
+
}
|
|
3730
|
+
const catalog = extractRubricFieldCatalog(surveyTemplate, result?.proof ?? "");
|
|
3731
|
+
if (catalog.fields.length === 0) {
|
|
3732
|
+
setFormError("None of this form\u2019s questions can be checked automatically. Rules need questions with answers \u2014 text, numbers, dates, choices, files.");
|
|
3733
|
+
}
|
|
3734
|
+
const title = collection ? getCollectionDisplayName(collection) : "";
|
|
3735
|
+
const resource = resourceFragment(result?.resourceId);
|
|
3736
|
+
const current = localRef.current.rubric;
|
|
3737
|
+
const protocol = protocolDidOf(collection) || current?.claimSchema.protocol || "";
|
|
3738
|
+
if (!protocol) {
|
|
3739
|
+
setFormError("This collection isn't linked to a protocol yet \u2014 the rules can't attach. Link a protocol to the collection, then reopen.");
|
|
3740
|
+
update({ claimSchemaSnapshot: catalog });
|
|
3741
|
+
return;
|
|
3742
|
+
}
|
|
3743
|
+
let base = current;
|
|
3744
|
+
if (!base && typeof handlers.getDeedRubric === "function") {
|
|
3745
|
+
const published = await handlers.getDeedRubric(deedDid).catch((error) => {
|
|
3746
|
+
console.warn("[eval.rubric] step prefill \u2014 could not read the published rules", error);
|
|
3747
|
+
return null;
|
|
3748
|
+
});
|
|
3749
|
+
if (generation !== fetchGenRef.current) return;
|
|
3750
|
+
const candidate = parsePublishedRubric(published?.rubric);
|
|
3751
|
+
const boundTo = String(candidate?.claimSchema?.protocol || "");
|
|
3752
|
+
const fetched = candidate && boundTo === protocol ? candidate : null;
|
|
3753
|
+
base = localRef.current.rubric ?? fetched;
|
|
3754
|
+
setPrefilledFromChain(!localRef.current.rubric && !!fetched);
|
|
3755
|
+
console.info(
|
|
3756
|
+
"[eval.rubric] step prefill \u2014",
|
|
3757
|
+
fetched ? `opened on the rules already published for protocol ${protocol}` : candidate ? `skipped: the published rules are bound to protocol ${boundTo}, not ${protocol}` : "no rules published yet; starting fresh"
|
|
3758
|
+
);
|
|
3759
|
+
}
|
|
3760
|
+
update({
|
|
3761
|
+
claimSchemaSnapshot: catalog,
|
|
3762
|
+
// Keep the rules the author already wrote (or the published ones just
|
|
3763
|
+
// read); re-pin them to the form that is actually loaded. `bindForm`
|
|
3764
|
+
// NEVER replaces an existing rubric with a blank one — only fills the
|
|
3765
|
+
// binding — so reopening cannot wipe work.
|
|
3766
|
+
rubric: bindForm(base, protocol, resource, catalog.proof, title)
|
|
3767
|
+
});
|
|
3768
|
+
} catch (error) {
|
|
3769
|
+
if (generation !== fetchGenRef.current) return;
|
|
3770
|
+
setFormError(error instanceof Error ? error.message : "Failed to load the claim form");
|
|
3771
|
+
} finally {
|
|
3772
|
+
if (generation === fetchGenRef.current) setFormLoading(false);
|
|
3773
|
+
}
|
|
3774
|
+
},
|
|
3775
|
+
[handlers, update]
|
|
3776
|
+
);
|
|
3777
|
+
const pickCollection = useCallback(
|
|
3778
|
+
(collectionId) => {
|
|
3779
|
+
const collection = collections.find((entry) => entry.id === collectionId);
|
|
3780
|
+
update({ collectionId });
|
|
3781
|
+
void loadForm(localRef.current.deedDid.trim(), collectionId, collection);
|
|
3782
|
+
},
|
|
3783
|
+
[collections, loadForm, update]
|
|
3784
|
+
);
|
|
3785
|
+
const reloadForm = useCallback(() => {
|
|
3786
|
+
const collection = collections.find((entry) => entry.id === localRef.current.collectionId);
|
|
3787
|
+
void loadForm(localRef.current.deedDid.trim(), localRef.current.collectionId, collection);
|
|
3788
|
+
}, [collections, loadForm]);
|
|
3789
|
+
useEffect5(() => {
|
|
3790
|
+
if (local.deedDid.trim() && local.collectionId && collections.length === 0 && !loadingCollections) void fetchCollections();
|
|
3791
|
+
}, [local.deedDid, local.collectionId]);
|
|
3792
|
+
useEffect5(() => {
|
|
3793
|
+
if (!local.deedDid.trim() || !local.collectionId || local.claimSchemaSnapshot || formLoading) return;
|
|
3794
|
+
const collection = collections.find((entry) => entry.id === local.collectionId);
|
|
3795
|
+
if (!collection && !localRef.current.rubric?.claimSchema.protocol) return;
|
|
3796
|
+
void loadForm(local.deedDid.trim(), local.collectionId, collection);
|
|
3797
|
+
}, [local.deedDid, local.collectionId, local.claimSchemaSnapshot, collections]);
|
|
3798
|
+
const [driftChecking, setDriftChecking] = useState8(false);
|
|
3799
|
+
const [driftProblems, setDriftProblems] = useState8(null);
|
|
3800
|
+
const [driftError, setDriftError] = useState8(null);
|
|
3801
|
+
const checkFormDrift = useCallback(async () => {
|
|
3802
|
+
const { deedDid, collectionId, rubric, claimSchemaSnapshot } = localRef.current;
|
|
3803
|
+
const entity = deedDid.trim();
|
|
3804
|
+
if (!entity || !collectionId || !rubric || !claimSchemaSnapshot) return;
|
|
3805
|
+
setDriftChecking(true);
|
|
3806
|
+
setDriftError(null);
|
|
3807
|
+
try {
|
|
3808
|
+
const result = await handlersRef.current.getDeedSurveyTemplate(entity, collectionId);
|
|
3809
|
+
if (!mountedRef.current) return;
|
|
3810
|
+
const surveyTemplate = result?.surveyTemplate;
|
|
3811
|
+
if (!surveyTemplate) {
|
|
3812
|
+
setDriftProblems(null);
|
|
3813
|
+
setDriftError("This collection has no claim form any more, so these rules have nothing to judge.");
|
|
3814
|
+
return;
|
|
3815
|
+
}
|
|
3816
|
+
const live = extractRubricFieldCatalog(surveyTemplate, result?.proof ?? claimSchemaSnapshot.proof);
|
|
3817
|
+
setDriftProblems(validateRubric(rubric, live, claimSchemaSnapshot).filter((d) => d.code === "RUB_SCHEMA_DRIFT"));
|
|
3818
|
+
} catch (error) {
|
|
3819
|
+
if (!mountedRef.current) return;
|
|
3820
|
+
setDriftProblems(null);
|
|
3821
|
+
setDriftError(error instanceof Error ? error.message : "Could not check these rules against the claim form");
|
|
3822
|
+
} finally {
|
|
3823
|
+
if (mountedRef.current) setDriftChecking(false);
|
|
3824
|
+
}
|
|
3825
|
+
}, []);
|
|
3826
|
+
const [shapeProblems, setShapeProblems] = useState8([]);
|
|
3827
|
+
const [checking, setChecking] = useState8(() => !!local.rubric && !!local.claimSchemaSnapshot);
|
|
3828
|
+
const [checkError, setCheckError] = useState8(null);
|
|
3829
|
+
useEffect5(() => {
|
|
3830
|
+
let mounted = true;
|
|
3831
|
+
const body = local.rubric;
|
|
3832
|
+
if (!body || !local.claimSchemaSnapshot) {
|
|
3833
|
+
setShapeProblems([]);
|
|
3834
|
+
setChecking(false);
|
|
3835
|
+
setCheckError(null);
|
|
3836
|
+
return;
|
|
3837
|
+
}
|
|
3838
|
+
setChecking(true);
|
|
3839
|
+
const timer = setTimeout(() => {
|
|
3840
|
+
void (async () => {
|
|
3841
|
+
const host = handlersRef.current;
|
|
3842
|
+
try {
|
|
3843
|
+
const engineUrl = local.evalEngineUrl.trim() || void 0;
|
|
3844
|
+
if (host.previewRubric && local.collectionId) {
|
|
3845
|
+
const ask = (interactive) => host.previewRubric?.({
|
|
3846
|
+
collectionId: local.collectionId,
|
|
3847
|
+
rubric: body,
|
|
3848
|
+
evalEngineUrl: engineUrl,
|
|
3849
|
+
interactive,
|
|
3850
|
+
...opts?.settings ? { settings: opts.settings } : {}
|
|
3851
|
+
}).catch((error) => {
|
|
3852
|
+
console.warn("[eval.rubric] engine check failed; using the local shape gate", error);
|
|
3853
|
+
return null;
|
|
3854
|
+
}) ?? Promise.resolve(null);
|
|
3855
|
+
let preview = await ask(false);
|
|
3856
|
+
if (!mounted) return;
|
|
3857
|
+
if (!preview) {
|
|
3858
|
+
console.info("[eval.rubric] no cached signer \u2014 asking for the pin once so rules can be checked");
|
|
3859
|
+
preview = await ask(true);
|
|
3860
|
+
if (!mounted) return;
|
|
3861
|
+
}
|
|
3862
|
+
if (preview) {
|
|
3863
|
+
setShapeProblems(preview.problems);
|
|
3864
|
+
setCheckError(null);
|
|
3865
|
+
return;
|
|
3866
|
+
}
|
|
3867
|
+
console.info("[eval.rubric] engine check unavailable; using the local shape gate");
|
|
3868
|
+
} else if (!host.previewRubric) {
|
|
3869
|
+
console.info("[eval.rubric] host has no previewRubric handler; using the local shape gate");
|
|
3870
|
+
}
|
|
3871
|
+
const shape = await validateRubricShape(body, host.getRubricSchema, engineUrl);
|
|
3872
|
+
if (!mounted) return;
|
|
3873
|
+
if (shape.ok) {
|
|
3874
|
+
setShapeProblems(shape.problems);
|
|
3875
|
+
setCheckError(null);
|
|
3876
|
+
} else {
|
|
3877
|
+
console.warn("[eval.rubric] the rules could not be checked at all", shape.reason);
|
|
3878
|
+
setShapeProblems([]);
|
|
3879
|
+
setCheckError(RULES_UNCHECKED_MESSAGE);
|
|
3880
|
+
}
|
|
3881
|
+
} catch (error) {
|
|
3882
|
+
console.warn("[eval.rubric] the rules check threw", error);
|
|
3883
|
+
if (mounted) {
|
|
3884
|
+
setShapeProblems([]);
|
|
3885
|
+
setCheckError(RULES_UNCHECKED_MESSAGE);
|
|
3886
|
+
}
|
|
3887
|
+
} finally {
|
|
3888
|
+
if (mounted) setChecking(false);
|
|
3889
|
+
}
|
|
3890
|
+
})();
|
|
3891
|
+
}, ENGINE_CHECK_DEBOUNCE_MS);
|
|
3892
|
+
return () => {
|
|
3893
|
+
mounted = false;
|
|
3894
|
+
clearTimeout(timer);
|
|
3895
|
+
};
|
|
3896
|
+
}, [local.rubric, local.claimSchemaSnapshot, local.collectionId, local.evalEngineUrl, opts?.settings]);
|
|
3897
|
+
const index = useMemo6(() => indexCatalog(local.claimSchemaSnapshot), [local.claimSchemaSnapshot]);
|
|
3898
|
+
const rules = useMemo6(() => listRules(local.rubric), [local.rubric]);
|
|
3899
|
+
const linked = useMemo6(() => listLinkedRules(local.rubric), [local.rubric]);
|
|
3900
|
+
const diagnostics = useMemo6(
|
|
3901
|
+
() => local.rubric && local.claimSchemaSnapshot ? [...validateRubric(local.rubric, local.claimSchemaSnapshot), ...shapeProblems] : [],
|
|
3902
|
+
[local.rubric, local.claimSchemaSnapshot, shapeProblems]
|
|
3903
|
+
);
|
|
3904
|
+
const errors = useMemo6(() => diagnostics.filter((diagnostic) => diagnostic.severity === "error"), [diagnostics]);
|
|
3905
|
+
return {
|
|
3906
|
+
local,
|
|
3907
|
+
update,
|
|
3908
|
+
setRubric,
|
|
3909
|
+
collections,
|
|
3910
|
+
loadingCollections,
|
|
3911
|
+
collectionsError,
|
|
3912
|
+
fetchCollections,
|
|
3913
|
+
pickCollection,
|
|
3914
|
+
formLoading,
|
|
3915
|
+
formError,
|
|
3916
|
+
reloadForm,
|
|
3917
|
+
prefilledFromChain,
|
|
3918
|
+
checking,
|
|
3919
|
+
checkError,
|
|
3920
|
+
checkFormDrift,
|
|
3921
|
+
driftChecking,
|
|
3922
|
+
driftProblems,
|
|
3923
|
+
driftError,
|
|
3924
|
+
index,
|
|
3925
|
+
diagnostics,
|
|
3926
|
+
errors,
|
|
3927
|
+
rules,
|
|
3928
|
+
linked
|
|
3929
|
+
};
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3932
|
+
// src/mantine/blocks/action/actionTypes/evalEngine/EvalEngineFlowDetail.tsx
|
|
3933
|
+
var LOG = "[eval.engine]";
|
|
3934
|
+
function truncate(value) {
|
|
3935
|
+
if (!value) return "";
|
|
3936
|
+
return value.length <= 16 ? value : `${value.slice(0, 10)}\u2026${value.slice(-4)}`;
|
|
3937
|
+
}
|
|
3938
|
+
function failureMessageOf(error) {
|
|
3939
|
+
if (error instanceof Error) return error.message;
|
|
3940
|
+
if (typeof error === "string" && error) return error;
|
|
3941
|
+
if (error && typeof error === "object" && "message" in error && error.message) {
|
|
3942
|
+
return String(error.message);
|
|
3943
|
+
}
|
|
3944
|
+
try {
|
|
3945
|
+
const serialized = JSON.stringify(error);
|
|
3946
|
+
if (serialized && serialized !== "{}" && serialized !== "null" && serialized !== "undefined") return serialized;
|
|
3947
|
+
} catch {
|
|
3948
|
+
}
|
|
3949
|
+
return "Failed to enable the Evaluation Engine";
|
|
3950
|
+
}
|
|
3951
|
+
var EvalEngineFlowDetail = ({
|
|
3952
|
+
inputs,
|
|
3953
|
+
editor,
|
|
3954
|
+
block,
|
|
3955
|
+
runtime,
|
|
3956
|
+
updateRuntime,
|
|
3957
|
+
isDisabled,
|
|
3958
|
+
provideSigningHandler,
|
|
3959
|
+
unlockSigning,
|
|
3960
|
+
registerRuntimeInputs,
|
|
3961
|
+
executeAction,
|
|
3962
|
+
activePendingInvocation,
|
|
3963
|
+
onPendingInvocationCompleted
|
|
3964
|
+
}) => {
|
|
3965
|
+
const handlers = useBlocknoteHandlers();
|
|
3966
|
+
const handlersRef = useRef5(handlers);
|
|
3967
|
+
useEffect6(() => {
|
|
3968
|
+
handlersRef.current = handlers;
|
|
3969
|
+
}, [handlers]);
|
|
3970
|
+
const parsed = useMemo7(() => parseEvalEngineInputs(inputs), [inputs]);
|
|
3971
|
+
const editorDocument = editor?.document || [];
|
|
3972
|
+
const resolveOpts = useMemo7(() => ({ yRuntime: editor?._yRuntime }), [editor?._yRuntime]);
|
|
3973
|
+
const templateDeedDid = useMemo7(() => resolveReferences(parsed.deedDid || "", editorDocument, resolveOpts).trim(), [parsed.deedDid, editorDocument, resolveOpts]);
|
|
3974
|
+
const templateCollectionId = useMemo7(() => resolveReferences(parsed.collectionId || "", editorDocument, resolveOpts).trim(), [parsed.collectionId, editorDocument, resolveOpts]);
|
|
3975
|
+
const pendingPayload = useMemo7(() => {
|
|
3976
|
+
if (!activePendingInvocation) return void 0;
|
|
3977
|
+
return {
|
|
3978
|
+
...activePendingInvocation.payload || {},
|
|
3979
|
+
...activePendingInvocation.refSnapshots || {}
|
|
3980
|
+
};
|
|
3981
|
+
}, [activePendingInvocation]);
|
|
3982
|
+
const deedDid = useMemo7(() => String(pendingPayload?.entity || "").trim() || templateDeedDid, [pendingPayload, templateDeedDid]);
|
|
3983
|
+
const collectionId = useMemo7(() => String(pendingPayload?.collectionId || "").trim() || templateCollectionId, [pendingPayload, templateCollectionId]);
|
|
3984
|
+
const [adminAddress, setAdminAddress] = useState9("");
|
|
3985
|
+
useEffect6(() => {
|
|
3986
|
+
let mounted = true;
|
|
3987
|
+
if (!deedDid) {
|
|
3988
|
+
setAdminAddress("");
|
|
3989
|
+
return;
|
|
3990
|
+
}
|
|
3991
|
+
(async () => {
|
|
3992
|
+
try {
|
|
3993
|
+
const addr = await handlersRef.current.getAdminAccountAddress(deedDid);
|
|
3994
|
+
if (mounted) setAdminAddress(String(addr || "").trim());
|
|
3995
|
+
} catch {
|
|
3996
|
+
if (mounted) setAdminAddress("");
|
|
3997
|
+
}
|
|
3998
|
+
})();
|
|
3999
|
+
return () => {
|
|
4000
|
+
mounted = false;
|
|
4001
|
+
};
|
|
4002
|
+
}, [deedDid]);
|
|
4003
|
+
const writeInputs = useCallback2(
|
|
4004
|
+
(rubricSubsetJson) => {
|
|
4005
|
+
const blockId = String(block?.id || "");
|
|
4006
|
+
if (!editor || !blockId) return;
|
|
4007
|
+
const current = (editor.document || []).find((candidate) => candidate?.id === blockId) || block;
|
|
4008
|
+
let subset = {};
|
|
4009
|
+
try {
|
|
4010
|
+
subset = JSON.parse(rubricSubsetJson);
|
|
4011
|
+
} catch {
|
|
4012
|
+
return;
|
|
4013
|
+
}
|
|
4014
|
+
let full = {};
|
|
4015
|
+
try {
|
|
4016
|
+
full = JSON.parse(String(current?.props?.inputs || "{}"));
|
|
4017
|
+
} catch {
|
|
4018
|
+
}
|
|
4019
|
+
const changed = Object.keys(subset).some((key) => {
|
|
4020
|
+
const before = full[key];
|
|
4021
|
+
const after = subset[key];
|
|
4022
|
+
if (before === after) return false;
|
|
4023
|
+
if ((before ?? "") === "" && (after ?? "") === "") return false;
|
|
4024
|
+
return JSON.stringify(before ?? null) !== JSON.stringify(after ?? null);
|
|
4025
|
+
});
|
|
4026
|
+
if (!changed) return;
|
|
4027
|
+
editor.updateBlock(blockId, { props: { inputs: JSON.stringify({ ...full, ...subset }) } });
|
|
4028
|
+
},
|
|
4029
|
+
[editor, block]
|
|
4030
|
+
);
|
|
4031
|
+
const intendedSettings = useMemo7(
|
|
4032
|
+
() => ({ allowAiChecks: parsed.allowAiChecks, allowImageChecks: parsed.allowImageChecks, allowChainEvaluation: parsed.allowChainEvaluation }),
|
|
4033
|
+
[parsed.allowAiChecks, parsed.allowImageChecks, parsed.allowChainEvaluation]
|
|
4034
|
+
);
|
|
4035
|
+
const draft = useRubricDraft(inputs, writeInputs, { settings: intendedSettings });
|
|
4036
|
+
const draftLocalRef = useRef5(draft.local);
|
|
4037
|
+
draftLocalRef.current = draft.local;
|
|
4038
|
+
useEffect6(() => {
|
|
4039
|
+
if (!pendingPayload) return;
|
|
4040
|
+
if (!collectionId && !deedDid) return;
|
|
4041
|
+
if (draft.local.collectionId === collectionId && draft.local.deedDid === deedDid) return;
|
|
4042
|
+
console.log(`${LOG} listener mode \u2014 pinning the event's collection into the block`, { collectionId });
|
|
4043
|
+
draft.update({ collectionId, deedDid });
|
|
4044
|
+
}, [pendingPayload, collectionId, deedDid]);
|
|
4045
|
+
const { card, live } = useEnginePricing({ collectionId, evalEngineUrl: parsed.evalEngineUrl });
|
|
4046
|
+
const payLine = ownerPaysLine(live);
|
|
4047
|
+
const output = runtime.output || void 0;
|
|
4048
|
+
const registrationId = String(output?.registrationId || "").trim();
|
|
4049
|
+
const rubricId = String(output?.rubricId || "").trim();
|
|
4050
|
+
const resourceTxHash = String(output?.resourceTxHash || "").trim();
|
|
4051
|
+
const isRunning = runtime.state === "running";
|
|
4052
|
+
const isFailed = runtime.state === "failed";
|
|
4053
|
+
const errorMessage = runtime.error?.message || null;
|
|
4054
|
+
const isCompletedWithProof = runtime.state === "completed" && !!registrationId && !!rubricId && !!resourceTxHash;
|
|
4055
|
+
const isStale = runtime.state === "completed" && !isCompletedWithProof;
|
|
4056
|
+
const catalog = draft.local.claimSchemaSnapshot;
|
|
4057
|
+
const body = draft.local.rubric;
|
|
4058
|
+
const allDiagnostics = useMemo7(() => draft.driftProblems ? [...draft.diagnostics, ...draft.driftProblems] : draft.diagnostics, [draft.diagnostics, draft.driftProblems]);
|
|
4059
|
+
const allErrors = useMemo7(() => allDiagnostics.filter((diagnostic) => diagnostic.severity === "error"), [allDiagnostics]);
|
|
4060
|
+
const configMissing = !collectionId || !deedDid;
|
|
4061
|
+
const adminMissing = !configMissing && !adminAddress && parsed.allowChainEvaluation;
|
|
4062
|
+
const emailMissing = !configMissing && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(parsed.notifyEmail || "").trim());
|
|
4063
|
+
const policyMemberCount = useMemo7(() => {
|
|
4064
|
+
const shape = policyShapeOf(body ?? null);
|
|
4065
|
+
return shape.kind === "policy" ? shape.members.length : 0;
|
|
4066
|
+
}, [body]);
|
|
4067
|
+
const checkCount = draft.rules.length + draft.linked.length + policyMemberCount;
|
|
4068
|
+
const rulesMissing = !body || checkCount === 0;
|
|
4069
|
+
const formBroken = !configMissing && !draft.formLoading && (!!draft.formError || !catalog);
|
|
4070
|
+
const rulesUnchecked = !configMissing && !rulesMissing && (draft.checking || !!draft.checkError);
|
|
4071
|
+
const driftUnchecked = !configMissing && !rulesMissing && (draft.driftChecking || !!draft.driftError || draft.driftProblems === null);
|
|
4072
|
+
const blocked = configMissing || adminMissing || emailMissing || draft.formLoading || formBroken || rulesMissing || rulesUnchecked || driftUnchecked || allErrors.length > 0;
|
|
4073
|
+
const driftCheckedRef = useRef5(false);
|
|
4074
|
+
useEffect6(() => {
|
|
4075
|
+
if (driftCheckedRef.current) return;
|
|
4076
|
+
if (configMissing || isCompletedWithProof) return;
|
|
4077
|
+
if (draft.formLoading || !catalog || !body) return;
|
|
4078
|
+
driftCheckedRef.current = true;
|
|
4079
|
+
console.log(`${LOG} checking the rules against the claim form as it is NOW`);
|
|
4080
|
+
void draft.checkFormDrift();
|
|
4081
|
+
}, [configMissing, isCompletedWithProof, catalog, body, draft.formLoading, draft.checkFormDrift]);
|
|
4082
|
+
useEffect6(() => {
|
|
4083
|
+
if (!registerRuntimeInputs) return;
|
|
4084
|
+
registerRuntimeInputs({ collectionId, deedDid, adminAddress: adminAddress || void 0 });
|
|
4085
|
+
}, [registerRuntimeInputs, collectionId, deedDid, adminAddress]);
|
|
4086
|
+
useEffect6(() => {
|
|
4087
|
+
unlockSigning?.(!isDisabled && !isRunning && !isCompletedWithProof && !blocked);
|
|
4088
|
+
}, [unlockSigning, isDisabled, isRunning, isCompletedWithProof, blocked]);
|
|
4089
|
+
const execute = useCallback2(async () => {
|
|
4090
|
+
if (isDisabled || isRunning || isCompletedWithProof) return;
|
|
4091
|
+
if (!executeAction) {
|
|
4092
|
+
updateRuntime({ state: "failed", error: { message: "Shared action executor is unavailable", at: Date.now() } });
|
|
4093
|
+
return;
|
|
4094
|
+
}
|
|
4095
|
+
if (configMissing) {
|
|
4096
|
+
updateRuntime({ state: "failed", error: { message: "No collection configured. Set one in template mode or wire a Claim Collection trigger.", at: Date.now() } });
|
|
4097
|
+
return;
|
|
4098
|
+
}
|
|
4099
|
+
if (blocked) {
|
|
4100
|
+
updateRuntime({ state: "failed", error: { message: "Finish the rules and fix the problems above before signing.", at: Date.now() } });
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
4103
|
+
const runtimeInputs = {
|
|
4104
|
+
collectionId,
|
|
4105
|
+
deedDid,
|
|
4106
|
+
adminAddress: adminAddress || void 0,
|
|
4107
|
+
// The rules ride ALONG WITH the run, not only in the block's saved blob.
|
|
4108
|
+
// Runtime inputs are merged LAST over `block.props.inputs`
|
|
4109
|
+
// (`actionExecutor.ts`'s `buildActionRunInputs`), and the block object that
|
|
4110
|
+
// blob is read from is the caller's own copy — `findBlock` returns it as
|
|
4111
|
+
// given, without re-resolving the id against `editor.document`, so it can
|
|
4112
|
+
// be one write behind the edit that was just made. The blob itself is
|
|
4113
|
+
// written correctly (`runInputs.test.tsx` pins that); this is what
|
|
4114
|
+
// guarantees the run is judged on the SAME rules the signing gate armed
|
|
4115
|
+
// on. Read through the ref so the freshest edit wins over whatever this
|
|
4116
|
+
// callback closed over; `blocked` above already refuses to run without
|
|
4117
|
+
// rules, so past the gate these are never absent.
|
|
4118
|
+
rubric: draftLocalRef.current.rubric ?? void 0,
|
|
4119
|
+
claimSchemaSnapshot: draftLocalRef.current.claimSchemaSnapshot ?? void 0
|
|
4120
|
+
};
|
|
4121
|
+
const pendingInvocationId = activePendingInvocation?.id;
|
|
4122
|
+
console.log(`${LOG} execute \u2192`, { collectionId, rules: draft.rules.length, listener: !!pendingInvocationId });
|
|
4123
|
+
try {
|
|
4124
|
+
const outcome = await executeAction(pendingInvocationId ? { runtimeInputs, pendingInvocationId } : { runtimeInputs });
|
|
4125
|
+
if (!outcome.success) throw new Error(outcome.error || "Enabling the Evaluation Engine failed");
|
|
4126
|
+
const payload = outcome.output;
|
|
4127
|
+
if (!String(payload?.registrationId || "").trim() || !String(payload?.rubricId || "").trim() || !String(payload?.resourceTxHash || "").trim()) {
|
|
4128
|
+
throw new Error("The run reported success without full proof (enrolment, published rules, and the transaction that anchors them). Check the [eval.engine] handler logs.");
|
|
4129
|
+
}
|
|
4130
|
+
console.log(`${LOG} execute success`, { registrationId: payload.registrationId, rubricId: payload.rubricId });
|
|
4131
|
+
if (pendingInvocationId) onPendingInvocationCompleted?.(pendingInvocationId);
|
|
4132
|
+
} catch (error) {
|
|
4133
|
+
console.error(`${LOG} execute failed`, error);
|
|
4134
|
+
updateRuntime({ state: "failed", error: { message: failureMessageOf(error), at: Date.now() } });
|
|
4135
|
+
}
|
|
4136
|
+
}, [
|
|
4137
|
+
isDisabled,
|
|
4138
|
+
isRunning,
|
|
4139
|
+
isCompletedWithProof,
|
|
4140
|
+
executeAction,
|
|
4141
|
+
configMissing,
|
|
4142
|
+
blocked,
|
|
4143
|
+
collectionId,
|
|
4144
|
+
deedDid,
|
|
4145
|
+
adminAddress,
|
|
4146
|
+
draft.rules.length,
|
|
4147
|
+
activePendingInvocation,
|
|
4148
|
+
updateRuntime,
|
|
4149
|
+
onPendingInvocationCompleted
|
|
4150
|
+
]);
|
|
4151
|
+
useEffect6(() => {
|
|
4152
|
+
if (!provideSigningHandler) return;
|
|
4153
|
+
provideSigningHandler(execute);
|
|
4154
|
+
}, [provideSigningHandler, execute]);
|
|
4155
|
+
const handleReset = useCallback2(() => {
|
|
4156
|
+
updateRuntime({ state: "idle", error: void 0, output: {} });
|
|
4157
|
+
}, [updateRuntime]);
|
|
4158
|
+
const [editorState, setEditorState] = useState9(null);
|
|
4159
|
+
const [linkedEditorState, setLinkedEditorState] = useState9(null);
|
|
4160
|
+
const cardPaths = useMemo7(() => new Set((catalog?.fields ?? []).map((field) => field.path)), [catalog]);
|
|
4161
|
+
const byCard = useMemo7(() => rulesByCard(body, cardPaths), [body, cardPaths]);
|
|
4162
|
+
const linkedByCard = useMemo7(() => {
|
|
4163
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4164
|
+
for (const entry of draft.linked) {
|
|
4165
|
+
const key = rootPath(entry.checkedField, cardPaths);
|
|
4166
|
+
const bucket = grouped.get(key);
|
|
4167
|
+
if (bucket) bucket.push(entry);
|
|
4168
|
+
else grouped.set(key, [entry]);
|
|
4169
|
+
}
|
|
4170
|
+
return grouped;
|
|
4171
|
+
}, [draft.linked, cardPaths]);
|
|
4172
|
+
const guardPaths = useMemo7(
|
|
4173
|
+
() => [...draft.index.values()].filter((entry) => (entry.kind === "text" || entry.kind === "number" || entry.kind === "choice") && !entry.path.includes("[*]")).map((entry) => entry.path),
|
|
4174
|
+
[draft.index]
|
|
4175
|
+
);
|
|
4176
|
+
const problemsByCard = useMemo7(() => {
|
|
4177
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4178
|
+
const loose = [];
|
|
4179
|
+
for (const diagnostic of allDiagnostics) {
|
|
4180
|
+
const cardPath = cardForDiagnosticPath(body, diagnostic.path, cardPaths);
|
|
4181
|
+
if (!cardPath) {
|
|
4182
|
+
loose.push(diagnostic);
|
|
4183
|
+
continue;
|
|
4184
|
+
}
|
|
4185
|
+
const bucket = grouped.get(cardPath);
|
|
4186
|
+
if (bucket) bucket.push(diagnostic);
|
|
4187
|
+
else grouped.set(cardPath, [diagnostic]);
|
|
4188
|
+
}
|
|
4189
|
+
return { grouped, loose };
|
|
4190
|
+
}, [body, allDiagnostics, cardPaths]);
|
|
4191
|
+
const orphanRules = useMemo7(() => draft.rules.filter((rule) => !rule.fieldPath || !cardPaths.has(rootPath(rule.fieldPath, cardPaths))), [draft.rules, cardPaths]);
|
|
4192
|
+
const orphanLinked = useMemo7(() => draft.linked.filter((entry) => !cardPaths.has(rootPath(entry.checkedField, cardPaths))), [draft.linked, cardPaths]);
|
|
4193
|
+
const cardRefs = useRef5(/* @__PURE__ */ new Map());
|
|
4194
|
+
const scrollToCard = useCallback2((cardPath) => {
|
|
4195
|
+
cardRefs.current.get(cardPath)?.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
4196
|
+
}, []);
|
|
4197
|
+
const problemList = useMemo7(() => {
|
|
4198
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4199
|
+
const out = [];
|
|
4200
|
+
for (const diagnostic of allErrors) {
|
|
4201
|
+
const cardPath = cardForDiagnosticPath(body, diagnostic.path, cardPaths);
|
|
4202
|
+
const text = diagnostic.code === "RUB_SCHEMA" && diagnostic.message.startsWith(SCHEMA_UNKNOWN_KEY_PREFIX) ? diagnostic.message : diagnostic.code === "RUB_SCHEMA" ? schemaProblemText(diagnostic.path, !!cardPath) : problemText(diagnostic);
|
|
4203
|
+
const target = cardPath && cardPaths.has(cardPath) ? cardPath : void 0;
|
|
4204
|
+
const key = `${text}@@${target ?? ""}`;
|
|
4205
|
+
if (seen.has(key)) continue;
|
|
4206
|
+
seen.add(key);
|
|
4207
|
+
out.push({ text, card: target });
|
|
4208
|
+
}
|
|
4209
|
+
return out;
|
|
4210
|
+
}, [allErrors, body, cardPaths]);
|
|
4211
|
+
const saveRule = (rule) => {
|
|
4212
|
+
if (!body) return;
|
|
4213
|
+
const subjectTitle = draft.index.get(rule.ext?.sendField ?? rule.ai?.sendField ?? rule.field)?.title;
|
|
4214
|
+
draft.setRubric(pruneDanglingSources(applyDraft(body, rule, editorState?.editing, subjectTitle)));
|
|
4215
|
+
setEditorState(null);
|
|
4216
|
+
};
|
|
4217
|
+
const deleteRule = (rule) => {
|
|
4218
|
+
if (!body) return;
|
|
4219
|
+
draft.setRubric(pruneDanglingSources(removeRule(body, rule)));
|
|
4220
|
+
};
|
|
4221
|
+
const saveLinked = (linkedDraft) => {
|
|
4222
|
+
if (!body) return;
|
|
4223
|
+
draft.setRubric(pruneDanglingSources(applyLinkedDraft(body, linkedDraft, draft.index)));
|
|
4224
|
+
setLinkedEditorState(null);
|
|
4225
|
+
};
|
|
4226
|
+
const deleteLinked = (entry) => {
|
|
4227
|
+
if (!body) return;
|
|
4228
|
+
draft.setRubric(pruneDanglingSources(removeGatesAt(body, entry.indices)));
|
|
4229
|
+
};
|
|
4230
|
+
const editingLocked = isDisabled || isRunning;
|
|
4231
|
+
const title = block?.props?.title || "Evaluation Engine";
|
|
4232
|
+
const description = block?.props?.description || "";
|
|
4233
|
+
return /* @__PURE__ */ React14.createElement(Stack12, { gap: "md" }, /* @__PURE__ */ React14.createElement(Stack12, { gap: 2 }, /* @__PURE__ */ React14.createElement(Text12, { fw: 600 }, title), description && /* @__PURE__ */ React14.createElement(Text12, { size: "sm", c: "dimmed" }, description)), configMissing && /* @__PURE__ */ React14.createElement(Alert6, { color: "yellow", styles: actionAlertStyles }, "Configure the DID and claim collection in template mode (or wire a Claim Collection trigger) before running this action."), !configMissing && !isCompletedWithProof && /* @__PURE__ */ React14.createElement(React14.Fragment, null, draft.formLoading && /* @__PURE__ */ React14.createElement(Group13, { gap: "xs" }, /* @__PURE__ */ React14.createElement(Loader3, { size: "xs" }), /* @__PURE__ */ React14.createElement(Text12, { size: "sm", c: "dimmed" }, "Loading the claim form\u2026")), draft.prefilledFromChain && /* @__PURE__ */ React14.createElement(Alert6, { color: "neutralColor", variant: "light", icon: /* @__PURE__ */ React14.createElement(IconInfoCircle, { size: 16 }), styles: actionAlertStyles }, /* @__PURE__ */ React14.createElement(Text12, { size: "xs" }, "These are the rules already published for this claim form, loaded from chain. Edit them and sign to replace what is live.")), draft.collectionsError && /* @__PURE__ */ React14.createElement(Alert6, { color: "red", icon: /* @__PURE__ */ React14.createElement(IconAlertCircle, { size: 16 }), styles: actionAlertStyles }, /* @__PURE__ */ React14.createElement(Text12, { size: "sm" }, draft.collectionsError)), !draft.formLoading && draft.formError && /* @__PURE__ */ React14.createElement(BaseAlert, { errMsg: draft.formError, onRetry: draft.reloadForm }), !draft.formLoading && !draft.formError && catalog && body && /* @__PURE__ */ React14.createElement(React14.Fragment, null, /* @__PURE__ */ React14.createElement(Group13, { justify: "space-between", align: "center" }, /* @__PURE__ */ React14.createElement(Text12, { size: "sm", fw: 600 }, body.title ? `Form: \u201C${body.title}\u201D` : "The claim form", " \xB7 ", catalog.fields.length, " question", catalog.fields.length === 1 ? "" : "s")), /* @__PURE__ */ React14.createElement(Stack12, { gap: 14 }, catalog.fields.map((field) => /* @__PURE__ */ React14.createElement(
|
|
4234
|
+
"div",
|
|
4235
|
+
{
|
|
4236
|
+
key: field.path,
|
|
4237
|
+
ref: (el) => {
|
|
4238
|
+
if (el) cardRefs.current.set(field.path, el);
|
|
4239
|
+
else cardRefs.current.delete(field.path);
|
|
4240
|
+
}
|
|
4241
|
+
},
|
|
4242
|
+
/* @__PURE__ */ React14.createElement(
|
|
4243
|
+
RuleCard,
|
|
4244
|
+
{
|
|
4245
|
+
field,
|
|
4246
|
+
rules: byCard.get(field.path) ?? [],
|
|
4247
|
+
linked: linkedByCard.get(field.path) ?? [],
|
|
4248
|
+
problems: problemsByCard.grouped.get(field.path) ?? [],
|
|
4249
|
+
index: draft.index,
|
|
4250
|
+
aiChecks: body.aiChecks,
|
|
4251
|
+
onAdd: () => setEditorState({ field }),
|
|
4252
|
+
onEdit: (rule) => setEditorState({ field, editing: rule }),
|
|
4253
|
+
onDelete: deleteRule,
|
|
4254
|
+
onAddLinked: guardPaths.some((path) => path !== field.path) ? () => setLinkedEditorState({ checkedField: field.path }) : void 0,
|
|
4255
|
+
onEditLinked: (entry) => setLinkedEditorState({ checkedField: entry.checkedField, editing: entry }),
|
|
4256
|
+
onDeleteLinked: deleteLinked,
|
|
4257
|
+
disabled: editingLocked
|
|
4258
|
+
}
|
|
4259
|
+
)
|
|
4260
|
+
))), (orphanRules.length > 0 || orphanLinked.length > 0) && /* @__PURE__ */ React14.createElement(Paper10, { p: 18, style: { ...cardStyle, borderColor: "var(--mantine-color-red-6)" } }, /* @__PURE__ */ React14.createElement(Stack12, { gap: "sm" }, /* @__PURE__ */ React14.createElement(Text12, { size: "sm", fw: 600 }, "Rules with no question"), /* @__PURE__ */ React14.createElement(Text12, { size: "xs", c: "dimmed" }, "These are about answers the form no longer asks for. Delete them, or put the questions back on the form."), orphanRules.map((rule) => /* @__PURE__ */ React14.createElement(Group13, { key: rule.code, gap: "sm", align: "flex-start", wrap: "nowrap" }, /* @__PURE__ */ React14.createElement(Box5, { mt: 2, style: consequencePillStyle(CONSEQUENCES.find((entry) => entry.value === rule.consequence)?.color ?? SLATE) }, consequenceIcon(rule.consequence, 15)), /* @__PURE__ */ React14.createElement(Box5, { style: { flex: 1 } }, /* @__PURE__ */ React14.createElement(RuleSentence, { text: describeRule(rule, { fields: draft.index, aiChecks: body.aiChecks }) })), /* @__PURE__ */ React14.createElement(ActionIcon6, { variant: "subtle", color: "red", onClick: () => deleteRule(rule), disabled: editingLocked, "aria-label": "Delete this rule" }, /* @__PURE__ */ React14.createElement(IconTrash4, { size: 16 })))), orphanLinked.map((entry) => /* @__PURE__ */ React14.createElement(Group13, { key: entry.elseCase.code, gap: "sm", align: "flex-start", wrap: "nowrap" }, /* @__PURE__ */ React14.createElement(Box5, { mt: 2, style: consequencePillStyle(entry.onFail === "review" ? "var(--mantine-color-grape-6)" : "var(--mantine-color-red-6)") }, consequenceIcon(entry.onFail === "review" ? "human" : "reject", 15)), /* @__PURE__ */ React14.createElement(Box5, { style: { flex: 1 } }, /* @__PURE__ */ React14.createElement(RuleSentence, { text: describeLinked(linkedSentenceOf(entry), { fields: draft.index }) })), /* @__PURE__ */ React14.createElement(ActionIcon6, { variant: "subtle", color: "red", onClick: () => deleteLinked(entry), disabled: editingLocked, "aria-label": "Delete this rule" }, /* @__PURE__ */ React14.createElement(IconTrash4, { size: 16 })))))), allErrors.filter((problem) => problem.code === "RUB_AI_CHECKS_DISABLED").map((problem) => /* @__PURE__ */ React14.createElement(Alert6, { key: problem.code, color: "red", icon: /* @__PURE__ */ React14.createElement(IconAlertTriangle5, { size: 16 }), title: "AI checks are turned off for this collection", styles: actionAlertStyles }, /* @__PURE__ */ React14.createElement(Text12, { size: "xs" }, problem.message))), /* @__PURE__ */ React14.createElement(ApprovalPolicy, { body, index: draft.index, checkCount, onChange: draft.setRubric, disabled: editingLocked }), /* @__PURE__ */ React14.createElement(UniqueRule, { body, catalog, onChange: draft.setRubric, disabled: editingLocked }), /* @__PURE__ */ React14.createElement(FrequencyRule, { body, onChange: draft.setRubric, disabled: editingLocked }), problemsByCard.loose.filter((problem) => problem.severity === "warning" && problem.code !== "RUB_WEIGHT_IGNORED").map((problem) => /* @__PURE__ */ React14.createElement(Alert6, { key: `${problem.code}${problem.path}`, color: "yellow", icon: /* @__PURE__ */ React14.createElement(IconInfoCircle, { size: 16 }), styles: actionAlertStyles }, /* @__PURE__ */ React14.createElement(Text12, { size: "xs" }, problemText(problem)))), /* @__PURE__ */ React14.createElement(Paper10, { p: "md", style: cardStyle }, /* @__PURE__ */ React14.createElement(Stack12, { gap: "xs" }, /* @__PURE__ */ React14.createElement(Group13, { justify: "space-between" }, /* @__PURE__ */ React14.createElement(Group13, { gap: "xs" }, allErrors.length === 0 ? /* @__PURE__ */ React14.createElement(IconCheck2, { size: 16, color: "var(--mantine-color-green-6)" }) : /* @__PURE__ */ React14.createElement(IconAlertTriangle5, { size: 16, color: "var(--mantine-color-red-6)" }), /* @__PURE__ */ React14.createElement(Text12, { size: "sm" }, describeTally(draft.rules, { linked: draft.linked.length, policyMembers: policyMemberCount }))), /* @__PURE__ */ React14.createElement(Text12, { size: "sm", fw: 600, c: allErrors.length > 0 ? "red" : "dimmed" }, allErrors.length > 0 ? `${allErrors.length} problem${allErrors.length === 1 ? "" : "s"} left to fix` : "Ready to sign")), problemList.length > 0 && /* @__PURE__ */ React14.createElement(Stack12, { gap: 6 }, problemList.map((problem, at) => /* @__PURE__ */ React14.createElement(Group13, { key: `${problem.text}${at}`, gap: "xs", wrap: "nowrap", align: "flex-start" }, /* @__PURE__ */ React14.createElement(Box5, { mt: 3, style: { flexShrink: 0 } }, /* @__PURE__ */ React14.createElement(IconAlertTriangle5, { size: 14, color: "var(--mantine-color-red-6)" })), /* @__PURE__ */ React14.createElement(Text12, { size: "xs", style: { flex: 1 } }, problem.text), problem.card && /* @__PURE__ */ React14.createElement(Button8, { size: "compact-xs", variant: "subtle", styles: quietButton, rightSection: /* @__PURE__ */ React14.createElement(IconArrowRight, { size: 12 }), onClick: () => scrollToCard(problem.card) }, "Take me there")))))), /* @__PURE__ */ React14.createElement(AdvancedPanel, { body }), editorState && /* @__PURE__ */ React14.createElement(
|
|
4261
|
+
RuleEditor,
|
|
4262
|
+
{
|
|
4263
|
+
opened: true,
|
|
4264
|
+
onClose: () => setEditorState(null),
|
|
4265
|
+
body,
|
|
4266
|
+
catalog,
|
|
4267
|
+
index: draft.index,
|
|
4268
|
+
field: editorState.field,
|
|
4269
|
+
editing: editorState.editing,
|
|
4270
|
+
onSave: saveRule
|
|
4271
|
+
}
|
|
4272
|
+
), linkedEditorState && /* @__PURE__ */ React14.createElement(
|
|
4273
|
+
LinkedRuleEditor,
|
|
4274
|
+
{
|
|
4275
|
+
opened: true,
|
|
4276
|
+
onClose: () => setLinkedEditorState(null),
|
|
4277
|
+
body,
|
|
4278
|
+
catalog,
|
|
4279
|
+
index: draft.index,
|
|
4280
|
+
initialChecked: linkedEditorState.checkedField,
|
|
4281
|
+
editing: linkedEditorState.editing,
|
|
4282
|
+
onSave: saveLinked
|
|
4283
|
+
}
|
|
4284
|
+
))), !configMissing && !isCompletedWithProof && !rulesMissing && (draft.checking || draft.driftChecking) && /* @__PURE__ */ React14.createElement(Group13, { gap: "xs" }, /* @__PURE__ */ React14.createElement(Loader3, { size: "xs" }), /* @__PURE__ */ React14.createElement(Text12, { size: "sm", c: "dimmed" }, draft.driftChecking ? "Checking your rules against the claim form\u2026" : "Checking your rules\u2026")), !configMissing && !isCompletedWithProof && !rulesMissing && !draft.checking && !draft.driftChecking && (draft.checkError || draft.driftError) && /* @__PURE__ */ React14.createElement(Alert6, { color: "red", icon: /* @__PURE__ */ React14.createElement(IconAlertCircle, { size: 16 }), styles: actionAlertStyles }, /* @__PURE__ */ React14.createElement(Stack12, { gap: "xs" }, draft.driftError && /* @__PURE__ */ React14.createElement(Text12, { size: "sm" }, draft.driftError), draft.checkError && /* @__PURE__ */ React14.createElement(Text12, { size: "sm" }, draft.checkError), /* @__PURE__ */ React14.createElement(Text12, { size: "xs", c: "dimmed" }, "Your rules can't be signed off until they have been checked \u2014 enrolling is not something that can be taken back."))), !configMissing && !isCompletedWithProof && /* @__PURE__ */ React14.createElement(Alert6, { color: "neutralColor", variant: "light", icon: /* @__PURE__ */ React14.createElement(IconInfoCircle, { size: 16 }), styles: actionAlertStyles }, /* @__PURE__ */ React14.createElement(Stack12, { gap: 6 }, /* @__PURE__ */ React14.createElement(Text12, { size: "xs" }, "Slide to sign to turn the Evaluation Engine on for this collection. One signature does both: it authorizes the engine to read this collection's claims on your behalf, and publishes the rules above so every new claim is judged by them."), /* @__PURE__ */ React14.createElement(Stack12, { gap: 2 }, /* @__PURE__ */ React14.createElement(Text12, { size: "xs" }, "Checking a claim \u2014 ", usdLabel(card.baseUsd), " / claim"), parsed.allowAiChecks && /* @__PURE__ */ React14.createElement(Text12, { size: "xs" }, "AI checks \u2014 ", usdLabel(card.aiRuleUsd), " per AI rule / claim"), parsed.allowAiChecks && parsed.allowImageChecks && /* @__PURE__ */ React14.createElement(Text12, { size: "xs" }, "Fake-photo detection \u2014 ", usdLabel(card.photoUsd), " / photo"), payLine && /* @__PURE__ */ React14.createElement(Text12, { size: "xs", fw: 500 }, payLine), /* @__PURE__ */ React14.createElement(Text12, { size: "xs" }, parsed.allowChainEvaluation ? "Decisions are submitted on chain \u2014 this releases payment" : "Decisions are recorded only \u2014 nothing is submitted on chain"), parsed.allowChainEvaluation && /* @__PURE__ */ React14.createElement(Text12, { size: "xs" }, "Engine may approve payouts up to ", formatCapCoins(parsed.evaluateMaxAmount, "no custom amount"), " / claim"), /* @__PURE__ */ React14.createElement(Text12, { size: "xs" }, describeTally(draft.rules, { policyMembers: policyMemberCount })), /* @__PURE__ */ React14.createElement(Text12, { size: "xs", c: "dimmed" }, billingNote(card, live))), /* @__PURE__ */ React14.createElement(Group13, { gap: "xs" }, /* @__PURE__ */ React14.createElement(Badge2, { size: "xs", variant: "light", color: "neutralColor" }, "Collection ", truncate(collectionId))))), !configMissing && !isCompletedWithProof && rulesMissing && !draft.formLoading && !draft.formError && /* @__PURE__ */ React14.createElement(Text12, { size: "xs", c: "dimmed" }, "Add at least one rule before signing \u2014 until then this engine would have nothing to judge by, and every claim would wait for a person."), emailMissing && !isCompletedWithProof && /* @__PURE__ */ React14.createElement(Text12, { size: "xs", c: "dimmed" }, "Add a billing email in template mode before signing \u2014 failed payments and paused evaluations are announced there."), adminMissing && !isCompletedWithProof && /* @__PURE__ */ React14.createElement(Text12, { size: "xs", c: "dimmed" }, "Waiting for the entity's admin account\u2026 (it signs the grant that lets the engine submit decisions on chain)"), isStale && /* @__PURE__ */ React14.createElement(Alert6, { icon: /* @__PURE__ */ React14.createElement(IconAlertCircle, { size: 16 }), color: "yellow", title: "Inconsistent State", styles: actionAlertStyles }, /* @__PURE__ */ React14.createElement(Stack12, { gap: "xs" }, /* @__PURE__ */ React14.createElement(Text12, { size: "sm" }, "This block was marked completed but did not record all of the enrolment, the published rules, and the transaction that anchors them. Reset and re-run."), /* @__PURE__ */ React14.createElement(Button8, { variant: "outline", size: "xs", onClick: handleReset }, "Reset"))), isFailed && /* @__PURE__ */ React14.createElement(Alert6, { icon: /* @__PURE__ */ React14.createElement(IconAlertCircle, { size: 16 }), color: "red", title: "The Evaluation Engine was not enabled", styles: actionAlertStyles }, /* @__PURE__ */ React14.createElement(Stack12, { gap: "xs" }, errorMessage && /* @__PURE__ */ React14.createElement(Text12, { size: "sm" }, errorMessage), /* @__PURE__ */ React14.createElement(Button8, { variant: "outline", size: "xs", onClick: handleReset }, "Reset"))), isCompletedWithProof && /* @__PURE__ */ React14.createElement(Alert6, { icon: /* @__PURE__ */ React14.createElement(IconCheck2, { size: 16 }), color: "green", styles: actionAlertStyles }, "Evaluation Engine enabled \u2014 this collection is enrolled and your rules are published (rules ", truncate(rubricId), "). Every new claim is judged by them."), isDisabled && /* @__PURE__ */ React14.createElement(Text12, { size: "xs", c: "dimmed" }, "This block is currently disabled."));
|
|
4285
|
+
};
|
|
4286
|
+
var EvalEngineFlowDetail_default = EvalEngineFlowDetail;
|
|
4287
|
+
export {
|
|
4288
|
+
EvalEngineFlowDetail,
|
|
4289
|
+
EvalEngineFlowDetail_default as default
|
|
4290
|
+
};
|
|
4291
|
+
//# sourceMappingURL=EvalEngineFlowDetail-PUFRNO4U.js.map
|