@graph8/sdk 0.370.0 → 0.374.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/README.md +22 -3
- package/blocks/_foundation/README.md +61 -0
- package/blocks/_foundation/block.json +57 -0
- package/blocks/_foundation/components/auth-gate.tsx +363 -0
- package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
- package/blocks/_foundation/components/empty-state.tsx +5 -2
- package/blocks/_foundation/components/list-select.tsx +112 -0
- package/blocks/_foundation/components/paging-notices.tsx +55 -0
- package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
- package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
- package/blocks/_foundation/components/screen/index.ts +1 -0
- package/blocks/_foundation/components/screen/split-view.tsx +1 -1
- package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
- package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
- package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
- package/blocks/_foundation/lib/paging.ts +57 -0
- package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
- package/blocks/_foundation/server/auth.ts +815 -0
- package/blocks/agents/README.md +81 -4
- package/blocks/agents/block.json +311 -2
- package/blocks/agents/components/agent-activity.tsx +112 -0
- package/blocks/agents/components/agent-approvals.tsx +135 -0
- package/blocks/agents/components/agent-builder.tsx +17 -3
- package/blocks/agents/components/agent-conversations.tsx +143 -0
- package/blocks/agents/components/agent-knowledge-panel.tsx +2 -2
- package/blocks/agents/components/agent-operators.tsx +164 -0
- package/blocks/agents/components/agent-run-detail.tsx +20 -3
- package/blocks/agents/components/agent-runs.tsx +3 -3
- package/blocks/agents/components/agent-skills-panel.tsx +7 -7
- package/blocks/agents/components/agent-test-history.tsx +121 -0
- package/blocks/agents/components/agents-screen.tsx +53 -11
- package/blocks/agents/components/assistant-sessions.tsx +150 -0
- package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
- package/blocks/agents/components/from-template-button.tsx +65 -0
- package/blocks/agents/components/knowledge-browser.tsx +107 -0
- package/blocks/agents/components/knowledge-detail.tsx +156 -0
- package/blocks/agents/components/knowledge-form.tsx +69 -0
- package/blocks/agents/components/name-dialog.tsx +32 -0
- package/blocks/agents/components/receptionist-button.tsx +46 -0
- package/blocks/agents/components/save-as-template-button.tsx +32 -0
- package/blocks/agents/components/skill-editor.tsx +147 -0
- package/blocks/agents/components/skill-library.tsx +111 -0
- package/blocks/agents/lib/agent-types.ts +7 -1
- package/blocks/agents/lib/facts.tsx +13 -0
- package/blocks/agents/lib/knowledge-utils.ts +49 -0
- package/blocks/agents/lib/load-more.tsx +141 -0
- package/blocks/agents/lib/skill-utils.ts +91 -0
- package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
- package/blocks/analytics/components/analytics-screen.tsx +16 -3
- package/blocks/analytics/components/kpi-tiles.tsx +1 -1
- package/blocks/analytics/components/report-builder.tsx +15 -2
- package/blocks/appointments/README.md +6 -0
- package/blocks/appointments/block.json +27 -0
- package/blocks/appointments/components/booking-page.tsx +2 -2
- package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
- package/blocks/appointments/components/meeting-detail.tsx +2 -2
- package/blocks/appointments/components/meeting-list.tsx +29 -13
- package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
- package/blocks/campaigns/README.md +1 -1
- package/blocks/campaigns/block.json +34 -1
- package/blocks/campaigns/components/campaign-assets.tsx +1 -1
- package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
- package/blocks/campaigns/components/campaign-links.tsx +180 -0
- package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
- package/blocks/campaigns/components/campaign-wizard.tsx +10 -5
- package/blocks/campaigns/components/icp-detail.tsx +11 -2
- package/blocks/campaigns/components/icp-editor.tsx +8 -3
- package/blocks/campaigns/components/persona-detail.tsx +11 -2
- package/blocks/campaigns/components/persona-editor.tsx +6 -1
- package/blocks/campaigns/lib/campaigns-utils.ts +4 -4
- package/blocks/coach/components/call-scorecard.tsx +7 -7
- package/blocks/company-intelligence/README.md +132 -0
- package/blocks/company-intelligence/block.json +318 -0
- package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
- package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
- package/blocks/company-intelligence/components/company-profile.tsx +68 -0
- package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
- package/blocks/company-intelligence/components/report-detail.tsx +189 -0
- package/blocks/company-intelligence/components/research-reports.tsx +136 -0
- package/blocks/company-intelligence/components/research-run.tsx +165 -0
- package/blocks/company-intelligence/components/source-detail.tsx +117 -0
- package/blocks/company-intelligence/components/source-view.tsx +274 -0
- package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
- package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
- package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
- package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
- package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
- package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
- package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
- package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
- package/blocks/copilot/components/copilot-memory.tsx +6 -2
- package/blocks/copilot/components/copilot-panel.tsx +1 -1
- package/blocks/copilot/components/copilot-team-chat.tsx +6 -1
- package/blocks/crm/README.md +25 -0
- package/blocks/crm/block.json +257 -9
- package/blocks/crm/components/bulk-update.tsx +119 -0
- package/blocks/crm/components/companies-screen.tsx +23 -9
- package/blocks/crm/components/company-detail.tsx +2 -2
- package/blocks/crm/components/contact-detail.tsx +4 -4
- package/blocks/crm/components/contact-documents-tab.tsx +144 -0
- package/blocks/crm/components/contact-insights-tab.tsx +118 -0
- package/blocks/crm/components/contact-lookup-link.tsx +85 -0
- package/blocks/crm/components/contact-row-actions.tsx +43 -0
- package/blocks/crm/components/contact-tag-chips.tsx +113 -0
- package/blocks/crm/components/contacts-screen.tsx +77 -18
- package/blocks/crm/components/dedupe-review.tsx +6 -1
- package/blocks/crm/components/import-wizard.tsx +3 -7
- package/blocks/crm/components/list-select.tsx +20 -0
- package/blocks/crm/components/owner-assign.tsx +132 -0
- package/blocks/crm/components/record-engagement-tab.tsx +103 -0
- package/blocks/crm/components/record-form.tsx +1 -1
- package/blocks/crm/components/record-open-links.tsx +79 -0
- package/blocks/crm/components/tag-assign.tsx +112 -0
- package/blocks/crm/components/tag-manager.tsx +178 -0
- package/blocks/crm/components/tasks-screen.tsx +35 -15
- package/blocks/crm/components/unlock-info-button.tsx +82 -0
- package/blocks/crm/components/verify-email-button.tsx +93 -0
- package/blocks/crm/hooks/use-all-lists.ts +18 -0
- package/blocks/crm/hooks/use-contact-tags.ts +29 -0
- package/blocks/crm/hooks/use-page-query.ts +3 -1
- package/blocks/crm/lib/crm-types.ts +14 -0
- package/blocks/crm/lib/read-lists.ts +16 -0
- package/blocks/crm/lib/record-actions.ts +102 -0
- package/blocks/deals/README.md +25 -6
- package/blocks/deals/block.json +168 -2
- package/blocks/deals/components/close-deal-dialog.tsx +169 -0
- package/blocks/deals/components/create-deal-button.tsx +109 -0
- package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
- package/blocks/deals/components/deal-detail.tsx +7 -5
- package/blocks/deals/components/deal-filters.tsx +65 -0
- package/blocks/deals/components/deal-line-items.tsx +120 -0
- package/blocks/deals/components/deal-notes-tab.tsx +97 -0
- package/blocks/deals/components/deal-related.tsx +30 -9
- package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
- package/blocks/deals/components/deals-screen.tsx +57 -22
- package/blocks/deals/components/forecast-view.tsx +4 -4
- package/blocks/deals/components/pipeline-board.tsx +8 -6
- package/blocks/deals/components/pipeline-editor.tsx +132 -0
- package/blocks/deals/components/record-deals-tab.tsx +62 -0
- package/blocks/deals/components/stage-editor.tsx +1 -1
- package/blocks/deals/hooks/use-pipeline-deals.ts +17 -4
- package/blocks/deals/lib/deal-extras.ts +156 -0
- package/blocks/dialer/README.md +14 -5
- package/blocks/dialer/block.json +17 -2
- package/blocks/dialer/components/call-contact-button.tsx +26 -9
- package/blocks/dialer/components/call-log-screen.tsx +10 -2
- package/blocks/dialer/components/call-log.tsx +16 -4
- package/blocks/dialer/components/call-queue.tsx +3 -3
- package/blocks/dialer/components/dialer-screen.tsx +69 -27
- package/blocks/dialer/components/disposition-panel.tsx +1 -1
- package/blocks/dialer/components/record-calls-tab.tsx +101 -0
- package/blocks/dialer/components/session-start-form.tsx +58 -11
- package/blocks/dialer/components/softphone.tsx +1 -1
- package/blocks/documents/README.md +86 -0
- package/blocks/documents/block.json +378 -0
- package/blocks/documents/components/account-bar.tsx +48 -0
- package/blocks/documents/components/correspondence-form.tsx +95 -0
- package/blocks/documents/components/correspondence-import.tsx +106 -0
- package/blocks/documents/components/correspondence-panel.tsx +191 -0
- package/blocks/documents/components/documents-screen.tsx +76 -0
- package/blocks/documents/components/library-document-detail.tsx +175 -0
- package/blocks/documents/components/library-document-form.tsx +105 -0
- package/blocks/documents/components/library-folder-form.tsx +74 -0
- package/blocks/documents/components/library-panel.tsx +235 -0
- package/blocks/documents/components/library-upload.tsx +160 -0
- package/blocks/documents/components/question-row.tsx +130 -0
- package/blocks/documents/components/questionnaire-detail.tsx +197 -0
- package/blocks/documents/components/questionnaires-panel.tsx +173 -0
- package/blocks/documents/components/record-documents-tab.tsx +109 -0
- package/blocks/documents/lib/documents-types.ts +252 -0
- package/blocks/documents/lib/download.ts +64 -0
- package/blocks/documents/recipes/follow-a-questionnaire.ts +26 -0
- package/blocks/documents/recipes/log-account-correspondence.ts +27 -0
- package/blocks/documents/recipes/organize-the-library.ts +25 -0
- package/blocks/email-signatures/README.md +123 -0
- package/blocks/email-signatures/block.json +341 -0
- package/blocks/email-signatures/components/email-signatures-screen.tsx +166 -0
- package/blocks/email-signatures/components/signature-assign-template.tsx +119 -0
- package/blocks/email-signatures/components/signature-block-editor.tsx +87 -0
- package/blocks/email-signatures/components/signature-campaign-form.tsx +165 -0
- package/blocks/email-signatures/components/signature-campaigns.tsx +75 -0
- package/blocks/email-signatures/components/signature-identity-form.tsx +131 -0
- package/blocks/email-signatures/components/signature-mailboxes.tsx +195 -0
- package/blocks/email-signatures/components/signature-policy.tsx +130 -0
- package/blocks/email-signatures/components/signature-preview.tsx +119 -0
- package/blocks/email-signatures/components/signature-report.tsx +77 -0
- package/blocks/email-signatures/components/signature-template-details.tsx +112 -0
- package/blocks/email-signatures/components/signature-template-form.tsx +109 -0
- package/blocks/email-signatures/hooks/use-signatures-data.ts +93 -0
- package/blocks/email-signatures/lib/signatures-types.ts +392 -0
- package/blocks/email-signatures/recipes/assign-a-signature-to-a-new-hire.ts +32 -0
- package/blocks/email-signatures/recipes/create-a-template-and-make-it-default.ts +35 -0
- package/blocks/email-signatures/recipes/signature-engagement-report.ts +16 -0
- package/blocks/enrichment/README.md +38 -10
- package/blocks/enrichment/block.json +164 -12
- package/blocks/enrichment/components/auto-enrichment-configs.tsx +134 -0
- package/blocks/enrichment/components/auto-enrichment-settings.tsx +82 -0
- package/blocks/enrichment/components/bulk-enrich-panel.tsx +79 -30
- package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
- package/blocks/enrichment/components/enrich-button.tsx +1 -1
- package/blocks/enrichment/components/enrichment-activity.tsx +93 -0
- package/blocks/enrichment/components/enrichment-job-detail.tsx +18 -0
- package/blocks/enrichment/components/enrichment-screen.tsx +103 -19
- package/blocks/enrichment/components/list-pipelines.tsx +97 -0
- package/blocks/enrichment/components/pipeline-detail.tsx +214 -0
- package/blocks/enrichment/components/pipeline-editor.tsx +144 -0
- package/blocks/enrichment/components/pipeline-templates.tsx +90 -0
- package/blocks/enrichment/components/waterfall-config.tsx +27 -7
- package/blocks/enrichment/lib/enrichment-types.ts +51 -0
- package/blocks/enrichment/lib/pipeline-types.ts +234 -0
- package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
- package/blocks/forms/README.md +10 -0
- package/blocks/forms/block.json +107 -0
- package/blocks/forms/components/form-activity.tsx +71 -0
- package/blocks/forms/components/form-backfill.tsx +108 -0
- package/blocks/forms/components/form-builder.tsx +1 -1
- package/blocks/forms/components/form-embed.tsx +1 -1
- package/blocks/forms/components/form-groups.tsx +151 -0
- package/blocks/forms/components/form-mapping-editor.tsx +6 -9
- package/blocks/forms/components/form-route-history.tsx +72 -0
- package/blocks/forms/components/form-saved-views.tsx +81 -0
- package/blocks/forms/components/forms-screen.tsx +57 -20
- package/blocks/forms/components/more-rows.tsx +30 -0
- package/blocks/forms/components/submissions-export.tsx +67 -0
- package/blocks/forms/components/submissions-table.tsx +15 -6
- package/blocks/forms/lib/forms-extra.ts +67 -0
- package/blocks/inbox/README.md +45 -4
- package/blocks/inbox/block.json +145 -8
- package/blocks/inbox/components/blocked-senders.tsx +103 -0
- package/blocks/inbox/components/compose-message.tsx +193 -0
- package/blocks/inbox/components/inbox-screen.tsx +54 -6
- package/blocks/inbox/components/inbox-search.tsx +106 -0
- package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
- package/blocks/inbox/components/tag-manager.tsx +136 -0
- package/blocks/inbox/components/thread-actions.tsx +133 -0
- package/blocks/inbox/components/thread-badge.tsx +2 -2
- package/blocks/inbox/components/thread-view.tsx +3 -1
- package/blocks/inbox/hooks/use-inbox-mailboxes.ts +31 -0
- package/blocks/inbox/lib/inbox-manage.ts +227 -0
- package/blocks/integrations/README.md +54 -1
- package/blocks/integrations/block.json +189 -15
- package/blocks/integrations/components/ad-accounts.tsx +82 -0
- package/blocks/integrations/components/connection-health.tsx +59 -0
- package/blocks/integrations/components/crm-detail.tsx +20 -1
- package/blocks/integrations/components/crm-records.tsx +103 -0
- package/blocks/integrations/components/field-mappings.tsx +61 -0
- package/blocks/integrations/components/integration-list.tsx +4 -4
- package/blocks/integrations/components/integrations-screen.tsx +20 -3
- package/blocks/integrations/components/migration-panel.tsx +133 -0
- package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
- package/blocks/integrations/components/sync-status.tsx +6 -6
- package/blocks/integrations/components/sync-triggers.tsx +134 -0
- package/blocks/integrations/components/webhook-detail.tsx +26 -7
- package/blocks/integrations/components/webhook-manager.tsx +30 -17
- package/blocks/integrations/components/workspace-tools.tsx +127 -0
- package/blocks/integrations/lib/crm-utils.ts +39 -0
- package/blocks/integrations/lib/facts.tsx +13 -0
- package/blocks/integrations/lib/integrations-utils.ts +5 -0
- package/blocks/integrations/lib/load-more.tsx +141 -0
- package/blocks/intent/components/account-intent-score.tsx +5 -5
- package/blocks/intent/components/intent-feed.tsx +8 -8
- package/blocks/intent/components/intent-keyword-manager.tsx +8 -8
- package/blocks/intent/lib/intent-utils.ts +4 -4
- package/blocks/lifecycle/components/account-related.tsx +8 -1
- package/blocks/lifecycle/components/churn-list.tsx +5 -2
- package/blocks/lifecycle/components/customer-table.tsx +1 -1
- package/blocks/lists/README.md +7 -1
- package/blocks/lists/block.json +31 -3
- package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
- package/blocks/lists/components/add-to-list-button.tsx +17 -7
- package/blocks/lists/components/list-picker.tsx +13 -3
- package/blocks/lists/components/list-table.tsx +1 -1
- package/blocks/lists/components/lists-screen.tsx +4 -2
- package/blocks/lists/components/record-lists-tab.tsx +99 -0
- package/blocks/lists/components/suppression-manager.tsx +27 -6
- package/blocks/lists/components/suppressions-screen.tsx +21 -12
- package/blocks/mailboxes/block.json +4 -0
- package/blocks/mailboxes/components/connect-mailbox.tsx +9 -8
- package/blocks/mailboxes/components/domain-detail.tsx +10 -4
- package/blocks/mailboxes/components/domain-purchase.tsx +14 -11
- package/blocks/mailboxes/components/mailbox-detail.tsx +3 -2
- package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
- package/blocks/mailboxes/components/mailboxes-screen.tsx +8 -3
- package/blocks/mailboxes/components/warmup-health.tsx +20 -20
- package/blocks/mailboxes/lib/mailboxes-utils.ts +4 -4
- package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
- package/blocks/newsletter/README.md +12 -0
- package/blocks/newsletter/block.json +117 -0
- package/blocks/newsletter/components/issue-scheduler.tsx +2 -2
- package/blocks/newsletter/components/issue-versions.tsx +93 -0
- package/blocks/newsletter/components/more-rows.tsx +30 -0
- package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
- package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
- package/blocks/newsletter/components/newsletter-screen.tsx +38 -13
- package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
- package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
- package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
- package/blocks/newsletter/components/subscriber-table.tsx +5 -7
- package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
- package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
- package/blocks/nurture/components/nurture-builder.tsx +10 -2
- package/blocks/nurture/components/nurture-enroll.tsx +6 -7
- package/blocks/nurture/components/nurture-form.tsx +5 -8
- package/blocks/nurture/components/nurture-screen.tsx +16 -5
- package/blocks/nurture/components/nurture-stats.tsx +5 -4
- package/blocks/objects/README.md +1 -1
- package/blocks/objects/block.json +13 -5
- package/blocks/objects/components/attribute-field-input.tsx +14 -1
- package/blocks/objects/components/object-record-detail.tsx +8 -1
- package/blocks/objects/components/object-schema-editor.tsx +1 -1
- package/blocks/objects/components/record-roles-panel.tsx +15 -5
- package/blocks/pages/README.md +14 -0
- package/blocks/pages/block.json +123 -2
- package/blocks/pages/components/more-rows.tsx +30 -0
- package/blocks/pages/components/page-ai-tools.tsx +196 -0
- package/blocks/pages/components/page-analytics.tsx +176 -0
- package/blocks/pages/components/page-clone.tsx +99 -0
- package/blocks/pages/components/page-domains.tsx +167 -0
- package/blocks/pages/components/page-editor.tsx +1 -1
- package/blocks/pages/components/page-generation-status.tsx +48 -0
- package/blocks/pages/components/page-tracking.tsx +137 -0
- package/blocks/pages/components/pages-screen.tsx +57 -16
- package/blocks/pages/components/seo-report.tsx +6 -1
- package/blocks/pages/hooks/use-landing-page-paths.ts +60 -0
- package/blocks/pages/lib/studio.ts +7 -0
- package/blocks/phone-numbers/README.md +74 -0
- package/blocks/phone-numbers/block.json +262 -0
- package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
- package/blocks/phone-numbers/components/assign-number.tsx +104 -0
- package/blocks/phone-numbers/components/number-search.tsx +239 -0
- package/blocks/phone-numbers/components/number-settings.tsx +134 -0
- package/blocks/phone-numbers/components/number-usage.tsx +97 -0
- package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
- package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
- package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
- package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
- package/blocks/phone-numbers/components/release-number.tsx +126 -0
- package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
- package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
- package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
- package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
- package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
- package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
- package/blocks/pickers/README.md +23 -0
- package/blocks/pickers/block.json +103 -0
- package/blocks/pickers/components/company-picker.tsx +90 -0
- package/blocks/pickers/components/contact-picker.tsx +131 -0
- package/blocks/pickers/components/member-picker.tsx +99 -0
- package/blocks/pickers/components/search-picker.tsx +98 -0
- package/blocks/pickers/hooks/use-current-member.ts +104 -0
- package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
- package/blocks/pickers/lib/use-debounced.ts +13 -0
- package/blocks/programmatic-pages/README.md +82 -0
- package/blocks/programmatic-pages/block.json +292 -0
- package/blocks/programmatic-pages/components/programmatic-campaign-form.tsx +136 -0
- package/blocks/programmatic-pages/components/programmatic-generate.tsx +112 -0
- package/blocks/programmatic-pages/components/programmatic-generated-pages.tsx +210 -0
- package/blocks/programmatic-pages/components/programmatic-overview.tsx +84 -0
- package/blocks/programmatic-pages/components/programmatic-pages-screen.tsx +221 -0
- package/blocks/programmatic-pages/components/programmatic-preview.tsx +66 -0
- package/blocks/programmatic-pages/components/programmatic-publish.tsx +102 -0
- package/blocks/programmatic-pages/components/programmatic-settings.tsx +96 -0
- package/blocks/programmatic-pages/components/programmatic-source-fields.tsx +75 -0
- package/blocks/programmatic-pages/components/programmatic-source.tsx +106 -0
- package/blocks/programmatic-pages/components/programmatic-variables.tsx +84 -0
- package/blocks/programmatic-pages/hooks/use-programmatic-workspace.ts +19 -0
- package/blocks/programmatic-pages/lib/programmatic-types.ts +330 -0
- package/blocks/programmatic-pages/recipes/create-a-programmatic-campaign.ts +23 -0
- package/blocks/programmatic-pages/recipes/render-and-follow.ts +21 -0
- package/blocks/programmatic-pages/recipes/review-then-publish.ts +17 -0
- package/blocks/quotes/README.md +38 -1
- package/blocks/quotes/block.json +170 -6
- package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
- package/blocks/quotes/components/manual-products.tsx +144 -0
- package/blocks/quotes/components/quote-batch-actions.tsx +86 -0
- package/blocks/quotes/components/quote-builder.tsx +1 -1
- package/blocks/quotes/components/quote-documents.tsx +94 -0
- package/blocks/quotes/components/quote-evidence.tsx +95 -0
- package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
- package/blocks/quotes/components/quote-list.tsx +28 -8
- package/blocks/quotes/components/quote-related.tsx +3 -2
- package/blocks/quotes/components/quote-settings.tsx +184 -0
- package/blocks/quotes/components/quote-templates.tsx +189 -0
- package/blocks/quotes/components/quote-viewer.tsx +23 -5
- package/blocks/quotes/components/quotes-screen.tsx +15 -5
- package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
- package/blocks/quotes/lib/quote-admin.ts +264 -0
- package/blocks/radar/components/competitor-battle-card.tsx +1 -1
- package/blocks/radar/components/radar-overview.tsx +4 -4
- package/blocks/radar/components/radar-screen.tsx +2 -2
- package/blocks/sequencer/README.md +61 -0
- package/blocks/sequencer/block.json +200 -6
- package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
- package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
- package/blocks/sequencer/components/enrollment-withdraw.tsx +71 -0
- package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
- package/blocks/sequencer/components/sequence-builder.tsx +32 -23
- package/blocks/sequencer/components/sequence-campaign-link.tsx +78 -0
- package/blocks/sequencer/components/sequence-detail.tsx +10 -2
- package/blocks/sequencer/components/sequence-edit-form.tsx +111 -0
- package/blocks/sequencer/components/sequence-enrollments.tsx +5 -2
- package/blocks/sequencer/components/sequence-list.tsx +1 -1
- package/blocks/sequencer/components/sequence-manage-actions.tsx +119 -0
- package/blocks/sequencer/components/sequence-senders-panel.tsx +84 -0
- package/blocks/sequencer/components/sequence-stats.tsx +8 -8
- package/blocks/sequencer/components/sequence-templates.tsx +160 -0
- package/blocks/sequencer/components/sequences-screen.tsx +39 -6
- package/blocks/sequencer/components/step-ai-assist.tsx +196 -0
- package/blocks/sequencer/components/step-editor.tsx +21 -12
- package/blocks/sequencer/components/step-templates.tsx +224 -0
- package/blocks/sequencer/lib/sequencer-manage.ts +235 -0
- package/blocks/sequencer/lib/sequencer-utils.ts +9 -5
- package/blocks/signals/README.md +39 -0
- package/blocks/signals/block.json +287 -0
- package/blocks/signals/components/auto-seed-panel.tsx +111 -0
- package/blocks/signals/components/listener-form.tsx +184 -0
- package/blocks/signals/components/matched-posts.tsx +195 -0
- package/blocks/signals/components/post-engagements.tsx +114 -0
- package/blocks/signals/components/record-social-tab.tsx +49 -0
- package/blocks/signals/components/social-listeners-screen.tsx +250 -0
- package/blocks/signals/components/social-settings.tsx +128 -0
- package/blocks/signals/components/source-lists.tsx +83 -0
- package/blocks/signals/components/top-engagers.tsx +68 -0
- package/blocks/signals/components/x-integration.tsx +129 -0
- package/blocks/signals/lib/signals-types.ts +228 -0
- package/blocks/signals/recipes/preview-and-create-a-listener.ts +16 -0
- package/blocks/signals/recipes/spend-against-caps.ts +14 -0
- package/blocks/signals/recipes/triage-matched-posts.ts +18 -0
- package/blocks/social-ads/README.md +8 -0
- package/blocks/social-ads/block.json +137 -4
- package/blocks/social-ads/components/audience-sync-detail.tsx +9 -3
- package/blocks/social-ads/components/audience-sync.tsx +6 -2
- package/blocks/social-ads/components/campaign-ads.tsx +166 -0
- package/blocks/social-ads/components/campaign-detail.tsx +6 -1
- package/blocks/social-ads/components/campaign-editor.tsx +134 -0
- package/blocks/social-ads/components/creative-concepts.tsx +74 -0
- package/blocks/social-ads/components/creative-detail.tsx +6 -1
- package/blocks/social-ads/components/creative-gallery.tsx +20 -2
- package/blocks/social-ads/components/creative-revisions.tsx +57 -0
- package/blocks/social-ads/components/creative-tools.tsx +176 -0
- package/blocks/social-ads/components/post-composer.tsx +4 -2
- package/blocks/social-ads/components/post-detail.tsx +3 -0
- package/blocks/social-ads/components/social-ideas.tsx +64 -0
- package/blocks/social-ads/components/social-performance.tsx +80 -0
- package/blocks/social-ads/components/social-screen.tsx +33 -6
- package/blocks/social-ads/lib/ads-extra.ts +67 -0
- package/blocks/surveys/README.md +91 -0
- package/blocks/surveys/block.json +299 -0
- package/blocks/surveys/components/account-nps.tsx +69 -0
- package/blocks/surveys/components/contact-survey-history.tsx +52 -0
- package/blocks/surveys/components/org-summary.tsx +82 -0
- package/blocks/surveys/components/sending-domains.tsx +113 -0
- package/blocks/surveys/components/survey-form.tsx +126 -0
- package/blocks/surveys/components/survey-responses.tsx +145 -0
- package/blocks/surveys/components/survey-results.tsx +72 -0
- package/blocks/surveys/components/survey-send-history.tsx +58 -0
- package/blocks/surveys/components/survey-send.tsx +162 -0
- package/blocks/surveys/components/survey-triggers.tsx +166 -0
- package/blocks/surveys/components/surveys-screen.tsx +234 -0
- package/blocks/surveys/lib/surveys-types.ts +228 -0
- package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
- package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
- package/blocks/surveys/recipes/survey-score-report.ts +27 -0
- package/blocks/visitors/components/snippet-install.tsx +18 -9
- package/blocks/visitors/components/traffic-chart.tsx +7 -6
- package/blocks/visitors/components/visitor-company-card.tsx +24 -14
- package/blocks/visitors/components/visitor-feed.tsx +5 -5
- package/blocks/web-analytics/README.md +102 -0
- package/blocks/web-analytics/block.json +292 -0
- package/blocks/web-analytics/components/acquisition-tab.tsx +154 -0
- package/blocks/web-analytics/components/analytics-filters.tsx +161 -0
- package/blocks/web-analytics/components/audience-tab.tsx +135 -0
- package/blocks/web-analytics/components/conversions-tab.tsx +128 -0
- package/blocks/web-analytics/components/engagement-tab.tsx +123 -0
- package/blocks/web-analytics/components/overview-tab.tsx +102 -0
- package/blocks/web-analytics/components/pages-tab.tsx +148 -0
- package/blocks/web-analytics/components/ranked-table.tsx +103 -0
- package/blocks/web-analytics/components/realtime-tab.tsx +103 -0
- package/blocks/web-analytics/components/report-card.tsx +71 -0
- package/blocks/web-analytics/components/web-analytics-screen.tsx +90 -0
- package/blocks/web-analytics/hooks/use-report.ts +48 -0
- package/blocks/web-analytics/lib/analytics-scope.ts +155 -0
- package/blocks/web-analytics/lib/page-path.ts +14 -0
- package/blocks/web-analytics/lib/web-analytics-types.ts +187 -0
- package/blocks/web-analytics/recipes/best-landing-pages.ts +26 -0
- package/blocks/web-analytics/recipes/form-conversion-check.ts +19 -0
- package/blocks/web-analytics/recipes/weekly-traffic-report.ts +37 -0
- package/blocks/webchat/components/chat-widget-config.tsx +1 -1
- package/blocks/webchat/components/conversation-list.tsx +1 -1
- package/blocks/workbench/README.md +54 -2
- package/blocks/workbench/block.json +234 -5
- package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
- package/blocks/workbench/components/run-progress.tsx +1 -1
- package/blocks/workbench/components/workbench-auto-enrich.tsx +88 -0
- package/blocks/workbench/components/workbench-automation.tsx +36 -0
- package/blocks/workbench/components/workbench-connections.tsx +136 -0
- package/blocks/workbench/components/workbench-crm-import.tsx +91 -0
- package/blocks/workbench/components/workbench-datafile-import.tsx +154 -0
- package/blocks/workbench/components/workbench-grid.tsx +71 -8
- package/blocks/workbench/components/workbench-pipelines.tsx +173 -0
- package/blocks/workbench/components/workbench-row-actions.tsx +8 -1
- package/blocks/workbench/components/workbench-schedule.tsx +191 -0
- package/blocks/workbench/components/workbench-screen.tsx +65 -22
- package/blocks/workbench/components/workbench-send-to-sequence.tsx +116 -0
- package/blocks/workbench/components/workbench-table-list.tsx +14 -2
- package/blocks/workbench/components/workbench-table-settings.tsx +53 -0
- package/blocks/workbench/components/workbench-triggers.tsx +178 -0
- package/blocks/workbench/hooks/use-connected-crms.ts +29 -0
- package/blocks/workbench/lib/api-status.ts +8 -0
- package/blocks/workbench/lib/automation-types.ts +257 -0
- package/blocks/workflows/components/workflow-editor.tsx +1 -1
- package/dist/cli.js +744 -394
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.mjs +1 -1
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react.browser.js +1 -1
- package/dist/react.browser.js.map +1 -1
- package/dist/react.browser.mjs +1 -1
- package/dist/react.browser.mjs.map +1 -1
- package/dist/react.d.mts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +1 -1
- package/dist/react.mjs.map +1 -1
- package/dist/types/blocks/auth.d.ts +45 -0
- package/dist/types/blocks/brand.d.ts +11 -2
- package/dist/types/blocks/create.d.ts +11 -4
- package/dist/types/blocks/doctor.d.ts +1 -0
- package/dist/types/blocks/flows.d.ts +7 -1
- package/dist/types/blocks/orgBrand.d.ts +34 -0
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/version.d.ts +2 -2
- package/docs/api/desk.d.ts +2 -2
- package/docs/api/desk.md +3 -3
- package/docs/api/forms.md +1 -1
- package/package.json +1 -1
- package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
- package/stacks/6sense/pages/contacts.tsx.tmpl +21 -0
- package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
- package/stacks/6sense.json +37 -4
- package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
- package/stacks/apollo/pages/campaigns.tsx.tmpl +27 -0
- package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
- package/stacks/apollo/pages/contacts.tsx.tmpl +35 -95
- package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
- package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
- package/stacks/apollo/pages/lists.tsx.tmpl +24 -0
- package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
- package/stacks/apollo/pages/sequences.tsx.tmpl +18 -0
- package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
- package/stacks/apollo.json +68 -9
- package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
- package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
- package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
- package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
- package/stacks/calendly.json +8 -4
- package/stacks/clay/pages/companies.tsx.tmpl +17 -1
- package/stacks/clay/pages/contacts.tsx.tmpl +14 -0
- package/stacks/clay/pages/lists.tsx.tmpl +24 -0
- package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
- package/stacks/clay.json +16 -3
- package/stacks/gong/pages/calls.tsx.tmpl +23 -0
- package/stacks/gong/pages/companies.tsx.tmpl +22 -0
- package/stacks/gong/pages/contacts.tsx.tmpl +34 -0
- package/stacks/gong/pages/lists.tsx.tmpl +12 -0
- package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
- package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
- package/stacks/gong.json +23 -6
- package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
- package/stacks/hootsuite/pages/contacts.tsx.tmpl +30 -0
- package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
- package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
- package/stacks/hootsuite.json +19 -4
- package/stacks/hubspot/pages/campaigns.tsx.tmpl +30 -0
- package/stacks/hubspot/pages/companies.tsx.tmpl +16 -35
- package/stacks/hubspot/pages/contacts.tsx.tmpl +35 -95
- package/stacks/hubspot/pages/deals.tsx.tmpl +14 -1
- package/stacks/hubspot/pages/forms.tsx.tmpl +36 -0
- package/stacks/hubspot/pages/inbox.tsx.tmpl +12 -1
- package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
- package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
- package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
- package/stacks/hubspot/pages/sequences.tsx.tmpl +18 -0
- package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
- package/stacks/hubspot/pages/web-analytics.tsx.tmpl +14 -0
- package/stacks/hubspot.json +66 -7
- package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
- package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
- package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
- package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
- package/stacks/intercom.json +18 -4
- package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
- package/stacks/mailchimp/pages/contacts.tsx.tmpl +30 -0
- package/stacks/mailchimp/pages/forms.tsx.tmpl +36 -0
- package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
- package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
- package/stacks/mailchimp.json +22 -6
- package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
- package/stacks/outreach/pages/campaigns.tsx.tmpl +27 -0
- package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
- package/stacks/outreach/pages/contacts.tsx.tmpl +29 -66
- package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
- package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
- package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
- package/stacks/outreach/pages/sequences.tsx.tmpl +18 -0
- package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
- package/stacks/outreach.json +38 -9
- package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
- package/stacks/pipedrive/pages/contacts.tsx.tmpl +17 -35
- package/stacks/pipedrive/pages/deals.tsx.tmpl +10 -1
- package/stacks/pipedrive/pages/inbox.tsx.tmpl +12 -1
- package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
- package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
- package/stacks/pipedrive.json +14 -2
- package/stacks/salesforce/pages/companies.tsx.tmpl +16 -35
- package/stacks/salesforce/pages/contacts.tsx.tmpl +26 -35
- package/stacks/salesforce/pages/deals.tsx.tmpl +14 -1
- package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
- package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
- package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
- package/stacks/salesforce.json +17 -3
- package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
- package/stacks/zoominfo/pages/contacts.tsx.tmpl +16 -0
- package/stacks/zoominfo/pages/lists.tsx.tmpl +24 -0
- package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
- package/stacks/zoominfo.json +36 -3
- package/styles/styles.css +1 -1
- package/templates/auth/auth-route.ts.tmpl +16 -0
- package/templates/auth/g8.auth.anonymous.ts.tmpl +15 -0
- package/templates/auth/g8.auth.basic.ts.tmpl +17 -0
- package/templates/auth/g8.auth.jwt.ts.tmpl +23 -0
- package/templates/auth/g8.auth.password.ts.tmpl +18 -0
- package/templates/auth/g8.auth.session.ts.tmpl +23 -0
- package/templates/auth/middleware.ts.tmpl +14 -0
- package/templates/next-app/README.md.tmpl +5 -0
- package/templates/next-app/app/globals.css.tmpl +349 -64
- package/templates/next-app/components/app-shell.tsx.tmpl +6 -0
- package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PipelineEditor: create or edit a list pipeline (an ordered chain of enrichment steps that runs over a whole list)
|
|
5
|
+
* in a side sheet. Steps are picked from the configs this list can already use (AI groups and provider waterfalls);
|
|
6
|
+
* each step can be turned off, told to fill only empty values, or to skip recently enriched records.
|
|
7
|
+
*
|
|
8
|
+
* SDK operations, in order:
|
|
9
|
+
* 1. listPipelineStepOptions GET /enrichment/lists/{list_id}/pipeline-step-options (read, free) the menu steps are picked from
|
|
10
|
+
* 2. createListPipeline POST /enrichment/lists/{list_id}/pipelines (billable tier; saved TURNED OFF, nothing runs)
|
|
11
|
+
* 3. updateListPipeline PUT /enrichment/lists/{list_id}/pipelines/{pipeline_id} (billable tier; replaces the steps, keeps on/off)
|
|
12
|
+
* Both saves go through ConfirmSpendDialog. Saving never runs the pipeline and charges nothing in this API: a run is a
|
|
13
|
+
* separate, priced step (see PipelineDetail). The dialog says so, because its generic tier note talks about credits.
|
|
14
|
+
*
|
|
15
|
+
* An edit seeds from the pipeline row the list already loaded (a full record) and sends the whole step list back with
|
|
16
|
+
* every field of every step kept (run conditions and builder state included), changing only what was edited. Saving
|
|
17
|
+
* is disabled until the step menu has loaded, and while nothing changed.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { useState } from "react";
|
|
21
|
+
import { ArrowDown, ArrowUp, Trash2 } from "lucide-react";
|
|
22
|
+
import { Button, Input, Label } from "@graph8/sdk/ui";
|
|
23
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
24
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
25
|
+
import { FormSheet } from "@/g8/components/screen/form-sheet";
|
|
26
|
+
import { StatusPill } from "@/g8/components/screen/status-pill";
|
|
27
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
28
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
29
|
+
import { g8Get, g8Post, g8Put } from "@/g8/lib/g8-client";
|
|
30
|
+
import { plural } from "@/g8/lib/plural";
|
|
31
|
+
import { itemsOf, moveItem, pipelineChanged, savePayload, stepFromOption, stepLabel } from "../lib/pipeline-types";
|
|
32
|
+
import type { ListPipeline, PipelineStep, StepOption } from "../lib/pipeline-types";
|
|
33
|
+
|
|
34
|
+
export interface PipelineEditorProps {
|
|
35
|
+
listId: number;
|
|
36
|
+
/** The pipeline to edit; omit to create a new one. */
|
|
37
|
+
pipeline?: ListPipeline;
|
|
38
|
+
open: boolean;
|
|
39
|
+
onOpenChange: (open: boolean) => void;
|
|
40
|
+
/** Called with the saved pipeline after the API accepted it. */
|
|
41
|
+
onSaved?: (pipeline: ListPipeline) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const MAX_STEPS = 20;
|
|
45
|
+
const toggle = "flex items-center gap-2 text-xs text-muted-foreground";
|
|
46
|
+
|
|
47
|
+
export function PipelineEditor({ listId, pipeline, open, onOpenChange, onSaved }: PipelineEditorProps) {
|
|
48
|
+
const [name, setName] = useState(pipeline?.name ?? "");
|
|
49
|
+
const [steps, setSteps] = useState<PipelineStep[]>(() => (pipeline?.steps ?? []).map((s) => ({ ...s })));
|
|
50
|
+
const [confirming, setConfirming] = useState(false);
|
|
51
|
+
const [pick, setPick] = useState("");
|
|
52
|
+
const options = useG8Query(async (signal) => itemsOf<StepOption>((await g8Get(`/enrichment/lists/${listId}/pipeline-step-options`, undefined, { signal })).data), [listId], { enabled: open, key: ["enrichment", "step-options", listId], staleTime: 30_000 });
|
|
53
|
+
const menu = options.data ?? [];
|
|
54
|
+
const editing = pipeline != null;
|
|
55
|
+
const save = useG8Mutation(
|
|
56
|
+
async () => {
|
|
57
|
+
const body = savePayload(name, steps);
|
|
58
|
+
const res = editing
|
|
59
|
+
? await g8Put<ListPipeline>(`/enrichment/lists/${listId}/pipelines/${pipeline.id}`, body)
|
|
60
|
+
: await g8Post<ListPipeline>(`/enrichment/lists/${listId}/pipelines`, body);
|
|
61
|
+
return res.data;
|
|
62
|
+
},
|
|
63
|
+
{ onSuccess: (saved) => { setConfirming(false); onSaved?.(saved); onOpenChange(false); }, onError: () => setConfirming(false) },
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const available = menu.filter((o) => !steps.some((s) => s.config_ref === o.config_ref && s.type === o.type));
|
|
67
|
+
const patch = (i: number, change: Partial<PipelineStep>) => setSteps((cur) => cur.map((s, at) => (at === i ? { ...s, ...change } : s)));
|
|
68
|
+
const addStep = () => {
|
|
69
|
+
const option = available.find((o) => `${o.type}:${o.config_ref}` === pick);
|
|
70
|
+
if (!option) return;
|
|
71
|
+
setSteps((cur) => [...cur, stepFromOption(option, cur.map((s) => s.id))]);
|
|
72
|
+
setPick("");
|
|
73
|
+
};
|
|
74
|
+
const valid = name.trim().length > 0 && name.trim().length <= 255 && steps.length > 0 && steps.length <= MAX_STEPS;
|
|
75
|
+
const dirty = editing ? pipelineChanged(pipeline, name, steps) : true;
|
|
76
|
+
const ready = valid && dirty && !options.loading && !options.error;
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<FormSheet
|
|
80
|
+
open={open}
|
|
81
|
+
onOpenChange={onOpenChange}
|
|
82
|
+
title={editing ? `Edit ${pipeline.name}` : "New list pipeline"}
|
|
83
|
+
description="Steps run in order over every record on the list. Saving does not run anything."
|
|
84
|
+
width="lg"
|
|
85
|
+
footer={<>
|
|
86
|
+
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>Cancel</Button>
|
|
87
|
+
<Button type="button" disabled={!ready} onClick={() => setConfirming(true)}>{editing ? "Save changes" : "Save pipeline"}</Button>
|
|
88
|
+
</>}
|
|
89
|
+
>
|
|
90
|
+
<div className="flex flex-col gap-1">
|
|
91
|
+
<Label htmlFor="pl-name">Name</Label>
|
|
92
|
+
<Input id="pl-name" value={name} maxLength={255} onChange={(e) => setName(e.target.value)} />
|
|
93
|
+
</div>
|
|
94
|
+
{options.error ? <ApiError error={options.error} title="Could not load the steps you can use" onRetry={() => void options.refetch()} /> : null}
|
|
95
|
+
<section className="flex flex-col gap-2" aria-label="Steps">
|
|
96
|
+
<h3 className="text-sm font-medium">Steps</h3>
|
|
97
|
+
{steps.length === 0 ? <p className="text-sm text-muted-foreground">Add at least one step.</p> : null}
|
|
98
|
+
<ol className="flex flex-col gap-2">
|
|
99
|
+
{steps.map((s, i) => (
|
|
100
|
+
<li key={s.id} className="rounded-md border p-3" data-testid="editor-step">
|
|
101
|
+
<div className="flex items-center gap-2">
|
|
102
|
+
<span className="w-5 text-sm text-muted-foreground">{i + 1}.</span>
|
|
103
|
+
<span className="min-w-0 flex-1 truncate text-sm font-medium">{stepLabel(s, menu)}</span>
|
|
104
|
+
<StatusPill tone="neutral">{s.type === "ai" ? "AI" : "Providers"}</StatusPill>
|
|
105
|
+
<Button type="button" variant="ghost" size="sm" aria-label={`Move step ${i + 1} up`} disabled={i === 0} onClick={() => setSteps((cur) => moveItem(cur, i, -1))}><ArrowUp className="size-4" aria-hidden /></Button>
|
|
106
|
+
<Button type="button" variant="ghost" size="sm" aria-label={`Move step ${i + 1} down`} disabled={i === steps.length - 1} onClick={() => setSteps((cur) => moveItem(cur, i, 1))}><ArrowDown className="size-4" aria-hidden /></Button>
|
|
107
|
+
<Button type="button" variant="ghost" size="sm" aria-label={`Remove step ${i + 1}`} onClick={() => setSteps((cur) => cur.filter((_, at) => at !== i))}><Trash2 className="size-4" aria-hidden /></Button>
|
|
108
|
+
</div>
|
|
109
|
+
<div className="mt-2 flex flex-col gap-1 pl-7">
|
|
110
|
+
<label className={toggle}><input type="checkbox" checked={s.enabled !== false} onChange={(e) => patch(i, { enabled: e.target.checked })} /> Run this step</label>
|
|
111
|
+
<label className={toggle}><input type="checkbox" checked={s.skip_existing_values !== false} onChange={(e) => patch(i, { skip_existing_values: e.target.checked })} /> Fill only empty values</label>
|
|
112
|
+
<label className={toggle}><input type="checkbox" checked={s.skip_recently_enriched !== false} onChange={(e) => patch(i, { skip_recently_enriched: e.target.checked })} /> Skip records enriched recently</label>
|
|
113
|
+
{s.run_condition ? <p className="text-xs text-muted-foreground">Runs only when: {s.run_condition}</p> : null}
|
|
114
|
+
</div>
|
|
115
|
+
</li>
|
|
116
|
+
))}
|
|
117
|
+
</ol>
|
|
118
|
+
<div className="flex items-end gap-2">
|
|
119
|
+
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
|
120
|
+
<Label htmlFor="pl-add">Add a step</Label>
|
|
121
|
+
<select id="pl-add" className="h-9 rounded-md border bg-background px-2 text-sm" value={pick} disabled={options.loading || steps.length >= MAX_STEPS} onChange={(e) => setPick(e.target.value)}>
|
|
122
|
+
<option value="">{options.loading ? "Loading steps..." : available.length ? "Choose a step" : "No more steps to add"}</option>
|
|
123
|
+
{available.map((o) => <option key={`${o.type}:${o.config_ref}`} value={`${o.type}:${o.config_ref}`}>{o.name}{o.is_qualification ? " (qualification)" : ""}</option>)}
|
|
124
|
+
</select>
|
|
125
|
+
</div>
|
|
126
|
+
<Button type="button" variant="outline" disabled={!pick} onClick={addStep}>Add step</Button>
|
|
127
|
+
</div>
|
|
128
|
+
{!options.loading && !options.error && menu.length === 0 ? <p className="text-xs text-muted-foreground">This list has no enrichment configs yet. Create a provider waterfall in the Pipelines tab, then come back.</p> : null}
|
|
129
|
+
{steps.length >= MAX_STEPS ? <p className="text-xs text-muted-foreground">A pipeline can have at most {MAX_STEPS} steps.</p> : null}
|
|
130
|
+
</section>
|
|
131
|
+
{save.error ? <ApiError error={save.error} title="Could not save the pipeline" /> : null}
|
|
132
|
+
<ConfirmSpendDialog
|
|
133
|
+
open={confirming}
|
|
134
|
+
tier="billable"
|
|
135
|
+
title={editing ? `Save changes to "${name.trim()}"` : `Save pipeline "${name.trim()}"`}
|
|
136
|
+
description={`${editing ? `Replaces the pipeline's steps with the ${plural(steps.length, "step")} shown; it stays ${pipeline.enabled ? "on" : "off"}. ` : `Saves ${plural(steps.length, "step")}, turned off. `}Saving does not run the pipeline and charges nothing. You see the price before every run.`}
|
|
137
|
+
confirmLabel="Save"
|
|
138
|
+
confirming={save.loading}
|
|
139
|
+
onConfirm={() => void save.mutate(undefined)}
|
|
140
|
+
onCancel={() => setConfirming(false)}
|
|
141
|
+
/>
|
|
142
|
+
</FormSheet>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PipelineTemplates: pick a ready-made pipeline for a list (for example "Find + verify missing emails") and add it.
|
|
5
|
+
* The catalog is filtered by the API to the recipes that fit this list's kind (people or companies).
|
|
6
|
+
*
|
|
7
|
+
* SDK operations, in order:
|
|
8
|
+
* 1. listPipelineTemplates GET /enrichment/lists/pipeline-templates?list_id (read, free) recipes + whether the workspace has a
|
|
9
|
+
* qualification template (recipes that score records first need one)
|
|
10
|
+
* 2. createListPipelineFromTemplate POST /enrichment/lists/{list_id}/pipelines/from-template { template_key } (write, free)
|
|
11
|
+
* saves the recipe TURNED OFF and creates any columns it needs. Adding a recipe the list already has REPLACES that
|
|
12
|
+
* pipeline's steps with the recipe's (it is saved by name), so that case goes through ConfirmSpendDialog (destructive).
|
|
13
|
+
*
|
|
14
|
+
* Nothing runs or charges here: open the new pipeline to price and run it.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useState } from "react";
|
|
18
|
+
import { Button } from "@graph8/sdk/ui";
|
|
19
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
20
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
21
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
22
|
+
import { FormSheet } from "@/g8/components/screen/form-sheet";
|
|
23
|
+
import { StatusPill } from "@/g8/components/screen/status-pill";
|
|
24
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
25
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
26
|
+
import { g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
27
|
+
import { itemsOf, templateExists } from "../lib/pipeline-types";
|
|
28
|
+
import type { ListPipeline, PipelineTemplate } from "../lib/pipeline-types";
|
|
29
|
+
|
|
30
|
+
export interface PipelineTemplatesProps {
|
|
31
|
+
listId: number;
|
|
32
|
+
open: boolean;
|
|
33
|
+
onOpenChange: (open: boolean) => void;
|
|
34
|
+
/** The pipelines already on the list, to warn before a recipe replaces one. */
|
|
35
|
+
existing: ListPipeline[];
|
|
36
|
+
/** Called with the saved pipeline. */
|
|
37
|
+
onAdded: (pipeline: ListPipeline) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface Catalog { items?: PipelineTemplate[]; has_qualification_templates?: boolean }
|
|
41
|
+
|
|
42
|
+
export function PipelineTemplates({ listId, open, onOpenChange, existing, onAdded }: PipelineTemplatesProps) {
|
|
43
|
+
const [replacing, setReplacing] = useState<PipelineTemplate | null>(null);
|
|
44
|
+
const [working, setWorking] = useState<string | null>(null);
|
|
45
|
+
const catalog = useG8Query(async (signal) => (await g8Get<Catalog>("/enrichment/lists/pipeline-templates", { list_id: listId }, { signal })).data, [listId], { enabled: open });
|
|
46
|
+
const add = useG8Mutation(
|
|
47
|
+
async (t: PipelineTemplate) => (await g8Post<ListPipeline>(`/enrichment/lists/${listId}/pipelines/from-template`, { template_key: t.key })).data,
|
|
48
|
+
{ onSuccess: (p) => { setReplacing(null); setWorking(null); onAdded(p); onOpenChange(false); }, onError: () => { setWorking(null); setReplacing(null); } },
|
|
49
|
+
);
|
|
50
|
+
const templates = itemsOf<PipelineTemplate>(catalog.data);
|
|
51
|
+
const hasQualification = catalog.data?.has_qualification_templates === true;
|
|
52
|
+
const choose = (t: PipelineTemplate) => {
|
|
53
|
+
if (templateExists(t, existing)) { setReplacing(t); return; }
|
|
54
|
+
setWorking(t.key);
|
|
55
|
+
void add.mutate(t);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<FormSheet open={open} onOpenChange={onOpenChange} title="Start from a template" description="Ready-made pipelines for this list. They are saved turned off; nothing runs until you run one." width="lg"
|
|
60
|
+
footer={<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>Close</Button>}>
|
|
61
|
+
{catalog.error ? <ApiError error={catalog.error} title="Could not load templates" onRetry={() => void catalog.refetch()} /> : null}
|
|
62
|
+
{catalog.loading && !catalog.data ? <p className="text-sm text-muted-foreground">Loading templates...</p> : null}
|
|
63
|
+
{!catalog.loading && !catalog.error && templates.length === 0 ? <EmptyState title="No templates for this list" description="The API has no ready-made pipeline for this kind of list." /> : null}
|
|
64
|
+
{add.error ? <ApiError error={add.error} title="Could not add the template" /> : null}
|
|
65
|
+
<ul className="flex flex-col gap-3">
|
|
66
|
+
{templates.map((t) => {
|
|
67
|
+
const needsQualification = t.requires_qualification && !hasQualification;
|
|
68
|
+
return (
|
|
69
|
+
<li key={t.key} className="rounded-md border p-3" data-testid="template">
|
|
70
|
+
<div className="flex items-start justify-between gap-3">
|
|
71
|
+
<div className="min-w-0">
|
|
72
|
+
<p className="text-sm font-medium">{t.name}</p>
|
|
73
|
+
<p className="text-xs text-muted-foreground">{t.description}</p>
|
|
74
|
+
<ol className="mt-2 flex flex-wrap gap-1" aria-label={`${t.name} steps`}>{t.step_names.map((n, i) => <li key={`${n}-${i}`}><StatusPill tone="neutral">{i + 1}. {n}</StatusPill></li>)}</ol>
|
|
75
|
+
{needsQualification ? <p className="mt-2 text-xs text-warning-foreground">Needs a qualification template, and this workspace has none yet.</p> : null}
|
|
76
|
+
{templateExists(t, existing) ? <p className="mt-2 text-xs text-muted-foreground">This list already has it. Adding it again resets its steps to the recipe.</p> : null}
|
|
77
|
+
</div>
|
|
78
|
+
<Button type="button" size="sm" variant="outline" disabled={needsQualification || working != null} loading={working === t.key} aria-label={`Add ${t.name}`} onClick={() => choose(t)}>{templateExists(t, existing) ? "Reset" : "Add"}</Button>
|
|
79
|
+
</div>
|
|
80
|
+
</li>
|
|
81
|
+
);
|
|
82
|
+
})}
|
|
83
|
+
</ul>
|
|
84
|
+
<ConfirmSpendDialog open={replacing != null} tier="destructive" title={`Reset "${replacing?.name ?? ""}" to the template`}
|
|
85
|
+
description="The pipeline's steps are replaced with the template's. Any edits you made to its steps are lost. Values it already wrote stay on the records."
|
|
86
|
+
affected={1} confirmLabel="Reset pipeline" confirming={add.loading}
|
|
87
|
+
onConfirm={() => replacing && void add.mutate(replacing)} onCancel={() => setReplacing(null)} />
|
|
88
|
+
</FormSheet>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
* providers, whatever name and providers were sent. The form then says so instead of claiming a new pipeline.
|
|
14
14
|
* 4. deleteWaterfallConfig DELETE /enrichment/waterfall/configs/{column_id} (destructive, ConfirmSpendDialog)
|
|
15
15
|
*
|
|
16
|
+
* The provider choices follow the chosen field (the API refuses a provider that cannot fill it with 400
|
|
17
|
+
* `unsupported_provider_for_field`): changing the field drops selected providers that no longer fit and says which.
|
|
18
|
+
*
|
|
16
19
|
* Providers on graph8's system key spend credits when the pipeline RUNS; providers on the org's own key (BYOK) do not.
|
|
17
20
|
* A BYOK provider without a key is listed under `missing_credentials` and is skipped at run time.
|
|
18
21
|
*/
|
|
@@ -27,7 +30,7 @@ import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
|
27
30
|
import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
28
31
|
import { plural } from "@/g8/lib/plural";
|
|
29
32
|
import { useProviders } from "../hooks/use-providers";
|
|
30
|
-
import { TARGET_FIELDS } from "../lib/enrichment-types";
|
|
33
|
+
import { FIELD_PROVIDERS, TARGET_FIELDS } from "../lib/enrichment-types";
|
|
31
34
|
import type { TargetField, WaterfallConfigSummary } from "../lib/enrichment-types";
|
|
32
35
|
import { useBrand } from "@/g8/hooks/use-brand";
|
|
33
36
|
|
|
@@ -54,6 +57,8 @@ export function WaterfallConfig({ listId, onChanged, onOpen, selectedId, onLoade
|
|
|
54
57
|
const [deleting, setDeleting] = useState<WaterfallConfigSummary | null>(null);
|
|
55
58
|
// The answer to the last create when the API reused the list's pipeline for that field instead of creating one.
|
|
56
59
|
const [reused, setReused] = useState<{ name?: string; field: TargetField; providers: string[]; asked: string[] } | null>(null);
|
|
60
|
+
// Providers dropped by the last field change, with the field they cannot fill, so the person sees why they went.
|
|
61
|
+
const [dropped, setDropped] = useState<{ providers: string[]; field: TargetField } | null>(null);
|
|
57
62
|
const catalog = useProviders();
|
|
58
63
|
|
|
59
64
|
const configs = useG8Query((signal) => g8Get<{ configs: WaterfallConfigSummary[] }>("/enrichment/waterfall/configs", { list_id: listId }, { signal }), [listId]);
|
|
@@ -76,6 +81,16 @@ export function WaterfallConfig({ listId, onChanged, onOpen, selectedId, onLoade
|
|
|
76
81
|
const loadedRef = useRef(onLoaded);
|
|
77
82
|
loadedRef.current = onLoaded;
|
|
78
83
|
useEffect(() => { if (configs.data) loadedRef.current?.(configs.data.data.configs ?? []); }, [configs.data]);
|
|
84
|
+
const fieldLabel = TARGET_FIELDS.find((f) => f.value === field)?.label ?? field;
|
|
85
|
+
const allowed = FIELD_PROVIDERS[field];
|
|
86
|
+
const unfit = order.filter((p) => !allowed.includes(p));
|
|
87
|
+
const changeField = (next: TargetField) => {
|
|
88
|
+
const gone = order.filter((p) => !FIELD_PROVIDERS[next].includes(p));
|
|
89
|
+
setField(next);
|
|
90
|
+
setOrder(order.filter((p) => FIELD_PROVIDERS[next].includes(p)));
|
|
91
|
+
setDropped(gone.length ? { providers: gone, field: next } : null);
|
|
92
|
+
setReused(null);
|
|
93
|
+
};
|
|
79
94
|
const byName = new Map(catalog.providers.map((p) => [p.provider, p]));
|
|
80
95
|
const systemUsed = order.filter((p) => byName.get(p)?.system_funded && !byName.get(p)?.byok_configured);
|
|
81
96
|
const move = (p: string, d: number) => {
|
|
@@ -102,7 +117,7 @@ export function WaterfallConfig({ listId, onChanged, onOpen, selectedId, onLoade
|
|
|
102
117
|
<Button variant="ghost" size="sm" onClick={() => setDeleting(c)}>Delete</Button>
|
|
103
118
|
</div>
|
|
104
119
|
<p className="text-xs text-muted-foreground">{c.field_to_enrich ?? "field"}: {c.providers.join(" > ") || "no providers"} ({plural(c.step_count, "step")})</p>
|
|
105
|
-
{c.missing_credentials.length ? <p role="alert" className="mt-1 text-xs text-
|
|
120
|
+
{c.missing_credentials.length ? <p role="alert" className="mt-1 text-xs text-warning-foreground">Skipped at run time, no key: {c.missing_credentials.join(", ")}</p> : null}
|
|
106
121
|
</li>
|
|
107
122
|
))}
|
|
108
123
|
</ul>
|
|
@@ -113,7 +128,7 @@ export function WaterfallConfig({ listId, onChanged, onOpen, selectedId, onLoade
|
|
|
113
128
|
<div className="flex flex-wrap items-end gap-3">
|
|
114
129
|
<div className="flex flex-col gap-1"><Label htmlFor="wf-name">Name</Label><Input id="wf-name" className="w-64" value={name} onChange={(e) => setName(e.target.value)} /></div>
|
|
115
130
|
<div className="flex flex-col gap-1"><Label htmlFor="wf-field">Fill</Label>
|
|
116
|
-
<select id="wf-field" className="h-9 rounded-md border bg-background px-2 text-sm" value={field} onChange={(e) =>
|
|
131
|
+
<select id="wf-field" className="h-9 rounded-md border bg-background px-2 text-sm" value={field} onChange={(e) => changeField(e.target.value as TargetField)}>
|
|
117
132
|
{TARGET_FIELDS.map((f) => <option key={f.value} value={f.value}>{f.label}</option>)}
|
|
118
133
|
</select>
|
|
119
134
|
</div>
|
|
@@ -130,9 +145,14 @@ export function WaterfallConfig({ listId, onChanged, onOpen, selectedId, onLoade
|
|
|
130
145
|
</li>
|
|
131
146
|
))}
|
|
132
147
|
</ol>
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
148
|
+
{dropped ? (
|
|
149
|
+
<p role="status" className="text-xs text-muted-foreground">
|
|
150
|
+
Removed {dropped.providers.join(", ")}: {dropped.providers.length === 1 ? "it cannot" : "they cannot"} find a {TARGET_FIELDS.find((f) => f.value === dropped.field)?.label.toLowerCase()}.
|
|
151
|
+
</p>
|
|
152
|
+
) : null}
|
|
153
|
+
<div role="group" className="flex flex-wrap gap-1" aria-label="Provider choices" title={`Providers that can find a ${fieldLabel.toLowerCase()}`}>
|
|
154
|
+
{catalog.providers.filter((p) => allowed.includes(p.provider) && !order.includes(p.provider)).map((p) => (
|
|
155
|
+
<Button key={p.provider} variant="outline" size="sm" onClick={() => { setDropped(null); setOrder([...order, p.provider]); }}>Add {p.provider}</Button>
|
|
136
156
|
))}
|
|
137
157
|
</div>
|
|
138
158
|
<label className="flex items-center gap-2 text-sm"><input type="checkbox" checked={skipExisting} onChange={(e) => setSkipExisting(e.target.checked)} /> Skip contacts that already have a value</label>
|
|
@@ -143,7 +163,7 @@ export function WaterfallConfig({ listId, onChanged, onOpen, selectedId, onLoade
|
|
|
143
163
|
{reused.providers.length && reused.providers.join() !== reused.asked.join() ? ` It keeps its providers: ${reused.providers.join(" > ")}. Delete it first to use ${reused.asked.join(" > ")}.` : ""}
|
|
144
164
|
</p>
|
|
145
165
|
) : null}
|
|
146
|
-
<div className="flex justify-end"><Button disabled={!name.trim() || order.length === 0} onClick={() => setConfirmCreate(true)}>Create pipeline</Button></div>
|
|
166
|
+
<div className="flex justify-end"><Button disabled={!name.trim() || order.length === 0 || unfit.length > 0} onClick={() => setConfirmCreate(true)}>Create pipeline</Button></div>
|
|
147
167
|
</section>
|
|
148
168
|
|
|
149
169
|
<ConfirmSpendDialog open={confirmCreate} tier="billable" title={`Create pipeline "${name.trim()}"`}
|
|
@@ -22,6 +22,45 @@ export const TARGET_FIELDS = [
|
|
|
22
22
|
] as const;
|
|
23
23
|
export type TargetField = (typeof TARGET_FIELDS)[number]["value"];
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Providers the API can run for each field: the per-field waterfall templates the server builds pipelines from
|
|
27
|
+
* (a phone runs leadmagic `mobile_finder`, never an email finder). Any other provider is refused with 400
|
|
28
|
+
* `unsupported_provider_for_field`, so the UI only offers these.
|
|
29
|
+
*/
|
|
30
|
+
export const FIELD_PROVIDERS: Record<TargetField, readonly string[]> = {
|
|
31
|
+
work_email: ["apollo", "leadmagic", "prospeo", "hunter", "icypeas", "dropcontact"],
|
|
32
|
+
direct_phone: ["leadmagic", "prospeo", "apollo"],
|
|
33
|
+
mobile_phone: ["leadmagic", "prospeo", "apollo"],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Providers a run tries for a field when nobody chose: the API's own defaults. */
|
|
37
|
+
export const FIELD_DEFAULT_PROVIDERS: Record<TargetField, readonly string[]> = {
|
|
38
|
+
work_email: ["prospeo", "dropcontact"],
|
|
39
|
+
direct_phone: ["leadmagic", "prospeo", "apollo"],
|
|
40
|
+
mobile_phone: ["leadmagic", "prospeo", "apollo"],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The providers to start from for `field`: `preferred` (in its order) kept to those the field supports, else the
|
|
45
|
+
* field's API defaults when none of `preferred` fits (for example email providers asked for a phone).
|
|
46
|
+
*/
|
|
47
|
+
export function providersForField(field: TargetField, preferred?: readonly string[]): string[] {
|
|
48
|
+
const allowed = FIELD_PROVIDERS[field];
|
|
49
|
+
const kept = (preferred ?? FIELD_DEFAULT_PROVIDERS[field]).filter((p) => allowed.includes(p));
|
|
50
|
+
return kept.length ? [...new Set(kept)] : [...FIELD_DEFAULT_PROVIDERS[field]];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** What POST /enrichment/waterfall/configs answers (created, or the list's existing pipeline for that field reused). */
|
|
54
|
+
export interface WaterfallPipelineResult {
|
|
55
|
+
column_id: string;
|
|
56
|
+
config_id?: string;
|
|
57
|
+
name?: string;
|
|
58
|
+
list_id?: number | null;
|
|
59
|
+
created: boolean;
|
|
60
|
+
providers?: string[];
|
|
61
|
+
missing_credentials?: string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
25
64
|
export interface ProviderEntry {
|
|
26
65
|
provider: string;
|
|
27
66
|
system_funded: boolean;
|
|
@@ -65,6 +104,7 @@ export interface EnrichJob {
|
|
|
65
104
|
successful_enrichments?: number | null;
|
|
66
105
|
failed_enrichments?: number | null;
|
|
67
106
|
total_credits_used?: number | null;
|
|
107
|
+
paused_at?: string | null;
|
|
68
108
|
warnings?: string[] | null;
|
|
69
109
|
provider_errors?: Array<{ record_id?: number; provider?: string; error?: string }> | null;
|
|
70
110
|
}
|
|
@@ -90,6 +130,17 @@ export function isJobTerminal(status: string | undefined | null): boolean {
|
|
|
90
130
|
}
|
|
91
131
|
|
|
92
132
|
/**
|
|
133
|
+
* The API's own quote for a run (`max_total_credits` from the preflight), or null when it could not price it. Null means
|
|
134
|
+
* "no total": callers show that, never a made-up number.
|
|
135
|
+
*/
|
|
136
|
+
export function quotedCredits(preflight: Pick<Preflight, "max_total_credits"> | undefined): number | null {
|
|
137
|
+
return preflight?.max_total_credits ?? null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @deprecated Guesses 2 credits per record when the preflight cannot price a run, which is an invented price. The bulk panel
|
|
142
|
+
* no longer uses it (see `quotedCredits`); kept exported so existing imports keep working.
|
|
143
|
+
*
|
|
93
144
|
* What to show as the cost of a run: the API's own quote when the preflight priced it, otherwise the flat
|
|
94
145
|
* per-record estimate times the contacts that are really on the list. Never a balance.
|
|
95
146
|
*/
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shapes and pure helpers for list pipelines, auto-enrichment and the enrichment activity view.
|
|
5
|
+
*
|
|
6
|
+
* Everything here is read off the API's own responses (enrichment/interfaces/fastapi/list_pipelines_router.py,
|
|
7
|
+
* auto_enrichment_router.py, router.py). The contract types these payloads as open objects, so the shapes below are the
|
|
8
|
+
* handlers' documented fields; a field the API leaves out is `undefined`/`null` here and the UI shows a dash, never a
|
|
9
|
+
* guessed default.
|
|
10
|
+
*
|
|
11
|
+
* No prices live here: the cost of a pipeline run is whatever `POST .../estimate` answers for that exact list and
|
|
12
|
+
* pipeline (`required_credits`, an upper bound that ignores run conditions).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** The step kinds a saved pipeline step can have. */
|
|
16
|
+
export type StepType = "ai" | "waterfall";
|
|
17
|
+
|
|
18
|
+
export interface PipelineStep {
|
|
19
|
+
id: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
type: StepType;
|
|
22
|
+
config_ref: string;
|
|
23
|
+
run_condition?: string | null;
|
|
24
|
+
skip_existing_values?: boolean;
|
|
25
|
+
skip_recently_enriched?: boolean;
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
ui_meta?: Record<string, unknown> | null;
|
|
28
|
+
[extra: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface StepProgress {
|
|
32
|
+
step_index: number;
|
|
33
|
+
step_id?: string | null;
|
|
34
|
+
name?: string | null;
|
|
35
|
+
job_id?: string | null;
|
|
36
|
+
status?: string | null;
|
|
37
|
+
total_records?: number;
|
|
38
|
+
processed_records?: number;
|
|
39
|
+
successful?: number;
|
|
40
|
+
failed?: number;
|
|
41
|
+
skipped_by_reason?: Record<string, number>;
|
|
42
|
+
note?: string | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface PipelineRun {
|
|
46
|
+
run_id: string;
|
|
47
|
+
status: string;
|
|
48
|
+
current_index: number;
|
|
49
|
+
total_steps: number;
|
|
50
|
+
started_at?: string | null;
|
|
51
|
+
error?: string | null;
|
|
52
|
+
cancelled_by?: string | null;
|
|
53
|
+
step_progress?: StepProgress[];
|
|
54
|
+
/** Present on a run read by id (GET .../pipeline-runs/{run_id}). */
|
|
55
|
+
pipeline_id?: string;
|
|
56
|
+
pipeline_name?: string;
|
|
57
|
+
list_id?: number;
|
|
58
|
+
current_job_id?: string | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ListPipeline {
|
|
62
|
+
id: string;
|
|
63
|
+
list_id: number;
|
|
64
|
+
name: string;
|
|
65
|
+
steps: PipelineStep[];
|
|
66
|
+
enabled: boolean;
|
|
67
|
+
created_at?: string | null;
|
|
68
|
+
last_run?: PipelineRun | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface PipelineEstimate {
|
|
72
|
+
can_run: boolean;
|
|
73
|
+
subscription_valid?: boolean;
|
|
74
|
+
estimated_evaluations: number;
|
|
75
|
+
required_credits: number;
|
|
76
|
+
available_credits: number;
|
|
77
|
+
reason?: string | null;
|
|
78
|
+
steps: Array<{ step_id: string; name: string; type: string; estimated_records: number; required_credits: number }>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface StepOption {
|
|
82
|
+
type: StepType;
|
|
83
|
+
config_ref: string;
|
|
84
|
+
name: string;
|
|
85
|
+
is_qualification?: boolean;
|
|
86
|
+
qualification_entity?: string | null;
|
|
87
|
+
uses_existing_field?: boolean;
|
|
88
|
+
target_column_name?: string | null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface PipelineTemplate {
|
|
92
|
+
key: string;
|
|
93
|
+
name: string;
|
|
94
|
+
description: string;
|
|
95
|
+
target_type: string;
|
|
96
|
+
requires_qualification: boolean;
|
|
97
|
+
step_names: string[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface AutoConfig {
|
|
101
|
+
id: string;
|
|
102
|
+
audience_id: number;
|
|
103
|
+
enrichment_type: StepType;
|
|
104
|
+
column_id: string;
|
|
105
|
+
trigger_on_insert: boolean;
|
|
106
|
+
created_at?: string | null;
|
|
107
|
+
created_by?: string | null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const MERGE_RULES = ["sticky", "user_wins", "mashup_wins"] as const;
|
|
111
|
+
export type MergeRule = (typeof MERGE_RULES)[number];
|
|
112
|
+
|
|
113
|
+
/** What each merge rule does, from enrichment/usecases/apply_mashup_merge/handler.py. */
|
|
114
|
+
export const MERGE_RULE_COPY: Record<MergeRule, { label: string; help: string }> = {
|
|
115
|
+
sticky: { label: "Keep edits, fill the rest", help: "Fields a person edited are left alone; every other field takes the matched value." },
|
|
116
|
+
user_wins: { label: "Never overwrite", help: "No matched value is copied onto the record." },
|
|
117
|
+
mashup_wins: { label: "Matched data wins", help: "Every field takes the matched value, including fields a person edited." },
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export interface AutoSettings {
|
|
121
|
+
entity_type: "contact" | "company";
|
|
122
|
+
enabled: boolean;
|
|
123
|
+
default_rule: MergeRule;
|
|
124
|
+
per_field_overrides: Record<string, MergeRule>;
|
|
125
|
+
sync_policy_version?: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface JobControlResult {
|
|
129
|
+
job_id?: string;
|
|
130
|
+
success?: boolean;
|
|
131
|
+
message?: string;
|
|
132
|
+
previous_status?: string;
|
|
133
|
+
paused_at?: string;
|
|
134
|
+
resumed_at?: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The analytics windows the API accepts (anything else is a 422). */
|
|
138
|
+
export const PERIODS = [
|
|
139
|
+
{ value: "last_7_days", label: "Last 7 days" },
|
|
140
|
+
{ value: "last_30_days", label: "Last 30 days" },
|
|
141
|
+
{ value: "last_90_days", label: "Last 90 days" },
|
|
142
|
+
{ value: "current_month", label: "This month" },
|
|
143
|
+
{ value: "last_month", label: "Last month" },
|
|
144
|
+
] as const;
|
|
145
|
+
export type Period = (typeof PERIODS)[number]["value"];
|
|
146
|
+
|
|
147
|
+
export const GRANULARITIES = ["day", "week", "month"] as const;
|
|
148
|
+
export type Granularity = (typeof GRANULARITIES)[number];
|
|
149
|
+
|
|
150
|
+
/** Rows of an open `{ items }` payload, or of a bare array; anything else is an empty list. */
|
|
151
|
+
export function itemsOf<T>(data: unknown, key = "items"): T[] {
|
|
152
|
+
if (Array.isArray(data)) return data as T[];
|
|
153
|
+
const v = (data as Record<string, unknown> | null | undefined)?.[key];
|
|
154
|
+
return Array.isArray(v) ? (v as T[]) : [];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The only states a run never leaves (the app's useListPipelines treats everything else as in progress). */
|
|
158
|
+
export const RUN_TERMINAL = ["completed", "failed", "cancelled"] as const;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* A run that can still change: poll it, offer Stop, block another Run. The backend creates every run as "pending", so
|
|
162
|
+
* "active" is "has a status and it is not terminal", never "equals running": a second run priced and confirmed while the
|
|
163
|
+
* first is pending would charge twice.
|
|
164
|
+
*/
|
|
165
|
+
export function runIsActive(status: string | null | undefined): boolean {
|
|
166
|
+
const s = String(status ?? "").toLowerCase();
|
|
167
|
+
return s !== "" && !(RUN_TERMINAL as readonly string[]).includes(s);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** The pipeline's steps that will run (a disabled step is skipped). */
|
|
171
|
+
export function enabledSteps(p: Pick<ListPipeline, "steps">): PipelineStep[] {
|
|
172
|
+
return p.steps.filter((s) => s.enabled !== false);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** A unique, stable step id (1 to 64 characters) for a step added in the editor. */
|
|
176
|
+
export function newStepId(taken: readonly string[]): string {
|
|
177
|
+
let n = taken.length + 1;
|
|
178
|
+
while (taken.includes(`step-${n}`)) n += 1;
|
|
179
|
+
return `step-${n}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** A step for a picked config. Defaults mirror the API's own: fill only empty values, skip recently enriched records. */
|
|
183
|
+
export function stepFromOption(option: StepOption, taken: readonly string[]): PipelineStep {
|
|
184
|
+
return { id: newStepId(taken), name: option.name, type: option.type, config_ref: option.config_ref, skip_existing_values: true, skip_recently_enriched: true, enabled: true };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Move the item at `index` by `delta` (clamped), returning a new array. */
|
|
188
|
+
export function moveItem<T>(items: readonly T[], index: number, delta: number): T[] {
|
|
189
|
+
const to = index + delta;
|
|
190
|
+
if (to < 0 || to >= items.length) return [...items];
|
|
191
|
+
const next = [...items];
|
|
192
|
+
[next[index], next[to]] = [next[to], next[index]];
|
|
193
|
+
return next;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** The body of a save: the name and the FULL step list (the API replaces steps), keeping every field of an edited step. */
|
|
197
|
+
export function savePayload(name: string, steps: readonly PipelineStep[]): { name: string; steps: PipelineStep[] } {
|
|
198
|
+
return { name: name.trim(), steps: steps.map((s) => ({ ...s })) };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Did the editor change anything the API would store? */
|
|
202
|
+
export function pipelineChanged(original: Pick<ListPipeline, "name" | "steps">, name: string, steps: readonly PipelineStep[]): boolean {
|
|
203
|
+
return original.name !== name.trim() || JSON.stringify(original.steps) !== JSON.stringify(steps);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The label of a step: its own name, else the name of the config it points at, else its kind. Never the raw id. */
|
|
207
|
+
export function stepLabel(step: PipelineStep, options: readonly StepOption[]): string {
|
|
208
|
+
const hit = options.find((o) => o.config_ref === step.config_ref && o.type === step.type);
|
|
209
|
+
return step.name?.trim() || hit?.name || (step.type === "ai" ? "AI step" : "Provider waterfall step");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** `n / total` as a whole percent, or null when there is nothing to divide by (shown as a dash, never 0%). */
|
|
213
|
+
export function percentOf(part: number | null | undefined, total: number | null | undefined): number | null {
|
|
214
|
+
if (part == null || !total) return null;
|
|
215
|
+
return Math.min(100, Math.round((part / total) * 100));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** The reason a run reads as "skipped" in plain words. */
|
|
219
|
+
export function skipReasonLabel(reason: string): string {
|
|
220
|
+
if (reason === "run_condition_not_met") return "skipped by the step condition";
|
|
221
|
+
return reason.replace(/_/g, " ");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** True when the pipeline named like this template already exists on the list (re-adding it replaces that pipeline's steps). */
|
|
225
|
+
export function templateExists(template: Pick<PipelineTemplate, "name">, pipelines: readonly Pick<ListPipeline, "name">[]): boolean {
|
|
226
|
+
return pipelines.some((p) => p.name === template.name);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** The most useful single sentence about why a pipeline cannot be run right now, or null when it can. */
|
|
230
|
+
export function runBlockedReason(p: Pick<ListPipeline, "steps" | "last_run">): string | null {
|
|
231
|
+
if (enabledSteps(p).length === 0) return "Every step is turned off, so there is nothing to run.";
|
|
232
|
+
if (runIsActive(p.last_run?.status)) return "A run is already in progress.";
|
|
233
|
+
return null;
|
|
234
|
+
}
|