@graph8/sdk 0.370.0 → 0.373.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 +19 -0
- package/blocks/_foundation/README.md +14 -0
- package/blocks/_foundation/block.json +44 -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/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/components/campaign-assets.tsx +1 -1
- package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
- 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 +161 -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 +6 -0
- package/blocks/crm/block.json +53 -4
- 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-lookup-link.tsx +44 -0
- package/blocks/crm/components/contacts-screen.tsx +31 -13
- 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/record-engagement-tab.tsx +103 -0
- package/blocks/crm/components/record-form.tsx +1 -1
- package/blocks/crm/components/tasks-screen.tsx +35 -15
- package/blocks/crm/hooks/use-all-lists.ts +18 -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/deals/README.md +24 -6
- package/blocks/deals/block.json +151 -2
- package/blocks/deals/components/close-deal-dialog.tsx +169 -0
- package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
- package/blocks/deals/components/deal-detail.tsx +2 -2
- 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 +4 -1
- 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/enrichment/README.md +16 -6
- package/blocks/enrichment/block.json +19 -7
- package/blocks/enrichment/components/bulk-enrich-panel.tsx +76 -27
- package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
- package/blocks/enrichment/components/enrich-button.tsx +1 -1
- package/blocks/enrichment/components/enrichment-screen.tsx +70 -17
- package/blocks/enrichment/components/waterfall-config.tsx +27 -7
- package/blocks/enrichment/lib/enrichment-types.ts +39 -0
- package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
- package/blocks/forms/README.md +8 -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 +51 -17
- 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 +5 -0
- package/blocks/inbox/block.json +15 -0
- package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
- package/blocks/inbox/components/thread-badge.tsx +2 -2
- package/blocks/inbox/components/thread-view.tsx +1 -1
- 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 +36 -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 +113 -1
- 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/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/quotes/README.md +13 -0
- package/blocks/quotes/block.json +48 -1
- package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
- package/blocks/quotes/components/quote-builder.tsx +1 -1
- package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
- package/blocks/quotes/components/quote-list.tsx +5 -3
- package/blocks/quotes/components/quote-related.tsx +3 -2
- package/blocks/quotes/components/quote-viewer.tsx +7 -2
- package/blocks/quotes/components/record-quotes-tab.tsx +90 -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 +6 -0
- package/blocks/sequencer/block.json +28 -1
- package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
- package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
- package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
- package/blocks/sequencer/components/sequence-builder.tsx +19 -20
- package/blocks/sequencer/components/sequence-detail.tsx +4 -2
- package/blocks/sequencer/components/sequence-enrollments.tsx +4 -2
- package/blocks/sequencer/components/sequence-list.tsx +1 -1
- package/blocks/sequencer/components/sequence-stats.tsx +8 -8
- package/blocks/sequencer/components/sequences-screen.tsx +3 -0
- package/blocks/sequencer/components/step-editor.tsx +11 -11
- package/blocks/sequencer/lib/sequencer-utils.ts +5 -5
- 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 +4 -4
- package/blocks/visitors/components/visitor-company-card.tsx +24 -14
- package/blocks/visitors/components/visitor-feed.tsx +5 -5
- package/blocks/webchat/components/chat-widget-config.tsx +1 -1
- package/blocks/webchat/components/conversation-list.tsx +1 -1
- package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
- package/blocks/workbench/components/run-progress.tsx +1 -1
- package/blocks/workbench/components/workbench-grid.tsx +1 -1
- package/blocks/workbench/components/workbench-screen.tsx +15 -5
- package/blocks/workbench/components/workbench-table-list.tsx +14 -2
- package/blocks/workflows/components/workflow-editor.tsx +1 -1
- package/dist/cli.js +536 -392
- 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/brand.d.ts +11 -2
- package/dist/types/blocks/create.d.ts +7 -3
- 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/package.json +1 -1
- package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
- package/stacks/6sense/pages/contacts.tsx.tmpl +19 -0
- package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
- package/stacks/6sense.json +17 -4
- package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
- package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
- package/stacks/apollo/pages/contacts.tsx.tmpl +29 -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 +12 -0
- package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
- package/stacks/apollo/pages/sequences.tsx.tmpl +12 -0
- package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
- package/stacks/apollo.json +36 -8
- 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 +12 -0
- package/stacks/clay/pages/lists.tsx.tmpl +12 -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 +32 -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 +28 -0
- package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
- package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
- package/stacks/hootsuite.json +9 -4
- package/stacks/hubspot/pages/companies.tsx.tmpl +14 -35
- package/stacks/hubspot/pages/contacts.tsx.tmpl +29 -95
- package/stacks/hubspot/pages/deals.tsx.tmpl +10 -1
- 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 +12 -0
- package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
- package/stacks/hubspot.json +21 -5
- 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 +8 -4
- package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
- package/stacks/mailchimp/pages/contacts.tsx.tmpl +28 -0
- package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
- package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
- package/stacks/mailchimp.json +20 -5
- package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
- package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
- package/stacks/outreach/pages/contacts.tsx.tmpl +27 -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 +12 -0
- package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
- package/stacks/outreach.json +25 -7
- package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
- package/stacks/pipedrive/pages/contacts.tsx.tmpl +15 -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 +4 -2
- package/stacks/salesforce/pages/companies.tsx.tmpl +14 -35
- package/stacks/salesforce/pages/contacts.tsx.tmpl +20 -35
- package/stacks/salesforce/pages/deals.tsx.tmpl +10 -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 +7 -3
- package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
- package/stacks/zoominfo/pages/contacts.tsx.tmpl +12 -0
- package/stacks/zoominfo/pages/lists.tsx.tmpl +12 -0
- package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
- package/stacks/zoominfo.json +16 -3
- package/styles/styles.css +1 -1
- 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,116 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WikiDocumentDetail: the complete screen for one wiki document. Finds it by id (so `?id=` deep links work), shows its
|
|
5
|
+
* content as safe formatted text (markdown from the API is never turned into HTML), its state and version, and can write
|
|
6
|
+
* it again from the collected research.
|
|
7
|
+
*
|
|
8
|
+
* SDK operations, in order:
|
|
9
|
+
* 1. getGlobalContextTreeFlat GET /global-context/tree/flat (read)
|
|
10
|
+
* 2. getIntelligencePrimaryWebsite GET /intelligence/primary-website (read, through useResearchTarget)
|
|
11
|
+
* 3. generateGlobalContextDocument POST /global-context/generate/{doc_type} (BILLABLE) a model rewrites this ONE document; ConfirmSpendDialog first
|
|
12
|
+
* After the call is accepted the document is read again until its update time moves. The confirm shows what this document
|
|
13
|
+
* used the last time it was written (a real number from the document), when the API reports one; there is no advance price.
|
|
14
|
+
*/
|
|
15
|
+
import { useRef, useState, type ReactNode } from "react";
|
|
16
|
+
import { RefreshCw } from "lucide-react";
|
|
17
|
+
import { Button, Skeleton } from "@graph8/sdk/ui";
|
|
18
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
19
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
20
|
+
import { useConfirmAction } from "@/g8/components/confirmed-action";
|
|
21
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
22
|
+
import { DetailPanel } from "@/g8/components/screen/detail-panel";
|
|
23
|
+
import { KeyValueList, EmptyValue } from "@/g8/components/screen/key-value-list";
|
|
24
|
+
import { StatusPill, toneForStatus } from "@/g8/components/screen/status-pill";
|
|
25
|
+
import { useJobPoll } from "@/g8/hooks/use-job-poll";
|
|
26
|
+
import { g8Post } from "@/g8/lib/g8-client";
|
|
27
|
+
import { refreshIntelligence, useResearchTarget, useWikiDocuments } from "../hooks/use-intelligence";
|
|
28
|
+
import { docTitle, humanize, num, text, when } from "../lib/intelligence-types";
|
|
29
|
+
import { SafeMarkdown } from "../lib/safe-markdown";
|
|
30
|
+
|
|
31
|
+
export interface WikiDocumentDetailProps {
|
|
32
|
+
docId: string;
|
|
33
|
+
onClose?: () => void;
|
|
34
|
+
onExpand?: () => void;
|
|
35
|
+
onCollapse?: () => void;
|
|
36
|
+
onChanged?: () => void;
|
|
37
|
+
extraActions?: ReactNode;
|
|
38
|
+
extraTabs?: { id: string; label: string; content: ReactNode }[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function WikiDocumentDetail({ docId, onClose, onExpand, onCollapse, onChanged, extraActions, extraTabs }: WikiDocumentDetailProps) {
|
|
42
|
+
const { ask, dialog, failure } = useConfirmAction();
|
|
43
|
+
const target = useResearchTarget();
|
|
44
|
+
const { docs, error, refetch } = useWikiDocuments();
|
|
45
|
+
const [waiting, setWaiting] = useState(false);
|
|
46
|
+
const before = useRef<string | null>(null);
|
|
47
|
+
const doc = docs?.find((d) => d.id === docId);
|
|
48
|
+
|
|
49
|
+
useJobPoll<string | null>(async () => { const rows = await refetch(); return text(rows?.find((d) => d.id === docId)?.updated_at); }, {
|
|
50
|
+
enabled: waiting, intervalMs: 4000, maxAttempts: 40,
|
|
51
|
+
isTerminal: (updated) => updated != null && updated !== before.current,
|
|
52
|
+
onDone: () => { setWaiting(false); void refreshIntelligence(); onChanged?.(); },
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (error) return <ApiError error={error} title="Could not load the document" onRetry={() => void refetch()} />;
|
|
56
|
+
if (!docs) return <Skeleton className="h-40 w-full" />;
|
|
57
|
+
if (!doc) return <EmptyState title="Document not found" description="It may have been deleted." />;
|
|
58
|
+
|
|
59
|
+
const title = docTitle(doc);
|
|
60
|
+
const lastCredits = num(doc.credits_used);
|
|
61
|
+
const canRegenerate = Boolean(doc.file_type && target.primary);
|
|
62
|
+
const askRegenerate = () => ask({
|
|
63
|
+
tier: "billable",
|
|
64
|
+
title: `Rewrite ${title}`,
|
|
65
|
+
confirmLabel: "Rewrite",
|
|
66
|
+
estimatedCredits: lastCredits != null && lastCredits > 0 ? lastCredits : undefined,
|
|
67
|
+
description: <p>A model writes this one document again from the collected research and replaces version {doc.version ?? "the current one"}.{lastCredits != null && lastCredits > 0 ? " The credits shown are what it used the last time." : " The API gives no price in advance."}</p>,
|
|
68
|
+
run: async () => {
|
|
69
|
+
before.current = text(doc.updated_at);
|
|
70
|
+
await g8Post(`/global-context/generate/${encodeURIComponent(doc.file_type as string)}`, { doc_type: doc.file_type, website_url: target.primary });
|
|
71
|
+
setWaiting(true);
|
|
72
|
+
},
|
|
73
|
+
onDone: () => { void refreshIntelligence(); },
|
|
74
|
+
});
|
|
75
|
+
const actions = (
|
|
76
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
77
|
+
<Button type="button" size="sm" variant="outline" disabled={!canRegenerate || waiting} onClick={askRegenerate} title={canRegenerate ? undefined : "Start the website research first"}>
|
|
78
|
+
<RefreshCw className="size-3.5" aria-hidden />{waiting ? "Writing..." : "Rewrite"}
|
|
79
|
+
</Button>
|
|
80
|
+
{extraActions}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
const content = text(doc.content);
|
|
84
|
+
const tabs = [
|
|
85
|
+
{ id: "document", label: "Document", content: content ? <SafeMarkdown markdown={content} /> : <EmptyState title="No content yet" description="This document has not been written." compact /> },
|
|
86
|
+
{
|
|
87
|
+
id: "details", label: "Details",
|
|
88
|
+
content: (
|
|
89
|
+
<KeyValueList columns={2} items={[
|
|
90
|
+
{ label: "Type", value: text(doc.file_type) ?? <EmptyValue />, mono: true },
|
|
91
|
+
{ label: "Folder", value: text(doc.folder_path) ?? <EmptyValue /> },
|
|
92
|
+
{ label: "Category", value: text(doc.category) ?? <EmptyValue /> },
|
|
93
|
+
{ label: "Version", value: doc.version ?? <EmptyValue /> },
|
|
94
|
+
{ label: "Last updated", value: when(doc.updated_at) ?? <EmptyValue /> },
|
|
95
|
+
{ label: "Credits used last time", value: lastCredits != null && lastCredits > 0 ? lastCredits : <EmptyValue /> },
|
|
96
|
+
]} />
|
|
97
|
+
),
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
return (
|
|
101
|
+
<>
|
|
102
|
+
<DetailPanel
|
|
103
|
+
title={title}
|
|
104
|
+
subtitle={text(doc.folder_path) ?? undefined}
|
|
105
|
+
badges={<StatusPill tone={toneForStatus(doc.status ?? "")}>{doc.status ? humanize(doc.status) : "Unknown"}</StatusPill>}
|
|
106
|
+
actions={actions}
|
|
107
|
+
tabs={extraTabs?.length ? [...tabs, ...extraTabs] : tabs}
|
|
108
|
+
onClose={onClose}
|
|
109
|
+
onExpand={onExpand}
|
|
110
|
+
onCollapse={onCollapse}
|
|
111
|
+
/>
|
|
112
|
+
{failure ? <ApiError error={failure} title="That did not work" /> : null}
|
|
113
|
+
<ConfirmSpendDialog {...dialog} />
|
|
114
|
+
</>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WikiDocuments: the wiki documents graph8 writes from the research (brand, audience, market, messaging ...), listed by
|
|
5
|
+
* folder with their state, version and last update. Search and a folder filter narrow the list. Click a row to open it.
|
|
6
|
+
*
|
|
7
|
+
* SDK operations, in order:
|
|
8
|
+
* 1. getGlobalContextTreeFlat GET /global-context/tree/flat (read) every wiki document, content included
|
|
9
|
+
* The API does not page this list (a few dozen documents); the header says how many there are. Folder and search filter on
|
|
10
|
+
* screen. Opening and regenerating a document is WikiDocumentDetail.
|
|
11
|
+
*/
|
|
12
|
+
import { useMemo, useState } from "react";
|
|
13
|
+
import { FileText } from "lucide-react";
|
|
14
|
+
import { Skeleton } from "@graph8/sdk/ui";
|
|
15
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
16
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
17
|
+
import { RecordLink } from "@/g8/components/screen/record-link";
|
|
18
|
+
import { SelectFilter } from "@/g8/components/screen/filter-controls";
|
|
19
|
+
import { StatusPill, toneForStatus } from "@/g8/components/screen/status-pill";
|
|
20
|
+
import { Toolbar } from "@/g8/components/screen/toolbar";
|
|
21
|
+
import { plural } from "@/g8/lib/plural";
|
|
22
|
+
import { useWikiDocuments } from "../hooks/use-intelligence";
|
|
23
|
+
import { docTitle, humanize, text, whenDate } from "../lib/intelligence-types";
|
|
24
|
+
import type { WikiDoc } from "../lib/intelligence-types";
|
|
25
|
+
|
|
26
|
+
export interface WikiDocumentsProps {
|
|
27
|
+
onSelect?: (doc: WikiDoc) => void;
|
|
28
|
+
selectedId?: string;
|
|
29
|
+
/** The URL param the owning screen reads for the open document, so Cmd-click opens it in a new tab. */
|
|
30
|
+
idParam?: string;
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function WikiDocuments({ onSelect, selectedId, idParam, className }: WikiDocumentsProps) {
|
|
35
|
+
const { docs, error, refetch } = useWikiDocuments();
|
|
36
|
+
const [search, setSearch] = useState("");
|
|
37
|
+
const [folder, setFolder] = useState("");
|
|
38
|
+
const folders = useMemo(() => [...new Set((docs ?? []).map((d) => text(d.folder_path)).filter(Boolean) as string[])].sort(), [docs]);
|
|
39
|
+
if (error) return <ApiError error={error} title="Could not load the documents" onRetry={() => void refetch()} />;
|
|
40
|
+
if (!docs) return <Skeleton className="h-40 w-full" />;
|
|
41
|
+
if (docs.length === 0) return <EmptyState title="No documents yet" description="The wiki documents are written from the research. Run the website research first." icon={<FileText />} />;
|
|
42
|
+
const needle = search.trim().toLowerCase();
|
|
43
|
+
const rows = docs.filter((d) => (!folder || d.folder_path === folder) && (!needle || docTitle(d).toLowerCase().includes(needle)));
|
|
44
|
+
return (
|
|
45
|
+
<div className={className}>
|
|
46
|
+
<Toolbar
|
|
47
|
+
search={{ value: search, onChange: setSearch, placeholder: "Search documents", label: "Search documents" }}
|
|
48
|
+
filters={folders.length > 1 ? <SelectFilter label="folder" value={folder} onChange={setFolder} any="All folders" options={folders} /> : undefined}
|
|
49
|
+
status={rows.length === docs.length ? `All ${plural(docs.length, "document")}` : `${rows.length.toLocaleString()} of ${plural(docs.length, "document")}`}
|
|
50
|
+
/>
|
|
51
|
+
{rows.length === 0 ? <EmptyState title="No documents match" description="Try another search or folder." compact /> : (
|
|
52
|
+
<ul className="mt-2 divide-y divide-border rounded-md border border-border">
|
|
53
|
+
{rows.map((d) => (
|
|
54
|
+
<li key={d.id} aria-current={d.id === selectedId ? "true" : undefined} className="flex items-center justify-between gap-3 px-3 py-2 text-sm aria-[current=true]:bg-muted">
|
|
55
|
+
<div className="min-w-0">
|
|
56
|
+
<RecordLink id={d.id} param={idParam} onOpen={() => onSelect?.(d)}>{docTitle(d)}</RecordLink>
|
|
57
|
+
<div className="text-xs text-muted-foreground">{[text(d.folder_path), d.version != null ? `Version ${d.version}` : null, whenDate(d.updated_at) ? `Updated ${whenDate(d.updated_at)}` : null].filter(Boolean).join(", ")}</div>
|
|
58
|
+
</div>
|
|
59
|
+
<StatusPill tone={toneForStatus(d.status ?? "")} quiet>{d.status ? humanize(d.status) : "Unknown"}</StatusPill>
|
|
60
|
+
</li>
|
|
61
|
+
))}
|
|
62
|
+
</ul>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared reads for the company-intelligence components, all cached under the `company-intelligence` key prefix so one
|
|
5
|
+
* `refreshIntelligence()` after any write refreshes every panel that shows the result.
|
|
6
|
+
*
|
|
7
|
+
* SDK operations, in order:
|
|
8
|
+
* 1. getIntelligencePrimaryWebsite GET /intelligence/primary-website (read) the website the research is locked to
|
|
9
|
+
* 2. getIntelligenceOrgDomain GET /intelligence/org-domain (read) a suggestion for it, read only while none is set
|
|
10
|
+
* 3. getLatestIntelligenceTask GET /intelligence/latest-task (read) the most recent research run
|
|
11
|
+
* 4. getAllIntelligenceData GET /intelligence/all/{website_url} (read) every source collected for one website
|
|
12
|
+
* 5. getIntelligenceStatus GET /intelligence/status/{task_id} (read) the step states of one run
|
|
13
|
+
* 6. listIntelligenceDocuments GET /intelligence/documents (read) every collected source
|
|
14
|
+
* 7. getGlobalContextTreeFlat GET /global-context/tree/flat (read) the wiki documents
|
|
15
|
+
* 8. getGlobalContextTree GET /global-context/tree (read) how many wiki documents exist and are finished
|
|
16
|
+
* 9. getResearchWorkflowStatus GET /research-reports/workflow (read) the six research reports and how far they are
|
|
17
|
+
* 10. getIntelligenceData GET /intelligence/data/{data_type} (read) one collected source
|
|
18
|
+
*/
|
|
19
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
20
|
+
import { invalidate } from "@/g8/lib/g8-cache";
|
|
21
|
+
import { g8Get } from "@/g8/lib/g8-client";
|
|
22
|
+
import { canonicalDomain, intelBody, isRec, num, rowsOf, text } from "../lib/intelligence-types";
|
|
23
|
+
import type { IntelDocument, LatestTask, Rec, RunStatus, WikiDoc, WorkflowStatus } from "../lib/intelligence-types";
|
|
24
|
+
|
|
25
|
+
export const INTEL_KEY = "company-intelligence";
|
|
26
|
+
|
|
27
|
+
/** Mark every intelligence read stale after a write, so the panels on screen show the new state. */
|
|
28
|
+
export function refreshIntelligence(): Promise<void> {
|
|
29
|
+
return invalidate([INTEL_KEY]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The website intelligence is about. `primary` is the workspace's own website (set by the first research run, then
|
|
34
|
+
* locked); `suggested` is the company domain in the workspace settings, offered only while no primary website exists.
|
|
35
|
+
* Neither is ever invented: with both null the person types the website.
|
|
36
|
+
*/
|
|
37
|
+
export function useResearchTarget() {
|
|
38
|
+
const primary = useG8Query(
|
|
39
|
+
async (signal) => {
|
|
40
|
+
const body = intelBody<unknown>(await g8Get("/intelligence/primary-website", undefined, { signal }));
|
|
41
|
+
return isRec(body) ? text(body.primary_website_url) : null;
|
|
42
|
+
},
|
|
43
|
+
[],
|
|
44
|
+
{ key: [INTEL_KEY, "primary-website"] },
|
|
45
|
+
);
|
|
46
|
+
const wantSuggestion = primary.data === null;
|
|
47
|
+
const suggestion = useG8Query(
|
|
48
|
+
async (signal) => {
|
|
49
|
+
const body = intelBody<unknown>(await g8Get("/intelligence/org-domain", undefined, { signal }));
|
|
50
|
+
return isRec(body) ? text(body.company_domain) : null;
|
|
51
|
+
},
|
|
52
|
+
[],
|
|
53
|
+
{ key: [INTEL_KEY, "org-domain"], enabled: wantSuggestion },
|
|
54
|
+
);
|
|
55
|
+
return {
|
|
56
|
+
primary: primary.data ?? null,
|
|
57
|
+
primaryDomain: primary.data ? canonicalDomain(primary.data) : null,
|
|
58
|
+
suggested: wantSuggestion ? suggestion.data ?? null : null,
|
|
59
|
+
loading: primary.loading && primary.data === undefined,
|
|
60
|
+
error: primary.error,
|
|
61
|
+
refetch: primary.refetch,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The most recent research run of the workspace, or null when none ran yet. */
|
|
66
|
+
export function useLatestRun() {
|
|
67
|
+
const q = useG8Query(
|
|
68
|
+
async (signal) => {
|
|
69
|
+
const body = intelBody<unknown>(await g8Get("/intelligence/latest-task", undefined, { signal }));
|
|
70
|
+
return isRec(body) && text(body.task_id) ? (body as LatestTask) : null;
|
|
71
|
+
},
|
|
72
|
+
[],
|
|
73
|
+
{ key: [INTEL_KEY, "latest-run"] },
|
|
74
|
+
);
|
|
75
|
+
return { run: q.data ?? null, loading: q.loading && q.data === undefined, error: q.error, refetch: q.refetch };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Everything collected for one website, keyed by source type (`GET /intelligence/all/{website_url}`, read).
|
|
80
|
+
* `host` is the website as a host (no scheme); pass null to hold the request.
|
|
81
|
+
*/
|
|
82
|
+
export function useAllIntelligence(host: string | null) {
|
|
83
|
+
const q = useG8Query(
|
|
84
|
+
async (signal) => {
|
|
85
|
+
const body = intelBody<unknown>(await g8Get(`/intelligence/all/${encodeURIComponent(host as string)}`, undefined, { signal }));
|
|
86
|
+
const out: Record<string, Rec> = {};
|
|
87
|
+
if (isRec(body)) for (const [type, entry] of Object.entries(body)) if (isRec(entry)) out[type] = entry;
|
|
88
|
+
return out;
|
|
89
|
+
},
|
|
90
|
+
[host],
|
|
91
|
+
{ key: [INTEL_KEY, "all", host ?? "none"], enabled: Boolean(host) },
|
|
92
|
+
);
|
|
93
|
+
return { entries: q.data ?? null, loading: q.loading && q.data === undefined, error: q.error, refetch: q.refetch };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The `data` blob of one source entry, or null. */
|
|
97
|
+
export function entryData(entry: Rec | undefined | null): Rec | null {
|
|
98
|
+
return entry && isRec(entry.data) ? entry.data : null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** One read of a run's step states (`GET /intelligence/status/{task_id}`, read). For the live view use ResearchRun, which polls. */
|
|
102
|
+
export function useRunStatus(taskId: string | null | undefined) {
|
|
103
|
+
const q = useG8Query(
|
|
104
|
+
async (signal) => intelBody<RunStatus>(await g8Get(`/intelligence/status/${encodeURIComponent(taskId as string)}`, undefined, { signal })),
|
|
105
|
+
[taskId],
|
|
106
|
+
{ key: [INTEL_KEY, "status", taskId ?? "none"], enabled: Boolean(taskId) },
|
|
107
|
+
);
|
|
108
|
+
return { status: q.data ?? null, loading: q.loading && q.data === undefined, error: q.error, refetch: q.refetch };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Every collected source of the workspace (`GET /intelligence/documents`, read), newest first as the API returns them. */
|
|
112
|
+
export function useSourceDocuments() {
|
|
113
|
+
const q = useG8Query(
|
|
114
|
+
async (signal) => rowsOf<IntelDocument>(intelBody(await g8Get("/intelligence/documents", undefined, { signal })), "documents"),
|
|
115
|
+
[],
|
|
116
|
+
{ key: [INTEL_KEY, "documents"] },
|
|
117
|
+
);
|
|
118
|
+
return { docs: q.data ?? null, loading: q.loading && q.data === undefined, error: q.error, refetch: q.refetch };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The wiki documents graph8 writes from the research (brand, audience, market, messaging ...), content included
|
|
123
|
+
* (`GET /global-context/tree/flat`, read). The API does not page this list and it holds a few dozen documents.
|
|
124
|
+
*/
|
|
125
|
+
export function useWikiDocuments() {
|
|
126
|
+
const q = useG8Query(
|
|
127
|
+
async (signal) => rowsOf<WikiDoc>(intelBody(await g8Get("/global-context/tree/flat", undefined, { signal })), "documents"),
|
|
128
|
+
[],
|
|
129
|
+
{ key: [INTEL_KEY, "wiki", "flat"] },
|
|
130
|
+
);
|
|
131
|
+
return { docs: q.data ?? null, loading: q.loading && q.data === undefined, error: q.error, refetch: q.refetch };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** How many wiki documents exist and how many are finished (`GET /global-context/tree`, read). Null when the API did not say. */
|
|
135
|
+
export function useWikiStats() {
|
|
136
|
+
const q = useG8Query(
|
|
137
|
+
async (signal) => {
|
|
138
|
+
const body = intelBody<unknown>(await g8Get("/global-context/tree", undefined, { signal }));
|
|
139
|
+
const stats = isRec(body) && isRec(body.stats) ? body.stats : null;
|
|
140
|
+
const total = num(stats?.total);
|
|
141
|
+
const completed = num(stats?.completed);
|
|
142
|
+
return total != null && completed != null ? { total, completed } : null;
|
|
143
|
+
},
|
|
144
|
+
[],
|
|
145
|
+
{ key: [INTEL_KEY, "wiki", "tree"] },
|
|
146
|
+
);
|
|
147
|
+
return { stats: q.data ?? null, loading: q.loading && q.data === undefined, error: q.error, refetch: q.refetch };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* How the six-report research workflow is going for a website (`GET /research-reports/workflow`, read). It always lists
|
|
152
|
+
* all six slots, including the ones not generated yet. `host` null holds the request.
|
|
153
|
+
*/
|
|
154
|
+
export function useWorkflow(host: string | null) {
|
|
155
|
+
const q = useG8Query(
|
|
156
|
+
async (signal) => intelBody<WorkflowStatus>(await g8Get("/research-reports/workflow", { website_url: host }, { signal })),
|
|
157
|
+
[host],
|
|
158
|
+
{ key: [INTEL_KEY, "workflow", host ?? "none"], enabled: Boolean(host) },
|
|
159
|
+
);
|
|
160
|
+
return { workflow: q.data ?? null, loading: q.loading && q.data === undefined, error: q.error, refetch: q.refetch };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* One collected source for a website (`GET /intelligence/data/{data_type}`, read): the entry with its `data` blob,
|
|
165
|
+
* collection time and any edits. A 404 means the source was not collected (the query errors with status 404).
|
|
166
|
+
*/
|
|
167
|
+
export function useSourceEntry(dataType: string, host: string | null, enabled = true) {
|
|
168
|
+
return useG8Query(
|
|
169
|
+
async (signal) => intelBody<Rec>(await g8Get(`/intelligence/data/${encodeURIComponent(dataType)}`, host ? { website_url: host } : undefined, { signal })),
|
|
170
|
+
[dataType, host],
|
|
171
|
+
{ key: [INTEL_KEY, "source", dataType, host ?? "any"], enabled },
|
|
172
|
+
);
|
|
173
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shapes and helpers for the company-intelligence components.
|
|
5
|
+
*
|
|
6
|
+
* Reality of this API (read from the handlers, not assumed): intelligence is the research graph8 runs on YOUR OWN website
|
|
7
|
+
* (the workspace's primary website). Every response is an open payload, so each field below is optional and read
|
|
8
|
+
* defensively; a missing value is shown as unknown (a dash), never as a guess and never as 0.
|
|
9
|
+
*
|
|
10
|
+
* SDK operations: none (types and pure helpers).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type Rec = Record<string, unknown>;
|
|
14
|
+
|
|
15
|
+
export const isRec = (v: unknown): v is Rec => v != null && typeof v === "object" && !Array.isArray(v);
|
|
16
|
+
export const asArray = (v: unknown): unknown[] => (Array.isArray(v) ? v : []);
|
|
17
|
+
|
|
18
|
+
/** A non-empty string (numbers are formatted), else null. */
|
|
19
|
+
export function text(v: unknown): string | null {
|
|
20
|
+
if (typeof v === "string") return v.trim() ? v.trim() : null;
|
|
21
|
+
if (typeof v === "number" && Number.isFinite(v)) return String(v);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function num(v: unknown): number | null {
|
|
26
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Read a body that may or may not be wrapped in `{ data }`. */
|
|
30
|
+
export function intelBody<T>(env: unknown): T {
|
|
31
|
+
const e = env as { data?: unknown } | null;
|
|
32
|
+
return (e && typeof e === "object" && "data" in e && e.data != null ? e.data : env) as T;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The rows of a list payload: a bare array, `{ items }`, or a named key (`documents`, `reports`). */
|
|
36
|
+
export function rowsOf<T = Rec>(payload: unknown, ...keys: string[]): T[] {
|
|
37
|
+
if (Array.isArray(payload)) return payload as T[];
|
|
38
|
+
if (!isRec(payload)) return [];
|
|
39
|
+
for (const k of ["items", ...keys]) if (Array.isArray(payload[k])) return payload[k] as T[];
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A host as a person types it: strips the scheme, www., port-less path and query. `https://www.Acme.com/x` -> `acme.com`. */
|
|
44
|
+
export function canonicalDomain(input: string | null | undefined): string {
|
|
45
|
+
return (input ?? "").trim().toLowerCase().replace(/^https?:\/\//, "").replace(/^www\./, "").split(/[/?#]/)[0];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** http(s) URLs only: the API puts web content in these payloads, and a `javascript:` URL would run on click. */
|
|
49
|
+
export function safeHttpUrl(value: unknown): string | null {
|
|
50
|
+
const s = text(value);
|
|
51
|
+
if (!s || !/^https?:\/\/[^\s/]+/i.test(s)) return null;
|
|
52
|
+
try {
|
|
53
|
+
const u = new URL(s);
|
|
54
|
+
return u.protocol === "http:" || u.protocol === "https:" ? u.toString() : null;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function humanize(key: string): string {
|
|
61
|
+
const s = key.replace(/[_-]+/g, " ").trim();
|
|
62
|
+
return s ? s.charAt(0).toUpperCase() + s.slice(1) : key;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** A key as a title: `brand_sentiment` -> `Brand Sentiment`, matching the display names the API gives collected sources. */
|
|
66
|
+
export function titleize(key: string): string {
|
|
67
|
+
return key.replace(/[_-]+/g, " ").trim().replace(/\b\w/g, (c) => c.toUpperCase());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function when(v: unknown): string | null {
|
|
71
|
+
const s = text(v);
|
|
72
|
+
if (!s) return null;
|
|
73
|
+
const d = new Date(s);
|
|
74
|
+
return Number.isNaN(d.getTime()) ? s : d.toLocaleString();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Date part only, in UTC, so the same instant reads the same in every time zone. */
|
|
78
|
+
export function whenDate(v: unknown): string | null {
|
|
79
|
+
const s = text(v);
|
|
80
|
+
if (!s) return null;
|
|
81
|
+
const d = new Date(s);
|
|
82
|
+
return Number.isNaN(d.getTime()) ? s : d.toLocaleDateString(undefined, { timeZone: "UTC" });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---- research run (intelligence) -------------------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
export interface RunStatus {
|
|
88
|
+
task_id?: string;
|
|
89
|
+
status?: string;
|
|
90
|
+
progress?: number;
|
|
91
|
+
current_step?: string | null;
|
|
92
|
+
steps?: Record<string, string | null>;
|
|
93
|
+
website_url?: string;
|
|
94
|
+
created_at?: string | null;
|
|
95
|
+
completed_at?: string | null;
|
|
96
|
+
error_message?: string | null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface LatestTask { task_id?: string; website_url?: string | null; status?: string; created_at?: string | null; completed_at?: string | null }
|
|
100
|
+
|
|
101
|
+
export interface AnalyzeAccepted { task_id?: string; message?: string; website_url?: string; domain_sync_warning?: string | null }
|
|
102
|
+
|
|
103
|
+
export interface IntelDocument {
|
|
104
|
+
id?: string;
|
|
105
|
+
data_type: string;
|
|
106
|
+
display_name?: string | null;
|
|
107
|
+
folder?: string | null;
|
|
108
|
+
website_url?: string | null;
|
|
109
|
+
status?: string | null;
|
|
110
|
+
collected_at?: string | null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** A run is final once it stops pending or running. */
|
|
114
|
+
export const isRunFinal = (status: string | undefined | null): boolean => status != null && status !== "pending" && status !== "running" && status !== "in_progress";
|
|
115
|
+
|
|
116
|
+
/** The steps of a run, in the order the API lists them, with their state. */
|
|
117
|
+
export function runSteps(run: RunStatus | undefined): Array<{ key: string; label: string; state: string }> {
|
|
118
|
+
return Object.entries(run?.steps ?? {}).map(([key, state]) => ({ key, label: humanize(key), state: state ?? "pending" }));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const stepTone = (state: string): "positive" | "negative" | "info" | "neutral" =>
|
|
122
|
+
state === "completed" ? "positive" : state === "failed" ? "negative" : state === "running" || state === "in_progress" ? "info" : "neutral";
|
|
123
|
+
|
|
124
|
+
/** True when a run finished less than an hour ago: the API answers a repeat request with that run and charges nothing (no `force`). */
|
|
125
|
+
export function finishedWithinHour(task: LatestTask | null | undefined, now = Date.now()): boolean {
|
|
126
|
+
const at = task?.completed_at ? Date.parse(task.completed_at) : NaN;
|
|
127
|
+
return task?.status === "completed" && Number.isFinite(at) && now - at < 3_600_000 && now - at >= 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---- the six research reports ----------------------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
/** The report types the retry operation accepts (its request enum, copied from the contract), by report number 1 to 6. */
|
|
133
|
+
export const REPORT_TYPES = ["review_sentiment", "competitive_teardown", "voice_of_customer", "gtm_channel", "analyst_media", "buyer_psychology"] as const;
|
|
134
|
+
export type ReportType = (typeof REPORT_TYPES)[number];
|
|
135
|
+
|
|
136
|
+
export interface ReportSlot {
|
|
137
|
+
id: string | null;
|
|
138
|
+
report_number: number;
|
|
139
|
+
report_type: string;
|
|
140
|
+
report_title?: string | null;
|
|
141
|
+
status: string;
|
|
142
|
+
has_content?: boolean;
|
|
143
|
+
approved?: boolean;
|
|
144
|
+
created_at?: string | null;
|
|
145
|
+
started_at?: string | null;
|
|
146
|
+
completed_at?: string | null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface WorkflowStatus {
|
|
150
|
+
website_url?: string;
|
|
151
|
+
workflow_status?: string;
|
|
152
|
+
reports_completed?: number;
|
|
153
|
+
total_reports?: number;
|
|
154
|
+
current_report?: string | null;
|
|
155
|
+
reports?: Record<string, ReportSlot>;
|
|
156
|
+
started_at?: string | null;
|
|
157
|
+
completed_at?: string | null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** The six slots in report order. `reports` is keyed `report_1`..`report_6`. */
|
|
161
|
+
export function slotsOf(w: WorkflowStatus | undefined): ReportSlot[] {
|
|
162
|
+
const map = w?.reports ?? {};
|
|
163
|
+
return Object.keys(map).sort((a, b) => Number(a.replace(/\D/g, "")) - Number(b.replace(/\D/g, ""))).map((k) => map[k]);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const isGenerating = (status: string | undefined | null): boolean => status === "generating" || status === "in_progress";
|
|
167
|
+
export const isReportDone = (status: string | undefined | null): boolean => status === "completed" || status === "approved";
|
|
168
|
+
|
|
169
|
+
/** Reports a workflow resume would generate: every slot that is not finished and is not already running. */
|
|
170
|
+
export function incompleteSlots(slots: ReportSlot[]): ReportSlot[] {
|
|
171
|
+
return slots.filter((s) => !isReportDone(s.status) && !isGenerating(s.status));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface ReportDetailBody {
|
|
175
|
+
id?: string;
|
|
176
|
+
report_title?: string | null;
|
|
177
|
+
report_type?: string;
|
|
178
|
+
report_number?: number;
|
|
179
|
+
website_url?: string;
|
|
180
|
+
status?: string;
|
|
181
|
+
markdown_content?: string | null;
|
|
182
|
+
error_message?: string | null;
|
|
183
|
+
approved?: boolean;
|
|
184
|
+
approved_at?: string | null;
|
|
185
|
+
estimated_cost_usd?: number | null;
|
|
186
|
+
credits_used?: number | null;
|
|
187
|
+
created_at?: string | null;
|
|
188
|
+
updated_at?: string | null;
|
|
189
|
+
completed_at?: string | null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** What the report used the last time it ran: a real number from the report itself, or null when the API did not say. */
|
|
193
|
+
export function lastRunCredits(r: ReportDetailBody | undefined): number | null {
|
|
194
|
+
const v = num(r?.credits_used) ?? num(r?.estimated_cost_usd);
|
|
195
|
+
return v != null && v > 0 ? v : null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The PATCH body for an edited report: only what changed. Clearing the text sends "" (the API only ignores null).
|
|
200
|
+
* Returns null when nothing changed.
|
|
201
|
+
*/
|
|
202
|
+
export function reportEdit(original: string | null | undefined, draft: string): { markdown_content: string } | null {
|
|
203
|
+
return (original ?? "") === draft ? null : { markdown_content: draft };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface ThinkingEvent { type?: string; timestamp?: string; data?: Rec | null }
|
|
207
|
+
|
|
208
|
+
/** One readable line for a recorded reasoning event, from the fields the stream writes (`message`, tool name, error). */
|
|
209
|
+
export function eventLine(e: ThinkingEvent): string {
|
|
210
|
+
const d = isRec(e.data) ? e.data : {};
|
|
211
|
+
return text(d.message) ?? text(d.tool) ?? text(d.tool_name) ?? text(d.error) ?? text(d.status) ?? humanize(e.type ?? "event");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ---- wiki (global context) documents ---------------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
export interface WikiDoc {
|
|
217
|
+
id: string;
|
|
218
|
+
file_type?: string | null;
|
|
219
|
+
display_name?: string | null;
|
|
220
|
+
category?: string | null;
|
|
221
|
+
folder_path?: string | null;
|
|
222
|
+
status?: string | null;
|
|
223
|
+
version?: number | null;
|
|
224
|
+
content?: string | null;
|
|
225
|
+
credits_used?: number | null;
|
|
226
|
+
updated_at?: string | null;
|
|
227
|
+
is_done?: boolean;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export const docTitle = (d: Pick<WikiDoc, "display_name" | "file_type" | "id">): string => text(d.display_name) ?? (d.file_type ? humanize(d.file_type) : d.id);
|