@graph8/sdk 0.308.1 → 0.357.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 +81 -0
- package/blocks/README.md +32 -0
- package/blocks/_foundation/README.md +78 -0
- package/blocks/_foundation/block.json +79 -0
- package/blocks/_foundation/components/api-error.tsx +62 -0
- package/blocks/_foundation/components/confirm-spend-dialog.tsx +142 -0
- package/blocks/_foundation/components/empty-state.tsx +24 -0
- package/blocks/_foundation/hooks/use-g8-mutation.ts +88 -0
- package/blocks/_foundation/hooks/use-g8-query.ts +87 -0
- package/blocks/_foundation/hooks/use-job-poll.ts +130 -0
- package/blocks/_foundation/hooks/use-paginated.ts +105 -0
- package/blocks/_foundation/lib/g8-client.ts +128 -0
- package/blocks/_foundation/recipes/verify-proxy-key.ts +18 -0
- package/blocks/_foundation/server/proxy.ts +233 -0
- package/blocks/_foundation/server/route.ts +30 -0
- package/blocks/agents/README.md +133 -0
- package/blocks/agents/block.json +223 -0
- package/blocks/agents/components/agent-builder.tsx +199 -0
- package/blocks/agents/components/agent-knowledge-panel.tsx +144 -0
- package/blocks/agents/components/agent-runs.tsx +175 -0
- package/blocks/agents/components/agent-skills-panel.tsx +126 -0
- package/blocks/agents/components/agent-test-console.tsx +175 -0
- package/blocks/agents/lib/agent-types.ts +114 -0
- package/blocks/agents/recipes/create-agent-and-attach-skill.ts +50 -0
- package/blocks/agents/recipes/run-and-follow-agent-run.ts +46 -0
- package/blocks/agents/recipes/test-agent-before-activating.ts +36 -0
- package/blocks/analytics/README.md +73 -0
- package/blocks/analytics/block.json +178 -0
- package/blocks/analytics/components/funnel-chart.tsx +79 -0
- package/blocks/analytics/components/kpi-tiles.tsx +74 -0
- package/blocks/analytics/components/leaderboard.tsx +86 -0
- package/blocks/analytics/components/report-builder.tsx +200 -0
- package/blocks/analytics/hooks/use-report.ts +26 -0
- package/blocks/analytics/lib/analytics-types.ts +108 -0
- package/blocks/analytics/lib/use-debounced.ts +16 -0
- package/blocks/analytics/recipes/01-weekly-outbound-report.ts +32 -0
- package/blocks/analytics/recipes/02-revenue-snapshot.ts +22 -0
- package/blocks/analytics/recipes/03-saved-report.ts +30 -0
- package/blocks/appointments/README.md +80 -0
- package/blocks/appointments/block.json +218 -0
- package/blocks/appointments/components/availability-editor.tsx +192 -0
- package/blocks/appointments/components/booking-page.tsx +154 -0
- package/blocks/appointments/components/event-type-editor.tsx +214 -0
- package/blocks/appointments/components/meeting-detail.tsx +147 -0
- package/blocks/appointments/components/meeting-list.tsx +148 -0
- package/blocks/appointments/hooks/use-available-slots.ts +29 -0
- package/blocks/appointments/hooks/use-schedules.ts +18 -0
- package/blocks/appointments/lib/appointments-types.ts +159 -0
- package/blocks/appointments/lib/idempotency.ts +30 -0
- package/blocks/appointments/recipes/01-create-event-type-and-hours.ts +27 -0
- package/blocks/appointments/recipes/02-find-slots-and-book.ts +34 -0
- package/blocks/appointments/recipes/03-bookings-digest.ts +22 -0
- package/blocks/appointments/recipes/04-calendar-invite.ts +21 -0
- package/blocks/block.schema.json +130 -0
- package/blocks/block.types.ts +109 -0
- package/blocks/campaigns/README.md +118 -0
- package/blocks/campaigns/block.json +361 -0
- package/blocks/campaigns/components/campaign-assets.tsx +180 -0
- package/blocks/campaigns/components/campaign-audience-step.tsx +91 -0
- package/blocks/campaigns/components/campaign-launch-step.tsx +147 -0
- package/blocks/campaigns/components/campaign-list.tsx +120 -0
- package/blocks/campaigns/components/campaign-metrics.tsx +93 -0
- package/blocks/campaigns/components/campaign-select.tsx +52 -0
- package/blocks/campaigns/components/campaign-sequence-step.tsx +158 -0
- package/blocks/campaigns/components/campaign-wizard.tsx +238 -0
- package/blocks/campaigns/components/form-field.tsx +29 -0
- package/blocks/campaigns/components/icp-editor.tsx +230 -0
- package/blocks/campaigns/components/persona-editor.tsx +210 -0
- package/blocks/campaigns/hooks/use-company-profile.ts +34 -0
- package/blocks/campaigns/lib/campaigns-utils.ts +155 -0
- package/blocks/campaigns/recipes/attach-audience-and-launch.ts +33 -0
- package/blocks/campaigns/recipes/campaign-report.ts +22 -0
- package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +51 -0
- package/blocks/campaigns/recipes/set-up-icp-and-personas.ts +37 -0
- package/blocks/coach/README.md +93 -0
- package/blocks/coach/block.json +125 -0
- package/blocks/coach/components/call-scorecard.tsx +126 -0
- package/blocks/coach/components/coaching-feed.tsx +140 -0
- package/blocks/coach/lib/coach-types.ts +116 -0
- package/blocks/coach/recipes/read-a-scorecard.ts +27 -0
- package/blocks/coach/recipes/weekly-coaching-digest.ts +53 -0
- package/blocks/copilot/README.md +99 -0
- package/blocks/copilot/block.json +151 -0
- package/blocks/copilot/components/copilot-memory.tsx +87 -0
- package/blocks/copilot/components/copilot-panel.tsx +189 -0
- package/blocks/copilot/components/copilot-team-chat.tsx +111 -0
- package/blocks/copilot/lib/copilot-types.ts +111 -0
- package/blocks/copilot/recipes/ask-the-copilot.ts +35 -0
- package/blocks/copilot/recipes/post-in-a-work-channel.ts +33 -0
- package/blocks/copilot/recipes/remember-and-recall.ts +19 -0
- package/blocks/crm/README.md +122 -0
- package/blocks/crm/block.json +378 -0
- package/blocks/crm/components/activity-timeline.tsx +99 -0
- package/blocks/crm/components/company-detail.tsx +118 -0
- package/blocks/crm/components/company-table.tsx +173 -0
- package/blocks/crm/components/contact-detail.tsx +135 -0
- package/blocks/crm/components/contact-table.tsx +217 -0
- package/blocks/crm/components/dedupe-review.tsx +164 -0
- package/blocks/crm/components/import-wizard.tsx +193 -0
- package/blocks/crm/components/notes-panel.tsx +112 -0
- package/blocks/crm/components/record-form.tsx +191 -0
- package/blocks/crm/components/task-list.tsx +107 -0
- package/blocks/crm/hooks/use-custom-fields.ts +18 -0
- package/blocks/crm/hooks/use-debounced.ts +13 -0
- package/blocks/crm/hooks/use-page-query.ts +52 -0
- package/blocks/crm/lib/crm-types.ts +136 -0
- package/blocks/crm/recipes/custom-fields-and-values.ts +28 -0
- package/blocks/crm/recipes/dedupe-and-merge.ts +30 -0
- package/blocks/crm/recipes/notes-tasks-and-activity.ts +23 -0
- package/blocks/crm/recipes/upsert-contact-with-company.ts +33 -0
- package/blocks/deals/README.md +78 -0
- package/blocks/deals/block.json +198 -0
- package/blocks/deals/components/deal-detail.tsx +134 -0
- package/blocks/deals/components/forecast-view.tsx +112 -0
- package/blocks/deals/components/pipeline-board.tsx +142 -0
- package/blocks/deals/components/stage-editor.tsx +96 -0
- package/blocks/deals/hooks/use-pipeline-deals.ts +67 -0
- package/blocks/deals/hooks/use-pipelines.ts +19 -0
- package/blocks/deals/lib/deals-types.ts +128 -0
- package/blocks/deals/recipes/01-create-deal-and-advance.ts +32 -0
- package/blocks/deals/recipes/02-pipeline-report.ts +35 -0
- package/blocks/deals/recipes/03-stale-deals-sweep.ts +20 -0
- package/blocks/dialer/README.md +114 -0
- package/blocks/dialer/block.json +260 -0
- package/blocks/dialer/components/call-log.tsx +78 -0
- package/blocks/dialer/components/call-queue.tsx +188 -0
- package/blocks/dialer/components/dialer-workspace.tsx +47 -0
- package/blocks/dialer/components/disposition-panel.tsx +75 -0
- package/blocks/dialer/components/live-calls-table.tsx +107 -0
- package/blocks/dialer/components/session-start-form.tsx +111 -0
- package/blocks/dialer/components/softphone.tsx +122 -0
- package/blocks/dialer/components/transcript-viewer.tsx +82 -0
- package/blocks/dialer/hooks/use-dialer-numbers.ts +19 -0
- package/blocks/dialer/hooks/use-dialer-session.ts +68 -0
- package/blocks/dialer/hooks/use-dispositions.ts +29 -0
- package/blocks/dialer/lib/dialer-types.ts +158 -0
- package/blocks/dialer/lib/softphone-transport.ts +67 -0
- package/blocks/dialer/lib/twilio-transport.ts +74 -0
- package/blocks/dialer/recipes/01-create-session-and-dial.ts +39 -0
- package/blocks/dialer/recipes/02-disposition-calls.ts +26 -0
- package/blocks/dialer/recipes/03-review-a-call.ts +23 -0
- package/blocks/enrichment/README.md +105 -0
- package/blocks/enrichment/block.json +204 -0
- package/blocks/enrichment/components/bulk-enrich-panel.tsx +205 -0
- package/blocks/enrichment/components/email-verify-badge.tsx +82 -0
- package/blocks/enrichment/components/enrich-button.tsx +136 -0
- package/blocks/enrichment/components/waterfall-config.tsx +127 -0
- package/blocks/enrichment/hooks/use-providers.ts +18 -0
- package/blocks/enrichment/lib/enrichment-types.ts +104 -0
- package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +41 -0
- package/blocks/enrichment/recipes/lookup-and-verify.ts +25 -0
- package/blocks/enrichment/recipes/waterfall-pipeline.ts +26 -0
- package/blocks/forms/README.md +112 -0
- package/blocks/forms/block.json +200 -0
- package/blocks/forms/components/form-builder.tsx +150 -0
- package/blocks/forms/components/form-embed.tsx +103 -0
- package/blocks/forms/components/form-mapping-editor.tsx +125 -0
- package/blocks/forms/components/site-audit-panel.tsx +73 -0
- package/blocks/forms/components/submissions-table.tsx +158 -0
- package/blocks/forms/lib/form-types.ts +127 -0
- package/blocks/forms/recipes/install-snippet-and-form.ts +21 -0
- package/blocks/forms/recipes/retry-failed-deliveries.ts +26 -0
- package/blocks/forms/recipes/route-form-to-list.ts +44 -0
- package/blocks/inbox/README.md +72 -0
- package/blocks/inbox/block.json +194 -0
- package/blocks/inbox/components/inbox-list.tsx +156 -0
- package/blocks/inbox/components/inbox-screen.tsx +49 -0
- package/blocks/inbox/components/reply-composer.tsx +183 -0
- package/blocks/inbox/components/tag-and-assign.tsx +189 -0
- package/blocks/inbox/components/thread-badge.tsx +17 -0
- package/blocks/inbox/components/thread-view.tsx +108 -0
- package/blocks/inbox/hooks/use-inbox-thread.ts +24 -0
- package/blocks/inbox/hooks/use-inbox-threads.ts +40 -0
- package/blocks/inbox/lib/idempotency.ts +30 -0
- package/blocks/inbox/lib/inbox-types.ts +100 -0
- package/blocks/inbox/recipes/01-triage-and-reply.ts +43 -0
- package/blocks/inbox/recipes/02-tag-and-assign.ts +31 -0
- package/blocks/inbox/recipes/03-hot-contacts-digest.ts +16 -0
- package/blocks/integrations/README.md +75 -0
- package/blocks/integrations/block.json +172 -0
- package/blocks/integrations/components/integration-list.tsx +165 -0
- package/blocks/integrations/components/sync-status.tsx +223 -0
- package/blocks/integrations/components/webhook-manager.tsx +300 -0
- package/blocks/integrations/lib/integrations-utils.ts +62 -0
- package/blocks/integrations/recipes/crm-sync-health.ts +19 -0
- package/blocks/integrations/recipes/push-contacts-to-crm.ts +20 -0
- package/blocks/integrations/recipes/register-webhook.ts +18 -0
- package/blocks/integrations/recipes/verify-webhook-route.ts +33 -0
- package/blocks/intent/README.md +57 -0
- package/blocks/intent/block.json +138 -0
- package/blocks/intent/components/account-intent-score.tsx +91 -0
- package/blocks/intent/components/intent-feed.tsx +215 -0
- package/blocks/intent/components/intent-keyword-manager.tsx +331 -0
- package/blocks/intent/hooks/use-account-intent.ts +46 -0
- package/blocks/intent/lib/intent-utils.ts +95 -0
- package/blocks/intent/recipes/rank-intent-accounts.ts +36 -0
- package/blocks/intent/recipes/track-competitor-domain.ts +23 -0
- package/blocks/intent/recipes/who-visited-url.ts +18 -0
- package/blocks/lifecycle/README.md +79 -0
- package/blocks/lifecycle/block.json +198 -0
- package/blocks/lifecycle/components/churn-list.tsx +92 -0
- package/blocks/lifecycle/components/customer-table.tsx +119 -0
- package/blocks/lifecycle/components/lead-queue.tsx +127 -0
- package/blocks/lifecycle/components/lifecycle-board.tsx +102 -0
- package/blocks/lifecycle/components/stage-move-dialog.tsx +75 -0
- package/blocks/lifecycle/hooks/use-debounced.ts +13 -0
- package/blocks/lifecycle/hooks/use-lifecycle-stages.ts +27 -0
- package/blocks/lifecycle/lib/lifecycle.ts +99 -0
- package/blocks/lifecycle/recipes/customer-brief.ts +23 -0
- package/blocks/lifecycle/recipes/move-company-stage.ts +17 -0
- package/blocks/lifecycle/recipes/read-lifecycle-board.ts +21 -0
- package/blocks/lifecycle/recipes/unlock-stage-contacts.ts +25 -0
- package/blocks/lists/README.md +96 -0
- package/blocks/lists/block.json +166 -0
- package/blocks/lists/components/add-to-list-button.tsx +111 -0
- package/blocks/lists/components/list-picker.tsx +94 -0
- package/blocks/lists/components/list-table.tsx +160 -0
- package/blocks/lists/components/suppression-manager.tsx +153 -0
- package/blocks/lists/recipes/create-list-and-add-contacts.ts +35 -0
- package/blocks/lists/recipes/refresh-and-check-lists.ts +21 -0
- package/blocks/lists/recipes/suppress-and-reinstate.ts +30 -0
- package/blocks/mailboxes/README.md +71 -0
- package/blocks/mailboxes/block.json +187 -0
- package/blocks/mailboxes/components/connect-mailbox.tsx +284 -0
- package/blocks/mailboxes/components/domain-purchase.tsx +271 -0
- package/blocks/mailboxes/components/mailbox-list.tsx +185 -0
- package/blocks/mailboxes/components/warmup-health.tsx +232 -0
- package/blocks/mailboxes/lib/mailboxes-utils.ts +69 -0
- package/blocks/mailboxes/recipes/buy-domain-and-mailboxes.ts +64 -0
- package/blocks/mailboxes/recipes/connect-imap-mailbox.ts +38 -0
- package/blocks/mailboxes/recipes/is-it-safe-to-send.ts +17 -0
- package/blocks/mailboxes/recipes/warmup-report.ts +31 -0
- package/blocks/newsletter/README.md +122 -0
- package/blocks/newsletter/block.json +224 -0
- package/blocks/newsletter/components/issue-form.tsx +124 -0
- package/blocks/newsletter/components/issue-scheduler.tsx +152 -0
- package/blocks/newsletter/components/newsletter-editor.tsx +131 -0
- package/blocks/newsletter/components/newsletter-stats.tsx +93 -0
- package/blocks/newsletter/components/subscriber-table.tsx +173 -0
- package/blocks/newsletter/lib/newsletter-types.ts +97 -0
- package/blocks/newsletter/recipes/create-newsletter-and-draft-issue.ts +27 -0
- package/blocks/newsletter/recipes/import-audience-and-send.ts +43 -0
- package/blocks/newsletter/recipes/issue-performance-report.ts +26 -0
- package/blocks/nurture/README.md +110 -0
- package/blocks/nurture/block.json +191 -0
- package/blocks/nurture/components/nurture-builder.tsx +114 -0
- package/blocks/nurture/components/nurture-enroll.tsx +107 -0
- package/blocks/nurture/components/nurture-form.tsx +120 -0
- package/blocks/nurture/components/nurture-stats.tsx +81 -0
- package/blocks/nurture/lib/nurture-types.ts +109 -0
- package/blocks/nurture/recipes/create-a-nurture.ts +34 -0
- package/blocks/nurture/recipes/enroll-contacts-and-launch.ts +24 -0
- package/blocks/nurture/recipes/nurture-health-check.ts +28 -0
- package/blocks/objects/README.md +120 -0
- package/blocks/objects/block.json +243 -0
- package/blocks/objects/components/attribute-field-input.tsx +82 -0
- package/blocks/objects/components/attribute-form-dialog.tsx +159 -0
- package/blocks/objects/components/object-not-found.tsx +34 -0
- package/blocks/objects/components/object-record-detail.tsx +143 -0
- package/blocks/objects/components/object-schema-editor.tsx +197 -0
- package/blocks/objects/components/object-table.tsx +130 -0
- package/blocks/objects/components/record-form-dialog.tsx +112 -0
- package/blocks/objects/components/record-roles-panel.tsx +106 -0
- package/blocks/objects/hooks/use-object-schema.ts +36 -0
- package/blocks/objects/lib/object-types.ts +214 -0
- package/blocks/objects/recipes/define-object-and-add-records.ts +24 -0
- package/blocks/objects/recipes/update-audit-and-archive-records.ts +39 -0
- package/blocks/objects/recipes/upsert-records-from-your-system.ts +30 -0
- package/blocks/pages/README.md +104 -0
- package/blocks/pages/block.json +223 -0
- package/blocks/pages/components/brand-kit-editor.tsx +152 -0
- package/blocks/pages/components/content-library.tsx +163 -0
- package/blocks/pages/components/page-editor.tsx +253 -0
- package/blocks/pages/components/page-list.tsx +159 -0
- package/blocks/pages/components/seo-report.tsx +159 -0
- package/blocks/pages/lib/studio.ts +77 -0
- package/blocks/pages/recipes/audit-site-seo.ts +28 -0
- package/blocks/pages/recipes/create-and-publish-page.ts +26 -0
- package/blocks/pages/recipes/edit-page-section.ts +25 -0
- package/blocks/pages/recipes/refresh-brand-kit.ts +22 -0
- package/blocks/quotes/README.md +79 -0
- package/blocks/quotes/block.json +183 -0
- package/blocks/quotes/components/product-picker.tsx +80 -0
- package/blocks/quotes/components/quote-builder.tsx +195 -0
- package/blocks/quotes/components/quote-list.tsx +79 -0
- package/blocks/quotes/components/quote-viewer.tsx +135 -0
- package/blocks/quotes/components/signature-status.tsx +77 -0
- package/blocks/quotes/hooks/use-debounced.ts +13 -0
- package/blocks/quotes/lib/quotes.ts +142 -0
- package/blocks/quotes/recipes/create-and-send-quote.ts +44 -0
- package/blocks/quotes/recipes/revise-sent-quote.ts +18 -0
- package/blocks/quotes/recipes/track-quote-signature.ts +22 -0
- package/blocks/radar/README.md +106 -0
- package/blocks/radar/block.json +189 -0
- package/blocks/radar/components/competitor-battle-card.tsx +53 -0
- package/blocks/radar/components/competitor-tracker.tsx +158 -0
- package/blocks/radar/components/radar-overview.tsx +67 -0
- package/blocks/radar/components/radar-report.tsx +86 -0
- package/blocks/radar/hooks/use-competitors.ts +23 -0
- package/blocks/radar/lib/radar-types.ts +124 -0
- package/blocks/radar/recipes/battle-card.ts +28 -0
- package/blocks/radar/recipes/deep-dive-report.ts +32 -0
- package/blocks/radar/recipes/marketing-snapshot.ts +18 -0
- package/blocks/radar/recipes/track-and-scan.ts +39 -0
- package/blocks/search/README.md +129 -0
- package/blocks/search/block.json +185 -0
- package/blocks/search/components/filter-builder.tsx +149 -0
- package/blocks/search/components/prospect-results.tsx +175 -0
- package/blocks/search/components/save-to-list-dialog.tsx +135 -0
- package/blocks/search/components/saved-searches.tsx +95 -0
- package/blocks/search/hooks/use-filter-options.ts +41 -0
- package/blocks/search/lib/search-types.ts +149 -0
- package/blocks/search/recipes/explore-filter-values.ts +27 -0
- package/blocks/search/recipes/saved-searches.ts +34 -0
- package/blocks/search/recipes/search-and-save-to-list.ts +37 -0
- package/blocks/sequencer/README.md +87 -0
- package/blocks/sequencer/block.json +214 -0
- package/blocks/sequencer/components/enroll-dialog.tsx +93 -0
- package/blocks/sequencer/components/sequence-builder.tsx +306 -0
- package/blocks/sequencer/components/sequence-list.tsx +200 -0
- package/blocks/sequencer/components/sequence-stats.tsx +256 -0
- package/blocks/sequencer/components/step-editor.tsx +213 -0
- package/blocks/sequencer/lib/sequencer-utils.ts +168 -0
- package/blocks/sequencer/recipes/create-multichannel-sequence.ts +51 -0
- package/blocks/sequencer/recipes/launch-and-enroll.ts +44 -0
- package/blocks/sequencer/recipes/pause-when-unsafe.ts +30 -0
- package/blocks/sequencer/recipes/sequence-report.ts +22 -0
- package/blocks/social-ads/README.md +128 -0
- package/blocks/social-ads/block.json +256 -0
- package/blocks/social-ads/components/ad-campaign-list.tsx +136 -0
- package/blocks/social-ads/components/audience-sync.tsx +150 -0
- package/blocks/social-ads/components/creative-gallery.tsx +121 -0
- package/blocks/social-ads/components/post-composer.tsx +122 -0
- package/blocks/social-ads/components/social-post-list.tsx +116 -0
- package/blocks/social-ads/hooks/use-social-accounts.ts +16 -0
- package/blocks/social-ads/lib/social-ads-types.ts +167 -0
- package/blocks/social-ads/recipes/creative-to-gallery.ts +41 -0
- package/blocks/social-ads/recipes/schedule-post.ts +51 -0
- package/blocks/social-ads/recipes/sync-audience-and-launch.ts +41 -0
- package/blocks/visitors/README.md +56 -0
- package/blocks/visitors/block.json +139 -0
- package/blocks/visitors/components/snippet-install.tsx +169 -0
- package/blocks/visitors/components/traffic-chart.tsx +156 -0
- package/blocks/visitors/components/visitor-company-card.tsx +92 -0
- package/blocks/visitors/components/visitor-feed.tsx +157 -0
- package/blocks/visitors/lib/visitors-utils.ts +78 -0
- package/blocks/visitors/recipes/check-snippet-installed.ts +24 -0
- package/blocks/visitors/recipes/recent-company-visits.ts +27 -0
- package/blocks/visitors/recipes/traffic-summary.ts +24 -0
- package/blocks/webchat/README.md +87 -0
- package/blocks/webchat/block.json +134 -0
- package/blocks/webchat/components/chat-widget-config.tsx +160 -0
- package/blocks/webchat/components/conversation-list.tsx +74 -0
- package/blocks/webchat/lib/webchat-types.ts +83 -0
- package/blocks/webchat/recipes/create-bot-and-style-widget.ts +24 -0
- package/blocks/webchat/recipes/list-chat-visitors.ts +24 -0
- package/blocks/workbench/README.md +135 -0
- package/blocks/workbench/block.json +259 -0
- package/blocks/workbench/components/column-recipe-editor.tsx +183 -0
- package/blocks/workbench/components/run-progress.tsx +132 -0
- package/blocks/workbench/components/workbench-add-rows.tsx +100 -0
- package/blocks/workbench/components/workbench-grid.tsx +159 -0
- package/blocks/workbench/components/workbench-row-actions.tsx +102 -0
- package/blocks/workbench/components/workbench-table-list.tsx +105 -0
- package/blocks/workbench/hooks/use-workbench-table.ts +30 -0
- package/blocks/workbench/lib/waterfall-presets.ts +119 -0
- package/blocks/workbench/lib/workbench-types.ts +191 -0
- package/blocks/workbench/recipes/build-enriched-table.ts +65 -0
- package/blocks/workbench/recipes/import-from-search.ts +42 -0
- package/blocks/workbench/recipes/scrape-into-table.ts +36 -0
- package/blocks/workbench/recipes/waterfall-column.ts +55 -0
- package/blocks/workflows/README.md +100 -0
- package/blocks/workflows/block.json +188 -0
- package/blocks/workflows/components/run-history.tsx +136 -0
- package/blocks/workflows/components/trigger-config.tsx +134 -0
- package/blocks/workflows/components/workflow-editor.tsx +142 -0
- package/blocks/workflows/components/workflow-list.tsx +153 -0
- package/blocks/workflows/hooks/use-workflow.ts +51 -0
- package/blocks/workflows/lib/graph.ts +187 -0
- package/blocks/workflows/recipes/create-workflow.ts +35 -0
- package/blocks/workflows/recipes/find-failed-runs.ts +15 -0
- package/blocks/workflows/recipes/run-workflow-and-wait.ts +32 -0
- package/blocks/workflows/recipes/set-trigger.ts +22 -0
- package/dist/cli.js +5330 -29
- package/dist/index.browser.js +7740 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.browser.mjs +7717 -0
- package/dist/index.browser.mjs.map +1 -0
- package/dist/index.d.mts +1440 -21
- package/dist/index.d.ts +1440 -21
- package/dist/index.js +42 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +42 -3
- package/dist/index.mjs.map +1 -1
- package/dist/react.browser.js +7598 -0
- package/dist/react.browser.js.map +1 -0
- package/dist/react.browser.mjs +7568 -0
- package/dist/react.browser.mjs.map +1 -0
- package/dist/react.d.mts +1340 -18
- package/dist/react.d.ts +1340 -18
- package/dist/react.js +41 -2
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +41 -2
- package/dist/react.mjs.map +1 -1
- package/dist/types/agentSetup.d.ts +6 -6
- package/dist/types/blocks/add.d.ts +53 -0
- package/dist/types/blocks/create.d.ts +46 -0
- package/dist/types/blocks/doctor.d.ts +26 -0
- package/dist/types/blocks/registry.d.ts +134 -0
- package/dist/types/cli.d.ts +16 -0
- package/dist/types/core.d.ts +1 -1
- package/dist/types/crypto-browser.d.ts +2 -0
- package/dist/types/crypto-node.d.ts +6 -0
- package/dist/types/react.d.ts +1 -1
- package/dist/types/version.d.ts +2 -2
- package/dist/types/webhooks.d.ts +2 -2
- package/dist/ui/index.d.mts +54 -0
- package/dist/ui/index.d.ts +54 -0
- package/dist/ui/index.js +105 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/index.mjs +44 -0
- package/dist/ui/index.mjs.map +1 -0
- package/dist/ui/lib/compose-refs.d.mts +19 -0
- package/dist/ui/lib/compose-refs.d.ts +19 -0
- package/dist/ui/lib/compose-refs.js +76 -0
- package/dist/ui/lib/compose-refs.js.map +1 -0
- package/dist/ui/lib/compose-refs.mjs +41 -0
- package/dist/ui/lib/compose-refs.mjs.map +1 -0
- package/dist/ui/lib/use-mobile.d.mts +4 -0
- package/dist/ui/lib/use-mobile.d.ts +4 -0
- package/dist/ui/lib/use-mobile.js +57 -0
- package/dist/ui/lib/use-mobile.js.map +1 -0
- package/dist/ui/lib/use-mobile.mjs +22 -0
- package/dist/ui/lib/use-mobile.mjs.map +1 -0
- package/dist/ui/lib/utils.d.mts +10 -0
- package/dist/ui/lib/utils.d.ts +10 -0
- package/dist/ui/lib/utils.js +33 -0
- package/dist/ui/lib/utils.js.map +1 -0
- package/dist/ui/lib/utils.mjs +9 -0
- package/dist/ui/lib/utils.mjs.map +1 -0
- package/dist/ui/primitives/alert-dialog.d.mts +22 -0
- package/dist/ui/primitives/alert-dialog.d.ts +22 -0
- package/dist/ui/primitives/alert-dialog.js +163 -0
- package/dist/ui/primitives/alert-dialog.js.map +1 -0
- package/dist/ui/primitives/alert-dialog.mjs +119 -0
- package/dist/ui/primitives/alert-dialog.mjs.map +1 -0
- package/dist/ui/primitives/avatar.d.mts +8 -0
- package/dist/ui/primitives/avatar.d.ts +8 -0
- package/dist/ui/primitives/avatar.js +91 -0
- package/dist/ui/primitives/avatar.js.map +1 -0
- package/dist/ui/primitives/avatar.mjs +55 -0
- package/dist/ui/primitives/avatar.mjs.map +1 -0
- package/dist/ui/primitives/breadcrumb.d.mts +13 -0
- package/dist/ui/primitives/breadcrumb.d.ts +13 -0
- package/dist/ui/primitives/breadcrumb.js +127 -0
- package/dist/ui/primitives/breadcrumb.js.map +1 -0
- package/dist/ui/primitives/breadcrumb.mjs +97 -0
- package/dist/ui/primitives/breadcrumb.mjs.map +1 -0
- package/dist/ui/primitives/button.d.mts +15 -0
- package/dist/ui/primitives/button.d.ts +15 -0
- package/dist/ui/primitives/button.js +91 -0
- package/dist/ui/primitives/button.js.map +1 -0
- package/dist/ui/primitives/button.mjs +56 -0
- package/dist/ui/primitives/button.mjs.map +1 -0
- package/dist/ui/primitives/card.d.mts +10 -0
- package/dist/ui/primitives/card.d.ts +10 -0
- package/dist/ui/primitives/card.js +101 -0
- package/dist/ui/primitives/card.js.map +1 -0
- package/dist/ui/primitives/card.mjs +62 -0
- package/dist/ui/primitives/card.mjs.map +1 -0
- package/dist/ui/primitives/carousel.d.mts +45 -0
- package/dist/ui/primitives/carousel.d.ts +45 -0
- package/dist/ui/primitives/carousel.js +300 -0
- package/dist/ui/primitives/carousel.js.map +1 -0
- package/dist/ui/primitives/carousel.mjs +261 -0
- package/dist/ui/primitives/carousel.mjs.map +1 -0
- package/dist/ui/primitives/chart.d.mts +68 -0
- package/dist/ui/primitives/chart.d.ts +68 -0
- package/dist/ui/primitives/chart.js +280 -0
- package/dist/ui/primitives/chart.js.map +1 -0
- package/dist/ui/primitives/chart.mjs +241 -0
- package/dist/ui/primitives/chart.mjs.map +1 -0
- package/dist/ui/primitives/collapsible.d.mts +8 -0
- package/dist/ui/primitives/collapsible.d.ts +8 -0
- package/dist/ui/primitives/collapsible.js +72 -0
- package/dist/ui/primitives/collapsible.js.map +1 -0
- package/dist/ui/primitives/collapsible.mjs +36 -0
- package/dist/ui/primitives/collapsible.mjs.map +1 -0
- package/dist/ui/primitives/context-menu.d.mts +29 -0
- package/dist/ui/primitives/context-menu.d.ts +29 -0
- package/dist/ui/primitives/context-menu.js +203 -0
- package/dist/ui/primitives/context-menu.js.map +1 -0
- package/dist/ui/primitives/context-menu.mjs +155 -0
- package/dist/ui/primitives/context-menu.mjs.map +1 -0
- package/dist/ui/primitives/dialog.d.mts +26 -0
- package/dist/ui/primitives/dialog.d.ts +26 -0
- package/dist/ui/primitives/dialog.js +192 -0
- package/dist/ui/primitives/dialog.js.map +1 -0
- package/dist/ui/primitives/dialog.mjs +148 -0
- package/dist/ui/primitives/dialog.mjs.map +1 -0
- package/dist/ui/primitives/drawer.d.mts +26 -0
- package/dist/ui/primitives/drawer.d.ts +26 -0
- package/dist/ui/primitives/drawer.js +146 -0
- package/dist/ui/primitives/drawer.js.map +1 -0
- package/dist/ui/primitives/drawer.mjs +103 -0
- package/dist/ui/primitives/drawer.mjs.map +1 -0
- package/dist/ui/primitives/input.d.mts +5 -0
- package/dist/ui/primitives/input.d.ts +5 -0
- package/dist/ui/primitives/input.js +46 -0
- package/dist/ui/primitives/input.js.map +1 -0
- package/dist/ui/primitives/input.mjs +22 -0
- package/dist/ui/primitives/input.mjs.map +1 -0
- package/dist/ui/primitives/item.d.mts +27 -0
- package/dist/ui/primitives/item.d.ts +27 -0
- package/dist/ui/primitives/item.js +221 -0
- package/dist/ui/primitives/item.js.map +1 -0
- package/dist/ui/primitives/item.mjs +188 -0
- package/dist/ui/primitives/item.mjs.map +1 -0
- package/dist/ui/primitives/kbd.d.mts +15 -0
- package/dist/ui/primitives/kbd.d.ts +15 -0
- package/dist/ui/primitives/kbd.js +45 -0
- package/dist/ui/primitives/kbd.js.map +1 -0
- package/dist/ui/primitives/kbd.mjs +21 -0
- package/dist/ui/primitives/kbd.mjs.map +1 -0
- package/dist/ui/primitives/label.d.mts +6 -0
- package/dist/ui/primitives/label.d.ts +6 -0
- package/dist/ui/primitives/label.js +55 -0
- package/dist/ui/primitives/label.js.map +1 -0
- package/dist/ui/primitives/label.mjs +21 -0
- package/dist/ui/primitives/label.mjs.map +1 -0
- package/dist/ui/primitives/resizable.d.mts +10 -0
- package/dist/ui/primitives/resizable.d.ts +10 -0
- package/dist/ui/primitives/resizable.js +68 -0
- package/dist/ui/primitives/resizable.js.map +1 -0
- package/dist/ui/primitives/resizable.mjs +42 -0
- package/dist/ui/primitives/resizable.mjs.map +1 -0
- package/dist/ui/primitives/save-status.d.mts +31 -0
- package/dist/ui/primitives/save-status.d.ts +31 -0
- package/dist/ui/primitives/save-status.js +150 -0
- package/dist/ui/primitives/save-status.js.map +1 -0
- package/dist/ui/primitives/save-status.mjs +126 -0
- package/dist/ui/primitives/save-status.mjs.map +1 -0
- package/dist/ui/primitives/separator.d.mts +6 -0
- package/dist/ui/primitives/separator.d.ts +6 -0
- package/dist/ui/primitives/separator.js +62 -0
- package/dist/ui/primitives/separator.js.map +1 -0
- package/dist/ui/primitives/separator.mjs +28 -0
- package/dist/ui/primitives/separator.mjs.map +1 -0
- package/dist/ui/primitives/sheet.d.mts +16 -0
- package/dist/ui/primitives/sheet.d.ts +16 -0
- package/dist/ui/primitives/sheet.js +170 -0
- package/dist/ui/primitives/sheet.js.map +1 -0
- package/dist/ui/primitives/sheet.mjs +129 -0
- package/dist/ui/primitives/sheet.mjs.map +1 -0
- package/dist/ui/primitives/sidebar.d.mts +74 -0
- package/dist/ui/primitives/sidebar.d.ts +74 -0
- package/dist/ui/primitives/sidebar.js +697 -0
- package/dist/ui/primitives/sidebar.js.map +1 -0
- package/dist/ui/primitives/sidebar.mjs +651 -0
- package/dist/ui/primitives/sidebar.mjs.map +1 -0
- package/dist/ui/primitives/skeleton.d.mts +5 -0
- package/dist/ui/primitives/skeleton.d.ts +5 -0
- package/dist/ui/primitives/skeleton.js +40 -0
- package/dist/ui/primitives/skeleton.js.map +1 -0
- package/dist/ui/primitives/skeleton.mjs +16 -0
- package/dist/ui/primitives/skeleton.mjs.map +1 -0
- package/dist/ui/primitives/slider.d.mts +20 -0
- package/dist/ui/primitives/slider.d.ts +20 -0
- package/dist/ui/primitives/slider.js +112 -0
- package/dist/ui/primitives/slider.js.map +1 -0
- package/dist/ui/primitives/slider.mjs +78 -0
- package/dist/ui/primitives/slider.mjs.map +1 -0
- package/dist/ui/primitives/sortable.d.mts +50 -0
- package/dist/ui/primitives/sortable.d.ts +50 -0
- package/dist/ui/primitives/sortable.js +456 -0
- package/dist/ui/primitives/sortable.js.map +1 -0
- package/dist/ui/primitives/sortable.mjs +435 -0
- package/dist/ui/primitives/sortable.mjs.map +1 -0
- package/dist/ui/primitives/switch.d.mts +6 -0
- package/dist/ui/primitives/switch.d.ts +6 -0
- package/dist/ui/primitives/switch.js +67 -0
- package/dist/ui/primitives/switch.js.map +1 -0
- package/dist/ui/primitives/switch.mjs +33 -0
- package/dist/ui/primitives/switch.mjs.map +1 -0
- package/dist/ui/primitives/table.d.mts +12 -0
- package/dist/ui/primitives/table.d.ts +12 -0
- package/dist/ui/primitives/table.js +130 -0
- package/dist/ui/primitives/table.js.map +1 -0
- package/dist/ui/primitives/table.mjs +89 -0
- package/dist/ui/primitives/table.mjs.map +1 -0
- package/dist/ui/primitives/tabs.d.mts +9 -0
- package/dist/ui/primitives/tabs.d.ts +9 -0
- package/dist/ui/primitives/tabs.js +86 -0
- package/dist/ui/primitives/tabs.js.map +1 -0
- package/dist/ui/primitives/tabs.mjs +49 -0
- package/dist/ui/primitives/tabs.mjs.map +1 -0
- package/dist/ui/primitives/textarea.d.mts +5 -0
- package/dist/ui/primitives/textarea.d.ts +5 -0
- package/dist/ui/primitives/textarea.js +43 -0
- package/dist/ui/primitives/textarea.js.map +1 -0
- package/dist/ui/primitives/textarea.mjs +19 -0
- package/dist/ui/primitives/textarea.mjs.map +1 -0
- package/dist/ui/primitives/tooltip.d.mts +9 -0
- package/dist/ui/primitives/tooltip.d.ts +9 -0
- package/dist/ui/primitives/tooltip.js +94 -0
- package/dist/ui/primitives/tooltip.js.map +1 -0
- package/dist/ui/primitives/tooltip.mjs +57 -0
- package/dist/ui/primitives/tooltip.mjs.map +1 -0
- package/docs/agents.md +72 -29
- package/docs/api/README.md +6 -3
- package/docs/api/appObjects.d.ts +1 -1
- package/docs/api/appObjects.md +1 -1
- package/docs/api/by-path.md +29 -0
- package/docs/api/objects.d.ts +72 -5
- package/docs/api/objects.md +84 -7
- package/docs/api/processLists.d.ts +247 -0
- package/docs/api/processLists.md +409 -0
- package/docs/api/reports.d.ts +476 -0
- package/docs/api/reports.md +539 -0
- package/docs/api/scheduleBuy.d.ts +12 -8
- package/docs/api/scheduleBuy.md +13 -10
- package/docs/api/seo.d.ts +406 -3
- package/docs/api/seo.md +505 -1
- package/docs/api/workspaceBackup.d.ts +324 -0
- package/docs/api/workspaceBackup.md +427 -0
- package/package.json +174 -5
- package/stacks/6sense.json +46 -0
- package/stacks/README.md +20 -0
- package/stacks/apollo.json +128 -0
- package/stacks/clay.json +59 -0
- package/stacks/gong.json +72 -0
- package/stacks/hubspot.json +162 -0
- package/stacks/intercom.json +53 -0
- package/stacks/mailchimp.json +71 -0
- package/stacks/outreach.json +86 -0
- package/stacks/salesforce.json +94 -0
- package/stacks/stack.schema.json +32 -0
- package/stacks/starter.json +22 -0
- package/styles/styles.css +2 -0
- package/styles/theme.css +181 -0
- package/styles/tokens.css +284 -0
- package/templates/next-app/README.md.tmpl +35 -0
- package/templates/next-app/_pages/redirect-page.tsx.tmpl +5 -0
- package/templates/next-app/_pages/route-page.tsx.tmpl +9 -0
- package/templates/next-app/app/globals.css.tmpl +8 -0
- package/templates/next-app/app/layout.tsx.tmpl +19 -0
- package/templates/next-app/components/app-shell.tsx.tmpl +34 -0
- package/templates/next-app/dot-env.local.example.tmpl +6 -0
- package/templates/next-app/dot-gitignore.tmpl +7 -0
- package/templates/next-app/lib/nav.ts.tmpl +2 -0
- package/templates/next-app/next.config.mjs.tmpl +4 -0
- package/templates/next-app/package.json.tmpl +25 -0
- package/templates/next-app/postcss.config.mjs.tmpl +4 -0
- package/templates/next-app/tsconfig.json.tmpl +21 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AgentSkillsPanel: the skills (single-purpose actions, LLM or API) an agent can use. Attach and detach
|
|
5
|
+
* existing skills, or write a new LLM skill in place.
|
|
6
|
+
*
|
|
7
|
+
* SDK operations, in order:
|
|
8
|
+
* 1. listAgentSkills GET /voice/agents/{agent_id}/skills (read)
|
|
9
|
+
* 2. listSkills GET /skills (read) org skills + system defaults
|
|
10
|
+
* 3. attachSkillsToAgent POST /voice/agents/{agent_id}/skills { action_ids } (write)
|
|
11
|
+
* 4. detachSkillFromAgent DELETE /voice/agents/{agent_id}/skills/{action_id} (destructive, ConfirmSpendDialog)
|
|
12
|
+
* 5. listModels GET /skills/models (read)
|
|
13
|
+
* 6. createSkill POST /skills { name, runtime_type: "llm", llm_config } (write)
|
|
14
|
+
*
|
|
15
|
+
* Detaching never deletes the skill; it only stops this agent from using it.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { useEffect, useState } from "react";
|
|
19
|
+
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Skeleton, Textarea } from "@graph8/sdk/ui";
|
|
20
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
21
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
22
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
23
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
24
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
25
|
+
import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
26
|
+
import { listOf } from "../lib/agent-types";
|
|
27
|
+
import type { SkillRow } from "../lib/agent-types";
|
|
28
|
+
|
|
29
|
+
export interface AgentSkillsPanelProps {
|
|
30
|
+
agentId: string;
|
|
31
|
+
/** Called after an attach, detach or create. */
|
|
32
|
+
onChanged?: () => void;
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const idOf = (s: SkillRow): string => s.action_id ?? (s as unknown as { id?: string }).id ?? "";
|
|
37
|
+
|
|
38
|
+
export function AgentSkillsPanel({ agentId, onChanged, className }: AgentSkillsPanelProps) {
|
|
39
|
+
const [detaching, setDetaching] = useState<SkillRow | null>(null);
|
|
40
|
+
const [creating, setCreating] = useState<{ name: string; description: string; prompt: string; model: string } | null>(null);
|
|
41
|
+
|
|
42
|
+
const [attachingId, setAttachingId] = useState<string | null>(null);
|
|
43
|
+
useEffect(() => { setDetaching(null); setCreating(null); setAttachingId(null); }, [agentId]);
|
|
44
|
+
|
|
45
|
+
const attached = useG8Query((signal) => g8Get(`/voice/agents/${agentId}/skills`, undefined, { signal }), [agentId]);
|
|
46
|
+
const all = useG8Query((signal) => g8Get("/skills", undefined, { signal }), []);
|
|
47
|
+
const models = useG8Query((signal) => g8Get("/skills/models", undefined, { signal }), [], { enabled: creating != null });
|
|
48
|
+
|
|
49
|
+
const changed = () => { void attached.refetch(); void all.refetch(); onChanged?.(); };
|
|
50
|
+
const attach = useG8Mutation((actionId: string) => g8Post(`/voice/agents/${agentId}/skills`, { action_ids: [actionId] }), { onSuccess: changed });
|
|
51
|
+
const attachOne = async (id: string) => { setAttachingId(id); await attach.mutate(id); setAttachingId(null); };
|
|
52
|
+
const detach = useG8Mutation((actionId: string) => g8Delete(`/voice/agents/${agentId}/skills/${actionId}`), { onSuccess: () => { setDetaching(null); changed(); } });
|
|
53
|
+
const create = useG8Mutation(
|
|
54
|
+
(v: NonNullable<typeof creating>) => g8Post("/skills", { name: v.name, description: v.description || undefined, runtime_type: "llm", llm_config: { prompt: v.prompt, model: v.model } }),
|
|
55
|
+
{ onSuccess: () => { setCreating(null); changed(); } },
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const mine = listOf<SkillRow>(attached.data, "actions", "skills", "items");
|
|
59
|
+
const attachedIds = new Set(mine.map(idOf));
|
|
60
|
+
const available = listOf<SkillRow>(all.data, "actions", "skills", "items").filter((s) => !attachedIds.has(idOf(s)));
|
|
61
|
+
const modelRows = listOf<{ id: string; label?: string }>(models.data, "models");
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className={`flex flex-col gap-5 ${className ?? ""}`}>
|
|
65
|
+
<div className="flex flex-col gap-2">
|
|
66
|
+
<h3 className="text-sm font-semibold">Attached skills</h3>
|
|
67
|
+
{attached.error ? <ApiError error={attached.error} title="Could not load the agent's skills" onRetry={() => void attached.refetch()} /> : null}
|
|
68
|
+
{attached.loading && mine.length === 0 ? <Skeleton className="h-10 w-full" /> : null}
|
|
69
|
+
<ul className="divide-y rounded-md border">
|
|
70
|
+
{mine.map((s) => (
|
|
71
|
+
<li key={idOf(s)} data-testid={`attached-${idOf(s)}`} className="flex items-center justify-between gap-3 p-2 text-sm">
|
|
72
|
+
<span><span className="font-medium">{s.name}</span>{s.description ? <span className="block text-xs text-muted-foreground">{s.description}</span> : null}</span>
|
|
73
|
+
<Button size="sm" variant="ghost" onClick={() => setDetaching(s)}>Detach</Button>
|
|
74
|
+
</li>
|
|
75
|
+
))}
|
|
76
|
+
</ul>
|
|
77
|
+
{!attached.loading && !attached.error && mine.length === 0 ? <EmptyState title="No skills attached" description="Attach a skill below so the agent can act, not only talk." /> : null}
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div className="flex flex-col gap-2">
|
|
81
|
+
<div className="flex items-center justify-between">
|
|
82
|
+
<h3 className="text-sm font-semibold">Available skills</h3>
|
|
83
|
+
<Button size="sm" variant="outline" onClick={() => setCreating({ name: "", description: "", prompt: "", model: "" })}>New skill</Button>
|
|
84
|
+
</div>
|
|
85
|
+
{all.error ? <ApiError error={all.error} title="Could not load skills" onRetry={() => void all.refetch()} /> : null}
|
|
86
|
+
{attach.error ? <ApiError error={attach.error} title="Could not attach the skill" /> : null}
|
|
87
|
+
<ul className="divide-y rounded-md border">
|
|
88
|
+
{available.map((s) => (
|
|
89
|
+
<li key={idOf(s)} data-testid={`available-${idOf(s)}`} className="flex items-center justify-between gap-3 p-2 text-sm">
|
|
90
|
+
<span><span className="font-medium">{s.name}</span><span className="ml-2 text-xs text-muted-foreground">{s.runtime_type}</span>{s.description ? <span className="block text-xs text-muted-foreground">{s.description}</span> : null}</span>
|
|
91
|
+
<Button size="sm" variant="outline" loading={attachingId === idOf(s)} disabled={attachingId != null && attachingId !== idOf(s)} onClick={() => void attachOne(idOf(s))}>Attach</Button>
|
|
92
|
+
</li>
|
|
93
|
+
))}
|
|
94
|
+
</ul>
|
|
95
|
+
{!all.loading && !all.error && available.length === 0 ? <p className="text-sm text-muted-foreground">Every skill is already attached.</p> : null}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<Dialog open={creating != null} onOpenChange={(o) => !o && setCreating(null)}>
|
|
99
|
+
<DialogContent>
|
|
100
|
+
<DialogHeader><DialogTitle>New LLM skill</DialogTitle><DialogDescription>Use {"{variable}"} placeholders in the prompt for values the agent fills in.</DialogDescription></DialogHeader>
|
|
101
|
+
<div className="flex flex-col gap-3">
|
|
102
|
+
<div className="flex flex-col gap-1"><Label htmlFor="skill-name">Name</Label><Input id="skill-name" value={creating?.name ?? ""} onChange={(e) => setCreating((v) => v && { ...v, name: e.target.value })} /></div>
|
|
103
|
+
<div className="flex flex-col gap-1"><Label htmlFor="skill-desc">What it does</Label><Input id="skill-desc" value={creating?.description ?? ""} onChange={(e) => setCreating((v) => v && { ...v, description: e.target.value })} /></div>
|
|
104
|
+
<div className="flex flex-col gap-1"><Label htmlFor="skill-prompt">Prompt</Label><Textarea id="skill-prompt" rows={5} value={creating?.prompt ?? ""} onChange={(e) => setCreating((v) => v && { ...v, prompt: e.target.value })} /></div>
|
|
105
|
+
<div className="flex flex-col gap-1">
|
|
106
|
+
<Label htmlFor="skill-model">Model</Label>
|
|
107
|
+
<select id="skill-model" className="h-9 rounded-md border bg-background px-2 text-sm" value={creating?.model ?? ""} onChange={(e) => setCreating((v) => v && { ...v, model: e.target.value })}>
|
|
108
|
+
<option value="">Choose a model</option>
|
|
109
|
+
{modelRows.map((m) => <option key={m.id} value={m.id}>{m.label ?? m.id}</option>)}
|
|
110
|
+
</select>
|
|
111
|
+
</div>
|
|
112
|
+
{create.error ? <ApiError error={create.error} title="Could not create the skill" /> : null}
|
|
113
|
+
</div>
|
|
114
|
+
<DialogFooter>
|
|
115
|
+
<Button variant="ghost" onClick={() => setCreating(null)}>Cancel</Button>
|
|
116
|
+
<Button loading={create.loading} disabled={!creating?.name.trim() || !creating.prompt.trim() || !creating.model} onClick={() => creating && void create.mutate(creating)}>Create skill</Button>
|
|
117
|
+
</DialogFooter>
|
|
118
|
+
</DialogContent>
|
|
119
|
+
</Dialog>
|
|
120
|
+
|
|
121
|
+
{detach.error ? <ApiError error={detach.error} title="Could not detach the skill" /> : null}
|
|
122
|
+
<ConfirmSpendDialog open={detaching != null} tier="destructive" title={`Detach "${detaching?.name ?? "skill"}"`} description="The agent can no longer use this skill. The skill itself is kept."
|
|
123
|
+
confirmLabel="Detach" confirming={detach.loading} onConfirm={() => detaching && void detach.mutate(idOf(detaching))} onCancel={() => setDetaching(null)} />
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AgentTestConsole: check an agent before it talks to anyone. "Dry run" resolves what the agent WOULD
|
|
5
|
+
* receive (instructions, tools, grounding) for a message and judges an assertion, with no model call, no
|
|
6
|
+
* call placed and no message sent. "Run a skill" actually executes one attached skill with real input.
|
|
7
|
+
*
|
|
8
|
+
* SDK operations, in order:
|
|
9
|
+
* 1. listAgentTestCases GET /voice/agents/{agent_id}/test-cases (read)
|
|
10
|
+
* 2. getAgentTestSuiteStatus GET /voice/agents/{agent_id}/test-suite-status (read) the activation gate
|
|
11
|
+
* 3. runAgentTest POST /voice/agents/{agent_id}/test { test_case_id | input_message, channel, ... } (write, free)
|
|
12
|
+
* 4. createAgentTestCase POST /voice/agents/{agent_id}/test-cases { name, input_message, assertion } (write)
|
|
13
|
+
* 5. runAllAgentTests POST /voice/agents/{agent_id}/test/run-all (write, free) regression sweep
|
|
14
|
+
* 6. listAgentSkills GET /voice/agents/{agent_id}/skills (read)
|
|
15
|
+
* 7. executeSkill POST /skills/{action_id}/execute { input_data } (external, ConfirmSpendDialog)
|
|
16
|
+
*
|
|
17
|
+
* A skill can call APIs, send messages or spend credits, so running one is confirmed first.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { useEffect, useState } from "react";
|
|
21
|
+
import { Button, Input, Label, Textarea } from "@graph8/sdk/ui";
|
|
22
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
23
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
24
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
25
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
26
|
+
import { g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
27
|
+
import { bodyOf, listOf, pretty } from "../lib/agent-types";
|
|
28
|
+
import type { SkillRow } from "../lib/agent-types";
|
|
29
|
+
|
|
30
|
+
export interface AgentTestConsoleProps {
|
|
31
|
+
agentId: string;
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface CaseRow { test_case_id?: string; id?: string; name: string; input_message?: string; assertion_type?: string }
|
|
36
|
+
type Channel = "chat" | "inbound" | "outbound";
|
|
37
|
+
type Assertion = "contains" | "not_contains" | "exact" | "refuses";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Verdict of a dry run. Voice answers `{ run: { assertion_result: "pass" | "fail" | "error", assertion_detail: { reason } } }`
|
|
41
|
+
* (`llm_judge` runs can be unmeasured or skipped: no verdict). true = pass, false = fail or error.
|
|
42
|
+
*/
|
|
43
|
+
export function verdictOf(body: unknown): boolean | undefined {
|
|
44
|
+
const run = ((body ?? {}) as { run?: Record<string, unknown> }).run ?? {};
|
|
45
|
+
const s = String(run.assertion_result ?? "").toLowerCase();
|
|
46
|
+
if (s === "pass") return true;
|
|
47
|
+
if (s === "fail" || s === "error") return false;
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Why a run failed or errored, when voice says. */
|
|
52
|
+
export function reasonOf(body: unknown): string | undefined {
|
|
53
|
+
const detail = (((body ?? {}) as { run?: { assertion_detail?: { reason?: unknown } } }).run?.assertion_detail ?? {}) as { reason?: unknown };
|
|
54
|
+
return typeof detail.reason === "string" ? detail.reason : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const NEEDS_TEXT: Record<string, boolean> = { contains: true, not_contains: true, exact: true, refuses: false };
|
|
58
|
+
|
|
59
|
+
const idOf = (s: SkillRow): string => s.action_id ?? (s as unknown as { id?: string }).id ?? "";
|
|
60
|
+
|
|
61
|
+
export function AgentTestConsole({ agentId, className }: AgentTestConsoleProps) {
|
|
62
|
+
const [mode, setMode] = useState<"dry" | "skill">("dry");
|
|
63
|
+
const [message, setMessage] = useState("");
|
|
64
|
+
const [channel, setChannel] = useState<Channel>("chat");
|
|
65
|
+
const [assertion, setAssertion] = useState<Assertion>("contains");
|
|
66
|
+
const [expected, setExpected] = useState("");
|
|
67
|
+
const [caseId, setCaseId] = useState("");
|
|
68
|
+
const [saveName, setSaveName] = useState("");
|
|
69
|
+
const [skillId, setSkillId] = useState("");
|
|
70
|
+
const [skillInput, setSkillInput] = useState("{}");
|
|
71
|
+
const [confirmRun, setConfirmRun] = useState(false);
|
|
72
|
+
|
|
73
|
+
// A different agent is a different test bench: clear inputs, results and any open confirmation.
|
|
74
|
+
useEffect(() => { setCaseId(""); setSkillId(""); setMessage(""); setExpected(""); setSaveName(""); setSkillInput("{}"); setConfirmRun(false); dry.reset(); sweep.reset(); live.reset(); }, [agentId]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
75
|
+
|
|
76
|
+
const cases = useG8Query((signal) => g8Get(`/voice/agents/${agentId}/test-cases`, undefined, { signal }), [agentId]);
|
|
77
|
+
const gate = useG8Query((signal) => g8Get(`/voice/agents/${agentId}/test-suite-status`, undefined, { signal }), [agentId]);
|
|
78
|
+
const skills = useG8Query((signal) => g8Get(`/voice/agents/${agentId}/skills`, undefined, { signal }), [agentId], { enabled: mode === "skill" });
|
|
79
|
+
|
|
80
|
+
// Always send the assertion type; only the types that compare against text carry (and require) it. "Must refuse" uses voice's default refusal markers.
|
|
81
|
+
const needsText = NEEDS_TEXT[assertion];
|
|
82
|
+
const assertionBody = { assertion_type: assertion, ...(needsText ? { expected_output: expected.trim() } : {}) };
|
|
83
|
+
const assertionReady = !needsText || expected.trim().length > 0;
|
|
84
|
+
const dry = useG8Mutation(() => g8Post(`/voice/agents/${agentId}/test`, caseId ? { test_case_id: caseId, mode: "dry_run" } : { input_message: message.trim(), channel, mode: "dry_run", ...assertionBody }));
|
|
85
|
+
const sweep = useG8Mutation(() => g8Post(`/voice/agents/${agentId}/test/run-all`, {}), { onSuccess: () => void gate.refetch() });
|
|
86
|
+
const save = useG8Mutation(() => g8Post(`/voice/agents/${agentId}/test-cases`, { name: saveName.trim(), input_message: message.trim(), channel, ...assertionBody }), {
|
|
87
|
+
onSuccess: () => { setSaveName(""); void cases.refetch(); void gate.refetch(); },
|
|
88
|
+
});
|
|
89
|
+
const live = useG8Mutation(() => g8Post(`/skills/${skillId}/execute`, { input_data: JSON.parse(skillInput || "{}") as Record<string, unknown> }), { onSuccess: () => setConfirmRun(false), onError: () => setConfirmRun(false) });
|
|
90
|
+
|
|
91
|
+
const caseRows = listOf<CaseRow>(cases.data, "items");
|
|
92
|
+
const skillRows = listOf<SkillRow>(skills.data, "actions", "skills", "items");
|
|
93
|
+
const gateBody = bodyOf<Record<string, unknown>>(gate.data);
|
|
94
|
+
const dryBody = bodyOf(dry.data);
|
|
95
|
+
const verdict = verdictOf(dryBody);
|
|
96
|
+
const inputOk = (() => { try { JSON.parse(skillInput || "{}"); return true; } catch { return false; } })();
|
|
97
|
+
const skillName = skillRows.find((s) => idOf(s) === skillId)?.name ?? "this skill";
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div className={`flex flex-col gap-4 ${className ?? ""}`}>
|
|
101
|
+
<div role="tablist" className="flex gap-1 border-b">
|
|
102
|
+
{([["dry", "Dry run"], ["skill", "Run a skill"]] as const).map(([id, label]) => (
|
|
103
|
+
<button key={id} role="tab" type="button" aria-selected={mode === id} onClick={() => setMode(id)}
|
|
104
|
+
className={`px-3 py-2 text-sm ${mode === id ? "border-b-2 border-primary font-medium" : "text-muted-foreground"}`}>{label}</button>
|
|
105
|
+
))}
|
|
106
|
+
</div>
|
|
107
|
+
{gate.data !== undefined ? <p className="text-sm text-muted-foreground">Activation gate: {String(gateBody.activation_gate ?? gateBody.status ?? gateBody.gate ?? "see details")}</p> : null}
|
|
108
|
+
|
|
109
|
+
{mode === "dry" ? (
|
|
110
|
+
<div className="flex flex-col gap-3">
|
|
111
|
+
{cases.error ? <ApiError error={cases.error} title="Could not load saved cases" onRetry={() => void cases.refetch()} /> : null}
|
|
112
|
+
<div className="flex flex-col gap-1">
|
|
113
|
+
<Label htmlFor="test-case">Saved case</Label>
|
|
114
|
+
<select id="test-case" className="h-9 rounded-md border bg-background px-2 text-sm" value={caseId} onChange={(e) => setCaseId(e.target.value)}>
|
|
115
|
+
<option value="">Ad-hoc message</option>
|
|
116
|
+
{caseRows.map((c) => { const id = c.test_case_id ?? c.id ?? ""; return <option key={id} value={id}>{c.name}</option>; })}
|
|
117
|
+
</select>
|
|
118
|
+
</div>
|
|
119
|
+
{!caseId ? (
|
|
120
|
+
<>
|
|
121
|
+
<div className="flex flex-col gap-1"><Label htmlFor="test-message">What the person says</Label><Textarea id="test-message" rows={3} value={message} onChange={(e) => setMessage(e.target.value)} /></div>
|
|
122
|
+
<div className="grid gap-3 sm:grid-cols-3">
|
|
123
|
+
<div className="flex flex-col gap-1"><Label htmlFor="test-channel">Channel</Label>
|
|
124
|
+
<select id="test-channel" className="h-9 rounded-md border bg-background px-2 text-sm" value={channel} onChange={(e) => setChannel(e.target.value as Channel)}><option value="chat">Chat</option><option value="inbound">Inbound call</option><option value="outbound">Outbound call</option></select></div>
|
|
125
|
+
<div className="flex flex-col gap-1"><Label htmlFor="test-assertion">The agent</Label>
|
|
126
|
+
<select id="test-assertion" className="h-9 rounded-md border bg-background px-2 text-sm" value={assertion} onChange={(e) => setAssertion(e.target.value as Assertion)}><option value="contains">Must include</option><option value="not_contains">Must not include</option><option value="exact">Must equal</option><option value="refuses">Must refuse</option></select></div>
|
|
127
|
+
<div className="flex flex-col gap-1"><Label htmlFor="test-expected">Text to check</Label><Input id="test-expected" value={expected} onChange={(e) => setExpected(e.target.value)} /></div>
|
|
128
|
+
</div>
|
|
129
|
+
</>
|
|
130
|
+
) : null}
|
|
131
|
+
<div className="flex flex-wrap gap-2">
|
|
132
|
+
<Button loading={dry.loading} disabled={caseId ? false : !message.trim() || !assertionReady} onClick={() => void dry.mutate(undefined)}>Run dry run</Button>
|
|
133
|
+
<Button variant="outline" loading={sweep.loading} onClick={() => void sweep.mutate(undefined)}>Run all saved cases</Button>
|
|
134
|
+
{!caseId && message.trim() ? (
|
|
135
|
+
<span className="ml-auto flex gap-2">
|
|
136
|
+
<Input aria-label="Case name" placeholder="Name this case" className="w-44" value={saveName} onChange={(e) => setSaveName(e.target.value)} />
|
|
137
|
+
<Button variant="outline" loading={save.loading} disabled={!saveName.trim() || !assertionReady} onClick={() => void save.mutate(undefined)}>Save case</Button>
|
|
138
|
+
</span>
|
|
139
|
+
) : null}
|
|
140
|
+
</div>
|
|
141
|
+
{dry.error ? <ApiError error={dry.error} title="The dry run failed" /> : null}
|
|
142
|
+
{sweep.error ? <ApiError error={sweep.error} title="The regression run failed" /> : null}
|
|
143
|
+
{save.error ? <ApiError error={save.error} title="Could not save the case" /> : null}
|
|
144
|
+
{dry.data !== undefined ? (
|
|
145
|
+
<div data-testid="dry-result" className="rounded-md border p-3">
|
|
146
|
+
<p className="mb-2 text-sm font-medium">{verdict === undefined ? "Result" : verdict ? "Passed" : "Failed"}</p>
|
|
147
|
+
{reasonOf(dryBody) ? <p className="mb-2 text-sm">{reasonOf(dryBody)}</p> : null}
|
|
148
|
+
<pre className="max-h-80 overflow-auto text-xs">{pretty(dryBody)}</pre>
|
|
149
|
+
</div>
|
|
150
|
+
) : null}
|
|
151
|
+
{sweep.data !== undefined ? <pre data-testid="sweep-result" className="max-h-60 overflow-auto rounded-md border p-3 text-xs">{pretty(bodyOf(sweep.data))}</pre> : null}
|
|
152
|
+
</div>
|
|
153
|
+
) : (
|
|
154
|
+
<div className="flex flex-col gap-3">
|
|
155
|
+
{skills.error ? <ApiError error={skills.error} title="Could not load the agent's skills" onRetry={() => void skills.refetch()} /> : null}
|
|
156
|
+
<div className="flex flex-col gap-1">
|
|
157
|
+
<Label htmlFor="live-skill">Skill</Label>
|
|
158
|
+
<select id="live-skill" className="h-9 rounded-md border bg-background px-2 text-sm" value={skillId} onChange={(e) => setSkillId(e.target.value)}>
|
|
159
|
+
<option value="">Choose an attached skill</option>
|
|
160
|
+
{skillRows.map((s) => <option key={idOf(s)} value={idOf(s)}>{s.name}</option>)}
|
|
161
|
+
</select>
|
|
162
|
+
</div>
|
|
163
|
+
<div className="flex flex-col gap-1"><Label htmlFor="live-input">Input (JSON)</Label><Textarea id="live-input" rows={4} className="font-mono text-xs" value={skillInput} onChange={(e) => setSkillInput(e.target.value)} /></div>
|
|
164
|
+
{!inputOk ? <p role="alert" className="text-sm text-destructive">The input is not valid JSON.</p> : null}
|
|
165
|
+
<div><Button disabled={!skillId || !inputOk} onClick={() => setConfirmRun(true)}>Run skill</Button></div>
|
|
166
|
+
{live.error ? <ApiError error={live.error} title="The skill run failed" /> : null}
|
|
167
|
+
{live.data !== undefined ? <pre data-testid="live-result" className="max-h-80 overflow-auto rounded-md border p-3 text-xs">{pretty(bodyOf(live.data))}</pre> : null}
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
|
|
171
|
+
<ConfirmSpendDialog open={confirmRun} tier="external" title={`Run ${skillName} now`} description="This really runs the skill. If it calls an API or messages someone, that happens for real and cannot be recalled."
|
|
172
|
+
confirmLabel="Run skill" confirming={live.loading} onConfirm={() => void live.mutate(undefined)} onCancel={() => setConfirmRun(false)} />
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shapes the agents block reads from the graph8 API, plus the small helpers every component uses to
|
|
5
|
+
* read them. The API answers `{ data: ... }`; several voice and skills routes return the service's
|
|
6
|
+
* own body inside `data` (or, for skills, at the top level), so the helpers accept both.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { G8Envelope } from "@/g8/lib/g8-client";
|
|
10
|
+
|
|
11
|
+
export interface AgentSummary {
|
|
12
|
+
agent_id: string;
|
|
13
|
+
agent_name: string;
|
|
14
|
+
role: string;
|
|
15
|
+
agent_status: "active" | "inactive" | "archived" | string;
|
|
16
|
+
entity_type?: string;
|
|
17
|
+
description?: string | null;
|
|
18
|
+
voice?: string | null;
|
|
19
|
+
phone?: string | null;
|
|
20
|
+
use_company_knowledge?: boolean;
|
|
21
|
+
collection_count?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AgentPersona {
|
|
25
|
+
agent_name: string;
|
|
26
|
+
persona: string;
|
|
27
|
+
description: string;
|
|
28
|
+
formality_level: number;
|
|
29
|
+
conciseness_level: number;
|
|
30
|
+
assertiveness_level: number;
|
|
31
|
+
inbound_instructions?: string | null;
|
|
32
|
+
outbound_instructions?: string | null;
|
|
33
|
+
outbound_voicemail_prompt?: string | null;
|
|
34
|
+
company_name_pronunciation?: string | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AgentIdentity {
|
|
38
|
+
voice?: string | null;
|
|
39
|
+
phone?: string | null;
|
|
40
|
+
calendar?: number | null;
|
|
41
|
+
avatar_url?: string | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface AgentDetail {
|
|
45
|
+
agent_id: string;
|
|
46
|
+
agent_status: string;
|
|
47
|
+
role: string;
|
|
48
|
+
entity_type?: string;
|
|
49
|
+
persona: AgentPersona;
|
|
50
|
+
identity: AgentIdentity;
|
|
51
|
+
knowledge?: { collections?: string[] } | null;
|
|
52
|
+
use_company_knowledge: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface SkillRow {
|
|
56
|
+
action_id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
description?: string | null;
|
|
59
|
+
runtime_type?: "llm" | "api" | string;
|
|
60
|
+
category?: string | null;
|
|
61
|
+
llm_config?: { prompt?: string; model?: string } | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface AgentRun {
|
|
65
|
+
work_item_id: string;
|
|
66
|
+
agent_id: string;
|
|
67
|
+
title: string;
|
|
68
|
+
status: string;
|
|
69
|
+
current_step?: string | null;
|
|
70
|
+
next_action?: string | null;
|
|
71
|
+
credits_spent?: number;
|
|
72
|
+
credit_budget?: number | null;
|
|
73
|
+
created_at?: string;
|
|
74
|
+
completed_at?: string | null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const DEFAULT_PERSONA: AgentPersona = {
|
|
78
|
+
agent_name: "",
|
|
79
|
+
persona: "",
|
|
80
|
+
description: "",
|
|
81
|
+
formality_level: 0.5,
|
|
82
|
+
conciseness_level: 0.5,
|
|
83
|
+
assertiveness_level: 0.5,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** `data` of an envelope, or the body itself when the route did not envelope it. */
|
|
87
|
+
export function bodyOf<T = Record<string, unknown>>(res: G8Envelope<unknown> | null | undefined): T {
|
|
88
|
+
const data = (res as { data?: unknown } | null | undefined)?.data;
|
|
89
|
+
return (data ?? res ?? {}) as T;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Find the first array under any of `keys` in the response body (`agents`, `items`, `actions`...). */
|
|
93
|
+
export function listOf<T>(res: G8Envelope<unknown> | null | undefined, ...keys: string[]): T[] {
|
|
94
|
+
const body = bodyOf<unknown>(res);
|
|
95
|
+
if (Array.isArray(body)) return body as T[];
|
|
96
|
+
const rec = (body ?? {}) as Record<string, unknown>;
|
|
97
|
+
for (const k of keys) if (Array.isArray(rec[k])) return rec[k] as T[];
|
|
98
|
+
const top = (res ?? {}) as Record<string, unknown>;
|
|
99
|
+
for (const k of keys) if (Array.isArray(top[k])) return top[k] as T[];
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function percent(n: number | undefined): string {
|
|
104
|
+
return `${Math.round((n ?? 0) * 100)}%`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** A readable one-line summary of any JSON value, for result panels. */
|
|
108
|
+
export function pretty(value: unknown): string {
|
|
109
|
+
try {
|
|
110
|
+
return JSON.stringify(value, null, 2);
|
|
111
|
+
} catch {
|
|
112
|
+
return String(value);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @title Create an agent, give it a skill and some knowledge
|
|
3
|
+
* @when Building an AI agent (voice or chat) from code instead of the AgentBuilder screen. The agent id
|
|
4
|
+
* comes from the create response; skill ids come from creating or listing skills, never a guess.
|
|
5
|
+
* `persona`, `identity` and `knowledge` are replaced whole on update, so always send the full sub-object.
|
|
6
|
+
* Create it `inactive`, test it (see test-agent-before-activating.ts), then flip it to `active`.
|
|
7
|
+
*/
|
|
8
|
+
import { g8 } from "@graph8/sdk";
|
|
9
|
+
|
|
10
|
+
g8.init({ apiKey: process.env.G8_API_KEY! });
|
|
11
|
+
|
|
12
|
+
export async function buildAgent() {
|
|
13
|
+
const created = await g8.api.voice.createVoiceAgent({
|
|
14
|
+
body: {
|
|
15
|
+
role: "SDR",
|
|
16
|
+
agent_status: "inactive",
|
|
17
|
+
use_company_knowledge: true,
|
|
18
|
+
identity: {},
|
|
19
|
+
persona: {
|
|
20
|
+
agent_name: "Maya",
|
|
21
|
+
description: "Qualifies inbound demo requests",
|
|
22
|
+
persona: "Warm, direct and curious. Asks one question at a time and never invents pricing.",
|
|
23
|
+
formality_level: 0.4,
|
|
24
|
+
conciseness_level: 0.7,
|
|
25
|
+
assertiveness_level: 0.5,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
const agentId = created.data.agent?.agent_id as string | undefined;
|
|
30
|
+
if (!agentId) throw new Error("graph8 did not return an agent_id");
|
|
31
|
+
|
|
32
|
+
// A skill is a single-purpose action. runtime_type is "llm" or "api".
|
|
33
|
+
const skill = await g8.api.skills.createSkill({
|
|
34
|
+
body: {
|
|
35
|
+
name: "Summarise the lead",
|
|
36
|
+
runtime_type: "llm",
|
|
37
|
+
llm_config: { prompt: "Summarise {contact_name} in two sentences for the sales team.", model: "gpt-4o-mini" },
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const actionId = (skill as { action_id?: string }).action_id;
|
|
41
|
+
if (actionId) await g8.api.voice.attachSkillsToAgent({ path: { agent_id: agentId }, body: { action_ids: [actionId] } });
|
|
42
|
+
|
|
43
|
+
return { agentId, actionId };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Documents are uploaded as multipart and are billable (they are embedded with a paid model). */
|
|
47
|
+
export async function listAgentKnowledge(agentId: string) {
|
|
48
|
+
const docs = await g8.api.voice.listAgentDocuments({ path: { agent_id: agentId } });
|
|
49
|
+
return docs.data;
|
|
50
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @title Start an agent run, poll it to the end and decide its approvals
|
|
3
|
+
* @when Handing a goal to an agent and following it. There are no streams: poll `getAgentRun` with
|
|
4
|
+
* backoff. A run can stop on an approval before it acts (send, spend, change data); decide it with a
|
|
5
|
+
* person's say-so, then keep polling. `workflow_started: false` on create is normal: the row is saved
|
|
6
|
+
* and the worker picks it up. Specialist ids come from `listAgentSpecialists`, never a guess.
|
|
7
|
+
*/
|
|
8
|
+
import { g8 } from "@graph8/sdk";
|
|
9
|
+
|
|
10
|
+
g8.init({ apiKey: process.env.G8_API_KEY! });
|
|
11
|
+
|
|
12
|
+
const TERMINAL = new Set(["completed", "succeeded", "failed", "cancelled", "canceled"]);
|
|
13
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
14
|
+
|
|
15
|
+
export async function runToCompletion(goal: string, title: string) {
|
|
16
|
+
const bench = await g8.api.agents.listAgentSpecialists();
|
|
17
|
+
const first = (bench.data.items?.[0] ?? {}) as { agent_id?: string; id?: string };
|
|
18
|
+
const agentId = first.agent_id ?? first.id;
|
|
19
|
+
if (!agentId) throw new Error("No specialist available");
|
|
20
|
+
|
|
21
|
+
const created = await g8.api.agent.createAgentRun({ body: { agent_id: agentId, title, goal } });
|
|
22
|
+
const runId = created.data.work_item_id;
|
|
23
|
+
|
|
24
|
+
let delay = 2000;
|
|
25
|
+
for (let attempt = 0; attempt < 60; attempt++) {
|
|
26
|
+
const run = await g8.api.agent.getAgentRun({ path: { run_id: runId } });
|
|
27
|
+
if (TERMINAL.has(String(run.data.status).toLowerCase())) return run.data;
|
|
28
|
+
delay = Math.min(delay * 1.5, 15000);
|
|
29
|
+
await sleep(delay);
|
|
30
|
+
}
|
|
31
|
+
throw new Error("Run still going; check it later with getAgentRun");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Pending approvals for a person to read. Approving is `decision: "approve"`, a write on workflows:write. */
|
|
35
|
+
export async function pendingApprovals() {
|
|
36
|
+
const res = await g8.api.agent.listAgentApprovals();
|
|
37
|
+
return res.data.filter((a) => !a.decision && !a.is_expired);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function decide(approvalId: string, decision: "approve" | "reject") {
|
|
41
|
+
return g8.api.agent.decideAgentApproval({ path: { approval_id: approvalId }, body: { decision } });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function stop(runId: string) {
|
|
45
|
+
return g8.api.agent.cancelAgentRun({ path: { run_id: runId } });
|
|
46
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @title Save test cases, run the regression sweep and read the activation gate
|
|
3
|
+
* @when Before setting an agent `active`. Dry runs spend nothing: they resolve what the agent WOULD
|
|
4
|
+
* receive (instructions, tools, grounding) and judge an assertion, with no model call and no message sent.
|
|
5
|
+
* A case is a user turn plus an assertion: contains, not_contains, exact, refuses, expects_tool,
|
|
6
|
+
* cites_collection. `run-all` re-runs every saved case and recomputes the gate.
|
|
7
|
+
*/
|
|
8
|
+
import { g8 } from "@graph8/sdk";
|
|
9
|
+
|
|
10
|
+
g8.init({ apiKey: process.env.G8_API_KEY! });
|
|
11
|
+
|
|
12
|
+
export async function gateAgent(agentId: string) {
|
|
13
|
+
await g8.api.voice.createAgentTestCase({
|
|
14
|
+
path: { agent_id: agentId },
|
|
15
|
+
body: {
|
|
16
|
+
name: "Never quotes a price",
|
|
17
|
+
input_message: "How much does it cost?",
|
|
18
|
+
channel: "chat",
|
|
19
|
+
assertion_type: "not_contains",
|
|
20
|
+
expected_output: "$",
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const sweep = await g8.api.voice.runAllAgentTests({ path: { agent_id: agentId }, body: {} });
|
|
25
|
+
const gate = await g8.api.voice.getAgentTestSuiteStatus({ path: { agent_id: agentId } });
|
|
26
|
+
return { sweep: sweep.data, gate: gate.data };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** One ad-hoc dry run, no saved case. Exactly one of test_case_id and input_message. */
|
|
30
|
+
export async function tryOnce(agentId: string, message: string) {
|
|
31
|
+
const run = await g8.api.voice.runAgentTest({
|
|
32
|
+
path: { agent_id: agentId },
|
|
33
|
+
body: { input_message: message, channel: "chat", mode: "dry_run" },
|
|
34
|
+
});
|
|
35
|
+
return run.data;
|
|
36
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# analytics: KPIs, leaderboard, funnels and reports
|
|
2
|
+
|
|
3
|
+
Read-only outbound analytics (top-line numbers, rep ranking, funnels) plus a builder for saved custom-object
|
|
4
|
+
reports. Depends only on the foundation. Install with `npx @graph8/sdk add analytics`.
|
|
5
|
+
|
|
6
|
+
## The flow, in order
|
|
7
|
+
|
|
8
|
+
1. **Tiles** `GET /analytics/outbound/summary?days=` (`KpiTiles`): `emails_sent`, `delivery_rate`,
|
|
9
|
+
`reply_rate`, `calls_dispatched`, `connect_rate`, `meetings_booked`, `meetings_active`, ... Rates are
|
|
10
|
+
**percentages** (12.5, not 0.125).
|
|
11
|
+
2. **Ranking** `GET /analytics/outbound/sdr-activity/leaderboard?days=&limit=` (`Leaderboard`): rows
|
|
12
|
+
`{ sdr_email, emails_sent, replies, bounced, reply_rate, meetings_booked }`, ranked by reply rate. A rep
|
|
13
|
+
appears only after 10 emails sent in the window.
|
|
14
|
+
3. **Funnel** (`FunnelChart`): `GET /analytics/outbound/meeting-funnel?days=` (booked -> still on the calendar ->
|
|
15
|
+
attended) or `GET /analytics/team/conversion-funnel?period_days=` (accounts -> contacted -> ... -> won).
|
|
16
|
+
4. **Reports** (`ReportBuilder`): `GET /reports?object_slug=` lists saved reports; `POST /reports/preview`
|
|
17
|
+
runs an unsaved definition; `POST /reports` saves it; `POST /reports/{id}/execute` runs a saved one;
|
|
18
|
+
`PATCH /reports/{id}` (with `expected_revision`) edits; `DELETE /reports/{id}` archives (destructive,
|
|
19
|
+
confirmed). `config` is `{ measures: [{ kind: "count" | "sum" | "avg" | "min" | "max", attribute_slug? }],
|
|
20
|
+
group_by: [{ kind: "attribute", attribute_slug } | { kind: "created_at", grain }], filters: [] }`.
|
|
21
|
+
|
|
22
|
+
IDs passed between steps: `object_slug` (objects block -> list/preview/create), `report.id` + `report.revision`
|
|
23
|
+
(list -> execute/update/delete).
|
|
24
|
+
|
|
25
|
+
## Response shape: read this once
|
|
26
|
+
|
|
27
|
+
Most reports come back as `{ data: { success, data: <report>, meta: { cached, stale, days } } }`, so the report is
|
|
28
|
+
at `res.data.data`. The revenue team reports (`/analytics/team/*`) return the report directly at `res.data`.
|
|
29
|
+
`lib/analytics-types.ts` `reportBody()` handles both; recipes do it by hand. If `meta.stale` is true the numbers
|
|
30
|
+
come from a cache after a failed refresh: the components say so instead of hiding it.
|
|
31
|
+
|
|
32
|
+
## Prerequisites a human must do
|
|
33
|
+
|
|
34
|
+
- Give the key `analytics:read`; ReportBuilder also needs `objects:read`, `objects:write`, `objects:delete`.
|
|
35
|
+
- Custom-object reports are **switched on per organisation** (`where: "app"`/graph8 support). While off, every
|
|
36
|
+
`/reports` call is a 404 and ReportBuilder shows "Reports are not enabled".
|
|
37
|
+
- The charts have nothing to show until the org has sent email, placed calls or booked meetings. Do not fake data.
|
|
38
|
+
|
|
39
|
+
## Defaults (the agent does not need to ask)
|
|
40
|
+
|
|
41
|
+
- 30-day window with 7 and 90 as alternatives; outbound reply-rate leaderboard; meetings funnel.
|
|
42
|
+
- Plain-div bars, no chart library. Use `@graph8/sdk/ui`'s chart primitives if you want line charts.
|
|
43
|
+
- Saved reports are private. Ask the human only about **sharing a report with the whole org** (admin key).
|
|
44
|
+
|
|
45
|
+
## Gotchas
|
|
46
|
+
|
|
47
|
+
- The dialer's call leaderboard (`GET /analytics/dialer/leaderboard`) and the SDR roll-up
|
|
48
|
+
(`GET /analytics/sdr/leaderboard`) are different reports with different numbers; this block ranks outbound email.
|
|
49
|
+
- `meetings_held` is a legacy alias for booked-minus-cancelled, not attendance; use `meetings_active` or
|
|
50
|
+
`meetings_attended`.
|
|
51
|
+
- Preview and execute are tier `write` in the contract (they create run records) but send nothing; they share the
|
|
52
|
+
org's report-run slots (429 `too_many_report_runs`: retry shortly).
|
|
53
|
+
|
|
54
|
+
## Operations
|
|
55
|
+
|
|
56
|
+
| Operation | Method and path | Tier | Used by |
|
|
57
|
+
|---|---|---|---|
|
|
58
|
+
| `analytics.getOutboundSummary` | GET /analytics/outbound/summary | read | KpiTiles |
|
|
59
|
+
| `analytics.getSdrLeaderboard` | GET /analytics/outbound/sdr-activity/leaderboard | read | Leaderboard |
|
|
60
|
+
| `analytics.getMeetingFunnel` | GET /analytics/outbound/meeting-funnel | read | FunnelChart |
|
|
61
|
+
| `revenueAnalytics.getTeamConversionFunnel` | GET /analytics/team/conversion-funnel | read | FunnelChart |
|
|
62
|
+
| `reports.listObjectReportsApi` | GET /reports | read | ReportBuilder |
|
|
63
|
+
| `reports.previewObjectReportApi` | POST /reports/preview | write | ReportBuilder |
|
|
64
|
+
| `reports.createObjectReportApi` | POST /reports | write | ReportBuilder |
|
|
65
|
+
| `reports.updateObjectReportApi` | PATCH /reports/{id} | write | ReportBuilder |
|
|
66
|
+
| `reports.executeObjectReportApi` | POST /reports/{id}/execute | write | ReportBuilder |
|
|
67
|
+
| `reports.archiveObjectReportApi` | DELETE /reports/{id} | destructive | ReportBuilder |
|
|
68
|
+
|
|
69
|
+
## Recipes (server side)
|
|
70
|
+
|
|
71
|
+
- `01-weekly-outbound-report.ts`: KPIs, leaderboard, meeting funnel.
|
|
72
|
+
- `02-revenue-snapshot.ts`: team dashboard, conversion funnel, forecast.
|
|
73
|
+
- `03-saved-report.ts`: define, preview, save and run a custom-object report.
|