@graph8/sdk 0.370.0 → 0.374.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -3
- package/blocks/_foundation/README.md +61 -0
- package/blocks/_foundation/block.json +57 -0
- package/blocks/_foundation/components/auth-gate.tsx +363 -0
- package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
- package/blocks/_foundation/components/empty-state.tsx +5 -2
- package/blocks/_foundation/components/list-select.tsx +112 -0
- package/blocks/_foundation/components/paging-notices.tsx +55 -0
- package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
- package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
- package/blocks/_foundation/components/screen/index.ts +1 -0
- package/blocks/_foundation/components/screen/split-view.tsx +1 -1
- package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
- package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
- package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
- package/blocks/_foundation/lib/paging.ts +57 -0
- package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
- package/blocks/_foundation/server/auth.ts +815 -0
- package/blocks/agents/README.md +81 -4
- package/blocks/agents/block.json +311 -2
- package/blocks/agents/components/agent-activity.tsx +112 -0
- package/blocks/agents/components/agent-approvals.tsx +135 -0
- package/blocks/agents/components/agent-builder.tsx +17 -3
- package/blocks/agents/components/agent-conversations.tsx +143 -0
- package/blocks/agents/components/agent-knowledge-panel.tsx +2 -2
- package/blocks/agents/components/agent-operators.tsx +164 -0
- package/blocks/agents/components/agent-run-detail.tsx +20 -3
- package/blocks/agents/components/agent-runs.tsx +3 -3
- package/blocks/agents/components/agent-skills-panel.tsx +7 -7
- package/blocks/agents/components/agent-test-history.tsx +121 -0
- package/blocks/agents/components/agents-screen.tsx +53 -11
- package/blocks/agents/components/assistant-sessions.tsx +150 -0
- package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
- package/blocks/agents/components/from-template-button.tsx +65 -0
- package/blocks/agents/components/knowledge-browser.tsx +107 -0
- package/blocks/agents/components/knowledge-detail.tsx +156 -0
- package/blocks/agents/components/knowledge-form.tsx +69 -0
- package/blocks/agents/components/name-dialog.tsx +32 -0
- package/blocks/agents/components/receptionist-button.tsx +46 -0
- package/blocks/agents/components/save-as-template-button.tsx +32 -0
- package/blocks/agents/components/skill-editor.tsx +147 -0
- package/blocks/agents/components/skill-library.tsx +111 -0
- package/blocks/agents/lib/agent-types.ts +7 -1
- package/blocks/agents/lib/facts.tsx +13 -0
- package/blocks/agents/lib/knowledge-utils.ts +49 -0
- package/blocks/agents/lib/load-more.tsx +141 -0
- package/blocks/agents/lib/skill-utils.ts +91 -0
- package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
- package/blocks/analytics/components/analytics-screen.tsx +16 -3
- package/blocks/analytics/components/kpi-tiles.tsx +1 -1
- package/blocks/analytics/components/report-builder.tsx +15 -2
- package/blocks/appointments/README.md +6 -0
- package/blocks/appointments/block.json +27 -0
- package/blocks/appointments/components/booking-page.tsx +2 -2
- package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
- package/blocks/appointments/components/meeting-detail.tsx +2 -2
- package/blocks/appointments/components/meeting-list.tsx +29 -13
- package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
- package/blocks/campaigns/README.md +1 -1
- package/blocks/campaigns/block.json +34 -1
- package/blocks/campaigns/components/campaign-assets.tsx +1 -1
- package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
- package/blocks/campaigns/components/campaign-links.tsx +180 -0
- package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
- package/blocks/campaigns/components/campaign-wizard.tsx +10 -5
- package/blocks/campaigns/components/icp-detail.tsx +11 -2
- package/blocks/campaigns/components/icp-editor.tsx +8 -3
- package/blocks/campaigns/components/persona-detail.tsx +11 -2
- package/blocks/campaigns/components/persona-editor.tsx +6 -1
- package/blocks/campaigns/lib/campaigns-utils.ts +4 -4
- package/blocks/coach/components/call-scorecard.tsx +7 -7
- package/blocks/company-intelligence/README.md +132 -0
- package/blocks/company-intelligence/block.json +318 -0
- package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
- package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
- package/blocks/company-intelligence/components/company-profile.tsx +68 -0
- package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
- package/blocks/company-intelligence/components/report-detail.tsx +189 -0
- package/blocks/company-intelligence/components/research-reports.tsx +136 -0
- package/blocks/company-intelligence/components/research-run.tsx +165 -0
- package/blocks/company-intelligence/components/source-detail.tsx +117 -0
- package/blocks/company-intelligence/components/source-view.tsx +274 -0
- package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
- package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
- package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
- package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
- package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
- package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
- package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
- package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
- package/blocks/copilot/components/copilot-memory.tsx +6 -2
- package/blocks/copilot/components/copilot-panel.tsx +1 -1
- package/blocks/copilot/components/copilot-team-chat.tsx +6 -1
- package/blocks/crm/README.md +25 -0
- package/blocks/crm/block.json +257 -9
- package/blocks/crm/components/bulk-update.tsx +119 -0
- package/blocks/crm/components/companies-screen.tsx +23 -9
- package/blocks/crm/components/company-detail.tsx +2 -2
- package/blocks/crm/components/contact-detail.tsx +4 -4
- package/blocks/crm/components/contact-documents-tab.tsx +144 -0
- package/blocks/crm/components/contact-insights-tab.tsx +118 -0
- package/blocks/crm/components/contact-lookup-link.tsx +85 -0
- package/blocks/crm/components/contact-row-actions.tsx +43 -0
- package/blocks/crm/components/contact-tag-chips.tsx +113 -0
- package/blocks/crm/components/contacts-screen.tsx +77 -18
- package/blocks/crm/components/dedupe-review.tsx +6 -1
- package/blocks/crm/components/import-wizard.tsx +3 -7
- package/blocks/crm/components/list-select.tsx +20 -0
- package/blocks/crm/components/owner-assign.tsx +132 -0
- package/blocks/crm/components/record-engagement-tab.tsx +103 -0
- package/blocks/crm/components/record-form.tsx +1 -1
- package/blocks/crm/components/record-open-links.tsx +79 -0
- package/blocks/crm/components/tag-assign.tsx +112 -0
- package/blocks/crm/components/tag-manager.tsx +178 -0
- package/blocks/crm/components/tasks-screen.tsx +35 -15
- package/blocks/crm/components/unlock-info-button.tsx +82 -0
- package/blocks/crm/components/verify-email-button.tsx +93 -0
- package/blocks/crm/hooks/use-all-lists.ts +18 -0
- package/blocks/crm/hooks/use-contact-tags.ts +29 -0
- package/blocks/crm/hooks/use-page-query.ts +3 -1
- package/blocks/crm/lib/crm-types.ts +14 -0
- package/blocks/crm/lib/read-lists.ts +16 -0
- package/blocks/crm/lib/record-actions.ts +102 -0
- package/blocks/deals/README.md +25 -6
- package/blocks/deals/block.json +168 -2
- package/blocks/deals/components/close-deal-dialog.tsx +169 -0
- package/blocks/deals/components/create-deal-button.tsx +109 -0
- package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
- package/blocks/deals/components/deal-detail.tsx +7 -5
- package/blocks/deals/components/deal-filters.tsx +65 -0
- package/blocks/deals/components/deal-line-items.tsx +120 -0
- package/blocks/deals/components/deal-notes-tab.tsx +97 -0
- package/blocks/deals/components/deal-related.tsx +30 -9
- package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
- package/blocks/deals/components/deals-screen.tsx +57 -22
- package/blocks/deals/components/forecast-view.tsx +4 -4
- package/blocks/deals/components/pipeline-board.tsx +8 -6
- package/blocks/deals/components/pipeline-editor.tsx +132 -0
- package/blocks/deals/components/record-deals-tab.tsx +62 -0
- package/blocks/deals/components/stage-editor.tsx +1 -1
- package/blocks/deals/hooks/use-pipeline-deals.ts +17 -4
- package/blocks/deals/lib/deal-extras.ts +156 -0
- package/blocks/dialer/README.md +14 -5
- package/blocks/dialer/block.json +17 -2
- package/blocks/dialer/components/call-contact-button.tsx +26 -9
- package/blocks/dialer/components/call-log-screen.tsx +10 -2
- package/blocks/dialer/components/call-log.tsx +16 -4
- package/blocks/dialer/components/call-queue.tsx +3 -3
- package/blocks/dialer/components/dialer-screen.tsx +69 -27
- package/blocks/dialer/components/disposition-panel.tsx +1 -1
- package/blocks/dialer/components/record-calls-tab.tsx +101 -0
- package/blocks/dialer/components/session-start-form.tsx +58 -11
- package/blocks/dialer/components/softphone.tsx +1 -1
- package/blocks/documents/README.md +86 -0
- package/blocks/documents/block.json +378 -0
- package/blocks/documents/components/account-bar.tsx +48 -0
- package/blocks/documents/components/correspondence-form.tsx +95 -0
- package/blocks/documents/components/correspondence-import.tsx +106 -0
- package/blocks/documents/components/correspondence-panel.tsx +191 -0
- package/blocks/documents/components/documents-screen.tsx +76 -0
- package/blocks/documents/components/library-document-detail.tsx +175 -0
- package/blocks/documents/components/library-document-form.tsx +105 -0
- package/blocks/documents/components/library-folder-form.tsx +74 -0
- package/blocks/documents/components/library-panel.tsx +235 -0
- package/blocks/documents/components/library-upload.tsx +160 -0
- package/blocks/documents/components/question-row.tsx +130 -0
- package/blocks/documents/components/questionnaire-detail.tsx +197 -0
- package/blocks/documents/components/questionnaires-panel.tsx +173 -0
- package/blocks/documents/components/record-documents-tab.tsx +109 -0
- package/blocks/documents/lib/documents-types.ts +252 -0
- package/blocks/documents/lib/download.ts +64 -0
- package/blocks/documents/recipes/follow-a-questionnaire.ts +26 -0
- package/blocks/documents/recipes/log-account-correspondence.ts +27 -0
- package/blocks/documents/recipes/organize-the-library.ts +25 -0
- package/blocks/email-signatures/README.md +123 -0
- package/blocks/email-signatures/block.json +341 -0
- package/blocks/email-signatures/components/email-signatures-screen.tsx +166 -0
- package/blocks/email-signatures/components/signature-assign-template.tsx +119 -0
- package/blocks/email-signatures/components/signature-block-editor.tsx +87 -0
- package/blocks/email-signatures/components/signature-campaign-form.tsx +165 -0
- package/blocks/email-signatures/components/signature-campaigns.tsx +75 -0
- package/blocks/email-signatures/components/signature-identity-form.tsx +131 -0
- package/blocks/email-signatures/components/signature-mailboxes.tsx +195 -0
- package/blocks/email-signatures/components/signature-policy.tsx +130 -0
- package/blocks/email-signatures/components/signature-preview.tsx +119 -0
- package/blocks/email-signatures/components/signature-report.tsx +77 -0
- package/blocks/email-signatures/components/signature-template-details.tsx +112 -0
- package/blocks/email-signatures/components/signature-template-form.tsx +109 -0
- package/blocks/email-signatures/hooks/use-signatures-data.ts +93 -0
- package/blocks/email-signatures/lib/signatures-types.ts +392 -0
- package/blocks/email-signatures/recipes/assign-a-signature-to-a-new-hire.ts +32 -0
- package/blocks/email-signatures/recipes/create-a-template-and-make-it-default.ts +35 -0
- package/blocks/email-signatures/recipes/signature-engagement-report.ts +16 -0
- package/blocks/enrichment/README.md +38 -10
- package/blocks/enrichment/block.json +164 -12
- package/blocks/enrichment/components/auto-enrichment-configs.tsx +134 -0
- package/blocks/enrichment/components/auto-enrichment-settings.tsx +82 -0
- package/blocks/enrichment/components/bulk-enrich-panel.tsx +79 -30
- package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
- package/blocks/enrichment/components/enrich-button.tsx +1 -1
- package/blocks/enrichment/components/enrichment-activity.tsx +93 -0
- package/blocks/enrichment/components/enrichment-job-detail.tsx +18 -0
- package/blocks/enrichment/components/enrichment-screen.tsx +103 -19
- package/blocks/enrichment/components/list-pipelines.tsx +97 -0
- package/blocks/enrichment/components/pipeline-detail.tsx +214 -0
- package/blocks/enrichment/components/pipeline-editor.tsx +144 -0
- package/blocks/enrichment/components/pipeline-templates.tsx +90 -0
- package/blocks/enrichment/components/waterfall-config.tsx +27 -7
- package/blocks/enrichment/lib/enrichment-types.ts +51 -0
- package/blocks/enrichment/lib/pipeline-types.ts +234 -0
- package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
- package/blocks/forms/README.md +10 -0
- package/blocks/forms/block.json +107 -0
- package/blocks/forms/components/form-activity.tsx +71 -0
- package/blocks/forms/components/form-backfill.tsx +108 -0
- package/blocks/forms/components/form-builder.tsx +1 -1
- package/blocks/forms/components/form-embed.tsx +1 -1
- package/blocks/forms/components/form-groups.tsx +151 -0
- package/blocks/forms/components/form-mapping-editor.tsx +6 -9
- package/blocks/forms/components/form-route-history.tsx +72 -0
- package/blocks/forms/components/form-saved-views.tsx +81 -0
- package/blocks/forms/components/forms-screen.tsx +57 -20
- package/blocks/forms/components/more-rows.tsx +30 -0
- package/blocks/forms/components/submissions-export.tsx +67 -0
- package/blocks/forms/components/submissions-table.tsx +15 -6
- package/blocks/forms/lib/forms-extra.ts +67 -0
- package/blocks/inbox/README.md +45 -4
- package/blocks/inbox/block.json +145 -8
- package/blocks/inbox/components/blocked-senders.tsx +103 -0
- package/blocks/inbox/components/compose-message.tsx +193 -0
- package/blocks/inbox/components/inbox-screen.tsx +54 -6
- package/blocks/inbox/components/inbox-search.tsx +106 -0
- package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
- package/blocks/inbox/components/tag-manager.tsx +136 -0
- package/blocks/inbox/components/thread-actions.tsx +133 -0
- package/blocks/inbox/components/thread-badge.tsx +2 -2
- package/blocks/inbox/components/thread-view.tsx +3 -1
- package/blocks/inbox/hooks/use-inbox-mailboxes.ts +31 -0
- package/blocks/inbox/lib/inbox-manage.ts +227 -0
- package/blocks/integrations/README.md +54 -1
- package/blocks/integrations/block.json +189 -15
- package/blocks/integrations/components/ad-accounts.tsx +82 -0
- package/blocks/integrations/components/connection-health.tsx +59 -0
- package/blocks/integrations/components/crm-detail.tsx +20 -1
- package/blocks/integrations/components/crm-records.tsx +103 -0
- package/blocks/integrations/components/field-mappings.tsx +61 -0
- package/blocks/integrations/components/integration-list.tsx +4 -4
- package/blocks/integrations/components/integrations-screen.tsx +20 -3
- package/blocks/integrations/components/migration-panel.tsx +133 -0
- package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
- package/blocks/integrations/components/sync-status.tsx +6 -6
- package/blocks/integrations/components/sync-triggers.tsx +134 -0
- package/blocks/integrations/components/webhook-detail.tsx +26 -7
- package/blocks/integrations/components/webhook-manager.tsx +30 -17
- package/blocks/integrations/components/workspace-tools.tsx +127 -0
- package/blocks/integrations/lib/crm-utils.ts +39 -0
- package/blocks/integrations/lib/facts.tsx +13 -0
- package/blocks/integrations/lib/integrations-utils.ts +5 -0
- package/blocks/integrations/lib/load-more.tsx +141 -0
- package/blocks/intent/components/account-intent-score.tsx +5 -5
- package/blocks/intent/components/intent-feed.tsx +8 -8
- package/blocks/intent/components/intent-keyword-manager.tsx +8 -8
- package/blocks/intent/lib/intent-utils.ts +4 -4
- package/blocks/lifecycle/components/account-related.tsx +8 -1
- package/blocks/lifecycle/components/churn-list.tsx +5 -2
- package/blocks/lifecycle/components/customer-table.tsx +1 -1
- package/blocks/lists/README.md +7 -1
- package/blocks/lists/block.json +31 -3
- package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
- package/blocks/lists/components/add-to-list-button.tsx +17 -7
- package/blocks/lists/components/list-picker.tsx +13 -3
- package/blocks/lists/components/list-table.tsx +1 -1
- package/blocks/lists/components/lists-screen.tsx +4 -2
- package/blocks/lists/components/record-lists-tab.tsx +99 -0
- package/blocks/lists/components/suppression-manager.tsx +27 -6
- package/blocks/lists/components/suppressions-screen.tsx +21 -12
- package/blocks/mailboxes/block.json +4 -0
- package/blocks/mailboxes/components/connect-mailbox.tsx +9 -8
- package/blocks/mailboxes/components/domain-detail.tsx +10 -4
- package/blocks/mailboxes/components/domain-purchase.tsx +14 -11
- package/blocks/mailboxes/components/mailbox-detail.tsx +3 -2
- package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
- package/blocks/mailboxes/components/mailboxes-screen.tsx +8 -3
- package/blocks/mailboxes/components/warmup-health.tsx +20 -20
- package/blocks/mailboxes/lib/mailboxes-utils.ts +4 -4
- package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
- package/blocks/newsletter/README.md +12 -0
- package/blocks/newsletter/block.json +117 -0
- package/blocks/newsletter/components/issue-scheduler.tsx +2 -2
- package/blocks/newsletter/components/issue-versions.tsx +93 -0
- package/blocks/newsletter/components/more-rows.tsx +30 -0
- package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
- package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
- package/blocks/newsletter/components/newsletter-screen.tsx +38 -13
- package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
- package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
- package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
- package/blocks/newsletter/components/subscriber-table.tsx +5 -7
- package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
- package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
- package/blocks/nurture/components/nurture-builder.tsx +10 -2
- package/blocks/nurture/components/nurture-enroll.tsx +6 -7
- package/blocks/nurture/components/nurture-form.tsx +5 -8
- package/blocks/nurture/components/nurture-screen.tsx +16 -5
- package/blocks/nurture/components/nurture-stats.tsx +5 -4
- package/blocks/objects/README.md +1 -1
- package/blocks/objects/block.json +13 -5
- package/blocks/objects/components/attribute-field-input.tsx +14 -1
- package/blocks/objects/components/object-record-detail.tsx +8 -1
- package/blocks/objects/components/object-schema-editor.tsx +1 -1
- package/blocks/objects/components/record-roles-panel.tsx +15 -5
- package/blocks/pages/README.md +14 -0
- package/blocks/pages/block.json +123 -2
- package/blocks/pages/components/more-rows.tsx +30 -0
- package/blocks/pages/components/page-ai-tools.tsx +196 -0
- package/blocks/pages/components/page-analytics.tsx +176 -0
- package/blocks/pages/components/page-clone.tsx +99 -0
- package/blocks/pages/components/page-domains.tsx +167 -0
- package/blocks/pages/components/page-editor.tsx +1 -1
- package/blocks/pages/components/page-generation-status.tsx +48 -0
- package/blocks/pages/components/page-tracking.tsx +137 -0
- package/blocks/pages/components/pages-screen.tsx +57 -16
- package/blocks/pages/components/seo-report.tsx +6 -1
- package/blocks/pages/hooks/use-landing-page-paths.ts +60 -0
- package/blocks/pages/lib/studio.ts +7 -0
- package/blocks/phone-numbers/README.md +74 -0
- package/blocks/phone-numbers/block.json +262 -0
- package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
- package/blocks/phone-numbers/components/assign-number.tsx +104 -0
- package/blocks/phone-numbers/components/number-search.tsx +239 -0
- package/blocks/phone-numbers/components/number-settings.tsx +134 -0
- package/blocks/phone-numbers/components/number-usage.tsx +97 -0
- package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
- package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
- package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
- package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
- package/blocks/phone-numbers/components/release-number.tsx +126 -0
- package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
- package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
- package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
- package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
- package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
- package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
- package/blocks/pickers/README.md +23 -0
- package/blocks/pickers/block.json +103 -0
- package/blocks/pickers/components/company-picker.tsx +90 -0
- package/blocks/pickers/components/contact-picker.tsx +131 -0
- package/blocks/pickers/components/member-picker.tsx +99 -0
- package/blocks/pickers/components/search-picker.tsx +98 -0
- package/blocks/pickers/hooks/use-current-member.ts +104 -0
- package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
- package/blocks/pickers/lib/use-debounced.ts +13 -0
- package/blocks/programmatic-pages/README.md +82 -0
- package/blocks/programmatic-pages/block.json +292 -0
- package/blocks/programmatic-pages/components/programmatic-campaign-form.tsx +136 -0
- package/blocks/programmatic-pages/components/programmatic-generate.tsx +112 -0
- package/blocks/programmatic-pages/components/programmatic-generated-pages.tsx +210 -0
- package/blocks/programmatic-pages/components/programmatic-overview.tsx +84 -0
- package/blocks/programmatic-pages/components/programmatic-pages-screen.tsx +221 -0
- package/blocks/programmatic-pages/components/programmatic-preview.tsx +66 -0
- package/blocks/programmatic-pages/components/programmatic-publish.tsx +102 -0
- package/blocks/programmatic-pages/components/programmatic-settings.tsx +96 -0
- package/blocks/programmatic-pages/components/programmatic-source-fields.tsx +75 -0
- package/blocks/programmatic-pages/components/programmatic-source.tsx +106 -0
- package/blocks/programmatic-pages/components/programmatic-variables.tsx +84 -0
- package/blocks/programmatic-pages/hooks/use-programmatic-workspace.ts +19 -0
- package/blocks/programmatic-pages/lib/programmatic-types.ts +330 -0
- package/blocks/programmatic-pages/recipes/create-a-programmatic-campaign.ts +23 -0
- package/blocks/programmatic-pages/recipes/render-and-follow.ts +21 -0
- package/blocks/programmatic-pages/recipes/review-then-publish.ts +17 -0
- package/blocks/quotes/README.md +38 -1
- package/blocks/quotes/block.json +170 -6
- package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
- package/blocks/quotes/components/manual-products.tsx +144 -0
- package/blocks/quotes/components/quote-batch-actions.tsx +86 -0
- package/blocks/quotes/components/quote-builder.tsx +1 -1
- package/blocks/quotes/components/quote-documents.tsx +94 -0
- package/blocks/quotes/components/quote-evidence.tsx +95 -0
- package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
- package/blocks/quotes/components/quote-list.tsx +28 -8
- package/blocks/quotes/components/quote-related.tsx +3 -2
- package/blocks/quotes/components/quote-settings.tsx +184 -0
- package/blocks/quotes/components/quote-templates.tsx +189 -0
- package/blocks/quotes/components/quote-viewer.tsx +23 -5
- package/blocks/quotes/components/quotes-screen.tsx +15 -5
- package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
- package/blocks/quotes/lib/quote-admin.ts +264 -0
- package/blocks/radar/components/competitor-battle-card.tsx +1 -1
- package/blocks/radar/components/radar-overview.tsx +4 -4
- package/blocks/radar/components/radar-screen.tsx +2 -2
- package/blocks/sequencer/README.md +61 -0
- package/blocks/sequencer/block.json +200 -6
- package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
- package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
- package/blocks/sequencer/components/enrollment-withdraw.tsx +71 -0
- package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
- package/blocks/sequencer/components/sequence-builder.tsx +32 -23
- package/blocks/sequencer/components/sequence-campaign-link.tsx +78 -0
- package/blocks/sequencer/components/sequence-detail.tsx +10 -2
- package/blocks/sequencer/components/sequence-edit-form.tsx +111 -0
- package/blocks/sequencer/components/sequence-enrollments.tsx +5 -2
- package/blocks/sequencer/components/sequence-list.tsx +1 -1
- package/blocks/sequencer/components/sequence-manage-actions.tsx +119 -0
- package/blocks/sequencer/components/sequence-senders-panel.tsx +84 -0
- package/blocks/sequencer/components/sequence-stats.tsx +8 -8
- package/blocks/sequencer/components/sequence-templates.tsx +160 -0
- package/blocks/sequencer/components/sequences-screen.tsx +39 -6
- package/blocks/sequencer/components/step-ai-assist.tsx +196 -0
- package/blocks/sequencer/components/step-editor.tsx +21 -12
- package/blocks/sequencer/components/step-templates.tsx +224 -0
- package/blocks/sequencer/lib/sequencer-manage.ts +235 -0
- package/blocks/sequencer/lib/sequencer-utils.ts +9 -5
- package/blocks/signals/README.md +39 -0
- package/blocks/signals/block.json +287 -0
- package/blocks/signals/components/auto-seed-panel.tsx +111 -0
- package/blocks/signals/components/listener-form.tsx +184 -0
- package/blocks/signals/components/matched-posts.tsx +195 -0
- package/blocks/signals/components/post-engagements.tsx +114 -0
- package/blocks/signals/components/record-social-tab.tsx +49 -0
- package/blocks/signals/components/social-listeners-screen.tsx +250 -0
- package/blocks/signals/components/social-settings.tsx +128 -0
- package/blocks/signals/components/source-lists.tsx +83 -0
- package/blocks/signals/components/top-engagers.tsx +68 -0
- package/blocks/signals/components/x-integration.tsx +129 -0
- package/blocks/signals/lib/signals-types.ts +228 -0
- package/blocks/signals/recipes/preview-and-create-a-listener.ts +16 -0
- package/blocks/signals/recipes/spend-against-caps.ts +14 -0
- package/blocks/signals/recipes/triage-matched-posts.ts +18 -0
- package/blocks/social-ads/README.md +8 -0
- package/blocks/social-ads/block.json +137 -4
- package/blocks/social-ads/components/audience-sync-detail.tsx +9 -3
- package/blocks/social-ads/components/audience-sync.tsx +6 -2
- package/blocks/social-ads/components/campaign-ads.tsx +166 -0
- package/blocks/social-ads/components/campaign-detail.tsx +6 -1
- package/blocks/social-ads/components/campaign-editor.tsx +134 -0
- package/blocks/social-ads/components/creative-concepts.tsx +74 -0
- package/blocks/social-ads/components/creative-detail.tsx +6 -1
- package/blocks/social-ads/components/creative-gallery.tsx +20 -2
- package/blocks/social-ads/components/creative-revisions.tsx +57 -0
- package/blocks/social-ads/components/creative-tools.tsx +176 -0
- package/blocks/social-ads/components/post-composer.tsx +4 -2
- package/blocks/social-ads/components/post-detail.tsx +3 -0
- package/blocks/social-ads/components/social-ideas.tsx +64 -0
- package/blocks/social-ads/components/social-performance.tsx +80 -0
- package/blocks/social-ads/components/social-screen.tsx +33 -6
- package/blocks/social-ads/lib/ads-extra.ts +67 -0
- package/blocks/surveys/README.md +91 -0
- package/blocks/surveys/block.json +299 -0
- package/blocks/surveys/components/account-nps.tsx +69 -0
- package/blocks/surveys/components/contact-survey-history.tsx +52 -0
- package/blocks/surveys/components/org-summary.tsx +82 -0
- package/blocks/surveys/components/sending-domains.tsx +113 -0
- package/blocks/surveys/components/survey-form.tsx +126 -0
- package/blocks/surveys/components/survey-responses.tsx +145 -0
- package/blocks/surveys/components/survey-results.tsx +72 -0
- package/blocks/surveys/components/survey-send-history.tsx +58 -0
- package/blocks/surveys/components/survey-send.tsx +162 -0
- package/blocks/surveys/components/survey-triggers.tsx +166 -0
- package/blocks/surveys/components/surveys-screen.tsx +234 -0
- package/blocks/surveys/lib/surveys-types.ts +228 -0
- package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
- package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
- package/blocks/surveys/recipes/survey-score-report.ts +27 -0
- package/blocks/visitors/components/snippet-install.tsx +18 -9
- package/blocks/visitors/components/traffic-chart.tsx +7 -6
- package/blocks/visitors/components/visitor-company-card.tsx +24 -14
- package/blocks/visitors/components/visitor-feed.tsx +5 -5
- package/blocks/web-analytics/README.md +102 -0
- package/blocks/web-analytics/block.json +292 -0
- package/blocks/web-analytics/components/acquisition-tab.tsx +154 -0
- package/blocks/web-analytics/components/analytics-filters.tsx +161 -0
- package/blocks/web-analytics/components/audience-tab.tsx +135 -0
- package/blocks/web-analytics/components/conversions-tab.tsx +128 -0
- package/blocks/web-analytics/components/engagement-tab.tsx +123 -0
- package/blocks/web-analytics/components/overview-tab.tsx +102 -0
- package/blocks/web-analytics/components/pages-tab.tsx +148 -0
- package/blocks/web-analytics/components/ranked-table.tsx +103 -0
- package/blocks/web-analytics/components/realtime-tab.tsx +103 -0
- package/blocks/web-analytics/components/report-card.tsx +71 -0
- package/blocks/web-analytics/components/web-analytics-screen.tsx +90 -0
- package/blocks/web-analytics/hooks/use-report.ts +48 -0
- package/blocks/web-analytics/lib/analytics-scope.ts +155 -0
- package/blocks/web-analytics/lib/page-path.ts +14 -0
- package/blocks/web-analytics/lib/web-analytics-types.ts +187 -0
- package/blocks/web-analytics/recipes/best-landing-pages.ts +26 -0
- package/blocks/web-analytics/recipes/form-conversion-check.ts +19 -0
- package/blocks/web-analytics/recipes/weekly-traffic-report.ts +37 -0
- package/blocks/webchat/components/chat-widget-config.tsx +1 -1
- package/blocks/webchat/components/conversation-list.tsx +1 -1
- package/blocks/workbench/README.md +54 -2
- package/blocks/workbench/block.json +234 -5
- package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
- package/blocks/workbench/components/run-progress.tsx +1 -1
- package/blocks/workbench/components/workbench-auto-enrich.tsx +88 -0
- package/blocks/workbench/components/workbench-automation.tsx +36 -0
- package/blocks/workbench/components/workbench-connections.tsx +136 -0
- package/blocks/workbench/components/workbench-crm-import.tsx +91 -0
- package/blocks/workbench/components/workbench-datafile-import.tsx +154 -0
- package/blocks/workbench/components/workbench-grid.tsx +71 -8
- package/blocks/workbench/components/workbench-pipelines.tsx +173 -0
- package/blocks/workbench/components/workbench-row-actions.tsx +8 -1
- package/blocks/workbench/components/workbench-schedule.tsx +191 -0
- package/blocks/workbench/components/workbench-screen.tsx +65 -22
- package/blocks/workbench/components/workbench-send-to-sequence.tsx +116 -0
- package/blocks/workbench/components/workbench-table-list.tsx +14 -2
- package/blocks/workbench/components/workbench-table-settings.tsx +53 -0
- package/blocks/workbench/components/workbench-triggers.tsx +178 -0
- package/blocks/workbench/hooks/use-connected-crms.ts +29 -0
- package/blocks/workbench/lib/api-status.ts +8 -0
- package/blocks/workbench/lib/automation-types.ts +257 -0
- package/blocks/workflows/components/workflow-editor.tsx +1 -1
- package/dist/cli.js +744 -394
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.mjs +1 -1
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react.browser.js +1 -1
- package/dist/react.browser.js.map +1 -1
- package/dist/react.browser.mjs +1 -1
- package/dist/react.browser.mjs.map +1 -1
- package/dist/react.d.mts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +1 -1
- package/dist/react.mjs.map +1 -1
- package/dist/types/blocks/auth.d.ts +45 -0
- package/dist/types/blocks/brand.d.ts +11 -2
- package/dist/types/blocks/create.d.ts +11 -4
- package/dist/types/blocks/doctor.d.ts +1 -0
- package/dist/types/blocks/flows.d.ts +7 -1
- package/dist/types/blocks/orgBrand.d.ts +34 -0
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/version.d.ts +2 -2
- package/docs/api/desk.d.ts +2 -2
- package/docs/api/desk.md +3 -3
- package/docs/api/forms.md +1 -1
- package/package.json +1 -1
- package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
- package/stacks/6sense/pages/contacts.tsx.tmpl +21 -0
- package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
- package/stacks/6sense.json +37 -4
- package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
- package/stacks/apollo/pages/campaigns.tsx.tmpl +27 -0
- package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
- package/stacks/apollo/pages/contacts.tsx.tmpl +35 -95
- package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
- package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
- package/stacks/apollo/pages/lists.tsx.tmpl +24 -0
- package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
- package/stacks/apollo/pages/sequences.tsx.tmpl +18 -0
- package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
- package/stacks/apollo.json +68 -9
- package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
- package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
- package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
- package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
- package/stacks/calendly.json +8 -4
- package/stacks/clay/pages/companies.tsx.tmpl +17 -1
- package/stacks/clay/pages/contacts.tsx.tmpl +14 -0
- package/stacks/clay/pages/lists.tsx.tmpl +24 -0
- package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
- package/stacks/clay.json +16 -3
- package/stacks/gong/pages/calls.tsx.tmpl +23 -0
- package/stacks/gong/pages/companies.tsx.tmpl +22 -0
- package/stacks/gong/pages/contacts.tsx.tmpl +34 -0
- package/stacks/gong/pages/lists.tsx.tmpl +12 -0
- package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
- package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
- package/stacks/gong.json +23 -6
- package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
- package/stacks/hootsuite/pages/contacts.tsx.tmpl +30 -0
- package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
- package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
- package/stacks/hootsuite.json +19 -4
- package/stacks/hubspot/pages/campaigns.tsx.tmpl +30 -0
- package/stacks/hubspot/pages/companies.tsx.tmpl +16 -35
- package/stacks/hubspot/pages/contacts.tsx.tmpl +35 -95
- package/stacks/hubspot/pages/deals.tsx.tmpl +14 -1
- package/stacks/hubspot/pages/forms.tsx.tmpl +36 -0
- package/stacks/hubspot/pages/inbox.tsx.tmpl +12 -1
- package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
- package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
- package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
- package/stacks/hubspot/pages/sequences.tsx.tmpl +18 -0
- package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
- package/stacks/hubspot/pages/web-analytics.tsx.tmpl +14 -0
- package/stacks/hubspot.json +66 -7
- package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
- package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
- package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
- package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
- package/stacks/intercom.json +18 -4
- package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
- package/stacks/mailchimp/pages/contacts.tsx.tmpl +30 -0
- package/stacks/mailchimp/pages/forms.tsx.tmpl +36 -0
- package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
- package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
- package/stacks/mailchimp.json +22 -6
- package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
- package/stacks/outreach/pages/campaigns.tsx.tmpl +27 -0
- package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
- package/stacks/outreach/pages/contacts.tsx.tmpl +29 -66
- package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
- package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
- package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
- package/stacks/outreach/pages/sequences.tsx.tmpl +18 -0
- package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
- package/stacks/outreach.json +38 -9
- package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
- package/stacks/pipedrive/pages/contacts.tsx.tmpl +17 -35
- package/stacks/pipedrive/pages/deals.tsx.tmpl +10 -1
- package/stacks/pipedrive/pages/inbox.tsx.tmpl +12 -1
- package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
- package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
- package/stacks/pipedrive.json +14 -2
- package/stacks/salesforce/pages/companies.tsx.tmpl +16 -35
- package/stacks/salesforce/pages/contacts.tsx.tmpl +26 -35
- package/stacks/salesforce/pages/deals.tsx.tmpl +14 -1
- package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
- package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
- package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
- package/stacks/salesforce.json +17 -3
- package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
- package/stacks/zoominfo/pages/contacts.tsx.tmpl +16 -0
- package/stacks/zoominfo/pages/lists.tsx.tmpl +24 -0
- package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
- package/stacks/zoominfo.json +36 -3
- package/styles/styles.css +1 -1
- package/templates/auth/auth-route.ts.tmpl +16 -0
- package/templates/auth/g8.auth.anonymous.ts.tmpl +15 -0
- package/templates/auth/g8.auth.basic.ts.tmpl +17 -0
- package/templates/auth/g8.auth.jwt.ts.tmpl +23 -0
- package/templates/auth/g8.auth.password.ts.tmpl +18 -0
- package/templates/auth/g8.auth.session.ts.tmpl +23 -0
- package/templates/auth/middleware.ts.tmpl +14 -0
- package/templates/next-app/README.md.tmpl +5 -0
- package/templates/next-app/app/globals.css.tmpl +349 -64
- package/templates/next-app/components/app-shell.tsx.tmpl +6 -0
- package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SkillLibrary: every skill in the workspace (your own plus graph8's defaults) in one list. Search by name, filter by kind
|
|
5
|
+
* and by enabled, open one to edit it (SkillEditor), write a new LLM or API skill, or start from a template. A skill that no
|
|
6
|
+
* agent uses yet can be attached from an agent's Skills tab.
|
|
7
|
+
*
|
|
8
|
+
* SDK operations, in order:
|
|
9
|
+
* 1. listSkills GET /skills?runtime_type&enabled&include_system (read) returns every skill, no cap
|
|
10
|
+
* 2. listTemplates GET /skills/templates (read)
|
|
11
|
+
* 3. createFromTemplate POST /skills/from-template { template_id, name } (write)
|
|
12
|
+
* 4. SkillEditor see skill-editor.tsx (getSkill, updateSkill, deleteSkill, createSkill, validateTemplate, extractVariables, listModels)
|
|
13
|
+
*/
|
|
14
|
+
import { useMemo, useRef, useState } from "react";
|
|
15
|
+
import { Wand2 } from "lucide-react";
|
|
16
|
+
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Skeleton } from "@graph8/sdk/ui";
|
|
17
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
18
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
19
|
+
import { StatusPill } from "@/g8/components/screen/status-pill";
|
|
20
|
+
import { Toolbar } from "@/g8/components/screen/toolbar";
|
|
21
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
22
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
23
|
+
import { g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
24
|
+
import { listOf } from "../lib/agent-types";
|
|
25
|
+
import type { SkillDetail } from "../lib/skill-utils";
|
|
26
|
+
import { skillOf } from "../lib/skill-utils";
|
|
27
|
+
import { SkillEditor } from "./skill-editor";
|
|
28
|
+
import type { SkillEditorTarget } from "./skill-editor";
|
|
29
|
+
|
|
30
|
+
export interface SkillLibraryProps {
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface Template { template_id?: string; action_id?: string; id?: string; name: string; description?: string | null; runtime_type?: string }
|
|
35
|
+
const tid = (t: Template): string => t.template_id ?? t.action_id ?? t.id ?? "";
|
|
36
|
+
const KINDS = [{ id: "", label: "All" }, { id: "llm", label: "LLM" }, { id: "api", label: "API" }];
|
|
37
|
+
const STATES = [{ id: "", label: "Any state" }, { id: "true", label: "Enabled" }, { id: "false", label: "Disabled" }];
|
|
38
|
+
|
|
39
|
+
function FromTemplate({ open, onClose, onCreated }: { open: boolean; onClose: () => void; onCreated: (id?: string) => void }) {
|
|
40
|
+
const [picked, setPicked] = useState<Template | null>(null);
|
|
41
|
+
const [name, setName] = useState("");
|
|
42
|
+
const made = useRef<{ id?: string } | null>(null);
|
|
43
|
+
const templates = useG8Query((signal) => g8Get("/skills/templates", undefined, { signal }), [], { enabled: open });
|
|
44
|
+
const create = useG8Mutation(async (v: { id: string; name: string }) => {
|
|
45
|
+
if (made.current) return made.current.id;
|
|
46
|
+
const id = skillOf(await g8Post("/skills/from-template", { template_id: v.id, name: v.name }))?.action_id;
|
|
47
|
+
made.current = { id };
|
|
48
|
+
return id;
|
|
49
|
+
}, { onSuccess: (id) => { made.current = null; setPicked(null); onCreated(id); } });
|
|
50
|
+
const rows = listOf<Template>(templates.data, "actions", "templates", "items");
|
|
51
|
+
return (
|
|
52
|
+
<Dialog open={open} onOpenChange={(o) => !o && !create.loading && onClose()}>
|
|
53
|
+
<DialogContent>
|
|
54
|
+
<DialogHeader><DialogTitle>New skill from a template</DialogTitle><DialogDescription>Templates are ready-made skills. Name yours and edit it afterwards.</DialogDescription></DialogHeader>
|
|
55
|
+
{templates.error ? <ApiError error={templates.error} title="Could not load templates" onRetry={() => void templates.refetch()} /> : null}
|
|
56
|
+
{templates.loading && !templates.data ? <Skeleton className="h-16 w-full" /> : null}
|
|
57
|
+
<ul className="max-h-64 divide-y overflow-y-auto rounded-md border">
|
|
58
|
+
{rows.map((t) => (
|
|
59
|
+
<li key={tid(t)}><button type="button" aria-pressed={picked != null && tid(picked) === tid(t)} className={`block w-full px-3 py-2 text-left text-sm ${picked != null && tid(picked) === tid(t) ? "bg-accent" : ""}`} onClick={() => { made.current = null; setPicked(t); setName(t.name); }}>
|
|
60
|
+
<span className="font-medium">{t.name}</span>{t.runtime_type ? <span className="ml-2 text-xs text-muted-foreground">{t.runtime_type}</span> : null}
|
|
61
|
+
{t.description ? <span className="block text-xs text-muted-foreground">{t.description}</span> : null}
|
|
62
|
+
</button></li>
|
|
63
|
+
))}
|
|
64
|
+
</ul>
|
|
65
|
+
{!templates.loading && !templates.error && rows.length === 0 ? <p className="text-sm text-muted-foreground">There are no templates yet.</p> : null}
|
|
66
|
+
{picked ? <div className="flex flex-col gap-1"><Label htmlFor="skill-from-template">Name</Label><Input id="skill-from-template" value={name} onChange={(e) => setName(e.target.value)} /></div> : null}
|
|
67
|
+
{create.error ? <ApiError error={create.error} title="Could not create the skill" /> : null}
|
|
68
|
+
<DialogFooter><Button variant="ghost" disabled={create.loading} onClick={onClose}>Cancel</Button><Button loading={create.loading} disabled={!picked || !name.trim()} onClick={() => picked && void create.mutate({ id: tid(picked), name: name.trim() })}>Create skill</Button></DialogFooter>
|
|
69
|
+
</DialogContent>
|
|
70
|
+
</Dialog>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function SkillLibrary({ className }: SkillLibraryProps) {
|
|
75
|
+
const [search, setSearch] = useState("");
|
|
76
|
+
const [kind, setKind] = useState("");
|
|
77
|
+
const [state, setState] = useState("");
|
|
78
|
+
const [target, setTarget] = useState<SkillEditorTarget | null>(null);
|
|
79
|
+
const [fromTemplate, setFromTemplate] = useState(false);
|
|
80
|
+
const skills = useG8Query((signal) => g8Get("/skills", { runtime_type: kind || undefined, enabled: state || undefined, include_system: true }, { signal }), [kind, state]);
|
|
81
|
+
const all = listOf<SkillDetail>(skills.data, "actions", "skills", "items");
|
|
82
|
+
const q = search.trim().toLowerCase();
|
|
83
|
+
const rows = useMemo(() => (q ? all.filter((s) => `${s.name} ${s.description ?? ""} ${s.category ?? ""}`.toLowerCase().includes(q)) : all), [all, q]);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className={`flex flex-col gap-3 ${className ?? ""}`}>
|
|
87
|
+
<Toolbar search={{ value: search, onChange: setSearch, placeholder: "Search skills" }}
|
|
88
|
+
filters={<><div role="group" aria-label="Skill kind" className="flex gap-1">{KINDS.map((k) => <Button key={k.id || "all"} size="sm" variant={kind === k.id ? "default" : "outline"} onClick={() => setKind(k.id)}>{k.label}</Button>)}</div>
|
|
89
|
+
<select aria-label="Skill state" className="h-8 rounded-md border bg-background px-2 text-sm" value={state} onChange={(e) => setState(e.target.value)}>{STATES.map((s) => <option key={s.id} value={s.id}>{s.label}</option>)}</select></>}
|
|
90
|
+
actions={<><Button size="sm" variant="outline" onClick={() => setFromTemplate(true)}>From a template</Button><Button size="sm" variant="outline" onClick={() => setTarget({ kind: "new", runtime: "api" })}>New API skill</Button><Button size="sm" onClick={() => setTarget({ kind: "new", runtime: "llm" })}>New LLM skill</Button></>} />
|
|
91
|
+
{skills.error ? <ApiError error={skills.error} title="Could not load skills" onRetry={() => void skills.refetch()} /> : null}
|
|
92
|
+
{skills.loading && !skills.data ? <div className="space-y-2">{[0, 1, 2].map((i) => <Skeleton key={i} className="h-12 w-full" />)}</div> : null}
|
|
93
|
+
<ul className="divide-y rounded-md border">
|
|
94
|
+
{rows.map((s) => (
|
|
95
|
+
<li key={s.action_id}>
|
|
96
|
+
<button type="button" data-testid={`skill-${s.action_id}`} className="flex w-full items-center justify-between gap-3 px-3 py-2 text-left text-sm" onClick={() => setTarget({ kind: "edit", id: s.action_id })}>
|
|
97
|
+
<span className="min-w-0"><span className="block truncate font-medium">{s.name}</span>{s.description ? <span className="block truncate text-xs text-muted-foreground">{s.description}</span> : null}</span>
|
|
98
|
+
<span className="flex shrink-0 items-center gap-2">{s.category ? <span className="text-xs text-muted-foreground">{s.category}</span> : null}<StatusPill tone="neutral">{s.runtime_type ?? "skill"}</StatusPill><StatusPill tone={s.enabled === false ? "warning" : "positive"} dot>{s.enabled === false ? "disabled" : "enabled"}</StatusPill></span>
|
|
99
|
+
</button>
|
|
100
|
+
</li>
|
|
101
|
+
))}
|
|
102
|
+
</ul>
|
|
103
|
+
{!skills.loading && !skills.error && rows.length === 0 ? (
|
|
104
|
+
<EmptyState icon={<Wand2 strokeWidth={1.5} />} title={all.length ? "No skills match" : "No skills yet"} description="A skill is one thing an agent can do: write a summary, call an API."
|
|
105
|
+
action={all.length ? undefined : <Button onClick={() => setTarget({ kind: "new", runtime: "llm" })}>New LLM skill</Button>} />
|
|
106
|
+
) : null}
|
|
107
|
+
<SkillEditor target={target} onClose={() => setTarget(null)} onChanged={() => void skills.refetch()} />
|
|
108
|
+
<FromTemplate open={fromTemplate} onClose={() => setFromTemplate(false)} onCreated={(id) => { setFromTemplate(false); void skills.refetch(); if (id) setTarget({ kind: "edit", id }); }} />
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -58,7 +58,7 @@ export interface SkillRow {
|
|
|
58
58
|
description?: string | null;
|
|
59
59
|
runtime_type?: "llm" | "api" | string;
|
|
60
60
|
category?: string | null;
|
|
61
|
-
llm_config?: {
|
|
61
|
+
llm_config?: { prompt_template?: string; model?: string } | null;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
export interface AgentRun {
|
|
@@ -112,3 +112,9 @@ export function pretty(value: unknown): string {
|
|
|
112
112
|
return String(value);
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
+
|
|
116
|
+
/** The id of the agent a create or clone answered with, whatever envelope it came in. */
|
|
117
|
+
export function agentIdOf(res: G8Envelope<unknown> | null | undefined): string {
|
|
118
|
+
const b = bodyOf<{ agent?: { agent_id?: string }; agent_id?: string; new_agent_id?: string }>(res);
|
|
119
|
+
return b.agent?.agent_id ?? b.agent_id ?? b.new_agent_id ?? "";
|
|
120
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/** Renders an open-shaped API object (health, scorecard, status) as labelled facts without guessing its fields. */
|
|
4
|
+
import { KeyValueList } from "@/g8/components/screen/key-value-list";
|
|
5
|
+
|
|
6
|
+
const label = (k: string): string => { const t = k.replace(/_/g, " "); return t.charAt(0).toUpperCase() + t.slice(1); };
|
|
7
|
+
const show = (v: unknown): string => (v == null ? "" : typeof v === "string" ? v : typeof v === "number" || typeof v === "boolean" ? String(v) : JSON.stringify(v));
|
|
8
|
+
|
|
9
|
+
export function FactList({ data, empty = "Nothing reported yet." }: { data: unknown; empty?: string }) {
|
|
10
|
+
const entries = Object.entries((data && typeof data === "object" && !Array.isArray(data) ? data : {}) as Record<string, unknown>).filter(([, v]) => v != null && v !== "");
|
|
11
|
+
if (entries.length === 0) return <p className="text-sm text-muted-foreground">{empty}</p>;
|
|
12
|
+
return <KeyValueList columns={2} items={entries.map(([k, v]) => ({ label: label(k), value: show(v), wide: typeof v === "object", mono: typeof v === "object" }))} />;
|
|
13
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/** Shapes of the company knowledge (the Knowledge Brain) the agents block reads, and the body helpers for authoring. */
|
|
4
|
+
|
|
5
|
+
export interface KnowledgeSummary {
|
|
6
|
+
concept_id: string;
|
|
7
|
+
concept_type: string;
|
|
8
|
+
title: string;
|
|
9
|
+
source_type?: string;
|
|
10
|
+
knowledge_status?: string;
|
|
11
|
+
trust_tier?: string;
|
|
12
|
+
visibility?: string;
|
|
13
|
+
stale?: boolean;
|
|
14
|
+
logical_path?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface KnowledgeConcept {
|
|
18
|
+
summary: KnowledgeSummary;
|
|
19
|
+
markdown?: string;
|
|
20
|
+
source_refs?: Array<{ resource?: string; title?: string | null; locator?: string | null; checksum?: string | null }>;
|
|
21
|
+
relationships?: string[];
|
|
22
|
+
warnings?: string[];
|
|
23
|
+
capabilities?: Record<string, boolean>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface KnowledgeInput {
|
|
27
|
+
title: string;
|
|
28
|
+
concept_type: string;
|
|
29
|
+
body: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const LIFECYCLES = ["draft", "stable", "deprecated"] as const;
|
|
33
|
+
|
|
34
|
+
/** Rendered markdown can start with a `---` frontmatter block; the authoring body must not carry it. */
|
|
35
|
+
export function bodyFromMarkdown(markdown: string | undefined): string {
|
|
36
|
+
const text = markdown ?? "";
|
|
37
|
+
const m = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/.exec(text);
|
|
38
|
+
return (m ? text.slice(m[0].length) : text).replace(/^\s+/, "");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Only authored entries can be edited, amended, verified or deprecated; generated ones come from the product's own records. */
|
|
42
|
+
export function isAuthored(s: KnowledgeSummary | undefined): boolean {
|
|
43
|
+
return Boolean(s?.source_type && /authored/i.test(s.source_type));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function conceptOf(res: unknown): KnowledgeConcept | undefined {
|
|
47
|
+
const d = ((res as { data?: unknown } | null)?.data ?? res) as Partial<KnowledgeConcept> | null;
|
|
48
|
+
return d && d.summary && d.summary.concept_id ? (d as KnowledgeConcept) : undefined;
|
|
49
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Paging helpers for the agents block, in the same shape as the foundation's paging primitives (a list never stops silently at a
|
|
5
|
+
* cap: it shows "Load more" while the last page was full).
|
|
6
|
+
*
|
|
7
|
+
* useLoadMoreList offset + limit APIs (the next page starts at items.length).
|
|
8
|
+
* useGrowingList limit-only APIs (a bigger limit refetches the list from the start).
|
|
9
|
+
* LoadMore the footer button plus the error of a failed "more" request.
|
|
10
|
+
*/
|
|
11
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
12
|
+
import { Button } from "@graph8/sdk/ui";
|
|
13
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
14
|
+
|
|
15
|
+
export interface LoadMoreList<T> {
|
|
16
|
+
items: T[];
|
|
17
|
+
/** The first page is loading (or reloading after the deps changed). */
|
|
18
|
+
loading: boolean;
|
|
19
|
+
loadingMore: boolean;
|
|
20
|
+
error: unknown;
|
|
21
|
+
moreError: unknown;
|
|
22
|
+
hasMore: boolean;
|
|
23
|
+
/** A limit-only API stopped at its own maximum: there are more rows the API will not return. */
|
|
24
|
+
capped?: boolean;
|
|
25
|
+
loadMore: () => void;
|
|
26
|
+
reload: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface Options {
|
|
30
|
+
pageSize?: number;
|
|
31
|
+
enabled?: boolean;
|
|
32
|
+
/** useGrowingList: the largest `limit` the API accepts. */
|
|
33
|
+
max?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Offset paging. `fetchPage(offset, limit)` returns that page's rows; a full page means there may be more. */
|
|
37
|
+
export function useLoadMoreList<T>(
|
|
38
|
+
fetchPage: (offset: number, limit: number, signal: AbortSignal) => Promise<T[]>,
|
|
39
|
+
deps: readonly unknown[],
|
|
40
|
+
{ pageSize = 50, enabled = true }: Options = {},
|
|
41
|
+
): LoadMoreList<T> {
|
|
42
|
+
const [items, setItems] = useState<T[]>([]);
|
|
43
|
+
const [loading, setLoading] = useState(enabled);
|
|
44
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
45
|
+
const [error, setError] = useState<unknown>(null);
|
|
46
|
+
const [moreError, setMoreError] = useState<unknown>(null);
|
|
47
|
+
const [hasMore, setHasMore] = useState(false);
|
|
48
|
+
const [tick, setTick] = useState(0);
|
|
49
|
+
const fetchRef = useRef(fetchPage);
|
|
50
|
+
fetchRef.current = fetchPage;
|
|
51
|
+
const gen = useRef(0);
|
|
52
|
+
const count = useRef(0);
|
|
53
|
+
const sig = JSON.stringify([...deps, enabled, pageSize]);
|
|
54
|
+
const lastSig = useRef(sig);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!enabled) { setLoading(false); return; }
|
|
58
|
+
const mine = ++gen.current;
|
|
59
|
+
const ctl = new AbortController();
|
|
60
|
+
// A reload() keeps showing the rows it has until the fresh first page lands; a changed filter starts from nothing.
|
|
61
|
+
const fresh = lastSig.current !== sig;
|
|
62
|
+
lastSig.current = sig;
|
|
63
|
+
setLoading(true); setError(null); setMoreError(null);
|
|
64
|
+
if (fresh) { setItems([]); setHasMore(false); count.current = 0; }
|
|
65
|
+
// A reload() refetches as many pages as were showing, so a row opened from page 2 does not vanish; a changed filter starts at page 1.
|
|
66
|
+
const want = fresh ? pageSize : Math.max(count.current, pageSize);
|
|
67
|
+
const load = async (): Promise<T[]> => {
|
|
68
|
+
const all: T[] = [];
|
|
69
|
+
let last = pageSize;
|
|
70
|
+
while (all.length < want && last >= pageSize) {
|
|
71
|
+
const page = await fetchRef.current(all.length, pageSize, ctl.signal);
|
|
72
|
+
all.push(...page);
|
|
73
|
+
last = page.length;
|
|
74
|
+
}
|
|
75
|
+
return all;
|
|
76
|
+
};
|
|
77
|
+
load().then((rows) => {
|
|
78
|
+
if (gen.current !== mine) return;
|
|
79
|
+
setItems(rows); count.current = rows.length; setHasMore(rows.length > 0 && rows.length % pageSize === 0); setLoading(false);
|
|
80
|
+
}, (e) => { if (gen.current === mine) { setError(e); setLoading(false); } });
|
|
81
|
+
return () => ctl.abort();
|
|
82
|
+
}, [...deps, enabled, pageSize, tick]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
83
|
+
|
|
84
|
+
const loadMore = useCallback(() => {
|
|
85
|
+
const mine = gen.current;
|
|
86
|
+
setLoadingMore(true); setMoreError(null);
|
|
87
|
+
fetchRef.current(count.current, pageSize, new AbortController().signal).then((rows) => {
|
|
88
|
+
if (gen.current !== mine) return;
|
|
89
|
+
count.current += rows.length;
|
|
90
|
+
setItems((prev) => [...prev, ...rows]); setHasMore(rows.length >= pageSize); setLoadingMore(false);
|
|
91
|
+
}, (e) => { if (gen.current === mine) { setMoreError(e); setLoadingMore(false); } });
|
|
92
|
+
}, [pageSize]);
|
|
93
|
+
|
|
94
|
+
return { items, loading, loadingMore, error, moreError, hasMore, loadMore, reload: () => setTick((t) => t + 1) };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Limit-only APIs: asks for `step` rows, and "Load more" asks again for `step` more from the start. */
|
|
98
|
+
export function useGrowingList<T>(
|
|
99
|
+
fetchRows: (limit: number, signal: AbortSignal) => Promise<T[]>,
|
|
100
|
+
deps: readonly unknown[],
|
|
101
|
+
{ pageSize = 50, enabled = true, max = Infinity }: Options = {},
|
|
102
|
+
): LoadMoreList<T> {
|
|
103
|
+
const [limit, setLimit] = useState(pageSize);
|
|
104
|
+
const [rows, setRows] = useState<T[]>([]);
|
|
105
|
+
const [loading, setLoading] = useState(enabled);
|
|
106
|
+
const [error, setError] = useState<unknown>(null);
|
|
107
|
+
const [tick, setTick] = useState(0);
|
|
108
|
+
const [growing, setGrowing] = useState(false);
|
|
109
|
+
const fetchRef = useRef(fetchRows);
|
|
110
|
+
fetchRef.current = fetchRows;
|
|
111
|
+
const depKey = JSON.stringify(deps);
|
|
112
|
+
const lastDeps = useRef(depKey);
|
|
113
|
+
|
|
114
|
+
useEffect(() => { if (lastDeps.current !== depKey) { lastDeps.current = depKey; setLimit(pageSize); } }, [depKey, pageSize]);
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (!enabled) { setLoading(false); return; }
|
|
117
|
+
let live = true;
|
|
118
|
+
const ctl = new AbortController();
|
|
119
|
+
setLoading(true); setError(null);
|
|
120
|
+
fetchRef.current(limit, ctl.signal).then((r) => { if (live) { setRows(r); setLoading(false); setGrowing(false); } }, (e) => { if (live) { setError(e); setLoading(false); setGrowing(false); } });
|
|
121
|
+
return () => { live = false; ctl.abort(); };
|
|
122
|
+
}, [limit, depKey, enabled, tick]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
items: rows, loading: loading && !growing, loadingMore: loading && growing, error: growing ? null : error, moreError: growing ? error : null,
|
|
126
|
+
hasMore: !loading && rows.length >= limit && limit < max, capped: !loading && rows.length >= limit && limit >= max,
|
|
127
|
+
loadMore: () => { setGrowing(true); setLimit((l) => Math.min(max, l + pageSize)); }, reload: () => setTick((t) => t + 1),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The footer of a paged list. Renders nothing when everything is shown. */
|
|
132
|
+
export function LoadMore<T>({ list, noun = "rows" }: { list: LoadMoreList<T>; noun?: string }) {
|
|
133
|
+
if (!list.hasMore && !list.moreError && !list.capped) return null;
|
|
134
|
+
return (
|
|
135
|
+
<div className="flex flex-col items-center gap-2 py-2">
|
|
136
|
+
{list.moreError ? <ApiError compact error={list.moreError} title={`Could not load more ${noun}`} onRetry={list.loadMore} /> : null}
|
|
137
|
+
{list.capped ? <p className="text-xs text-muted-foreground">Showing the latest {list.items.length} {noun}. The API returns no more than this.</p> : null}
|
|
138
|
+
{list.hasMore && !list.moreError ? <Button size="sm" variant="outline" loading={list.loadingMore} onClick={list.loadMore}>Load more {noun}</Button> : null}
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/** Shapes and diffing for the skill library. Skills answer un-enveloped or enveloped, flat or under `action`; `skillOf` reads all of them. */
|
|
4
|
+
import type { G8Envelope } from "@/g8/lib/g8-client";
|
|
5
|
+
import { bodyOf } from "./agent-types";
|
|
6
|
+
|
|
7
|
+
export interface SkillDetail {
|
|
8
|
+
action_id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string | null;
|
|
11
|
+
category?: string | null;
|
|
12
|
+
runtime_type?: "llm" | "api" | string;
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
requires_approval?: boolean;
|
|
15
|
+
llm_config?: { model?: string; temperature?: number; max_tokens?: number; prompt_template?: string; prompt?: string } | null;
|
|
16
|
+
api_config?: { endpoint?: string; method?: string; headers?: Record<string, string> | null; body_template?: string | null; requires_auth?: boolean } | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SkillDraft {
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
category: string;
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
model: string;
|
|
25
|
+
prompt_template: string;
|
|
26
|
+
temperature: string;
|
|
27
|
+
max_tokens: string;
|
|
28
|
+
endpoint: string;
|
|
29
|
+
method: string;
|
|
30
|
+
headers: string;
|
|
31
|
+
body_template: string;
|
|
32
|
+
requires_auth: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const BLANK_DRAFT: SkillDraft = { name: "", description: "", category: "", enabled: true, model: "", prompt_template: "", temperature: "0.7", max_tokens: "1000", endpoint: "", method: "POST", headers: "", body_template: "", requires_auth: false };
|
|
36
|
+
export const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"] as const;
|
|
37
|
+
|
|
38
|
+
export function skillOf(res: G8Envelope<unknown> | null | undefined): SkillDetail | undefined {
|
|
39
|
+
const b = bodyOf<{ action?: SkillDetail } & Partial<SkillDetail>>(res);
|
|
40
|
+
const s = (b.action ?? b) as SkillDetail;
|
|
41
|
+
return s && s.action_id ? s : undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function draftOf(s: SkillDetail): SkillDraft {
|
|
45
|
+
const l = s.llm_config ?? {}, a = s.api_config ?? {};
|
|
46
|
+
return {
|
|
47
|
+
name: s.name, description: s.description ?? "", category: s.category ?? "", enabled: s.enabled !== false,
|
|
48
|
+
model: l.model ?? "", prompt_template: l.prompt_template ?? l.prompt ?? "", temperature: String(l.temperature ?? 0.7), max_tokens: String(l.max_tokens ?? 1000),
|
|
49
|
+
endpoint: a.endpoint ?? "", method: a.method ?? "POST", headers: a.headers && Object.keys(a.headers).length ? JSON.stringify(a.headers, null, 2) : "", body_template: a.body_template ?? "", requires_auth: Boolean(a.requires_auth),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The headers textarea as an object; undefined when blank, null when it is not a JSON object of strings. */
|
|
54
|
+
export function parseHeaders(text: string): Record<string, string> | undefined | null {
|
|
55
|
+
if (!text.trim()) return undefined;
|
|
56
|
+
try {
|
|
57
|
+
const v = JSON.parse(text) as unknown;
|
|
58
|
+
if (!v || typeof v !== "object" || Array.isArray(v) || !Object.values(v).every((x) => typeof x === "string")) return null;
|
|
59
|
+
return v as Record<string, string>;
|
|
60
|
+
} catch { return null; }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The PUT body: only what changed against the LOADED skill. A runtime config is sent whole when any field in it changed
|
|
65
|
+
* (voice validates it as a unit: a model and a prompt, or an endpoint).
|
|
66
|
+
*/
|
|
67
|
+
export function skillPatch(loaded: SkillDetail, d: SkillDraft): Record<string, unknown> {
|
|
68
|
+
const before = draftOf(loaded);
|
|
69
|
+
const changed = (...keys: (keyof SkillDraft)[]) => keys.some((k) => before[k] !== d[k]);
|
|
70
|
+
const out: Record<string, unknown> = {};
|
|
71
|
+
if (changed("name")) out.name = d.name.trim();
|
|
72
|
+
if (changed("description")) out.description = d.description.trim();
|
|
73
|
+
if (changed("category")) out.category = d.category.trim();
|
|
74
|
+
if (changed("enabled")) out.enabled = d.enabled;
|
|
75
|
+
if (loaded.runtime_type === "llm" && changed("model", "prompt_template", "temperature", "max_tokens")) {
|
|
76
|
+
out.llm_config = { model: d.model, prompt_template: d.prompt_template, temperature: Number(d.temperature), max_tokens: Number(d.max_tokens) };
|
|
77
|
+
}
|
|
78
|
+
if (loaded.runtime_type === "api" && changed("endpoint", "method", "headers", "body_template", "requires_auth")) {
|
|
79
|
+
const headers = parseHeaders(d.headers);
|
|
80
|
+
out.api_config = { endpoint: d.endpoint.trim(), method: d.method, body_template: d.body_template || undefined, requires_auth: d.requires_auth, ...(headers ? { headers } : {}) };
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The POST body for a new skill. */
|
|
86
|
+
export function newSkillBody(runtime: "llm" | "api", d: SkillDraft): Record<string, unknown> {
|
|
87
|
+
const base = { name: d.name.trim(), description: d.description.trim() || undefined, category: d.category.trim() || undefined, runtime_type: runtime };
|
|
88
|
+
if (runtime === "llm") return { ...base, llm_config: { model: d.model, prompt_template: d.prompt_template, temperature: Number(d.temperature), max_tokens: Number(d.max_tokens) } };
|
|
89
|
+
const headers = parseHeaders(d.headers);
|
|
90
|
+
return { ...base, api_config: { endpoint: d.endpoint.trim(), method: d.method, body_template: d.body_template || undefined, requires_auth: d.requires_auth, ...(headers ? { headers } : {}) } };
|
|
91
|
+
}
|
|
@@ -34,7 +34,7 @@ export async function buildAgent() {
|
|
|
34
34
|
body: {
|
|
35
35
|
name: "Summarise the lead",
|
|
36
36
|
runtime_type: "llm",
|
|
37
|
-
llm_config: {
|
|
37
|
+
llm_config: { model: "gpt-4o-mini", prompt_template: "Summarise {contact_name} in two sentences for the sales team." },
|
|
38
38
|
},
|
|
39
39
|
});
|
|
40
40
|
const actionId = (skill as { action_id?: string }).action_id;
|
|
@@ -23,10 +23,13 @@
|
|
|
23
23
|
* stale-cache notice instead of silent old numbers.
|
|
24
24
|
* The tab and range are kept in the URL (?tab=, ?days=) so a link opens the same view.
|
|
25
25
|
*/
|
|
26
|
-
import { useState, type ReactNode } from "react";
|
|
26
|
+
import { useEffect, useState, type ReactNode } from "react";
|
|
27
27
|
import { BarChart3, FileText, Plus } from "lucide-react";
|
|
28
28
|
import { Button, Skeleton } from "@graph8/sdk/ui";
|
|
29
29
|
import { ApiError } from "@/g8/components/api-error";
|
|
30
|
+
import { useLoadMoreList } from "@/g8/hooks/use-load-more-list";
|
|
31
|
+
import { byOffset, hasNextOf } from "@/g8/lib/paging";
|
|
32
|
+
import { LoadMore } from "@/g8/components/paging-notices";
|
|
30
33
|
import { EmptyState } from "@/g8/components/empty-state";
|
|
31
34
|
import { DetailPanel } from "@/g8/components/screen/detail-panel";
|
|
32
35
|
import { FormSheet } from "@/g8/components/screen/form-sheet";
|
|
@@ -128,6 +131,9 @@ function Overview({ days, nameFor }: { days: number; nameFor?: (email: string) =
|
|
|
128
131
|
);
|
|
129
132
|
}
|
|
130
133
|
|
|
134
|
+
/** Reports per read; "Load more" appends the next page (the list used to stop silently at 50). */
|
|
135
|
+
const REPORT_PAGE = 50;
|
|
136
|
+
|
|
131
137
|
function ReportsPane({ objectSlug }: { objectSlug?: string }) {
|
|
132
138
|
const brand = useBrand();
|
|
133
139
|
const [selected, setSelected] = useUrlState("id", "");
|
|
@@ -136,11 +142,17 @@ function ReportsPane({ objectSlug }: { objectSlug?: string }) {
|
|
|
136
142
|
const [creating, setCreating] = useState(false);
|
|
137
143
|
// Results live here so switching between Result and Edit never runs the report again (each run is a write).
|
|
138
144
|
const [results, setResults] = useState<Record<string, ReportResultData | null>>({});
|
|
139
|
-
const list =
|
|
145
|
+
const list = useLoadMoreList(byOffset<SavedReport>(REPORT_PAGE, async (offset, signal) => {
|
|
146
|
+
const env = await g8Get("/reports", { object_slug: objectSlug || undefined, limit: REPORT_PAGE, offset: offset || undefined }, { signal });
|
|
147
|
+
const rows = reportBody<{ reports?: SavedReport[] }>(env).reports ?? [];
|
|
148
|
+
return { rows, hasNext: hasNextOf(env, REPORT_PAGE, rows.length) };
|
|
149
|
+
}), [objectSlug]);
|
|
140
150
|
const switchedOff = (list.error as { status?: number } | undefined)?.status === 404;
|
|
141
151
|
const q = search.trim().toLowerCase();
|
|
142
152
|
const rows = (list.data ?? []).filter((r) => !q || `${r.title} ${r.source.plural_noun}`.toLowerCase().includes(q));
|
|
143
153
|
const open = (list.data ?? []).find((r) => r.id === selected);
|
|
154
|
+
// A deep link to a report on a later page: keep loading pages until it is found.
|
|
155
|
+
useEffect(() => { if (selected && !open && list.hasMore && !list.loading && !list.loadingMore) list.loadMore(); }, [selected, open, list.hasMore, list.loading, list.loadingMore]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
144
156
|
|
|
145
157
|
const detail = open ? (
|
|
146
158
|
<DetailPanel title={open.title} subtitle={open.source.plural_noun} badges={<StatusPill tone={open.visibility === "private" ? "neutral" : "info"}>{open.visibility}</StatusPill>} onClose={() => { setSelected(""); setView(""); }} onExpand={() => setView("full")}
|
|
@@ -148,7 +160,7 @@ function ReportsPane({ objectSlug }: { objectSlug?: string }) {
|
|
|
148
160
|
{ id: "result", label: "Result", content: <ReportDetail key={open.id} reportId={open.id} result={results[open.id]} onResult={(r) => setResults((m) => ({ ...m, [open.id]: r }))} onDeleted={() => { setSelected(""); void list.refetch(); }} /> },
|
|
149
161
|
{ id: "edit", label: "Edit", content: <ReportBuilder key={open.id} hideList reportId={open.id} autoRun={false} onSaved={() => { setResults((m) => Object.fromEntries(Object.entries(m).filter(([k]) => k !== open.id))); void list.refetch(); }} /> }, // a saved edit changes the query: drop the stale result
|
|
150
162
|
]} />
|
|
151
|
-
) : selected && !list.loading ? <EmptyState title="Report not found" description="It may have been deleted." /> : null;
|
|
163
|
+
) : selected && !list.loading && !list.hasMore ? <EmptyState title="Report not found" description="It may have been deleted." /> : null;
|
|
152
164
|
|
|
153
165
|
const listView = (
|
|
154
166
|
<div className="flex flex-col gap-3">
|
|
@@ -166,6 +178,7 @@ function ReportsPane({ objectSlug }: { objectSlug?: string }) {
|
|
|
166
178
|
</li>
|
|
167
179
|
))}
|
|
168
180
|
</ul>
|
|
181
|
+
<LoadMore shown={(list.data ?? []).length} noun="reports" hasMore={list.hasMore} loading={list.loadingMore} onLoadMore={list.loadMore} />
|
|
169
182
|
{!list.loading && !list.error && rows.length === 0 ? <EmptyState title={list.data?.length ? "No reports match" : "No saved reports yet"} description="Pick an object, a measure and a grouping, preview it, then save it."
|
|
170
183
|
action={list.data?.length ? undefined : <Button onClick={() => setCreating(true)}>New report</Button>} /> : null}
|
|
171
184
|
</div>
|
|
@@ -61,7 +61,7 @@ export function KpiTiles({ days: initialDays = 30, tiles = DEFAULT_TILES, classN
|
|
|
61
61
|
return (
|
|
62
62
|
<div key={t.key} className="rounded-lg border p-4">
|
|
63
63
|
<div className="text-xs uppercase text-muted-foreground">{t.label}</div>
|
|
64
|
-
<div className="mt-1 text-2xl font-
|
|
64
|
+
<div className="mt-1 text-2xl font-medium">{t.kind === "percent" ? formatPercent(raw) : formatNumber(raw)}</div>
|
|
65
65
|
{data && t.hint ? <div className="mt-1 text-xs text-muted-foreground">{t.hint(data)}</div> : null}
|
|
66
66
|
</div>
|
|
67
67
|
);
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
import { useEffect, useRef, useState } from "react";
|
|
20
20
|
import { Button, Input, Label, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@graph8/sdk/ui";
|
|
21
21
|
import { ApiError } from "@/g8/components/api-error";
|
|
22
|
+
import { useLoadMoreList } from "@/g8/hooks/use-load-more-list";
|
|
23
|
+
import { byOffset, hasNextOf } from "@/g8/lib/paging";
|
|
24
|
+
import { LoadMore } from "@/g8/components/paging-notices";
|
|
22
25
|
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
23
26
|
import { EmptyState } from "@/g8/components/empty-state";
|
|
24
27
|
import { g8Delete, g8Get, g8Patch, g8Post } from "@/g8/lib/g8-client";
|
|
@@ -114,6 +117,9 @@ export function ResultTable({ result }: { result: ReportResultData }) {
|
|
|
114
117
|
);
|
|
115
118
|
}
|
|
116
119
|
|
|
120
|
+
/** Reports per read; "Load more" appends the next page (the list used to stop silently at 50). */
|
|
121
|
+
const REPORT_PAGE = 50;
|
|
122
|
+
|
|
117
123
|
export function ReportBuilder({ objectSlug: initialSlug = "", onSaved, reportId, hideList, autoRun = true, className }: ReportBuilderProps) {
|
|
118
124
|
const brand = useBrand();
|
|
119
125
|
const [slug, setSlug] = useState(initialSlug);
|
|
@@ -124,7 +130,11 @@ export function ReportBuilder({ objectSlug: initialSlug = "", onSaved, reportId,
|
|
|
124
130
|
const set = <K extends keyof Draft>(k: K, v: Draft[K]) => setDraft((d) => ({ ...d, [k]: v }));
|
|
125
131
|
|
|
126
132
|
const listSlug = useDebounced(slug.trim());
|
|
127
|
-
const saved =
|
|
133
|
+
const saved = useLoadMoreList(byOffset<SavedReport>(REPORT_PAGE, async (offset, signal) => {
|
|
134
|
+
const env = await g8Get("/reports", { object_slug: listSlug || undefined, limit: REPORT_PAGE, offset: offset || undefined }, { signal });
|
|
135
|
+
const rows = reportBody<{ reports?: SavedReport[] }>(env).reports ?? [];
|
|
136
|
+
return { rows, hasNext: hasNextOf(env, REPORT_PAGE, rows.length) };
|
|
137
|
+
}), [listSlug]);
|
|
128
138
|
const body = () => ({ object_slug: slug, chart_type: draft.chart, config: configOf(draft, loaded?.config as Cfg) });
|
|
129
139
|
const preview = useG8Mutation(async () => { const r = reportBody<ReportResultData>(await g8Post("/reports/preview", body())); setResult(r); return r; });
|
|
130
140
|
const run = useG8Mutation(async (r: SavedReport) => { const out = reportBody<ReportResultData>(await g8Post(`/reports/${encodeURIComponent(r.id)}/execute`, {})); setResult(out); return out; });
|
|
@@ -162,6 +172,8 @@ export function ReportBuilder({ objectSlug: initialSlug = "", onSaved, reportId,
|
|
|
162
172
|
useEffect(() => {
|
|
163
173
|
const row = reportId ? (saved.data ?? []).find((r) => r.id === reportId) : undefined;
|
|
164
174
|
if (row && preloaded.current !== reportId) { preloaded.current = reportId ?? null; open(row, autoRun); }
|
|
175
|
+
// The report sits on a later page: keep loading pages until it is found.
|
|
176
|
+
else if (reportId && !row && saved.hasMore && !saved.loading && !saved.loadingMore) saved.loadMore();
|
|
165
177
|
}); // eslint-disable-line react-hooks/exhaustive-deps
|
|
166
178
|
const failure = preview.error ?? run.error ?? save.error ?? remove.error;
|
|
167
179
|
const switchedOff = (saved.error as { status?: number } | undefined)?.status === 404;
|
|
@@ -169,7 +181,7 @@ export function ReportBuilder({ objectSlug: initialSlug = "", onSaved, reportId,
|
|
|
169
181
|
return (
|
|
170
182
|
<div className={`grid gap-4 ${hideList ? "" : "lg:grid-cols-[minmax(0,2fr)_minmax(0,3fr)]"} ${className ?? ""}`}>
|
|
171
183
|
{hideList ? null : <section aria-label="Saved reports" className="space-y-2">
|
|
172
|
-
<h3 className="text-sm font-
|
|
184
|
+
<h3 className="text-sm font-medium">Saved reports</h3>
|
|
173
185
|
{switchedOff ? <EmptyState title="Reports are not enabled" description={`Custom-object reports are switched on per organisation in ${brand.linkLabel}.`} /> : saved.error ? <ApiError error={saved.error} title="Could not load saved reports" onRetry={() => void saved.refetch()} /> : null}
|
|
174
186
|
{(saved.data ?? []).map((r) => (
|
|
175
187
|
<div key={r.id} className="flex items-center justify-between rounded border p-2 text-sm">
|
|
@@ -177,6 +189,7 @@ export function ReportBuilder({ objectSlug: initialSlug = "", onSaved, reportId,
|
|
|
177
189
|
{r.can_delete ? <Button size="sm" variant="ghost" aria-label={`Delete ${r.title}`} onClick={() => setDeleting(r)}>Delete</Button> : null}
|
|
178
190
|
</div>
|
|
179
191
|
))}
|
|
192
|
+
<LoadMore shown={(saved.data ?? []).length} noun="reports" hasMore={saved.hasMore} loading={saved.loadingMore} onLoadMore={saved.loadMore} />
|
|
180
193
|
{!saved.loading && !saved.error && (saved.data ?? []).length === 0 ? <p className="text-sm text-muted-foreground">Nothing saved yet.</p> : null}
|
|
181
194
|
</section>}
|
|
182
195
|
|
|
@@ -98,3 +98,9 @@ with the full API key.
|
|
|
98
98
|
- `03-bookings-digest.ts`: upcoming bookings, pending approvals, recorded meetings.
|
|
99
99
|
- `04-calendar-invite.ts`: ad-hoc invite via `calendarInvites.createCalendarInvite` (not part of the block's
|
|
100
100
|
UI; add the operation to your own proxy allowlist if you call it from the browser).
|
|
101
|
+
|
|
102
|
+
## Cross-block glue
|
|
103
|
+
|
|
104
|
+
- `MeetingContactLinks` (`components/meeting-contact-links.tsx`): "Open <attendee>" buttons for a booked or recorded meeting, one per attendee who is a CRM contact. Use it in `MeetingsScreen`'s `renderDetailActions`.
|
|
105
|
+
|
|
106
|
+
- `RecordMeetingsTab` (`components/record-meetings-tab.tsx`): meetings booked with a contact (`getContactMeetings`, the API's 50 most recent booking events), each opening `/meetings?sel=booking:<id>`. `extraDetailTabs` entry for ContactsScreen.
|
|
@@ -23,10 +23,12 @@
|
|
|
23
23
|
"get_availability_appointments_schedules__schedule_id__availability_get",
|
|
24
24
|
"get_available_slots_appointments_slots_get",
|
|
25
25
|
"get_booking_appointments_bookings__booking_uid__get",
|
|
26
|
+
"get_contact_meetings_contacts__contact_id__meetings_get",
|
|
26
27
|
"get_event_type_appointments_event_types__event_type_id__get",
|
|
27
28
|
"get_meeting_inbox_meetings__meeting_id__get",
|
|
28
29
|
"list_bookings_appointments_bookings_get",
|
|
29
30
|
"list_calendars_appointments_calendars_get",
|
|
31
|
+
"list_contacts_contacts_get",
|
|
30
32
|
"list_event_types_event_types_get",
|
|
31
33
|
"list_meetings_inbox_meetings_get",
|
|
32
34
|
"list_schedules_appointments_schedules_get",
|
|
@@ -138,6 +140,30 @@
|
|
|
138
140
|
"tiers": [
|
|
139
141
|
"read"
|
|
140
142
|
]
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "MeetingContactLinks",
|
|
146
|
+
"file": "components/meeting-contact-links.tsx",
|
|
147
|
+
"purpose": "\"Open <attendee>\" buttons for a booked or recorded meeting: one per attendee who is a CRM contact; for MeetingsScreen renderDetailActions.",
|
|
148
|
+
"operations": [
|
|
149
|
+
"get_booking_appointments_bookings__booking_uid__get",
|
|
150
|
+
"get_meeting_inbox_meetings__meeting_id__get",
|
|
151
|
+
"list_contacts_contacts_get"
|
|
152
|
+
],
|
|
153
|
+
"tiers": [
|
|
154
|
+
"read"
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"name": "RecordMeetingsTab",
|
|
159
|
+
"file": "components/record-meetings-tab.tsx",
|
|
160
|
+
"purpose": "The meetings booked with one contact (booked, rescheduled, cancelled), each opening in the meetings screen; an extraDetailTabs entry for ContactsScreen.",
|
|
161
|
+
"operations": [
|
|
162
|
+
"get_contact_meetings_contacts__contact_id__meetings_get"
|
|
163
|
+
],
|
|
164
|
+
"tiers": [
|
|
165
|
+
"read"
|
|
166
|
+
]
|
|
141
167
|
}
|
|
142
168
|
],
|
|
143
169
|
"hooks": [
|
|
@@ -220,6 +246,7 @@
|
|
|
220
246
|
],
|
|
221
247
|
"scopes": [
|
|
222
248
|
"campaigns:read",
|
|
249
|
+
"contacts:read",
|
|
223
250
|
"meetings:delete",
|
|
224
251
|
"meetings:read",
|
|
225
252
|
"meetings:run",
|