@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
|
@@ -7,14 +7,19 @@
|
|
|
7
7
|
* - Look up: find one person or company by email, LinkedIn, name and domain (2 credits only when found) and verify
|
|
8
8
|
* an email address (1 credit). Nothing is written to a record without a tick.
|
|
9
9
|
* - Pipelines: the saved provider waterfalls of a list, create and delete them.
|
|
10
|
+
* - List pipelines: ordered chains of steps run over a whole list (templates, build, price, run, watch, stop, edit, delete).
|
|
11
|
+
* - Auto-enrichment: what a list enriches by itself as records are added, and the workspace-wide switch and merge rule.
|
|
12
|
+
* - Jobs: running and recent jobs with live progress; pause, resume or cancel one.
|
|
13
|
+
* - Activity: how much ran, how often it worked, which fields it filled.
|
|
10
14
|
* - Providers: which providers graph8 funds, which need your own key, and what each action costs.
|
|
11
15
|
*
|
|
12
16
|
* Composes: ScreenLayout, StatStrip, SplitView, DetailPanel, SectionCard, KeyValueList, StatusPill (screen kit),
|
|
13
|
-
* BulkEnrichPanel, WaterfallConfig,
|
|
17
|
+
* BulkEnrichPanel, WaterfallConfig, ListPipelines, AutoEnrichmentConfigs, AutoEnrichmentSettings, EnrichmentActivity, EnrichButton,
|
|
18
|
+
* EmailVerifyBadge, ContactTable / ContactDetail (crm), ListPicker (lists). Deep link from a list: `?tab=list-pipelines&list=<list id>`.
|
|
14
19
|
* Provider keys are added in graph8 settings (no API), so the Providers tab links out for them.
|
|
15
20
|
*
|
|
16
21
|
* SDK operations (inside the parts it composes): listEnrichmentProviders, preflightEnrichment (read),
|
|
17
|
-
*
|
|
22
|
+
* createDefaultWaterfallConfig + runWaterfallPipeline (billable, ConfirmSpendDialog), getEnrichmentJob, cancelEnrichmentJob, lookupPerson / lookupCompany
|
|
18
23
|
* (billable, confirmed), verifyEmail (billable, confirmed), listWaterfallConfigs, createDefaultWaterfallConfig (billable,
|
|
19
24
|
* confirmed), deleteWaterfallConfig (destructive, confirmed), listContacts, getContact.
|
|
20
25
|
*/
|
|
@@ -31,7 +36,13 @@ import { SplitView } from "@/g8/components/screen/split-view";
|
|
|
31
36
|
import { ScreenLayout } from "@/g8/components/screen/screen-layout";
|
|
32
37
|
import { SectionCard } from "@/g8/components/screen/section-card";
|
|
33
38
|
import { StatusPill } from "@/g8/components/screen/status-pill";
|
|
39
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
34
40
|
import { useUrlState } from "@/g8/hooks/use-url-state";
|
|
41
|
+
import { g8Get } from "@/g8/lib/g8-client";
|
|
42
|
+
import { CompanyPicker } from "@/g8/pickers/components/company-picker";
|
|
43
|
+
import type { CompanyOption } from "@/g8/pickers/components/company-picker";
|
|
44
|
+
import { ContactPicker } from "@/g8/pickers/components/contact-picker";
|
|
45
|
+
import type { ContactOption } from "@/g8/pickers/components/contact-picker";
|
|
35
46
|
import { ContactDetail } from "@/g8/crm/components/contact-detail";
|
|
36
47
|
import { ContactTable } from "@/g8/crm/components/contact-table";
|
|
37
48
|
import type { ContactRecord } from "@/g8/crm/lib/crm-types";
|
|
@@ -42,7 +53,11 @@ import { CREDITS_PER_EMAIL_VERIFICATION, CREDITS_PER_FOUND_LOOKUP } from "../lib
|
|
|
42
53
|
import { BulkEnrichPanel } from "./bulk-enrich-panel";
|
|
43
54
|
import { EmailVerifyBadge } from "./email-verify-badge";
|
|
44
55
|
import { EnrichButton } from "./enrich-button";
|
|
56
|
+
import { AutoEnrichmentConfigs } from "./auto-enrichment-configs";
|
|
57
|
+
import { AutoEnrichmentSettings } from "./auto-enrichment-settings";
|
|
58
|
+
import { EnrichmentActivity } from "./enrichment-activity";
|
|
45
59
|
import { EnrichmentJobDetail } from "./enrichment-job-detail";
|
|
60
|
+
import { ListPipelines } from "./list-pipelines";
|
|
46
61
|
import { WaterfallConfig } from "./waterfall-config";
|
|
47
62
|
import type { WaterfallConfigSummary } from "../lib/enrichment-types";
|
|
48
63
|
import { useBrand, brandUrl } from "@/g8/hooks/use-brand";
|
|
@@ -57,7 +72,7 @@ export interface EnrichmentScreenProps {
|
|
|
57
72
|
className?: string;
|
|
58
73
|
}
|
|
59
74
|
|
|
60
|
-
const TABS = [{ id: "list", label: "Enrich a list" }, { id: "lookup", label: "Look up" }, { id: "pipelines", label: "Pipelines" }, { id: "jobs", label: "Jobs" }, { id: "providers", label: "Providers" }];
|
|
75
|
+
const TABS = [{ id: "list", label: "Enrich a list" }, { id: "lookup", label: "Look up" }, { id: "pipelines", label: "Pipelines" }, { id: "list-pipelines", label: "List pipelines" }, { id: "auto", label: "Auto-enrichment" }, { id: "jobs", label: "Jobs" }, { id: "activity", label: "Activity" }, { id: "providers", label: "Providers" }];
|
|
61
76
|
const FIELD = "flex flex-col gap-1";
|
|
62
77
|
|
|
63
78
|
|
|
@@ -75,7 +90,7 @@ function PipelineDetail({ config, onClose, onExpand, onCollapse }: { config: Wat
|
|
|
75
90
|
<DetailPanel onExpand={onExpand} onCollapse={onCollapse} title={config.name} subtitle={`Fills ${config.field_to_enrich ?? "a field"} in ${plural(config.step_count, "step")}`} onClose={onClose}
|
|
76
91
|
badges={config.is_active === false ? <StatusPill tone="neutral" dot>Inactive</StatusPill> : <StatusPill tone="positive" dot>Active</StatusPill>}>
|
|
77
92
|
<div className="flex flex-col gap-4 p-1">
|
|
78
|
-
{config.missing_credentials.length ? <p role="alert" className="text-sm text-
|
|
93
|
+
{config.missing_credentials.length ? <p role="alert" className="text-sm text-warning-foreground">No key for {config.missing_credentials.join(", ")}: those steps are skipped when it runs. Add the keys in {brand.linkLabel} settings.</p> : null}
|
|
79
94
|
<ol className="flex flex-col gap-2" aria-label="Pipeline steps">
|
|
80
95
|
{(config.providers_detail.length ? config.providers_detail : config.providers.map((p) => ({ provider: p, is_system_provider: false, credential_source: "system" as const, credential_ready: true }))).map((p, i) => (
|
|
81
96
|
<li key={`${p.provider}-${i}`} className="flex items-center gap-2 text-sm">
|
|
@@ -94,18 +109,42 @@ function PipelineDetail({ config, onClose, onExpand, onCollapse }: { config: Wat
|
|
|
94
109
|
);
|
|
95
110
|
}
|
|
96
111
|
|
|
112
|
+
interface ActiveJob { job_id: string; status?: string | null; column_id?: string | null; enrichment_type?: string | null; completed?: number | null; total?: number | null; failed?: number | null; paused_at?: string | null }
|
|
113
|
+
|
|
114
|
+
/** Jobs running, paused or pending in the last six hours, org-wide (GET /enrichment/active-jobs, read, free): no job id to remember. */
|
|
115
|
+
function ActiveJobs({ jobParam, onOpen }: { jobParam: string; onOpen: (id: string) => void }) {
|
|
116
|
+
const q = useG8Query(async (signal) => {
|
|
117
|
+
const d = (await g8Get("/enrichment/active-jobs", undefined, { signal })).data as { jobs?: ActiveJob[] } | ActiveJob[] | undefined;
|
|
118
|
+
return (Array.isArray(d) ? d : (d?.jobs ?? [])).filter((j) => j?.job_id);
|
|
119
|
+
}, []);
|
|
120
|
+
const rows = q.data ?? [];
|
|
121
|
+
return (
|
|
122
|
+
<SectionCard title="Running now" description="Jobs from the last 6 hours, from anyone on your team." flush actions={<Button size="sm" variant="ghost" onClick={() => void q.refetch()}>Refresh</Button>}>
|
|
123
|
+
{q.error ? <div className="p-3"><ApiError error={q.error} title="Could not load running jobs" onRetry={() => void q.refetch()} /></div> : null}
|
|
124
|
+
{!q.error && q.loading && rows.length === 0 ? <p className="p-4 text-sm text-muted-foreground">Loading jobs...</p> : null}
|
|
125
|
+
{!q.error && !q.loading && rows.length === 0 ? <EmptyState title="Nothing running" description="Jobs that are running, paused or pending show here." /> : null}
|
|
126
|
+
<ul className="divide-y">{rows.map((j) => (
|
|
127
|
+
<li key={j.job_id}>
|
|
128
|
+
<button type="button" aria-label={`Open job ${j.column_id ?? j.enrichment_type ?? j.job_id}`} className={`flex w-full items-center justify-between gap-2 px-4 py-2 text-left text-sm ${j.job_id === jobParam ? "bg-muted/50" : ""}`} onClick={() => onOpen(j.job_id)}>
|
|
129
|
+
<span className="truncate">{j.column_id ?? j.enrichment_type ?? "Enrichment"}</span>
|
|
130
|
+
<span className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
131
|
+
<span className="tabular-nums">{j.completed ?? 0}/{j.total ?? 0}</span>
|
|
132
|
+
<StatusPill tone={j.status === "running" ? "positive" : "neutral"} dot>{j.status ?? "unknown"}</StatusPill>
|
|
133
|
+
</span>
|
|
134
|
+
</button>
|
|
135
|
+
</li>
|
|
136
|
+
))}</ul>
|
|
137
|
+
</SectionCard>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
97
141
|
function JobsTab({ jobs, jobParam, setJobParam }: { jobs: string[]; jobParam: string; setJobParam: (v: string | null) => void }) {
|
|
98
142
|
const [typed, setTyped] = useState("");
|
|
99
143
|
const [view, setView] = useUrlState("view");
|
|
100
144
|
const full = view === "full";
|
|
101
145
|
const list = (
|
|
102
146
|
<div className="flex flex-col gap-3 p-1">
|
|
103
|
-
<
|
|
104
|
-
<form className="flex gap-2" onSubmit={(e) => { e.preventDefault(); if (typed.trim()) setJobParam(typed.trim()); }}>
|
|
105
|
-
<Input aria-label="Job id" value={typed} onChange={(e) => setTyped(e.target.value)} placeholder="Job id" />
|
|
106
|
-
<Button type="submit" size="sm" variant="outline" disabled={!typed.trim()}>Open</Button>
|
|
107
|
-
</form>
|
|
108
|
-
</SectionCard>
|
|
147
|
+
<ActiveJobs jobParam={jobParam} onOpen={setJobParam} />
|
|
109
148
|
<SectionCard title="Started in this session" flush>
|
|
110
149
|
{jobs.length === 0 ? <EmptyState title="No jobs yet" description="Enrich contacts and the job appears here, with live progress." /> : (
|
|
111
150
|
<ul className="divide-y">{jobs.map((id) => (
|
|
@@ -113,6 +152,13 @@ function JobsTab({ jobs, jobParam, setJobParam }: { jobs: string[]; jobParam: st
|
|
|
113
152
|
))}</ul>
|
|
114
153
|
)}
|
|
115
154
|
</SectionCard>
|
|
155
|
+
<details className="px-1 text-sm">
|
|
156
|
+
<summary className="cursor-pointer text-muted-foreground">Open an older job by id</summary>
|
|
157
|
+
<form className="mt-2 flex gap-2" onSubmit={(e) => { e.preventDefault(); if (typed.trim()) setJobParam(typed.trim()); }}>
|
|
158
|
+
<Input aria-label="Job id" value={typed} onChange={(e) => setTyped(e.target.value)} placeholder="Job id" />
|
|
159
|
+
<Button type="submit" size="sm" variant="outline" disabled={!typed.trim()}>Open</Button>
|
|
160
|
+
</form>
|
|
161
|
+
</details>
|
|
116
162
|
</div>
|
|
117
163
|
);
|
|
118
164
|
return (
|
|
@@ -138,8 +184,8 @@ function LookupTab() {
|
|
|
138
184
|
const [cDomain, setCDomain] = useState("");
|
|
139
185
|
const [cName, setCName] = useState("");
|
|
140
186
|
const [verifyEmail, setVerifyEmail] = useState("");
|
|
141
|
-
const [
|
|
142
|
-
const [
|
|
187
|
+
const [contactPick, setContactPick] = useState<ContactOption | null>(null);
|
|
188
|
+
const [companyPick, setCompanyPick] = useState<CompanyOption | null>(null);
|
|
143
189
|
const [applied, setApplied] = useState<string | null>(null);
|
|
144
190
|
const person = { email: email.trim() || undefined, linkedin_url: linkedin.trim() || undefined, first_name: first.trim() || undefined, last_name: last.trim() || undefined, company_domain: domain.trim() || undefined };
|
|
145
191
|
const company = { domain: cDomain.trim() || undefined, name: cName.trim() || undefined };
|
|
@@ -155,9 +201,15 @@ function LookupTab() {
|
|
|
155
201
|
</div>
|
|
156
202
|
<div className={FIELD}><Label htmlFor="lk-domain">Company domain</Label><Input id="lk-domain" placeholder="acme.com" value={domain} onChange={(e) => setDomain(e.target.value)} /></div>
|
|
157
203
|
<p className="text-xs text-muted-foreground">Use an email, a LinkedIn URL, or first name, last name and company domain together.</p>
|
|
158
|
-
<div className={FIELD}
|
|
204
|
+
<div className={FIELD}>
|
|
205
|
+
<Label htmlFor="lk-rec">Save the result on a contact (optional)</Label>
|
|
206
|
+
<div className="flex items-center gap-2">
|
|
207
|
+
<ContactPicker id="lk-rec" label="Contact to update" placeholder="Choose a contact" value={contactPick} onChange={setContactPick} />
|
|
208
|
+
{contactPick ? <Button size="sm" variant="ghost" onClick={() => setContactPick(null)}>Clear</Button> : null}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
159
211
|
<div className="flex items-center gap-2">
|
|
160
|
-
<EnrichButton kind="person" lookup={person} record={
|
|
212
|
+
<EnrichButton kind="person" lookup={person} record={contactPick ? { id: contactPick.id } : undefined} label="Find person" variant="default" size="default" onApplied={() => setApplied(`Updated ${contactPick?.name ?? "the contact"}.`)} />
|
|
161
213
|
{applied ? <span role="status" className="text-sm">{applied}</span> : null}
|
|
162
214
|
</div>
|
|
163
215
|
</div>
|
|
@@ -167,8 +219,14 @@ function LookupTab() {
|
|
|
167
219
|
<div className="flex flex-col gap-3">
|
|
168
220
|
<div className={FIELD}><Label htmlFor="lk-cdomain">Domain</Label><Input id="lk-cdomain" placeholder="acme.com" value={cDomain} onChange={(e) => setCDomain(e.target.value)} /></div>
|
|
169
221
|
<div className={FIELD}><Label htmlFor="lk-cname">Name</Label><Input id="lk-cname" value={cName} onChange={(e) => setCName(e.target.value)} /></div>
|
|
170
|
-
<div className={FIELD}
|
|
171
|
-
|
|
222
|
+
<div className={FIELD}>
|
|
223
|
+
<Label htmlFor="lk-crec">Save the result on a company (optional)</Label>
|
|
224
|
+
<div className="flex items-center gap-2">
|
|
225
|
+
<CompanyPicker id="lk-crec" label="Company to update" placeholder="Choose a company" value={companyPick} onChange={setCompanyPick} />
|
|
226
|
+
{companyPick ? <Button size="sm" variant="ghost" onClick={() => setCompanyPick(null)}>Clear</Button> : null}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
<div><EnrichButton kind="company" lookup={company} record={companyPick ? { id: companyPick.id } : undefined} label="Find company" variant="default" size="default" /></div>
|
|
172
230
|
</div>
|
|
173
231
|
</SectionCard>
|
|
174
232
|
<SectionCard title="Verify an email" description={`${CREDITS_PER_EMAIL_VERIFICATION} credit per check.`}>
|
|
@@ -210,17 +268,22 @@ export function EnrichmentScreen({ toolbarActions, renderDetailActions, extraDet
|
|
|
210
268
|
const [tab, setTab] = useUrlState("tab", "list");
|
|
211
269
|
const [listParam, setListParam] = useUrlState("list");
|
|
212
270
|
const [picked, setPicked] = useState<number[]>([]);
|
|
271
|
+
// A finished run reloads the contact table (tableVersion) so the filled values show; the reload clears the ticks,
|
|
272
|
+
// so `runShown` keeps the panel and its result on screen until the person ticks contacts again.
|
|
273
|
+
const [tableVersion, setTableVersion] = useState(0);
|
|
274
|
+
const [runShown, setRunShown] = useState(false);
|
|
213
275
|
const [opened, setOpened] = useState<ContactRecord | null>(null);
|
|
214
276
|
const catalog = useProviders(tab === "providers");
|
|
215
277
|
const [jobParam, setJobParam] = useUrlState("job");
|
|
216
278
|
const [pipelineParam, setPipelineParam] = useUrlState("pipeline");
|
|
279
|
+
const [, setListPipelineParam] = useUrlState("lp");
|
|
217
280
|
const [view, setView] = useUrlState("view");
|
|
218
281
|
const full = view === "full";
|
|
219
282
|
const [jobs, setJobs] = useState<string[]>([]);
|
|
220
283
|
useEffect(() => setJobs(loadJobs()), []);
|
|
221
284
|
const started = (id: string) => setJobs((cur) => { const next = [id, ...cur.filter((x) => x !== id)]; rememberJobs(next); return next; });
|
|
222
285
|
const listId = listParam ? Number(listParam) : null;
|
|
223
|
-
const pick = (id: number) => { setListParam(String(id)); setPicked([]); setOpened(null); setPipelineParam(null); };
|
|
286
|
+
const pick = (id: number) => { setListParam(String(id)); setPicked([]); setRunShown(false); setOpened(null); setPipelineParam(null); setListPipelineParam(null); };
|
|
224
287
|
|
|
225
288
|
const stats = tab === "providers" && catalog.providers.length > 0 ? [
|
|
226
289
|
{ label: "Providers", value: catalog.providers.length },
|
|
@@ -234,7 +297,28 @@ export function EnrichmentScreen({ toolbarActions, renderDetailActions, extraDet
|
|
|
234
297
|
const openPipeline = (loaded.listId === listId ? loaded.configs : []).find((c) => c.column_id === pipelineParam);
|
|
235
298
|
if (tab === "lookup") body = <LookupTab />;
|
|
236
299
|
else if (tab === "jobs") body = <JobsTab jobs={jobs} jobParam={jobParam} setJobParam={setJobParam} />;
|
|
300
|
+
else if (tab === "activity") body = <EnrichmentActivity />;
|
|
237
301
|
else if (tab === "providers") body = <ProvidersTab settingsUrl={providerSettingsUrl} />;
|
|
302
|
+
else if (tab === "list-pipelines" || tab === "auto") {
|
|
303
|
+
body = (
|
|
304
|
+
<div className="grid gap-4 lg:grid-cols-[18rem_minmax(0,1fr)]">
|
|
305
|
+
<ListRail listId={listId} onPick={pick} />
|
|
306
|
+
{listId == null ? (
|
|
307
|
+
<EmptyState icon={<Sparkles className="size-6" aria-hidden />} title="Choose a list" description={tab === "auto" ? "Automatic enrichment belongs to a list. Pick one to see what it runs on new records." : "Pipelines belong to a list. Pick one to see, build and run them."} />
|
|
308
|
+
) : tab === "auto" ? (
|
|
309
|
+
<div className="flex min-w-0 flex-col gap-6">
|
|
310
|
+
<AutoEnrichmentConfigs key={listId} listId={listId} />
|
|
311
|
+
<section aria-label="Workspace settings" className="flex flex-col gap-2">
|
|
312
|
+
<h3 className="text-sm font-medium">Workspace-wide settings</h3>
|
|
313
|
+
<AutoEnrichmentSettings />
|
|
314
|
+
</section>
|
|
315
|
+
</div>
|
|
316
|
+
) : (
|
|
317
|
+
<div className="min-w-0"><ListPipelines key={listId} listId={listId} /></div>
|
|
318
|
+
)}
|
|
319
|
+
</div>
|
|
320
|
+
);
|
|
321
|
+
}
|
|
238
322
|
else {
|
|
239
323
|
body = (
|
|
240
324
|
<div className="grid gap-4 lg:grid-cols-[18rem_minmax(0,1fr)]">
|
|
@@ -248,10 +332,10 @@ export function EnrichmentScreen({ toolbarActions, renderDetailActions, extraDet
|
|
|
248
332
|
detail={openPipeline ? <PipelineDetail config={openPipeline} onClose={() => setPipelineParam(null)} onExpand={full ? undefined : () => setView("full")} onCollapse={full ? () => setView(null) : undefined} /> : null} />
|
|
249
333
|
) : (
|
|
250
334
|
<div className="grid min-w-0 gap-4 xl:grid-cols-2">
|
|
251
|
-
<ContactTable key={listId} listId={listId} selectable pageSize={10} onSelectionChange={(ids) => setPicked(ids)} onOpen={setOpened} />
|
|
335
|
+
<ContactTable key={listId} listId={listId} selectable pageSize={10} refreshKey={tableVersion} onSelectionChange={(ids) => setPicked(ids)} onOpen={setOpened} />
|
|
252
336
|
<div className="flex min-w-0 flex-col gap-4">
|
|
253
337
|
<SectionCard title={`Enrich ${picked.length} selected`} description="Fill work email or phones with a provider waterfall. You confirm the price first.">
|
|
254
|
-
{picked.length === 0 ? <p className="text-sm text-muted-foreground">Tick contacts on the left.</p> : <BulkEnrichPanel key={listId} listId={listId} contactIds={picked} onStarted={started} />}
|
|
338
|
+
{picked.length === 0 && !runShown ? <p className="text-sm text-muted-foreground">Tick contacts on the left.</p> : <BulkEnrichPanel key={listId} listId={listId} contactIds={picked} onStarted={(id) => { setRunShown(true); started(id); }} onDone={() => setTableVersion((v) => v + 1)} />}
|
|
255
339
|
</SectionCard>
|
|
256
340
|
{opened ? (
|
|
257
341
|
<DetailPanel key={opened.id} title={opened.full_name ?? ([opened.first_name, opened.last_name].filter(Boolean).join(" ") || `Contact #${opened.id}`)} subtitle={opened.job_title} onClose={() => setOpened(null)}>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ListPipelines: the pipelines of one list (ordered chains of enrichment steps run over the whole list) with the open
|
|
5
|
+
* pipeline beside them. Pick a ready-made template or build one, then open it to price it, run it, watch it and stop it.
|
|
6
|
+
*
|
|
7
|
+
* Composes PipelineDetail (run, stop, on/off, delete, history), PipelineEditor (create, edit) and PipelineTemplates.
|
|
8
|
+
* The open pipeline is in the URL (`?lp=<pipeline id>`) so a refresh or a shared link comes back to it.
|
|
9
|
+
*
|
|
10
|
+
* SDK operations (the list read here; the rest are inside the parts above):
|
|
11
|
+
* 1. listListPipelines GET /enrichment/lists/{list_id}/pipelines (read, free) every pipeline of the list with its latest run
|
|
12
|
+
*
|
|
13
|
+
* The list is not paged by the API. A run that is active is re-read after it settles, so on/off and last-run state
|
|
14
|
+
* always come from the server, never from what this screen assumes.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useState } from "react";
|
|
18
|
+
import { Layers, Plus, Sparkles } from "lucide-react";
|
|
19
|
+
import { Button } from "@graph8/sdk/ui";
|
|
20
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
21
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
22
|
+
import { DetailPanel } from "@/g8/components/screen/detail-panel";
|
|
23
|
+
import { SectionCard } from "@/g8/components/screen/section-card";
|
|
24
|
+
import { SplitView } from "@/g8/components/screen/split-view";
|
|
25
|
+
import { StatusPill, humanizeStatus, toneForStatus } from "@/g8/components/screen/status-pill";
|
|
26
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
27
|
+
import { useUrlState } from "@/g8/hooks/use-url-state";
|
|
28
|
+
import { g8Get } from "@/g8/lib/g8-client";
|
|
29
|
+
import { plural } from "@/g8/lib/plural";
|
|
30
|
+
import { enabledSteps, itemsOf } from "../lib/pipeline-types";
|
|
31
|
+
import type { ListPipeline } from "../lib/pipeline-types";
|
|
32
|
+
import { PipelineDetail } from "./pipeline-detail";
|
|
33
|
+
import { PipelineEditor } from "./pipeline-editor";
|
|
34
|
+
import { PipelineTemplates } from "./pipeline-templates";
|
|
35
|
+
|
|
36
|
+
export interface ListPipelinesProps {
|
|
37
|
+
listId: number;
|
|
38
|
+
/** Called after any change, for example to refresh a sibling table. */
|
|
39
|
+
onChanged?: () => void;
|
|
40
|
+
className?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function ListPipelines({ listId, onChanged, className }: ListPipelinesProps) {
|
|
44
|
+
const [open, setOpen] = useUrlState("lp");
|
|
45
|
+
const [view, setView] = useUrlState("view");
|
|
46
|
+
const [editing, setEditing] = useState<ListPipeline | "new" | null>(null);
|
|
47
|
+
const [templates, setTemplates] = useState(false);
|
|
48
|
+
const pipelines = useG8Query(async (signal) => itemsOf<ListPipeline>((await g8Get(`/enrichment/lists/${listId}/pipelines`, undefined, { signal })).data), [listId]);
|
|
49
|
+
const rows = pipelines.data ?? [];
|
|
50
|
+
const selected = rows.find((p) => p.id === open);
|
|
51
|
+
const full = view === "full";
|
|
52
|
+
const changed = () => { void pipelines.refetch(); onChanged?.(); };
|
|
53
|
+
|
|
54
|
+
const list = (
|
|
55
|
+
<SectionCard title="List pipelines" description="Each one runs its steps, in order, over every record on this list."
|
|
56
|
+
actions={<div className="flex gap-2"><Button size="sm" variant="outline" onClick={() => setTemplates(true)}><Sparkles className="mr-1 size-4" aria-hidden />Templates</Button><Button size="sm" onClick={() => setEditing("new")}><Plus className="mr-1 size-4" aria-hidden />New pipeline</Button></div>} flush>
|
|
57
|
+
{pipelines.error ? <div className="p-3"><ApiError error={pipelines.error} title="Could not load pipelines" onRetry={() => void pipelines.refetch()} /></div> : null}
|
|
58
|
+
{!pipelines.error && pipelines.loading && !pipelines.data ? <p className="p-4 text-sm text-muted-foreground">Loading pipelines...</p> : null}
|
|
59
|
+
{!pipelines.error && pipelines.data && rows.length === 0 ? (
|
|
60
|
+
<EmptyState icon={<Layers className="size-6" aria-hidden />} title="No pipelines on this list" description="Start from a template, or build your own from the enrichment steps this list can use."
|
|
61
|
+
action={<Button size="sm" onClick={() => setTemplates(true)}>Start from a template</Button>} />
|
|
62
|
+
) : null}
|
|
63
|
+
<ul className="divide-y">{(pipelines.error ? [] : rows).map((p) => (
|
|
64
|
+
<li key={p.id}>
|
|
65
|
+
<button type="button" aria-label={`Open pipeline ${p.name}`} className={`flex w-full items-center justify-between gap-3 px-4 py-3 text-left ${p.id === open ? "bg-muted/50" : ""}`} onClick={() => setOpen(p.id)}>
|
|
66
|
+
<span className="min-w-0">
|
|
67
|
+
<span className="block truncate text-sm font-medium">{p.name}</span>
|
|
68
|
+
<span className="block text-xs text-muted-foreground">{plural(enabledSteps(p).length, "step")}</span>
|
|
69
|
+
</span>
|
|
70
|
+
<span className="flex shrink-0 items-center gap-2">
|
|
71
|
+
{p.last_run ? <StatusPill tone={toneForStatus(p.last_run.status)} dot>{humanizeStatus(p.last_run.status)}</StatusPill> : null}
|
|
72
|
+
<StatusPill tone={p.enabled ? "positive" : "neutral"}>{p.enabled ? "On" : "Off"}</StatusPill>
|
|
73
|
+
</span>
|
|
74
|
+
</button>
|
|
75
|
+
</li>
|
|
76
|
+
))}</ul>
|
|
77
|
+
</SectionCard>
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div className={className}>
|
|
82
|
+
<SplitView list={list} expanded={full} onExpandedChange={(e) => setView(e ? "full" : null)} detailOpen={selected != null} onCloseDetail={() => setOpen(null)} detailTitle="Pipeline"
|
|
83
|
+
detail={selected ? (
|
|
84
|
+
<DetailPanel key={selected.id} title={selected.name} subtitle={plural(selected.steps.length, "step")} onClose={() => setOpen(null)}
|
|
85
|
+
onExpand={full ? undefined : () => setView("full")} onCollapse={full ? () => setView(null) : undefined}
|
|
86
|
+
badges={<StatusPill tone={selected.enabled ? "positive" : "neutral"} dot>{selected.enabled ? "On" : "Off"}</StatusPill>}>
|
|
87
|
+
<PipelineDetail key={selected.id} listId={listId} pipeline={selected} onChanged={changed} onEdit={() => setEditing(selected)} onDeleted={() => { setOpen(null); changed(); }} />
|
|
88
|
+
</DetailPanel>
|
|
89
|
+
) : null} />
|
|
90
|
+
{editing ? (
|
|
91
|
+
<PipelineEditor key={editing === "new" ? "new" : editing.id} listId={listId} pipeline={editing === "new" ? undefined : editing} open onOpenChange={(o) => { if (!o) setEditing(null); }}
|
|
92
|
+
onSaved={(saved) => { setOpen(saved.id); changed(); }} />
|
|
93
|
+
) : null}
|
|
94
|
+
<PipelineTemplates listId={listId} open={templates} onOpenChange={setTemplates} existing={rows} onAdded={(p) => { setOpen(p.id); changed(); }} />
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PipelineDetail: one list pipeline in full, and everything you can do to it: run it (priced first), watch the run,
|
|
5
|
+
* stop it, turn it on or off, edit it, delete it, and read its past runs.
|
|
6
|
+
*
|
|
7
|
+
* SDK operations, in order:
|
|
8
|
+
* 1. listPipelineStepOptions GET /enrichment/lists/{list_id}/pipeline-step-options (read) names the steps
|
|
9
|
+
* 2. estimateListPipeline POST /enrichment/lists/{list_id}/pipelines/{id}/estimate (read, FREE) prices the run for THIS list
|
|
10
|
+
* -> { can_run, required_credits (an upper bound: run conditions are ignored), available_credits, estimated_evaluations, steps[], reason }
|
|
11
|
+
* 3. runListPipeline POST /enrichment/lists/{list_id}/pipelines/{id}/runs (billable, ConfirmSpendDialog)
|
|
12
|
+
* charges per record per step; also turns a pipeline that is off on, as the app does
|
|
13
|
+
* 4. getPipelineRun GET /enrichment/lists/pipeline-runs/{run_id} (read) polled with useJobPoll
|
|
14
|
+
* 5. cancelListPipelineRun POST /enrichment/lists/{list_id}/pipelines/{id}/cancel (write, free) the step in flight finishes
|
|
15
|
+
* 6. listListPipelineRuns GET /enrichment/lists/{list_id}/pipelines/{id}/runs (read) the latest runs, newest first
|
|
16
|
+
* 7. setPipelineEnabled POST /enrichment/lists/{list_id}/pipelines/{id}/enabled { enabled } (billable tier, ConfirmSpendDialog)
|
|
17
|
+
* a flag only: in this API a pipeline runs when you run it, so turning it on does not start or charge anything
|
|
18
|
+
* 8. deleteListPipeline DELETE /enrichment/lists/{list_id}/pipelines/{id} (destructive, ConfirmSpendDialog; 409 while running)
|
|
19
|
+
*
|
|
20
|
+
* The price in the run confirm is the estimate's own number for this exact list and pipeline, never a constant. The
|
|
21
|
+
* confirm opens only after the estimate has loaded; a run the estimate refuses (`can_run: false`) shows the API's
|
|
22
|
+
* reason and never reaches the confirm.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { useState } from "react";
|
|
26
|
+
import { Pencil, Play, Square, Trash2 } from "lucide-react";
|
|
27
|
+
import { Button } from "@graph8/sdk/ui";
|
|
28
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
29
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
30
|
+
import { useConfirmAction } from "@/g8/components/confirmed-action";
|
|
31
|
+
import { KeyValueList } from "@/g8/components/screen/key-value-list";
|
|
32
|
+
import { SectionCard } from "@/g8/components/screen/section-card";
|
|
33
|
+
import { StatusPill, humanizeStatus, toneForStatus } from "@/g8/components/screen/status-pill";
|
|
34
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
35
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
36
|
+
import { useJobPoll } from "@/g8/hooks/use-job-poll";
|
|
37
|
+
import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
38
|
+
import { plural } from "@/g8/lib/plural";
|
|
39
|
+
import { enabledSteps, itemsOf, percentOf, runBlockedReason, runIsActive, skipReasonLabel, stepLabel } from "../lib/pipeline-types";
|
|
40
|
+
import type { ListPipeline, PipelineEstimate, PipelineRun, StepOption, StepProgress } from "../lib/pipeline-types";
|
|
41
|
+
|
|
42
|
+
export interface PipelineDetailProps {
|
|
43
|
+
listId: number;
|
|
44
|
+
pipeline: ListPipeline;
|
|
45
|
+
/** Reload the pipelines after anything changed (a run started or finished, on/off, stop). */
|
|
46
|
+
onChanged: () => void;
|
|
47
|
+
onEdit: () => void;
|
|
48
|
+
/** Called after the pipeline was deleted. */
|
|
49
|
+
onDeleted: () => void;
|
|
50
|
+
className?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const base = (listId: number, id: string) => `/enrichment/lists/${listId}/pipelines/${id}`;
|
|
54
|
+
const when = (iso?: string | null) => (iso ? new Date(iso).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }) : "");
|
|
55
|
+
|
|
56
|
+
function StepRows({ progress }: { progress: StepProgress[] }) {
|
|
57
|
+
if (progress.length === 0) return <p className="text-sm text-muted-foreground">No step has started yet.</p>;
|
|
58
|
+
return (
|
|
59
|
+
<ul className="flex flex-col gap-3" aria-label="Run progress">
|
|
60
|
+
{progress.map((p, i) => {
|
|
61
|
+
const pct = percentOf(p.processed_records, p.total_records);
|
|
62
|
+
const skipped = Object.entries(p.skipped_by_reason ?? {}).filter(([, n]) => n > 0);
|
|
63
|
+
return (
|
|
64
|
+
<li key={`${p.step_index}-${i}`} className="flex flex-col gap-1">
|
|
65
|
+
<div className="flex items-center justify-between gap-2 text-sm">
|
|
66
|
+
<span className="truncate font-medium">{p.name || `Step ${p.step_index + 1}`}</span>
|
|
67
|
+
<span className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
68
|
+
<span className="tabular-nums">{pct == null ? "-" : `${(p.processed_records ?? 0).toLocaleString()} of ${(p.total_records ?? 0).toLocaleString()}`}</span>
|
|
69
|
+
{p.status ? <StatusPill tone={toneForStatus(p.status)} dot>{humanizeStatus(p.status)}</StatusPill> : null}
|
|
70
|
+
</span>
|
|
71
|
+
</div>
|
|
72
|
+
<div className="h-1.5 overflow-hidden rounded bg-muted" aria-label={`Step ${p.step_index + 1} progress`}><div className="h-full bg-primary" style={{ width: `${pct ?? 0}%` }} /></div>
|
|
73
|
+
<p className="text-xs text-muted-foreground tabular-nums">{(p.successful ?? 0).toLocaleString()} enriched, {(p.failed ?? 0).toLocaleString()} failed{skipped.map(([r, n]) => `, ${n.toLocaleString()} ${skipReasonLabel(r)}`).join("")}</p>
|
|
74
|
+
{p.note ? <p className="text-xs text-muted-foreground">{p.note}</p> : null}
|
|
75
|
+
</li>
|
|
76
|
+
);
|
|
77
|
+
})}
|
|
78
|
+
</ul>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Polls one active run (a fresh instance per run id) and shows its steps; reports when it is no longer running. */
|
|
83
|
+
function RunStatus({ runId, initial, onSettled }: { runId: string; initial?: PipelineRun | null; onSettled: () => void }) {
|
|
84
|
+
const poll = useJobPoll<PipelineRun>(
|
|
85
|
+
async (signal) => (await g8Get<PipelineRun>(`/enrichment/lists/pipeline-runs/${runId}`, undefined, { signal })).data,
|
|
86
|
+
{ intervalMs: 2000, isTerminal: (r) => !runIsActive(r.status), onDone: onSettled },
|
|
87
|
+
);
|
|
88
|
+
const run = poll.status ?? initial ?? undefined;
|
|
89
|
+
if (poll.error && !run) return <ApiError error={poll.error} title="Could not read the run" onRetry={() => poll.start()} />;
|
|
90
|
+
return (
|
|
91
|
+
<div className="flex flex-col gap-2" role="status">
|
|
92
|
+
<div className="flex items-center gap-2 text-sm">
|
|
93
|
+
{run ? <StatusPill tone={toneForStatus(run.status)} dot>{humanizeStatus(run.status)}</StatusPill> : <span className="text-muted-foreground">Starting...</span>}
|
|
94
|
+
{run ? <span className="text-xs text-muted-foreground">Step {Math.min(run.current_index + 1, run.total_steps)} of {run.total_steps}</span> : null}
|
|
95
|
+
</div>
|
|
96
|
+
{poll.error && run ? <p role="alert" className="text-xs text-destructive">Could not refresh the run. Showing the last known progress.</p> : null}
|
|
97
|
+
{run?.error ? <p role="alert" className="text-sm text-destructive">{run.error}</p> : null}
|
|
98
|
+
<StepRows progress={run?.step_progress ?? []} />
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function PipelineDetail({ listId, pipeline, onChanged, onEdit, onDeleted, className }: PipelineDetailProps) {
|
|
104
|
+
const [historyKey, setHistoryKey] = useState(0);
|
|
105
|
+
const [started, setStarted] = useState<string | null>(null);
|
|
106
|
+
const [blocked, setBlocked] = useState<string | null>(null);
|
|
107
|
+
const { ask, dialog, failure } = useConfirmAction();
|
|
108
|
+
const options = useG8Query(async (signal) => itemsOf<StepOption>((await g8Get(`/enrichment/lists/${listId}/pipeline-step-options`, undefined, { signal })).data), [listId], { key: ["enrichment", "step-options", listId], staleTime: 30_000 });
|
|
109
|
+
const history = useG8Query(async (signal) => itemsOf<PipelineRun>((await g8Get(`${base(listId, pipeline.id)}/runs`, undefined, { signal })).data, "runs"), [listId, pipeline.id, historyKey]);
|
|
110
|
+
const price = useG8Mutation(async () => (await g8Post<PipelineEstimate>(`${base(listId, pipeline.id)}/estimate`, {})).data);
|
|
111
|
+
const stop = useG8Mutation(async () => g8Post(`${base(listId, pipeline.id)}/cancel`, {}), { onSuccess: () => { setStarted(null); setHistoryKey((k) => k + 1); onChanged(); } });
|
|
112
|
+
|
|
113
|
+
const menu = options.data ?? [];
|
|
114
|
+
const active = runIsActive(pipeline.last_run?.status) || started != null;
|
|
115
|
+
const runId = started ?? (runIsActive(pipeline.last_run?.status) ? pipeline.last_run?.run_id ?? null : null);
|
|
116
|
+
const live = enabledSteps(pipeline);
|
|
117
|
+
const reason = active ? "A run is in progress." : runBlockedReason(pipeline);
|
|
118
|
+
const settled = () => { setStarted(null); setHistoryKey((k) => k + 1); onChanged(); };
|
|
119
|
+
|
|
120
|
+
const startRun = (est: PipelineEstimate) => ask({
|
|
121
|
+
tier: "billable",
|
|
122
|
+
title: `Run "${pipeline.name}" over this list`,
|
|
123
|
+
estimatedCredits: est.required_credits,
|
|
124
|
+
confirmLabel: "Run pipeline",
|
|
125
|
+
description: (
|
|
126
|
+
<div className="flex flex-col gap-2">
|
|
127
|
+
<p>{plural(est.estimated_evaluations, "record check")} across {plural(est.steps.length, "step")}. This is an upper bound: run conditions are ignored when pricing, so the real charge can be lower.</p>
|
|
128
|
+
<ul className="list-disc pl-5">{est.steps.map((s) => <li key={s.step_id}>{s.name}: {plural(s.estimated_records, "record")}, up to {plural(s.required_credits, "credit")}</li>)}</ul>
|
|
129
|
+
<p>Your balance is {est.available_credits.toLocaleString()} credits{est.available_credits < est.required_credits ? ", which may not cover every step: the run stops cleanly when credits run out" : ""}.</p>
|
|
130
|
+
{pipeline.enabled ? null : <p>This also turns the pipeline on.</p>}
|
|
131
|
+
</div>
|
|
132
|
+
),
|
|
133
|
+
run: async () => { const res = await g8Post<PipelineRun>(`${base(listId, pipeline.id)}/runs`, {}); setStarted(res.data.run_id); onChanged(); },
|
|
134
|
+
});
|
|
135
|
+
const onRun = async () => {
|
|
136
|
+
setBlocked(null);
|
|
137
|
+
const est = await price.mutate(undefined);
|
|
138
|
+
if (!est) return;
|
|
139
|
+
if (est.can_run) startRun(est);
|
|
140
|
+
else setBlocked(est.reason || "The API did not give a reason this run cannot start.");
|
|
141
|
+
};
|
|
142
|
+
const setEnabled = (next: boolean) => ask({
|
|
143
|
+
tier: "billable",
|
|
144
|
+
title: `${next ? "Turn on" : "Turn off"} "${pipeline.name}"`,
|
|
145
|
+
confirmLabel: next ? "Turn on" : "Turn off",
|
|
146
|
+
description: next ? "Marks the pipeline as on. It does not start a run and charges nothing by itself: a run starts when you press Run, and shows its price first." : "Marks the pipeline as off. Running it again turns it back on. A run already in progress is not stopped.",
|
|
147
|
+
run: () => g8Post(`${base(listId, pipeline.id)}/enabled`, { enabled: next }),
|
|
148
|
+
onDone: onChanged,
|
|
149
|
+
});
|
|
150
|
+
const remove = () => ask({
|
|
151
|
+
tier: "destructive",
|
|
152
|
+
title: `Delete pipeline "${pipeline.name}"`,
|
|
153
|
+
description: "Its steps and run history go. Values it already wrote stay on the records. A running pipeline cannot be deleted.",
|
|
154
|
+
affected: 1,
|
|
155
|
+
tolerateStatus: [404],
|
|
156
|
+
run: () => g8Delete(base(listId, pipeline.id)),
|
|
157
|
+
onDone: onDeleted,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<div className={`flex flex-col gap-4 ${className ?? ""}`}>
|
|
162
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
163
|
+
<Button size="sm" disabled={reason != null || price.loading} loading={price.loading} onClick={() => void onRun()}><Play className="mr-1 size-4" aria-hidden />{price.loading ? "Pricing..." : "Run"}</Button>
|
|
164
|
+
{active ? <Button size="sm" variant="outline" loading={stop.loading} onClick={() => void stop.mutate(undefined)}><Square className="mr-1 size-4" aria-hidden />Stop run</Button> : null}
|
|
165
|
+
<Button size="sm" variant="outline" disabled={active} onClick={() => setEnabled(!pipeline.enabled)}>{pipeline.enabled ? "Turn off" : "Turn on"}</Button>
|
|
166
|
+
<Button size="sm" variant="outline" disabled={active} onClick={onEdit}><Pencil className="mr-1 size-4" aria-hidden />Edit</Button>
|
|
167
|
+
<Button size="sm" variant="ghost" disabled={active} onClick={remove}><Trash2 className="mr-1 size-4" aria-hidden />Delete</Button>
|
|
168
|
+
</div>
|
|
169
|
+
{reason ? <p className="text-xs text-muted-foreground">{reason}</p> : null}
|
|
170
|
+
{blocked ? <p role="alert" className="text-sm text-destructive">Cannot run: {blocked}</p> : null}
|
|
171
|
+
{price.error ? <ApiError error={price.error} title="Could not price the run" /> : null}
|
|
172
|
+
{stop.error ? <ApiError error={stop.error} title="Could not stop the run" /> : null}
|
|
173
|
+
{failure ? <ApiError error={failure} title="That did not go through" /> : null}
|
|
174
|
+
|
|
175
|
+
<SectionCard title="Steps" description={`${plural(live.length, "step")} will run, in this order.`}>
|
|
176
|
+
<ol className="flex flex-col gap-2" aria-label="Pipeline steps">
|
|
177
|
+
{pipeline.steps.map((s, i) => (
|
|
178
|
+
<li key={s.id} className="flex flex-col gap-0.5 text-sm">
|
|
179
|
+
<span className="flex items-center gap-2"><span className="w-5 text-muted-foreground">{i + 1}.</span><span className="font-medium">{stepLabel(s, menu)}</span>{s.enabled === false ? <StatusPill tone="neutral">Off</StatusPill> : null}</span>
|
|
180
|
+
<span className="pl-7 text-xs text-muted-foreground">{s.skip_existing_values === false ? "Overwrites existing values" : "Fills only empty values"}{s.skip_recently_enriched === false ? "" : ", skips records enriched recently"}{s.run_condition ? `. Runs only when: ${s.run_condition}` : ""}</span>
|
|
181
|
+
</li>
|
|
182
|
+
))}
|
|
183
|
+
</ol>
|
|
184
|
+
</SectionCard>
|
|
185
|
+
|
|
186
|
+
{runId ? (
|
|
187
|
+
<SectionCard title="Current run"><RunStatus key={runId} runId={runId} initial={pipeline.last_run} onSettled={settled} /></SectionCard>
|
|
188
|
+
) : pipeline.last_run ? (
|
|
189
|
+
<SectionCard title="Last run" description={when(pipeline.last_run.started_at)}>
|
|
190
|
+
<div className="flex flex-col gap-2">
|
|
191
|
+
<StatusPill tone={toneForStatus(pipeline.last_run.status)} dot>{humanizeStatus(pipeline.last_run.status)}</StatusPill>
|
|
192
|
+
{pipeline.last_run.error ? <p role="alert" className="text-sm text-destructive">{pipeline.last_run.error}</p> : null}
|
|
193
|
+
<StepRows progress={pipeline.last_run.step_progress ?? []} />
|
|
194
|
+
</div>
|
|
195
|
+
</SectionCard>
|
|
196
|
+
) : <p className="text-sm text-muted-foreground">This pipeline has not run yet.</p>}
|
|
197
|
+
|
|
198
|
+
<SectionCard title="Run history" description="The latest runs, newest first." flush>
|
|
199
|
+
{history.error ? <div className="p-3"><ApiError error={history.error} title="Could not load run history" onRetry={() => void history.refetch()} /></div> : null}
|
|
200
|
+
{!history.error && history.loading && !history.data ? <p className="p-4 text-sm text-muted-foreground">Loading runs...</p> : null}
|
|
201
|
+
{!history.error && history.data && history.data.length === 0 ? <p className="p-4 text-sm text-muted-foreground">No finished runs yet.</p> : null}
|
|
202
|
+
<ul className="divide-y">{(history.error ? [] : history.data ?? []).map((r) => (
|
|
203
|
+
<li key={r.run_id} className="flex items-center justify-between gap-2 px-4 py-2 text-sm">
|
|
204
|
+
<span>{when(r.started_at) || "Run"}</span>
|
|
205
|
+
<StatusPill tone={toneForStatus(r.status)} dot>{humanizeStatus(r.status)}</StatusPill>
|
|
206
|
+
</li>
|
|
207
|
+
))}</ul>
|
|
208
|
+
</SectionCard>
|
|
209
|
+
|
|
210
|
+
<KeyValueList columns={2} items={[{ label: "State", value: pipeline.enabled ? "On" : "Off" }, { label: "Created", value: when(pipeline.created_at) }]} />
|
|
211
|
+
<ConfirmSpendDialog {...dialog} />
|
|
212
|
+
</div>
|
|
213
|
+
);
|
|
214
|
+
}
|