@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.
Files changed (654) hide show
  1. package/README.md +81 -0
  2. package/blocks/README.md +32 -0
  3. package/blocks/_foundation/README.md +78 -0
  4. package/blocks/_foundation/block.json +79 -0
  5. package/blocks/_foundation/components/api-error.tsx +62 -0
  6. package/blocks/_foundation/components/confirm-spend-dialog.tsx +142 -0
  7. package/blocks/_foundation/components/empty-state.tsx +24 -0
  8. package/blocks/_foundation/hooks/use-g8-mutation.ts +88 -0
  9. package/blocks/_foundation/hooks/use-g8-query.ts +87 -0
  10. package/blocks/_foundation/hooks/use-job-poll.ts +130 -0
  11. package/blocks/_foundation/hooks/use-paginated.ts +105 -0
  12. package/blocks/_foundation/lib/g8-client.ts +128 -0
  13. package/blocks/_foundation/recipes/verify-proxy-key.ts +18 -0
  14. package/blocks/_foundation/server/proxy.ts +233 -0
  15. package/blocks/_foundation/server/route.ts +30 -0
  16. package/blocks/agents/README.md +133 -0
  17. package/blocks/agents/block.json +223 -0
  18. package/blocks/agents/components/agent-builder.tsx +199 -0
  19. package/blocks/agents/components/agent-knowledge-panel.tsx +144 -0
  20. package/blocks/agents/components/agent-runs.tsx +175 -0
  21. package/blocks/agents/components/agent-skills-panel.tsx +126 -0
  22. package/blocks/agents/components/agent-test-console.tsx +175 -0
  23. package/blocks/agents/lib/agent-types.ts +114 -0
  24. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +50 -0
  25. package/blocks/agents/recipes/run-and-follow-agent-run.ts +46 -0
  26. package/blocks/agents/recipes/test-agent-before-activating.ts +36 -0
  27. package/blocks/analytics/README.md +73 -0
  28. package/blocks/analytics/block.json +178 -0
  29. package/blocks/analytics/components/funnel-chart.tsx +79 -0
  30. package/blocks/analytics/components/kpi-tiles.tsx +74 -0
  31. package/blocks/analytics/components/leaderboard.tsx +86 -0
  32. package/blocks/analytics/components/report-builder.tsx +200 -0
  33. package/blocks/analytics/hooks/use-report.ts +26 -0
  34. package/blocks/analytics/lib/analytics-types.ts +108 -0
  35. package/blocks/analytics/lib/use-debounced.ts +16 -0
  36. package/blocks/analytics/recipes/01-weekly-outbound-report.ts +32 -0
  37. package/blocks/analytics/recipes/02-revenue-snapshot.ts +22 -0
  38. package/blocks/analytics/recipes/03-saved-report.ts +30 -0
  39. package/blocks/appointments/README.md +80 -0
  40. package/blocks/appointments/block.json +218 -0
  41. package/blocks/appointments/components/availability-editor.tsx +192 -0
  42. package/blocks/appointments/components/booking-page.tsx +154 -0
  43. package/blocks/appointments/components/event-type-editor.tsx +214 -0
  44. package/blocks/appointments/components/meeting-detail.tsx +147 -0
  45. package/blocks/appointments/components/meeting-list.tsx +148 -0
  46. package/blocks/appointments/hooks/use-available-slots.ts +29 -0
  47. package/blocks/appointments/hooks/use-schedules.ts +18 -0
  48. package/blocks/appointments/lib/appointments-types.ts +159 -0
  49. package/blocks/appointments/lib/idempotency.ts +30 -0
  50. package/blocks/appointments/recipes/01-create-event-type-and-hours.ts +27 -0
  51. package/blocks/appointments/recipes/02-find-slots-and-book.ts +34 -0
  52. package/blocks/appointments/recipes/03-bookings-digest.ts +22 -0
  53. package/blocks/appointments/recipes/04-calendar-invite.ts +21 -0
  54. package/blocks/block.schema.json +130 -0
  55. package/blocks/block.types.ts +109 -0
  56. package/blocks/campaigns/README.md +118 -0
  57. package/blocks/campaigns/block.json +361 -0
  58. package/blocks/campaigns/components/campaign-assets.tsx +180 -0
  59. package/blocks/campaigns/components/campaign-audience-step.tsx +91 -0
  60. package/blocks/campaigns/components/campaign-launch-step.tsx +147 -0
  61. package/blocks/campaigns/components/campaign-list.tsx +120 -0
  62. package/blocks/campaigns/components/campaign-metrics.tsx +93 -0
  63. package/blocks/campaigns/components/campaign-select.tsx +52 -0
  64. package/blocks/campaigns/components/campaign-sequence-step.tsx +158 -0
  65. package/blocks/campaigns/components/campaign-wizard.tsx +238 -0
  66. package/blocks/campaigns/components/form-field.tsx +29 -0
  67. package/blocks/campaigns/components/icp-editor.tsx +230 -0
  68. package/blocks/campaigns/components/persona-editor.tsx +210 -0
  69. package/blocks/campaigns/hooks/use-company-profile.ts +34 -0
  70. package/blocks/campaigns/lib/campaigns-utils.ts +155 -0
  71. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +33 -0
  72. package/blocks/campaigns/recipes/campaign-report.ts +22 -0
  73. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +51 -0
  74. package/blocks/campaigns/recipes/set-up-icp-and-personas.ts +37 -0
  75. package/blocks/coach/README.md +93 -0
  76. package/blocks/coach/block.json +125 -0
  77. package/blocks/coach/components/call-scorecard.tsx +126 -0
  78. package/blocks/coach/components/coaching-feed.tsx +140 -0
  79. package/blocks/coach/lib/coach-types.ts +116 -0
  80. package/blocks/coach/recipes/read-a-scorecard.ts +27 -0
  81. package/blocks/coach/recipes/weekly-coaching-digest.ts +53 -0
  82. package/blocks/copilot/README.md +99 -0
  83. package/blocks/copilot/block.json +151 -0
  84. package/blocks/copilot/components/copilot-memory.tsx +87 -0
  85. package/blocks/copilot/components/copilot-panel.tsx +189 -0
  86. package/blocks/copilot/components/copilot-team-chat.tsx +111 -0
  87. package/blocks/copilot/lib/copilot-types.ts +111 -0
  88. package/blocks/copilot/recipes/ask-the-copilot.ts +35 -0
  89. package/blocks/copilot/recipes/post-in-a-work-channel.ts +33 -0
  90. package/blocks/copilot/recipes/remember-and-recall.ts +19 -0
  91. package/blocks/crm/README.md +122 -0
  92. package/blocks/crm/block.json +378 -0
  93. package/blocks/crm/components/activity-timeline.tsx +99 -0
  94. package/blocks/crm/components/company-detail.tsx +118 -0
  95. package/blocks/crm/components/company-table.tsx +173 -0
  96. package/blocks/crm/components/contact-detail.tsx +135 -0
  97. package/blocks/crm/components/contact-table.tsx +217 -0
  98. package/blocks/crm/components/dedupe-review.tsx +164 -0
  99. package/blocks/crm/components/import-wizard.tsx +193 -0
  100. package/blocks/crm/components/notes-panel.tsx +112 -0
  101. package/blocks/crm/components/record-form.tsx +191 -0
  102. package/blocks/crm/components/task-list.tsx +107 -0
  103. package/blocks/crm/hooks/use-custom-fields.ts +18 -0
  104. package/blocks/crm/hooks/use-debounced.ts +13 -0
  105. package/blocks/crm/hooks/use-page-query.ts +52 -0
  106. package/blocks/crm/lib/crm-types.ts +136 -0
  107. package/blocks/crm/recipes/custom-fields-and-values.ts +28 -0
  108. package/blocks/crm/recipes/dedupe-and-merge.ts +30 -0
  109. package/blocks/crm/recipes/notes-tasks-and-activity.ts +23 -0
  110. package/blocks/crm/recipes/upsert-contact-with-company.ts +33 -0
  111. package/blocks/deals/README.md +78 -0
  112. package/blocks/deals/block.json +198 -0
  113. package/blocks/deals/components/deal-detail.tsx +134 -0
  114. package/blocks/deals/components/forecast-view.tsx +112 -0
  115. package/blocks/deals/components/pipeline-board.tsx +142 -0
  116. package/blocks/deals/components/stage-editor.tsx +96 -0
  117. package/blocks/deals/hooks/use-pipeline-deals.ts +67 -0
  118. package/blocks/deals/hooks/use-pipelines.ts +19 -0
  119. package/blocks/deals/lib/deals-types.ts +128 -0
  120. package/blocks/deals/recipes/01-create-deal-and-advance.ts +32 -0
  121. package/blocks/deals/recipes/02-pipeline-report.ts +35 -0
  122. package/blocks/deals/recipes/03-stale-deals-sweep.ts +20 -0
  123. package/blocks/dialer/README.md +114 -0
  124. package/blocks/dialer/block.json +260 -0
  125. package/blocks/dialer/components/call-log.tsx +78 -0
  126. package/blocks/dialer/components/call-queue.tsx +188 -0
  127. package/blocks/dialer/components/dialer-workspace.tsx +47 -0
  128. package/blocks/dialer/components/disposition-panel.tsx +75 -0
  129. package/blocks/dialer/components/live-calls-table.tsx +107 -0
  130. package/blocks/dialer/components/session-start-form.tsx +111 -0
  131. package/blocks/dialer/components/softphone.tsx +122 -0
  132. package/blocks/dialer/components/transcript-viewer.tsx +82 -0
  133. package/blocks/dialer/hooks/use-dialer-numbers.ts +19 -0
  134. package/blocks/dialer/hooks/use-dialer-session.ts +68 -0
  135. package/blocks/dialer/hooks/use-dispositions.ts +29 -0
  136. package/blocks/dialer/lib/dialer-types.ts +158 -0
  137. package/blocks/dialer/lib/softphone-transport.ts +67 -0
  138. package/blocks/dialer/lib/twilio-transport.ts +74 -0
  139. package/blocks/dialer/recipes/01-create-session-and-dial.ts +39 -0
  140. package/blocks/dialer/recipes/02-disposition-calls.ts +26 -0
  141. package/blocks/dialer/recipes/03-review-a-call.ts +23 -0
  142. package/blocks/enrichment/README.md +105 -0
  143. package/blocks/enrichment/block.json +204 -0
  144. package/blocks/enrichment/components/bulk-enrich-panel.tsx +205 -0
  145. package/blocks/enrichment/components/email-verify-badge.tsx +82 -0
  146. package/blocks/enrichment/components/enrich-button.tsx +136 -0
  147. package/blocks/enrichment/components/waterfall-config.tsx +127 -0
  148. package/blocks/enrichment/hooks/use-providers.ts +18 -0
  149. package/blocks/enrichment/lib/enrichment-types.ts +104 -0
  150. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +41 -0
  151. package/blocks/enrichment/recipes/lookup-and-verify.ts +25 -0
  152. package/blocks/enrichment/recipes/waterfall-pipeline.ts +26 -0
  153. package/blocks/forms/README.md +112 -0
  154. package/blocks/forms/block.json +200 -0
  155. package/blocks/forms/components/form-builder.tsx +150 -0
  156. package/blocks/forms/components/form-embed.tsx +103 -0
  157. package/blocks/forms/components/form-mapping-editor.tsx +125 -0
  158. package/blocks/forms/components/site-audit-panel.tsx +73 -0
  159. package/blocks/forms/components/submissions-table.tsx +158 -0
  160. package/blocks/forms/lib/form-types.ts +127 -0
  161. package/blocks/forms/recipes/install-snippet-and-form.ts +21 -0
  162. package/blocks/forms/recipes/retry-failed-deliveries.ts +26 -0
  163. package/blocks/forms/recipes/route-form-to-list.ts +44 -0
  164. package/blocks/inbox/README.md +72 -0
  165. package/blocks/inbox/block.json +194 -0
  166. package/blocks/inbox/components/inbox-list.tsx +156 -0
  167. package/blocks/inbox/components/inbox-screen.tsx +49 -0
  168. package/blocks/inbox/components/reply-composer.tsx +183 -0
  169. package/blocks/inbox/components/tag-and-assign.tsx +189 -0
  170. package/blocks/inbox/components/thread-badge.tsx +17 -0
  171. package/blocks/inbox/components/thread-view.tsx +108 -0
  172. package/blocks/inbox/hooks/use-inbox-thread.ts +24 -0
  173. package/blocks/inbox/hooks/use-inbox-threads.ts +40 -0
  174. package/blocks/inbox/lib/idempotency.ts +30 -0
  175. package/blocks/inbox/lib/inbox-types.ts +100 -0
  176. package/blocks/inbox/recipes/01-triage-and-reply.ts +43 -0
  177. package/blocks/inbox/recipes/02-tag-and-assign.ts +31 -0
  178. package/blocks/inbox/recipes/03-hot-contacts-digest.ts +16 -0
  179. package/blocks/integrations/README.md +75 -0
  180. package/blocks/integrations/block.json +172 -0
  181. package/blocks/integrations/components/integration-list.tsx +165 -0
  182. package/blocks/integrations/components/sync-status.tsx +223 -0
  183. package/blocks/integrations/components/webhook-manager.tsx +300 -0
  184. package/blocks/integrations/lib/integrations-utils.ts +62 -0
  185. package/blocks/integrations/recipes/crm-sync-health.ts +19 -0
  186. package/blocks/integrations/recipes/push-contacts-to-crm.ts +20 -0
  187. package/blocks/integrations/recipes/register-webhook.ts +18 -0
  188. package/blocks/integrations/recipes/verify-webhook-route.ts +33 -0
  189. package/blocks/intent/README.md +57 -0
  190. package/blocks/intent/block.json +138 -0
  191. package/blocks/intent/components/account-intent-score.tsx +91 -0
  192. package/blocks/intent/components/intent-feed.tsx +215 -0
  193. package/blocks/intent/components/intent-keyword-manager.tsx +331 -0
  194. package/blocks/intent/hooks/use-account-intent.ts +46 -0
  195. package/blocks/intent/lib/intent-utils.ts +95 -0
  196. package/blocks/intent/recipes/rank-intent-accounts.ts +36 -0
  197. package/blocks/intent/recipes/track-competitor-domain.ts +23 -0
  198. package/blocks/intent/recipes/who-visited-url.ts +18 -0
  199. package/blocks/lifecycle/README.md +79 -0
  200. package/blocks/lifecycle/block.json +198 -0
  201. package/blocks/lifecycle/components/churn-list.tsx +92 -0
  202. package/blocks/lifecycle/components/customer-table.tsx +119 -0
  203. package/blocks/lifecycle/components/lead-queue.tsx +127 -0
  204. package/blocks/lifecycle/components/lifecycle-board.tsx +102 -0
  205. package/blocks/lifecycle/components/stage-move-dialog.tsx +75 -0
  206. package/blocks/lifecycle/hooks/use-debounced.ts +13 -0
  207. package/blocks/lifecycle/hooks/use-lifecycle-stages.ts +27 -0
  208. package/blocks/lifecycle/lib/lifecycle.ts +99 -0
  209. package/blocks/lifecycle/recipes/customer-brief.ts +23 -0
  210. package/blocks/lifecycle/recipes/move-company-stage.ts +17 -0
  211. package/blocks/lifecycle/recipes/read-lifecycle-board.ts +21 -0
  212. package/blocks/lifecycle/recipes/unlock-stage-contacts.ts +25 -0
  213. package/blocks/lists/README.md +96 -0
  214. package/blocks/lists/block.json +166 -0
  215. package/blocks/lists/components/add-to-list-button.tsx +111 -0
  216. package/blocks/lists/components/list-picker.tsx +94 -0
  217. package/blocks/lists/components/list-table.tsx +160 -0
  218. package/blocks/lists/components/suppression-manager.tsx +153 -0
  219. package/blocks/lists/recipes/create-list-and-add-contacts.ts +35 -0
  220. package/blocks/lists/recipes/refresh-and-check-lists.ts +21 -0
  221. package/blocks/lists/recipes/suppress-and-reinstate.ts +30 -0
  222. package/blocks/mailboxes/README.md +71 -0
  223. package/blocks/mailboxes/block.json +187 -0
  224. package/blocks/mailboxes/components/connect-mailbox.tsx +284 -0
  225. package/blocks/mailboxes/components/domain-purchase.tsx +271 -0
  226. package/blocks/mailboxes/components/mailbox-list.tsx +185 -0
  227. package/blocks/mailboxes/components/warmup-health.tsx +232 -0
  228. package/blocks/mailboxes/lib/mailboxes-utils.ts +69 -0
  229. package/blocks/mailboxes/recipes/buy-domain-and-mailboxes.ts +64 -0
  230. package/blocks/mailboxes/recipes/connect-imap-mailbox.ts +38 -0
  231. package/blocks/mailboxes/recipes/is-it-safe-to-send.ts +17 -0
  232. package/blocks/mailboxes/recipes/warmup-report.ts +31 -0
  233. package/blocks/newsletter/README.md +122 -0
  234. package/blocks/newsletter/block.json +224 -0
  235. package/blocks/newsletter/components/issue-form.tsx +124 -0
  236. package/blocks/newsletter/components/issue-scheduler.tsx +152 -0
  237. package/blocks/newsletter/components/newsletter-editor.tsx +131 -0
  238. package/blocks/newsletter/components/newsletter-stats.tsx +93 -0
  239. package/blocks/newsletter/components/subscriber-table.tsx +173 -0
  240. package/blocks/newsletter/lib/newsletter-types.ts +97 -0
  241. package/blocks/newsletter/recipes/create-newsletter-and-draft-issue.ts +27 -0
  242. package/blocks/newsletter/recipes/import-audience-and-send.ts +43 -0
  243. package/blocks/newsletter/recipes/issue-performance-report.ts +26 -0
  244. package/blocks/nurture/README.md +110 -0
  245. package/blocks/nurture/block.json +191 -0
  246. package/blocks/nurture/components/nurture-builder.tsx +114 -0
  247. package/blocks/nurture/components/nurture-enroll.tsx +107 -0
  248. package/blocks/nurture/components/nurture-form.tsx +120 -0
  249. package/blocks/nurture/components/nurture-stats.tsx +81 -0
  250. package/blocks/nurture/lib/nurture-types.ts +109 -0
  251. package/blocks/nurture/recipes/create-a-nurture.ts +34 -0
  252. package/blocks/nurture/recipes/enroll-contacts-and-launch.ts +24 -0
  253. package/blocks/nurture/recipes/nurture-health-check.ts +28 -0
  254. package/blocks/objects/README.md +120 -0
  255. package/blocks/objects/block.json +243 -0
  256. package/blocks/objects/components/attribute-field-input.tsx +82 -0
  257. package/blocks/objects/components/attribute-form-dialog.tsx +159 -0
  258. package/blocks/objects/components/object-not-found.tsx +34 -0
  259. package/blocks/objects/components/object-record-detail.tsx +143 -0
  260. package/blocks/objects/components/object-schema-editor.tsx +197 -0
  261. package/blocks/objects/components/object-table.tsx +130 -0
  262. package/blocks/objects/components/record-form-dialog.tsx +112 -0
  263. package/blocks/objects/components/record-roles-panel.tsx +106 -0
  264. package/blocks/objects/hooks/use-object-schema.ts +36 -0
  265. package/blocks/objects/lib/object-types.ts +214 -0
  266. package/blocks/objects/recipes/define-object-and-add-records.ts +24 -0
  267. package/blocks/objects/recipes/update-audit-and-archive-records.ts +39 -0
  268. package/blocks/objects/recipes/upsert-records-from-your-system.ts +30 -0
  269. package/blocks/pages/README.md +104 -0
  270. package/blocks/pages/block.json +223 -0
  271. package/blocks/pages/components/brand-kit-editor.tsx +152 -0
  272. package/blocks/pages/components/content-library.tsx +163 -0
  273. package/blocks/pages/components/page-editor.tsx +253 -0
  274. package/blocks/pages/components/page-list.tsx +159 -0
  275. package/blocks/pages/components/seo-report.tsx +159 -0
  276. package/blocks/pages/lib/studio.ts +77 -0
  277. package/blocks/pages/recipes/audit-site-seo.ts +28 -0
  278. package/blocks/pages/recipes/create-and-publish-page.ts +26 -0
  279. package/blocks/pages/recipes/edit-page-section.ts +25 -0
  280. package/blocks/pages/recipes/refresh-brand-kit.ts +22 -0
  281. package/blocks/quotes/README.md +79 -0
  282. package/blocks/quotes/block.json +183 -0
  283. package/blocks/quotes/components/product-picker.tsx +80 -0
  284. package/blocks/quotes/components/quote-builder.tsx +195 -0
  285. package/blocks/quotes/components/quote-list.tsx +79 -0
  286. package/blocks/quotes/components/quote-viewer.tsx +135 -0
  287. package/blocks/quotes/components/signature-status.tsx +77 -0
  288. package/blocks/quotes/hooks/use-debounced.ts +13 -0
  289. package/blocks/quotes/lib/quotes.ts +142 -0
  290. package/blocks/quotes/recipes/create-and-send-quote.ts +44 -0
  291. package/blocks/quotes/recipes/revise-sent-quote.ts +18 -0
  292. package/blocks/quotes/recipes/track-quote-signature.ts +22 -0
  293. package/blocks/radar/README.md +106 -0
  294. package/blocks/radar/block.json +189 -0
  295. package/blocks/radar/components/competitor-battle-card.tsx +53 -0
  296. package/blocks/radar/components/competitor-tracker.tsx +158 -0
  297. package/blocks/radar/components/radar-overview.tsx +67 -0
  298. package/blocks/radar/components/radar-report.tsx +86 -0
  299. package/blocks/radar/hooks/use-competitors.ts +23 -0
  300. package/blocks/radar/lib/radar-types.ts +124 -0
  301. package/blocks/radar/recipes/battle-card.ts +28 -0
  302. package/blocks/radar/recipes/deep-dive-report.ts +32 -0
  303. package/blocks/radar/recipes/marketing-snapshot.ts +18 -0
  304. package/blocks/radar/recipes/track-and-scan.ts +39 -0
  305. package/blocks/search/README.md +129 -0
  306. package/blocks/search/block.json +185 -0
  307. package/blocks/search/components/filter-builder.tsx +149 -0
  308. package/blocks/search/components/prospect-results.tsx +175 -0
  309. package/blocks/search/components/save-to-list-dialog.tsx +135 -0
  310. package/blocks/search/components/saved-searches.tsx +95 -0
  311. package/blocks/search/hooks/use-filter-options.ts +41 -0
  312. package/blocks/search/lib/search-types.ts +149 -0
  313. package/blocks/search/recipes/explore-filter-values.ts +27 -0
  314. package/blocks/search/recipes/saved-searches.ts +34 -0
  315. package/blocks/search/recipes/search-and-save-to-list.ts +37 -0
  316. package/blocks/sequencer/README.md +87 -0
  317. package/blocks/sequencer/block.json +214 -0
  318. package/blocks/sequencer/components/enroll-dialog.tsx +93 -0
  319. package/blocks/sequencer/components/sequence-builder.tsx +306 -0
  320. package/blocks/sequencer/components/sequence-list.tsx +200 -0
  321. package/blocks/sequencer/components/sequence-stats.tsx +256 -0
  322. package/blocks/sequencer/components/step-editor.tsx +213 -0
  323. package/blocks/sequencer/lib/sequencer-utils.ts +168 -0
  324. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +51 -0
  325. package/blocks/sequencer/recipes/launch-and-enroll.ts +44 -0
  326. package/blocks/sequencer/recipes/pause-when-unsafe.ts +30 -0
  327. package/blocks/sequencer/recipes/sequence-report.ts +22 -0
  328. package/blocks/social-ads/README.md +128 -0
  329. package/blocks/social-ads/block.json +256 -0
  330. package/blocks/social-ads/components/ad-campaign-list.tsx +136 -0
  331. package/blocks/social-ads/components/audience-sync.tsx +150 -0
  332. package/blocks/social-ads/components/creative-gallery.tsx +121 -0
  333. package/blocks/social-ads/components/post-composer.tsx +122 -0
  334. package/blocks/social-ads/components/social-post-list.tsx +116 -0
  335. package/blocks/social-ads/hooks/use-social-accounts.ts +16 -0
  336. package/blocks/social-ads/lib/social-ads-types.ts +167 -0
  337. package/blocks/social-ads/recipes/creative-to-gallery.ts +41 -0
  338. package/blocks/social-ads/recipes/schedule-post.ts +51 -0
  339. package/blocks/social-ads/recipes/sync-audience-and-launch.ts +41 -0
  340. package/blocks/visitors/README.md +56 -0
  341. package/blocks/visitors/block.json +139 -0
  342. package/blocks/visitors/components/snippet-install.tsx +169 -0
  343. package/blocks/visitors/components/traffic-chart.tsx +156 -0
  344. package/blocks/visitors/components/visitor-company-card.tsx +92 -0
  345. package/blocks/visitors/components/visitor-feed.tsx +157 -0
  346. package/blocks/visitors/lib/visitors-utils.ts +78 -0
  347. package/blocks/visitors/recipes/check-snippet-installed.ts +24 -0
  348. package/blocks/visitors/recipes/recent-company-visits.ts +27 -0
  349. package/blocks/visitors/recipes/traffic-summary.ts +24 -0
  350. package/blocks/webchat/README.md +87 -0
  351. package/blocks/webchat/block.json +134 -0
  352. package/blocks/webchat/components/chat-widget-config.tsx +160 -0
  353. package/blocks/webchat/components/conversation-list.tsx +74 -0
  354. package/blocks/webchat/lib/webchat-types.ts +83 -0
  355. package/blocks/webchat/recipes/create-bot-and-style-widget.ts +24 -0
  356. package/blocks/webchat/recipes/list-chat-visitors.ts +24 -0
  357. package/blocks/workbench/README.md +135 -0
  358. package/blocks/workbench/block.json +259 -0
  359. package/blocks/workbench/components/column-recipe-editor.tsx +183 -0
  360. package/blocks/workbench/components/run-progress.tsx +132 -0
  361. package/blocks/workbench/components/workbench-add-rows.tsx +100 -0
  362. package/blocks/workbench/components/workbench-grid.tsx +159 -0
  363. package/blocks/workbench/components/workbench-row-actions.tsx +102 -0
  364. package/blocks/workbench/components/workbench-table-list.tsx +105 -0
  365. package/blocks/workbench/hooks/use-workbench-table.ts +30 -0
  366. package/blocks/workbench/lib/waterfall-presets.ts +119 -0
  367. package/blocks/workbench/lib/workbench-types.ts +191 -0
  368. package/blocks/workbench/recipes/build-enriched-table.ts +65 -0
  369. package/blocks/workbench/recipes/import-from-search.ts +42 -0
  370. package/blocks/workbench/recipes/scrape-into-table.ts +36 -0
  371. package/blocks/workbench/recipes/waterfall-column.ts +55 -0
  372. package/blocks/workflows/README.md +100 -0
  373. package/blocks/workflows/block.json +188 -0
  374. package/blocks/workflows/components/run-history.tsx +136 -0
  375. package/blocks/workflows/components/trigger-config.tsx +134 -0
  376. package/blocks/workflows/components/workflow-editor.tsx +142 -0
  377. package/blocks/workflows/components/workflow-list.tsx +153 -0
  378. package/blocks/workflows/hooks/use-workflow.ts +51 -0
  379. package/blocks/workflows/lib/graph.ts +187 -0
  380. package/blocks/workflows/recipes/create-workflow.ts +35 -0
  381. package/blocks/workflows/recipes/find-failed-runs.ts +15 -0
  382. package/blocks/workflows/recipes/run-workflow-and-wait.ts +32 -0
  383. package/blocks/workflows/recipes/set-trigger.ts +22 -0
  384. package/dist/cli.js +5330 -29
  385. package/dist/index.browser.js +7740 -0
  386. package/dist/index.browser.js.map +1 -0
  387. package/dist/index.browser.mjs +7717 -0
  388. package/dist/index.browser.mjs.map +1 -0
  389. package/dist/index.d.mts +1440 -21
  390. package/dist/index.d.ts +1440 -21
  391. package/dist/index.js +42 -3
  392. package/dist/index.js.map +1 -1
  393. package/dist/index.mjs +42 -3
  394. package/dist/index.mjs.map +1 -1
  395. package/dist/react.browser.js +7598 -0
  396. package/dist/react.browser.js.map +1 -0
  397. package/dist/react.browser.mjs +7568 -0
  398. package/dist/react.browser.mjs.map +1 -0
  399. package/dist/react.d.mts +1340 -18
  400. package/dist/react.d.ts +1340 -18
  401. package/dist/react.js +41 -2
  402. package/dist/react.js.map +1 -1
  403. package/dist/react.mjs +41 -2
  404. package/dist/react.mjs.map +1 -1
  405. package/dist/types/agentSetup.d.ts +6 -6
  406. package/dist/types/blocks/add.d.ts +53 -0
  407. package/dist/types/blocks/create.d.ts +46 -0
  408. package/dist/types/blocks/doctor.d.ts +26 -0
  409. package/dist/types/blocks/registry.d.ts +134 -0
  410. package/dist/types/cli.d.ts +16 -0
  411. package/dist/types/core.d.ts +1 -1
  412. package/dist/types/crypto-browser.d.ts +2 -0
  413. package/dist/types/crypto-node.d.ts +6 -0
  414. package/dist/types/react.d.ts +1 -1
  415. package/dist/types/version.d.ts +2 -2
  416. package/dist/types/webhooks.d.ts +2 -2
  417. package/dist/ui/index.d.mts +54 -0
  418. package/dist/ui/index.d.ts +54 -0
  419. package/dist/ui/index.js +105 -0
  420. package/dist/ui/index.js.map +1 -0
  421. package/dist/ui/index.mjs +44 -0
  422. package/dist/ui/index.mjs.map +1 -0
  423. package/dist/ui/lib/compose-refs.d.mts +19 -0
  424. package/dist/ui/lib/compose-refs.d.ts +19 -0
  425. package/dist/ui/lib/compose-refs.js +76 -0
  426. package/dist/ui/lib/compose-refs.js.map +1 -0
  427. package/dist/ui/lib/compose-refs.mjs +41 -0
  428. package/dist/ui/lib/compose-refs.mjs.map +1 -0
  429. package/dist/ui/lib/use-mobile.d.mts +4 -0
  430. package/dist/ui/lib/use-mobile.d.ts +4 -0
  431. package/dist/ui/lib/use-mobile.js +57 -0
  432. package/dist/ui/lib/use-mobile.js.map +1 -0
  433. package/dist/ui/lib/use-mobile.mjs +22 -0
  434. package/dist/ui/lib/use-mobile.mjs.map +1 -0
  435. package/dist/ui/lib/utils.d.mts +10 -0
  436. package/dist/ui/lib/utils.d.ts +10 -0
  437. package/dist/ui/lib/utils.js +33 -0
  438. package/dist/ui/lib/utils.js.map +1 -0
  439. package/dist/ui/lib/utils.mjs +9 -0
  440. package/dist/ui/lib/utils.mjs.map +1 -0
  441. package/dist/ui/primitives/alert-dialog.d.mts +22 -0
  442. package/dist/ui/primitives/alert-dialog.d.ts +22 -0
  443. package/dist/ui/primitives/alert-dialog.js +163 -0
  444. package/dist/ui/primitives/alert-dialog.js.map +1 -0
  445. package/dist/ui/primitives/alert-dialog.mjs +119 -0
  446. package/dist/ui/primitives/alert-dialog.mjs.map +1 -0
  447. package/dist/ui/primitives/avatar.d.mts +8 -0
  448. package/dist/ui/primitives/avatar.d.ts +8 -0
  449. package/dist/ui/primitives/avatar.js +91 -0
  450. package/dist/ui/primitives/avatar.js.map +1 -0
  451. package/dist/ui/primitives/avatar.mjs +55 -0
  452. package/dist/ui/primitives/avatar.mjs.map +1 -0
  453. package/dist/ui/primitives/breadcrumb.d.mts +13 -0
  454. package/dist/ui/primitives/breadcrumb.d.ts +13 -0
  455. package/dist/ui/primitives/breadcrumb.js +127 -0
  456. package/dist/ui/primitives/breadcrumb.js.map +1 -0
  457. package/dist/ui/primitives/breadcrumb.mjs +97 -0
  458. package/dist/ui/primitives/breadcrumb.mjs.map +1 -0
  459. package/dist/ui/primitives/button.d.mts +15 -0
  460. package/dist/ui/primitives/button.d.ts +15 -0
  461. package/dist/ui/primitives/button.js +91 -0
  462. package/dist/ui/primitives/button.js.map +1 -0
  463. package/dist/ui/primitives/button.mjs +56 -0
  464. package/dist/ui/primitives/button.mjs.map +1 -0
  465. package/dist/ui/primitives/card.d.mts +10 -0
  466. package/dist/ui/primitives/card.d.ts +10 -0
  467. package/dist/ui/primitives/card.js +101 -0
  468. package/dist/ui/primitives/card.js.map +1 -0
  469. package/dist/ui/primitives/card.mjs +62 -0
  470. package/dist/ui/primitives/card.mjs.map +1 -0
  471. package/dist/ui/primitives/carousel.d.mts +45 -0
  472. package/dist/ui/primitives/carousel.d.ts +45 -0
  473. package/dist/ui/primitives/carousel.js +300 -0
  474. package/dist/ui/primitives/carousel.js.map +1 -0
  475. package/dist/ui/primitives/carousel.mjs +261 -0
  476. package/dist/ui/primitives/carousel.mjs.map +1 -0
  477. package/dist/ui/primitives/chart.d.mts +68 -0
  478. package/dist/ui/primitives/chart.d.ts +68 -0
  479. package/dist/ui/primitives/chart.js +280 -0
  480. package/dist/ui/primitives/chart.js.map +1 -0
  481. package/dist/ui/primitives/chart.mjs +241 -0
  482. package/dist/ui/primitives/chart.mjs.map +1 -0
  483. package/dist/ui/primitives/collapsible.d.mts +8 -0
  484. package/dist/ui/primitives/collapsible.d.ts +8 -0
  485. package/dist/ui/primitives/collapsible.js +72 -0
  486. package/dist/ui/primitives/collapsible.js.map +1 -0
  487. package/dist/ui/primitives/collapsible.mjs +36 -0
  488. package/dist/ui/primitives/collapsible.mjs.map +1 -0
  489. package/dist/ui/primitives/context-menu.d.mts +29 -0
  490. package/dist/ui/primitives/context-menu.d.ts +29 -0
  491. package/dist/ui/primitives/context-menu.js +203 -0
  492. package/dist/ui/primitives/context-menu.js.map +1 -0
  493. package/dist/ui/primitives/context-menu.mjs +155 -0
  494. package/dist/ui/primitives/context-menu.mjs.map +1 -0
  495. package/dist/ui/primitives/dialog.d.mts +26 -0
  496. package/dist/ui/primitives/dialog.d.ts +26 -0
  497. package/dist/ui/primitives/dialog.js +192 -0
  498. package/dist/ui/primitives/dialog.js.map +1 -0
  499. package/dist/ui/primitives/dialog.mjs +148 -0
  500. package/dist/ui/primitives/dialog.mjs.map +1 -0
  501. package/dist/ui/primitives/drawer.d.mts +26 -0
  502. package/dist/ui/primitives/drawer.d.ts +26 -0
  503. package/dist/ui/primitives/drawer.js +146 -0
  504. package/dist/ui/primitives/drawer.js.map +1 -0
  505. package/dist/ui/primitives/drawer.mjs +103 -0
  506. package/dist/ui/primitives/drawer.mjs.map +1 -0
  507. package/dist/ui/primitives/input.d.mts +5 -0
  508. package/dist/ui/primitives/input.d.ts +5 -0
  509. package/dist/ui/primitives/input.js +46 -0
  510. package/dist/ui/primitives/input.js.map +1 -0
  511. package/dist/ui/primitives/input.mjs +22 -0
  512. package/dist/ui/primitives/input.mjs.map +1 -0
  513. package/dist/ui/primitives/item.d.mts +27 -0
  514. package/dist/ui/primitives/item.d.ts +27 -0
  515. package/dist/ui/primitives/item.js +221 -0
  516. package/dist/ui/primitives/item.js.map +1 -0
  517. package/dist/ui/primitives/item.mjs +188 -0
  518. package/dist/ui/primitives/item.mjs.map +1 -0
  519. package/dist/ui/primitives/kbd.d.mts +15 -0
  520. package/dist/ui/primitives/kbd.d.ts +15 -0
  521. package/dist/ui/primitives/kbd.js +45 -0
  522. package/dist/ui/primitives/kbd.js.map +1 -0
  523. package/dist/ui/primitives/kbd.mjs +21 -0
  524. package/dist/ui/primitives/kbd.mjs.map +1 -0
  525. package/dist/ui/primitives/label.d.mts +6 -0
  526. package/dist/ui/primitives/label.d.ts +6 -0
  527. package/dist/ui/primitives/label.js +55 -0
  528. package/dist/ui/primitives/label.js.map +1 -0
  529. package/dist/ui/primitives/label.mjs +21 -0
  530. package/dist/ui/primitives/label.mjs.map +1 -0
  531. package/dist/ui/primitives/resizable.d.mts +10 -0
  532. package/dist/ui/primitives/resizable.d.ts +10 -0
  533. package/dist/ui/primitives/resizable.js +68 -0
  534. package/dist/ui/primitives/resizable.js.map +1 -0
  535. package/dist/ui/primitives/resizable.mjs +42 -0
  536. package/dist/ui/primitives/resizable.mjs.map +1 -0
  537. package/dist/ui/primitives/save-status.d.mts +31 -0
  538. package/dist/ui/primitives/save-status.d.ts +31 -0
  539. package/dist/ui/primitives/save-status.js +150 -0
  540. package/dist/ui/primitives/save-status.js.map +1 -0
  541. package/dist/ui/primitives/save-status.mjs +126 -0
  542. package/dist/ui/primitives/save-status.mjs.map +1 -0
  543. package/dist/ui/primitives/separator.d.mts +6 -0
  544. package/dist/ui/primitives/separator.d.ts +6 -0
  545. package/dist/ui/primitives/separator.js +62 -0
  546. package/dist/ui/primitives/separator.js.map +1 -0
  547. package/dist/ui/primitives/separator.mjs +28 -0
  548. package/dist/ui/primitives/separator.mjs.map +1 -0
  549. package/dist/ui/primitives/sheet.d.mts +16 -0
  550. package/dist/ui/primitives/sheet.d.ts +16 -0
  551. package/dist/ui/primitives/sheet.js +170 -0
  552. package/dist/ui/primitives/sheet.js.map +1 -0
  553. package/dist/ui/primitives/sheet.mjs +129 -0
  554. package/dist/ui/primitives/sheet.mjs.map +1 -0
  555. package/dist/ui/primitives/sidebar.d.mts +74 -0
  556. package/dist/ui/primitives/sidebar.d.ts +74 -0
  557. package/dist/ui/primitives/sidebar.js +697 -0
  558. package/dist/ui/primitives/sidebar.js.map +1 -0
  559. package/dist/ui/primitives/sidebar.mjs +651 -0
  560. package/dist/ui/primitives/sidebar.mjs.map +1 -0
  561. package/dist/ui/primitives/skeleton.d.mts +5 -0
  562. package/dist/ui/primitives/skeleton.d.ts +5 -0
  563. package/dist/ui/primitives/skeleton.js +40 -0
  564. package/dist/ui/primitives/skeleton.js.map +1 -0
  565. package/dist/ui/primitives/skeleton.mjs +16 -0
  566. package/dist/ui/primitives/skeleton.mjs.map +1 -0
  567. package/dist/ui/primitives/slider.d.mts +20 -0
  568. package/dist/ui/primitives/slider.d.ts +20 -0
  569. package/dist/ui/primitives/slider.js +112 -0
  570. package/dist/ui/primitives/slider.js.map +1 -0
  571. package/dist/ui/primitives/slider.mjs +78 -0
  572. package/dist/ui/primitives/slider.mjs.map +1 -0
  573. package/dist/ui/primitives/sortable.d.mts +50 -0
  574. package/dist/ui/primitives/sortable.d.ts +50 -0
  575. package/dist/ui/primitives/sortable.js +456 -0
  576. package/dist/ui/primitives/sortable.js.map +1 -0
  577. package/dist/ui/primitives/sortable.mjs +435 -0
  578. package/dist/ui/primitives/sortable.mjs.map +1 -0
  579. package/dist/ui/primitives/switch.d.mts +6 -0
  580. package/dist/ui/primitives/switch.d.ts +6 -0
  581. package/dist/ui/primitives/switch.js +67 -0
  582. package/dist/ui/primitives/switch.js.map +1 -0
  583. package/dist/ui/primitives/switch.mjs +33 -0
  584. package/dist/ui/primitives/switch.mjs.map +1 -0
  585. package/dist/ui/primitives/table.d.mts +12 -0
  586. package/dist/ui/primitives/table.d.ts +12 -0
  587. package/dist/ui/primitives/table.js +130 -0
  588. package/dist/ui/primitives/table.js.map +1 -0
  589. package/dist/ui/primitives/table.mjs +89 -0
  590. package/dist/ui/primitives/table.mjs.map +1 -0
  591. package/dist/ui/primitives/tabs.d.mts +9 -0
  592. package/dist/ui/primitives/tabs.d.ts +9 -0
  593. package/dist/ui/primitives/tabs.js +86 -0
  594. package/dist/ui/primitives/tabs.js.map +1 -0
  595. package/dist/ui/primitives/tabs.mjs +49 -0
  596. package/dist/ui/primitives/tabs.mjs.map +1 -0
  597. package/dist/ui/primitives/textarea.d.mts +5 -0
  598. package/dist/ui/primitives/textarea.d.ts +5 -0
  599. package/dist/ui/primitives/textarea.js +43 -0
  600. package/dist/ui/primitives/textarea.js.map +1 -0
  601. package/dist/ui/primitives/textarea.mjs +19 -0
  602. package/dist/ui/primitives/textarea.mjs.map +1 -0
  603. package/dist/ui/primitives/tooltip.d.mts +9 -0
  604. package/dist/ui/primitives/tooltip.d.ts +9 -0
  605. package/dist/ui/primitives/tooltip.js +94 -0
  606. package/dist/ui/primitives/tooltip.js.map +1 -0
  607. package/dist/ui/primitives/tooltip.mjs +57 -0
  608. package/dist/ui/primitives/tooltip.mjs.map +1 -0
  609. package/docs/agents.md +72 -29
  610. package/docs/api/README.md +6 -3
  611. package/docs/api/appObjects.d.ts +1 -1
  612. package/docs/api/appObjects.md +1 -1
  613. package/docs/api/by-path.md +29 -0
  614. package/docs/api/objects.d.ts +72 -5
  615. package/docs/api/objects.md +84 -7
  616. package/docs/api/processLists.d.ts +247 -0
  617. package/docs/api/processLists.md +409 -0
  618. package/docs/api/reports.d.ts +476 -0
  619. package/docs/api/reports.md +539 -0
  620. package/docs/api/scheduleBuy.d.ts +12 -8
  621. package/docs/api/scheduleBuy.md +13 -10
  622. package/docs/api/seo.d.ts +406 -3
  623. package/docs/api/seo.md +505 -1
  624. package/docs/api/workspaceBackup.d.ts +324 -0
  625. package/docs/api/workspaceBackup.md +427 -0
  626. package/package.json +174 -5
  627. package/stacks/6sense.json +46 -0
  628. package/stacks/README.md +20 -0
  629. package/stacks/apollo.json +128 -0
  630. package/stacks/clay.json +59 -0
  631. package/stacks/gong.json +72 -0
  632. package/stacks/hubspot.json +162 -0
  633. package/stacks/intercom.json +53 -0
  634. package/stacks/mailchimp.json +71 -0
  635. package/stacks/outreach.json +86 -0
  636. package/stacks/salesforce.json +94 -0
  637. package/stacks/stack.schema.json +32 -0
  638. package/stacks/starter.json +22 -0
  639. package/styles/styles.css +2 -0
  640. package/styles/theme.css +181 -0
  641. package/styles/tokens.css +284 -0
  642. package/templates/next-app/README.md.tmpl +35 -0
  643. package/templates/next-app/_pages/redirect-page.tsx.tmpl +5 -0
  644. package/templates/next-app/_pages/route-page.tsx.tmpl +9 -0
  645. package/templates/next-app/app/globals.css.tmpl +8 -0
  646. package/templates/next-app/app/layout.tsx.tmpl +19 -0
  647. package/templates/next-app/components/app-shell.tsx.tmpl +34 -0
  648. package/templates/next-app/dot-env.local.example.tmpl +6 -0
  649. package/templates/next-app/dot-gitignore.tmpl +7 -0
  650. package/templates/next-app/lib/nav.ts.tmpl +2 -0
  651. package/templates/next-app/next.config.mjs.tmpl +4 -0
  652. package/templates/next-app/package.json.tmpl +25 -0
  653. package/templates/next-app/postcss.config.mjs.tmpl +4 -0
  654. package/templates/next-app/tsconfig.json.tmpl +21 -0
@@ -0,0 +1,259 @@
1
+ {
2
+ "$schema": "../block.schema.json",
3
+ "id": "workbench",
4
+ "title": "Workbench (Clay-style)",
5
+ "summary": "A spreadsheet of records whose columns are filled by recipes: AI prompts, provider waterfalls and formulas. Priced runs, polled to the end, results sent to a list.",
6
+ "features": [
7
+ "workbench list with create and delete, and a grid with selectable rows and editable cells",
8
+ "add rows by typing or pasting a spreadsheet; locked rows from a search import show masked cells",
9
+ "column recipes: AI prompt with {{column}} placeholders, enrichment waterfall presets, formulas, plain columns, target-field mapping",
10
+ "runs are quoted for free, confirmed with the exact credits, started with the quote as the cap and followed to a final receipt",
11
+ "unlock locked rows (1 credit each), send rows to a list, delete rows"
12
+ ],
13
+ "dependsOn": [
14
+ "enrichment",
15
+ "lists"
16
+ ],
17
+ "operations": [
18
+ "add_workbench_column_workbench_tables__table_id__columns_post",
19
+ "add_workbench_row_workbench_tables__table_id__rows_post",
20
+ "bulk_delete_workbench_rows_workbench_tables__table_id__rows_bulk_delete_post",
21
+ "create_workbench_formula_columns_workbench_tables__table_id__formula_columns_post",
22
+ "create_workbench_table_workbench_tables_post",
23
+ "delete_workbench_table_workbench_tables__table_id__delete",
24
+ "estimate_workbench_scraper_credits_workbench_scraper_credit_estimate_get",
25
+ "export_workbench_table_workbench_tables__table_id__export_post",
26
+ "get_workbench_analytics_workbench_tables__table_id__analytics_get",
27
+ "get_workbench_history_workbench_tables__table_id__history_get",
28
+ "get_workbench_scraper_run_workbench_tables__table_id__scraper_runs__run_id__get",
29
+ "get_workbench_table_workbench_tables__table_id__get",
30
+ "import_workbench_from_search_workbench_tables__table_id__import_search_post",
31
+ "list_enrichment_providers_enrichment_providers_get",
32
+ "list_workbench_columns_workbench_tables__table_id__columns_get",
33
+ "list_workbench_rows_workbench_tables__table_id__rows_get",
34
+ "list_workbench_tables_workbench_tables_get",
35
+ "list_workbench_target_fields_workbench_target_fields_get",
36
+ "map_workbench_column_workbench_tables__table_id__columns__column_id__mapping_put",
37
+ "paste_import_workbench_rows_workbench_tables__table_id__import_paste_post",
38
+ "preview_workbench_search_import_workbench_tables__table_id__import_search_preview_post",
39
+ "quote_workbench_ai_run_workbench_tables__table_id__ai_validate_credits_post",
40
+ "quote_workbench_unlock_workbench_tables__table_id__unlock_quote_post",
41
+ "quote_workbench_waterfall_run_workbench_tables__table_id__waterfall_configs__column_id__validate_credits_post",
42
+ "run_workbench_column_ai_workbench_tables__table_id__columns__col_id__ai_run_post",
43
+ "run_workbench_default_ai_workbench_tables__table_id__ai_run_post",
44
+ "run_workbench_scraper_workbench_tables__table_id__scraper_runs_post",
45
+ "run_workbench_waterfall_workbench_tables__table_id__waterfall_configs__column_id__run_post",
46
+ "save_workbench_column_ai_config_workbench_tables__table_id__columns__col_id__ai_config_put",
47
+ "save_workbench_waterfall_workbench_tables__table_id__waterfall_configs_post",
48
+ "unlock_workbench_rows_workbench_tables__table_id__unlock_rows_post",
49
+ "update_workbench_row_workbench_tables__table_id__rows__row_id__patch"
50
+ ],
51
+ "components": [
52
+ {
53
+ "name": "WorkbenchTableList",
54
+ "file": "components/workbench-table-list.tsx",
55
+ "purpose": "The workspace's workbenches with search, create (people or companies) and delete; opening one hands it to onOpen, or (no onOpen) shows it in a WorkbenchGrid.",
56
+ "operations": [
57
+ "list_workbench_tables_workbench_tables_get",
58
+ "create_workbench_table_workbench_tables_post",
59
+ "delete_workbench_table_workbench_tables__table_id__delete"
60
+ ],
61
+ "tiers": [
62
+ "read",
63
+ "write",
64
+ "destructive"
65
+ ]
66
+ },
67
+ {
68
+ "name": "WorkbenchGrid",
69
+ "file": "components/workbench-grid.tsx",
70
+ "purpose": "The table itself: rows by columns, selection, double-click cell edit, run buttons on recipe columns, and the panels for adding rows, columns and acting on the selection.",
71
+ "operations": [
72
+ "get_workbench_table_workbench_tables__table_id__get",
73
+ "list_workbench_columns_workbench_tables__table_id__columns_get",
74
+ "list_workbench_rows_workbench_tables__table_id__rows_get",
75
+ "update_workbench_row_workbench_tables__table_id__rows__row_id__patch"
76
+ ],
77
+ "tiers": [
78
+ "read",
79
+ "write"
80
+ ]
81
+ },
82
+ {
83
+ "name": "WorkbenchAddRows",
84
+ "file": "components/workbench-add-rows.tsx",
85
+ "purpose": "Add one row typed into the table's columns, or paste many rows from a spreadsheet (the header line creates columns).",
86
+ "operations": [
87
+ "add_workbench_row_workbench_tables__table_id__rows_post",
88
+ "paste_import_workbench_rows_workbench_tables__table_id__import_paste_post"
89
+ ],
90
+ "tiers": [
91
+ "write"
92
+ ]
93
+ },
94
+ {
95
+ "name": "ColumnRecipeEditor",
96
+ "file": "components/column-recipe-editor.tsx",
97
+ "purpose": "Add a column and say how it is filled: AI prompt, enrichment waterfall preset, formula or plain data; map your columns to graph8 target fields. Saving never spends credits.",
98
+ "operations": [
99
+ "add_workbench_column_workbench_tables__table_id__columns_post",
100
+ "save_workbench_column_ai_config_workbench_tables__table_id__columns__col_id__ai_config_put",
101
+ "save_workbench_waterfall_workbench_tables__table_id__waterfall_configs_post",
102
+ "create_workbench_formula_columns_workbench_tables__table_id__formula_columns_post",
103
+ "list_workbench_target_fields_workbench_target_fields_get",
104
+ "map_workbench_column_workbench_tables__table_id__columns__column_id__mapping_put",
105
+ "list_enrichment_providers_enrichment_providers_get"
106
+ ],
107
+ "tiers": [
108
+ "read",
109
+ "write"
110
+ ]
111
+ },
112
+ {
113
+ "name": "RunProgress",
114
+ "file": "components/run-progress.tsx",
115
+ "purpose": "Run an AI or waterfall column over some or all rows: free quote, ConfirmSpendDialog with the exact credits, start with the quote as the cap, poll the history receipt to a final state.",
116
+ "operations": [
117
+ "quote_workbench_ai_run_workbench_tables__table_id__ai_validate_credits_post",
118
+ "quote_workbench_waterfall_run_workbench_tables__table_id__waterfall_configs__column_id__validate_credits_post",
119
+ "run_workbench_column_ai_workbench_tables__table_id__columns__col_id__ai_run_post",
120
+ "run_workbench_default_ai_workbench_tables__table_id__ai_run_post",
121
+ "run_workbench_waterfall_workbench_tables__table_id__waterfall_configs__column_id__run_post",
122
+ "get_workbench_history_workbench_tables__table_id__history_get",
123
+ "get_workbench_analytics_workbench_tables__table_id__analytics_get"
124
+ ],
125
+ "tiers": [
126
+ "read",
127
+ "billable"
128
+ ]
129
+ },
130
+ {
131
+ "name": "WorkbenchRowActions",
132
+ "file": "components/workbench-row-actions.tsx",
133
+ "purpose": "Act on the selected rows: quote then unlock locked rows (1 credit each), send rows to a list, delete rows (confirmed).",
134
+ "operations": [
135
+ "quote_workbench_unlock_workbench_tables__table_id__unlock_quote_post",
136
+ "unlock_workbench_rows_workbench_tables__table_id__unlock_rows_post",
137
+ "export_workbench_table_workbench_tables__table_id__export_post",
138
+ "bulk_delete_workbench_rows_workbench_tables__table_id__rows_bulk_delete_post"
139
+ ],
140
+ "tiers": [
141
+ "read",
142
+ "write",
143
+ "billable",
144
+ "destructive"
145
+ ]
146
+ }
147
+ ],
148
+ "hooks": [
149
+ {
150
+ "name": "useWorkbenchTable",
151
+ "file": "hooks/use-workbench-table.ts",
152
+ "purpose": "A table and its columns, with one refetch for both.",
153
+ "operations": [
154
+ "get_workbench_table_workbench_tables__table_id__get",
155
+ "list_workbench_columns_workbench_tables__table_id__columns_get"
156
+ ]
157
+ }
158
+ ],
159
+ "lib": [
160
+ {
161
+ "file": "lib/workbench-types.ts",
162
+ "purpose": "Table, column, row, quote and receipt shapes; cellValue, isLocked, toMachineName, parsePasted and the run-receipt matching helpers."
163
+ },
164
+ {
165
+ "file": "lib/waterfall-presets.ts",
166
+ "purpose": "Waterfall presets (work email, mobile phone, LinkedIn URL, company phone) copied from graph8's own templates, and buildWaterfallConfig for the save body."
167
+ }
168
+ ],
169
+ "recipes": [
170
+ {
171
+ "file": "recipes/build-enriched-table.ts",
172
+ "title": "Build a workbench: rows in, an AI column, a quote, a run, the results",
173
+ "when": "Starting a table from a pasted list. Create the table, paste rows, add an AI column (free), QUOTE the run (free), get a human to approve the price, run with the quote's numbers as the cap, poll the history receipt, read the rows. A run without row_ids runs every row and the quote prices exactly that."
174
+ },
175
+ {
176
+ "file": "recipes/waterfall-column.ts",
177
+ "title": "Add an enrichment waterfall column, price it, run it",
178
+ "when": "Finding work emails or phones for people in a table. Map columns to graph8 targets first, save the waterfall (free, never runs), quote, approve, run. Providers on graph8's key spend credits; providers on your own key do not."
179
+ },
180
+ {
181
+ "file": "recipes/import-from-search.ts",
182
+ "title": "Import people from a prospect search, unlock the ones you want, send them to a list",
183
+ "when": "Filling a workbench from graph8's people index. The preview is free; the import is billable and lands locked rows with masked emails and phones. Quote the unlock, approve, unlock (1 credit per row), then export the rows to a list."
184
+ },
185
+ {
186
+ "file": "recipes/scrape-into-table.ts",
187
+ "title": "Scrape the web into a table with the built-in scraper",
188
+ "when": "Collecting companies or people from a description. The scraper charges per result and auto_enrich adds enrichment of each on top: get the free credit estimate, approve it, run with it as the cap, poll the run."
189
+ }
190
+ ],
191
+ "prerequisites": [
192
+ {
193
+ "id": "enrichment-and-lists-installed",
194
+ "where": "app",
195
+ "description": "The enrichment and lists blocks are installed (the CLI does it): the waterfall presets read the provider catalog and rows are sent to lists with the list picker.",
196
+ "verify": "components/g8/enrichment/ and components/g8/lists/ exist."
197
+ },
198
+ {
199
+ "id": "proxy-allowlist",
200
+ "where": "app",
201
+ "description": "allowedPrefixes in the /api/g8 route includes `workbench`, `enrichment` and `lists`.",
202
+ "verify": "GET /api/g8/workbench/tables returns 200 when signed in."
203
+ },
204
+ {
205
+ "id": "provider-keys",
206
+ "where": "api",
207
+ "description": "Waterfall steps on providers that graph8 does not fund need the workspace's own API key saved in graph8 settings (Integrations). There is no API to add provider keys.",
208
+ "verify": "GET /enrichment/providers shows byok_configured true for the provider."
209
+ },
210
+ {
211
+ "id": "credits",
212
+ "where": "api",
213
+ "description": "The workspace has credits for AI columns, graph8-funded providers, scraping and unlocking rows. The quotes show the live balance and a shortfall; a run without enough credits is refused.",
214
+ "verify": "A quote answers can_run true."
215
+ }
216
+ ],
217
+ "scopes": [
218
+ "enrichment:read",
219
+ "lists:delete",
220
+ "lists:read",
221
+ "lists:run",
222
+ "lists:write"
223
+ ],
224
+ "defaults": [
225
+ {
226
+ "decision": "Rows a run covers",
227
+ "default": "The selected rows; with no selection, every row (the quote and the confirmation say so)",
228
+ "askHuman": false
229
+ },
230
+ {
231
+ "decision": "Who approves a run's price",
232
+ "default": "The person at the confirmation dialog; never an automatic approval of the quote",
233
+ "askHuman": true
234
+ },
235
+ {
236
+ "decision": "Waterfall auto-update on new rows",
237
+ "default": "Off: saving a waterfall never starts spending on new rows",
238
+ "askHuman": false
239
+ },
240
+ {
241
+ "decision": "Waterfall providers and order",
242
+ "default": "The preset's order (for work email: apollo, leadmagic, prospeo, hunter, dropcontact), each on your own key if configured, else graph8's",
243
+ "askHuman": false
244
+ },
245
+ {
246
+ "decision": "AI column web research",
247
+ "default": "Off (content generation); the person can tick web research per column",
248
+ "askHuman": false
249
+ },
250
+ {
251
+ "decision": "Where finished rows go",
252
+ "default": "A list picked or created in the row actions panel",
253
+ "askHuman": false
254
+ }
255
+ ],
256
+ "peerDependencies": {
257
+ "react": ">=18"
258
+ }
259
+ }
@@ -0,0 +1,183 @@
1
+ "use client";
2
+
3
+ /**
4
+ * ColumnRecipeEditor: add a column to a workbench table and say how it is filled. Five tabs:
5
+ * AI a prompt that can use other columns as {{column_name}}; fills the column per row
6
+ * Enrichment a provider waterfall (try provider A, then B...) for work email, phone or LinkedIn URL
7
+ * Formula a formula that derives a column from the others
8
+ * Column a plain data column
9
+ * Map tell graph8 which of YOUR columns is the first name, website... (waterfall steps read those)
10
+ *
11
+ * SDK operations, in order (per tab):
12
+ * AI: addWorkbenchColumn POST .../columns {name, display_name} -> { id }
13
+ * saveWorkbenchColumnAiConfig PUT .../columns/{col_id}/ai-config { prompt, usecase }
14
+ * Enrichment: listEnrichmentProviders GET /enrichment/providers (read; own key vs graph8's key)
15
+ * saveWorkbenchWaterfall POST .../waterfall/configs { config } (write: saving never runs or charges anything)
16
+ * Formula: createWorkbenchFormulaColumns POST .../formula-columns { formula, output_columns }
17
+ * Column: addWorkbenchColumn
18
+ * Map: listWorkbenchTargetFields GET /workbench/target-fields?entity_type, mapWorkbenchColumn PUT .../columns/{id}/mapping { mapping_target }
19
+ * Nothing here spends credits: running the recipe is RunProgress (quote, confirm, run). The waterfall is saved with
20
+ * auto_update off so it does not start spending on new rows by itself.
21
+ */
22
+
23
+ import { useRef, useState } from "react";
24
+ import { Button, Input, Label, Textarea } from "@graph8/sdk/ui";
25
+ import { ApiError } from "@/g8/components/api-error";
26
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
27
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
28
+ import { g8Get, g8Post, g8Put } from "@/g8/lib/g8-client";
29
+ import { useProviders } from "@/g8/enrichment/hooks/use-providers";
30
+ import { WATERFALL_PRESETS, buildWaterfallConfig, requiredTargets } from "../lib/waterfall-presets";
31
+ import { pageItems, toMachineName } from "../lib/workbench-types";
32
+ import type { WorkbenchColumn } from "../lib/workbench-types";
33
+
34
+ export interface ColumnRecipeEditorProps {
35
+ tableId: string;
36
+ columns: WorkbenchColumn[];
37
+ /** The table's record kind; decides which target fields the Map tab offers. Default "contact". */
38
+ entityType?: "contact" | "company";
39
+ /** Called after a column was created or mapped so the grid can reload its columns. */
40
+ onSaved?: () => void;
41
+ className?: string;
42
+ }
43
+
44
+ type Tab = "ai" | "enrichment" | "formula" | "column" | "map";
45
+ const TABS: Array<{ id: Tab; label: string }> = [
46
+ { id: "ai", label: "AI" },
47
+ { id: "enrichment", label: "Enrichment" },
48
+ { id: "formula", label: "Formula" },
49
+ { id: "column", label: "Column" },
50
+ { id: "map", label: "Map columns" },
51
+ ];
52
+ const newId = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(16).slice(2)}`;
53
+
54
+ export function ColumnRecipeEditor({ tableId, columns, entityType = "contact", onSaved, className }: ColumnRecipeEditorProps) {
55
+ const [tab, setTab] = useState<Tab>("ai");
56
+ const [name, setName] = useState("");
57
+ const [prompt, setPrompt] = useState("");
58
+ const [research, setResearch] = useState(false);
59
+ const [presetId, setPresetId] = useState(WATERFALL_PRESETS[0].id);
60
+ const [skipped, setSkipped] = useState<string[]>([]);
61
+ const [formula, setFormula] = useState("");
62
+ const [notice, setNotice] = useState<string | null>(null);
63
+ const catalog = useProviders(tab === "enrichment");
64
+ const targets = useG8Query(async (signal) => pageItems<{ field: string; label: string }>((await g8Get("/workbench/target-fields", { entity_type: entityType }, { signal })).data), [entityType], { enabled: tab === "map" });
65
+
66
+ const preset = WATERFALL_PRESETS.find((p) => p.id === presetId)!;
67
+ const steps = preset.steps.filter((s) => !skipped.includes(s.provider));
68
+ const machine = toMachineName(name);
69
+ const done = (message: string) => { setNotice(message); setName(""); setPrompt(""); setFormula(""); onSaved?.(); };
70
+ const mapped = new Set(columns.map((c) => c.mapping_target).filter(Boolean));
71
+ const unmapped = requiredTargets(steps).filter((t) => !mapped.has(t));
72
+
73
+ // Retry-safe: the column made by step 1 is remembered, so a retry after step 2 failed does not create it twice (409).
74
+ const created = useRef<{ key: string; id: number } | null>(null);
75
+ const addAi = useG8Mutation(async () => {
76
+ const key = `${tableId}:${machine}`;
77
+ if (created.current?.key !== key) {
78
+ const col = await g8Post<{ id: number }>(`/workbench/tables/${tableId}/columns`, { name: machine, display_name: name.trim(), data_type: "text" });
79
+ created.current = { key, id: col.data.id };
80
+ }
81
+ await g8Put(`/workbench/tables/${tableId}/columns/${created.current.id}/ai-config`, { prompt: prompt.trim(), usecase: research ? "web-research" : "content_generation" });
82
+ }, { onSuccess: () => { created.current = null; done("AI column saved. Nothing has run yet."); } });
83
+ const addPlain = useG8Mutation(() => g8Post(`/workbench/tables/${tableId}/columns`, { name: machine, display_name: name.trim(), data_type: "text" }), { onSuccess: () => done("Column added.") });
84
+ const addFormula = useG8Mutation(
85
+ () => g8Post(`/workbench/tables/${tableId}/formula-columns`, { formula: formula.trim(), output_columns: [{ name: machine, display_name: name.trim() }] }),
86
+ { onSuccess: () => done("Formula column added.") },
87
+ );
88
+ const addWaterfall = useG8Mutation(
89
+ () => g8Post(`/workbench/tables/${tableId}/waterfall/configs`, buildWaterfallConfig(name.trim() || preset.columnName, steps, catalog.providers, newId)),
90
+ { onSuccess: () => done("Enrichment column saved. Nothing has run yet.") },
91
+ );
92
+ const map = useG8Mutation((v: { id: number; target: string | null }) => g8Put(`/workbench/tables/${tableId}/columns/${v.id}/mapping`, { mapping_target: v.target }), { onSuccess: () => onSaved?.() });
93
+
94
+ const nameField = (
95
+ <div className="flex flex-col gap-1">
96
+ <Label htmlFor="wb-col-name">Column name</Label>
97
+ <Input id="wb-col-name" value={name} onChange={(e) => setName(e.target.value)} placeholder={tab === "enrichment" ? preset.columnName : "Company summary"} />
98
+ </div>
99
+ );
100
+
101
+ return (
102
+ <div className={`flex flex-col gap-3 rounded-md border p-3 ${className ?? ""}`}>
103
+ <div role="tablist" aria-label="Column recipe" className="flex flex-wrap gap-2">
104
+ {TABS.map((t) => <Button key={t.id} role="tab" aria-selected={tab === t.id} size="sm" variant={tab === t.id ? "default" : "outline"} onClick={() => { setTab(t.id); setNotice(null); }}>{t.label}</Button>)}
105
+ </div>
106
+
107
+ {tab === "ai" ? (
108
+ <div className="flex flex-col gap-2">
109
+ {nameField}
110
+ <Label htmlFor="wb-ai-prompt">Prompt</Label>
111
+ <Textarea id="wb-ai-prompt" rows={5} value={prompt} onChange={(e) => setPrompt(e.target.value)} placeholder="Write a one-line summary of {{company_name}} ({{company_domain}})." />
112
+ <div className="flex flex-wrap gap-1" aria-label="Insert a column">
113
+ {columns.slice(0, 12).map((c) => <Button key={c.id} size="sm" variant="ghost" onClick={() => setPrompt(`${prompt}{{${c.name}}}`)}>{`{{${c.name}}}`}</Button>)}
114
+ </div>
115
+ <label className="flex items-center gap-2 text-sm"><input type="checkbox" checked={research} onChange={(e) => setResearch(e.target.checked)} /> Let the AI search the web (slower, costs more per row)</label>
116
+ {addAi.error ? <ApiError error={addAi.error} title="Could not save the AI column" /> : null}
117
+ <div><Button size="sm" loading={addAi.loading} disabled={!machine || !prompt.trim()} onClick={() => void addAi.mutate(undefined)}>Save AI column</Button></div>
118
+ </div>
119
+ ) : null}
120
+
121
+ {tab === "enrichment" ? (
122
+ <div className="flex flex-col gap-2">
123
+ <Label htmlFor="wb-preset">What to find</Label>
124
+ <select id="wb-preset" className="h-9 rounded-md border bg-background px-2" value={presetId} onChange={(e) => { setPresetId(e.target.value); setSkipped([]); }}>
125
+ {WATERFALL_PRESETS.map((p) => <option key={p.id} value={p.id}>{p.label}</option>)}
126
+ </select>
127
+ {nameField}
128
+ <fieldset className="flex flex-col gap-1"><legend className="mb-1 text-sm font-medium">Providers, tried in this order</legend>
129
+ {preset.steps.map((s, i) => {
130
+ const info = catalog.providers.find((p) => p.provider === s.provider);
131
+ return (
132
+ <label key={s.provider} className="flex items-center gap-2 text-sm">
133
+ <input type="checkbox" aria-label={`Use ${s.provider}`} checked={!skipped.includes(s.provider)} onChange={() => setSkipped(skipped.includes(s.provider) ? skipped.filter((x) => x !== s.provider) : [...skipped, s.provider])} />
134
+ <span className="w-6 text-muted-foreground">{i + 1}.</span><span className="font-medium">{s.provider}</span>
135
+ <span className="text-xs text-muted-foreground">{info?.byok_configured ? "your key" : info?.system_funded ? "graph8 key, uses credits when run" : "needs your key"}</span>
136
+ </label>
137
+ );
138
+ })}
139
+ </fieldset>
140
+ {unmapped.length ? <p role="alert" className="text-sm text-amber-700">Not mapped yet: {unmapped.join(", ")}. Use the Map columns tab, or the steps that need them find nothing.</p> : null}
141
+ {addWaterfall.error ? <ApiError error={addWaterfall.error} title="Could not save the enrichment column" /> : null}
142
+ <div><Button size="sm" loading={addWaterfall.loading} disabled={steps.length === 0} onClick={() => void addWaterfall.mutate(undefined)}>Save enrichment column</Button></div>
143
+ </div>
144
+ ) : null}
145
+
146
+ {tab === "formula" ? (
147
+ <div className="flex flex-col gap-2">
148
+ {nameField}
149
+ <Label htmlFor="wb-formula">Formula</Label>
150
+ <Textarea id="wb-formula" rows={3} value={formula} onChange={(e) => setFormula(e.target.value)} placeholder="{{first_name}} {{last_name}}" />
151
+ {addFormula.error ? <ApiError error={addFormula.error} title="Could not add the formula column" /> : null}
152
+ <div><Button size="sm" loading={addFormula.loading} disabled={!machine || !formula.trim()} onClick={() => void addFormula.mutate(undefined)}>Add formula column</Button></div>
153
+ </div>
154
+ ) : null}
155
+
156
+ {tab === "column" ? (
157
+ <div className="flex flex-col gap-2">
158
+ {nameField}
159
+ {addPlain.error ? <ApiError error={addPlain.error} title="Could not add the column" /> : null}
160
+ <div><Button size="sm" loading={addPlain.loading} disabled={!machine} onClick={() => void addPlain.mutate(undefined)}>Add column</Button></div>
161
+ </div>
162
+ ) : null}
163
+
164
+ {tab === "map" ? (
165
+ <div className="flex flex-col gap-2">
166
+ <p className="text-sm text-muted-foreground">Tell graph8 what each of your columns holds, so enrichment steps know where the first name, last name and website are.</p>
167
+ {targets.error ? <ApiError error={targets.error} title="Could not load the fields" onRetry={() => void targets.refetch()} /> : null}
168
+ {map.error ? <ApiError error={map.error} title="Could not save the mapping" /> : null}
169
+ {columns.filter((c) => (c.column_type ?? "data") === "data" && !c.enrichment_type).map((c) => (
170
+ <label key={c.id} className="flex items-center gap-2 text-sm">
171
+ <span className="w-40 truncate font-medium">{c.display_name || c.name}</span>
172
+ <select aria-label={`Map ${c.display_name || c.name}`} className="h-9 rounded-md border bg-background px-2" value={c.mapping_target ?? ""} onChange={(e) => void map.mutate({ id: c.id, target: e.target.value || null })}>
173
+ <option value="">Not mapped</option>
174
+ {(targets.data ?? []).map((t) => <option key={t.field} value={t.field}>{t.label}</option>)}
175
+ </select>
176
+ </label>
177
+ ))}
178
+ </div>
179
+ ) : null}
180
+ {notice ? <p role="status" className="text-sm text-muted-foreground">{notice}</p> : null}
181
+ </div>
182
+ );
183
+ }
@@ -0,0 +1,132 @@
1
+ "use client";
2
+
3
+ /**
4
+ * RunProgress: run a recipe column (AI prompt or enrichment waterfall) over the rows of a table, with the price
5
+ * confirmed first and the run followed to the end. Runs are BILLABLE and an omitted row list means EVERY row.
6
+ *
7
+ * SDK operations, in order:
8
+ * 1. quoteWorkbenchAiRun / quoteWorkbenchWaterfallRun POST .../ai/validate-credits | .../waterfall/configs/{column_id}/validate-credits
9
+ * (read, free) body { column_id, row_ids? } -> required_credits, available_credits, estimated_records, quote_id, quote_expires_at
10
+ * 2. runWorkbenchColumnAi / runWorkbenchDefaultAi / runWorkbenchWaterfall (billable)
11
+ * POST .../columns/{col_id}/ai/run | .../ai/run | .../waterfall/configs/{column_id}/run, 202 { job_id, status: "queued" }
12
+ * Goes through ConfirmSpendDialog first with the quote's required_credits. The same number goes back as
13
+ * `approved_max_credits` with `quote_expires_at` and `quote_id`; a run whose price grew is refused until re-quoted.
14
+ * 3. getWorkbenchHistory GET .../history?limit=10 (read) polled with useJobPoll (the API has no streams); the run is the
15
+ * receipt whose external_ref is the job id (else the newest receipt of that type that was not in the history before the run started), final when its
16
+ * status is completed, partial, failed or cancelled.
17
+ * 4. getWorkbenchAnalytics GET .../analytics (read) rows per status while the run is going.
18
+ */
19
+
20
+ import { useState } from "react";
21
+ import { Button } 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 { useJobPoll } from "@/g8/hooks/use-job-poll";
26
+ import { g8Get, g8Post } from "@/g8/lib/g8-client";
27
+ import { findRunReceipt, isReceiptTerminal, pageItems, runTypes } from "../lib/workbench-types";
28
+ import type { CreditQuote, OperationReceipt, RunTarget, TableAnalytics } from "../lib/workbench-types";
29
+
30
+ export interface RunProgressProps {
31
+ tableId: string;
32
+ target: RunTarget;
33
+ /** Rows to run. Leave undefined to run every row; the quote and the confirmation then say so. */
34
+ rowIds?: number[];
35
+ /** Words for the button and dialog, for example the column's display name. */
36
+ label?: string;
37
+ onFinished?: (receipt: OperationReceipt) => void;
38
+ className?: string;
39
+ }
40
+
41
+ interface RunSnapshot { receipt?: OperationReceipt; analytics?: TableAnalytics }
42
+
43
+ function quotePath(tableId: string, target: RunTarget): string {
44
+ return target.kind === "waterfall"
45
+ ? `/workbench/tables/${tableId}/waterfall/configs/${target.columnId}/validate-credits`
46
+ : `/workbench/tables/${tableId}/ai/validate-credits`;
47
+ }
48
+
49
+ function runPath(tableId: string, target: RunTarget): string {
50
+ if (target.kind === "waterfall") return `/workbench/tables/${tableId}/waterfall/configs/${target.columnId}/run`;
51
+ if (target.kind === "ai-column") return `/workbench/tables/${tableId}/columns/${target.columnId}/ai/run`;
52
+ return `/workbench/tables/${tableId}/ai/run`;
53
+ }
54
+
55
+ export function RunProgress({ tableId, target, rowIds, label = "this column", onFinished, className }: RunProgressProps) {
56
+ const [quote, setQuote] = useState<CreditQuote | null>(null);
57
+ const [job, setJob] = useState<{ id: string; before: string[] } | null>(null);
58
+ const scope = rowIds?.length ? `${rowIds.length.toLocaleString()} selected rows` : "every row";
59
+
60
+ const getQuote = useG8Mutation(
61
+ async () => {
62
+ const body = { ...(target.kind === "ai-default" ? {} : { column_id: target.columnId }), ...(rowIds?.length ? { row_ids: rowIds } : {}) };
63
+ return (await g8Post<CreditQuote>(quotePath(tableId, target), body)).data;
64
+ },
65
+ { onSuccess: (q) => setQuote(q) },
66
+ );
67
+ const start = useG8Mutation(
68
+ async (q: CreditQuote) => {
69
+ // Receipts that exist before the run: whatever appears afterwards, of this type, is this run's (clock-free matching).
70
+ const before = pageItems<OperationReceipt>((await g8Get(`/workbench/tables/${tableId}/history`, { limit: 50 })).data).map((r) => r.id);
71
+ const body = {
72
+ ...(rowIds?.length ? { row_ids: rowIds } : {}),
73
+ approved_max_credits: q.required_credits,
74
+ quote_expires_at: q.quote_expires_at,
75
+ ...(q.quote_id ? { quote_id: q.quote_id } : {}),
76
+ };
77
+ return { ...(await g8Post<{ job_id: string }>(runPath(tableId, target), body)).data, before };
78
+ },
79
+ { onSuccess: (res) => { setQuote(null); setJob({ id: res.job_id, before: res.before }); } },
80
+ );
81
+ const poll = useJobPoll<RunSnapshot>(
82
+ async (signal) => {
83
+ const history = await g8Get(`/workbench/tables/${tableId}/history`, { limit: 10 }, { signal });
84
+ const analytics = await g8Get<TableAnalytics>(`/workbench/tables/${tableId}/analytics`, undefined, { signal }).catch(() => null);
85
+ return { receipt: findRunReceipt(pageItems<OperationReceipt>(history.data), job!.id, job!.before, runTypes(target)), analytics: analytics?.data };
86
+ },
87
+ { enabled: job != null, intervalMs: 2000, maxAttempts: 150, isTerminal: (s) => isReceiptTerminal(s.receipt?.status), onDone: (s) => s.receipt && onFinished?.(s.receipt) },
88
+ );
89
+
90
+ const receipt = poll.status?.receipt;
91
+ const distribution = poll.status?.analytics?.status_distribution ?? {};
92
+ const short = quote?.can_run === false;
93
+
94
+ if (job) {
95
+ return (
96
+ <div className={`flex flex-col gap-2 rounded-md border p-3 ${className ?? ""}`} role="status">
97
+ <h3 className="text-sm font-semibold">{poll.done ? `Run ${receipt?.status ?? "finished"}` : "Running..."}</h3>
98
+ <p className="text-sm text-muted-foreground">
99
+ {receipt ? `${receipt.credits_charged ?? 0} credits charged so far.` : "Queued. The first update can take a few seconds."}
100
+ {Object.entries(distribution).length ? ` Rows: ${Object.entries(distribution).map(([k, v]) => `${v} ${k}`).join(", ")}.` : ""}
101
+ </p>
102
+ {receipt?.error_message ? <p className="text-sm text-destructive">{receipt.error_message}</p> : null}
103
+ {poll.error ? <ApiError error={poll.error} title="Could not read the run" onRetry={() => poll.start()} /> : null}
104
+ {!poll.done && !poll.polling && !poll.error ? <p className="text-xs text-muted-foreground">Stopped checking. The run may still be going: check again later.</p> : null}
105
+ <div className="flex gap-2">
106
+ {!poll.done ? <Button size="sm" variant="outline" onClick={() => poll.stop()}>Stop watching</Button> : null}
107
+ {poll.done || !poll.polling ? <Button size="sm" variant="outline" onClick={() => { poll.stop(); setJob(null); }}>Done</Button> : null}
108
+ </div>
109
+ </div>
110
+ );
111
+ }
112
+
113
+ return (
114
+ <div className={`flex flex-col gap-2 ${className ?? ""}`}>
115
+ {getQuote.error ? <ApiError error={getQuote.error} title="Could not price the run" onRetry={() => void getQuote.mutate(undefined)} /> : null}
116
+ {start.error ? <ApiError error={start.error} title="Could not start the run" /> : null}
117
+ <div><Button size="sm" loading={getQuote.loading} onClick={() => void getQuote.mutate(undefined)}>Run {label} on {scope}</Button></div>
118
+ <ConfirmSpendDialog
119
+ open={quote != null}
120
+ tier="billable"
121
+ title={`Run ${label} on ${quote?.estimated_records ?? scope}${quote?.estimated_records != null ? " rows" : ""}`}
122
+ description={short ? `Not enough credits: you need ${quote?.required_credits} and have ${quote?.available_credits}. ${quote?.message ?? ""}` : (quote?.message ?? "The price is the most this run can charge. Rows that already have a value are skipped.")}
123
+ estimatedCredits={quote?.required_credits}
124
+ affected={quote?.estimated_records}
125
+ confirmLabel={short ? "Not enough credits" : "Start run"}
126
+ confirming={start.loading}
127
+ onConfirm={() => { if (quote && !short) void start.mutate(quote); }}
128
+ onCancel={() => setQuote(null)}
129
+ />
130
+ </div>
131
+ );
132
+ }