@graph8/sdk 0.362.0 → 0.373.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (615) hide show
  1. package/README.md +33 -10
  2. package/blocks/_foundation/README.md +78 -1
  3. package/blocks/_foundation/block.json +123 -6
  4. package/blocks/_foundation/components/api-error.tsx +9 -7
  5. package/blocks/_foundation/components/brand-provider.tsx +23 -0
  6. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  7. package/blocks/_foundation/components/confirmed-action.tsx +90 -0
  8. package/blocks/_foundation/components/connect-graph8.tsx +280 -0
  9. package/blocks/_foundation/components/empty-state.tsx +5 -2
  10. package/blocks/_foundation/components/list-select.tsx +112 -0
  11. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  12. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  13. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  14. package/blocks/_foundation/components/screen/index.ts +2 -0
  15. package/blocks/_foundation/components/screen/record-link.tsx +76 -0
  16. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  17. package/blocks/_foundation/components/screen/time-series-chart-recharts.tsx +65 -0
  18. package/blocks/_foundation/components/screen/time-series-chart.tsx +15 -97
  19. package/blocks/_foundation/components/screen/time-series-format.ts +57 -0
  20. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  21. package/blocks/_foundation/hooks/use-brand.ts +52 -0
  22. package/blocks/_foundation/hooks/use-g8-mutation.ts +23 -0
  23. package/blocks/_foundation/hooks/use-g8-query.ts +127 -1
  24. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  25. package/blocks/_foundation/hooks/use-paginated.ts +120 -0
  26. package/blocks/_foundation/hooks/use-row-activation.ts +57 -0
  27. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  28. package/blocks/_foundation/lib/g8-cache.ts +454 -0
  29. package/blocks/_foundation/lib/g8-client.ts +1 -1
  30. package/blocks/_foundation/lib/paging.ts +57 -0
  31. package/blocks/_foundation/lib/plural.ts +23 -0
  32. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  33. package/blocks/_foundation/server/connect-route.ts +15 -0
  34. package/blocks/_foundation/server/connect.ts +127 -0
  35. package/blocks/_foundation/server/local-key.ts +70 -0
  36. package/blocks/_foundation/server/proxy.ts +103 -3
  37. package/blocks/_foundation/server/route.ts +16 -4
  38. package/blocks/agents/README.md +93 -2
  39. package/blocks/agents/block.json +343 -2
  40. package/blocks/agents/components/agent-activity.tsx +112 -0
  41. package/blocks/agents/components/agent-approvals.tsx +135 -0
  42. package/blocks/agents/components/agent-builder.tsx +29 -10
  43. package/blocks/agents/components/agent-conversations.tsx +143 -0
  44. package/blocks/agents/components/agent-knowledge-panel.tsx +4 -3
  45. package/blocks/agents/components/agent-operators.tsx +164 -0
  46. package/blocks/agents/components/agent-run-detail.tsx +119 -0
  47. package/blocks/agents/components/agent-runs.tsx +19 -10
  48. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  49. package/blocks/agents/components/agent-test-history.tsx +121 -0
  50. package/blocks/agents/components/agents-screen.tsx +226 -0
  51. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  52. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  53. package/blocks/agents/components/from-template-button.tsx +65 -0
  54. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  55. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  56. package/blocks/agents/components/knowledge-form.tsx +69 -0
  57. package/blocks/agents/components/name-dialog.tsx +32 -0
  58. package/blocks/agents/components/receptionist-button.tsx +46 -0
  59. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  60. package/blocks/agents/components/skill-editor.tsx +147 -0
  61. package/blocks/agents/components/skill-library.tsx +111 -0
  62. package/blocks/agents/lib/agent-types.ts +7 -1
  63. package/blocks/agents/lib/facts.tsx +13 -0
  64. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  65. package/blocks/agents/lib/load-more.tsx +141 -0
  66. package/blocks/agents/lib/skill-utils.ts +91 -0
  67. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  68. package/blocks/analytics/README.md +13 -0
  69. package/blocks/analytics/block.json +45 -1
  70. package/blocks/analytics/components/analytics-screen.tsx +222 -0
  71. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  72. package/blocks/analytics/components/leaderboard.tsx +14 -7
  73. package/blocks/analytics/components/report-builder.tsx +38 -11
  74. package/blocks/analytics/components/report-detail.tsx +84 -0
  75. package/blocks/analytics/components/trend-chart.tsx +62 -0
  76. package/blocks/appointments/README.md +6 -0
  77. package/blocks/appointments/block.json +33 -2
  78. package/blocks/appointments/components/availability-editor.tsx +17 -5
  79. package/blocks/appointments/components/booking-page.tsx +2 -2
  80. package/blocks/appointments/components/event-type-editor.tsx +2 -1
  81. package/blocks/appointments/components/event-type-related.tsx +4 -2
  82. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  83. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  84. package/blocks/appointments/components/meeting-list.tsx +38 -18
  85. package/blocks/appointments/components/meetings-screen.tsx +7 -4
  86. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  87. package/blocks/appointments/components/scheduling-screen.tsx +2 -1
  88. package/blocks/block.schema.json +18 -0
  89. package/blocks/block.types.ts +17 -0
  90. package/blocks/campaigns/README.md +41 -11
  91. package/blocks/campaigns/block.json +105 -42
  92. package/blocks/campaigns/components/campaign-assets.tsx +17 -6
  93. package/blocks/campaigns/components/campaign-launch-step.tsx +114 -26
  94. package/blocks/campaigns/components/campaign-list.tsx +7 -3
  95. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  96. package/blocks/campaigns/components/campaign-wizard.tsx +52 -14
  97. package/blocks/campaigns/components/campaigns-screen.tsx +267 -0
  98. package/blocks/campaigns/components/icp-detail.tsx +163 -0
  99. package/blocks/campaigns/components/icp-editor.tsx +22 -17
  100. package/blocks/campaigns/components/persona-detail.tsx +181 -0
  101. package/blocks/campaigns/components/persona-editor.tsx +19 -11
  102. package/blocks/campaigns/lib/campaigns-utils.ts +138 -5
  103. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +16 -3
  104. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +8 -4
  105. package/blocks/coach/README.md +10 -0
  106. package/blocks/coach/block.json +16 -1
  107. package/blocks/coach/components/call-scorecard.tsx +7 -7
  108. package/blocks/coach/components/coach-screen.tsx +222 -0
  109. package/blocks/company-intelligence/README.md +132 -0
  110. package/blocks/company-intelligence/block.json +318 -0
  111. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  112. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  113. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  114. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  115. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  116. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  117. package/blocks/company-intelligence/components/research-run.tsx +161 -0
  118. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  119. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  120. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  121. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  122. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  123. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  124. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  125. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  126. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  127. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  128. package/blocks/copilot/README.md +13 -0
  129. package/blocks/copilot/block.json +14 -1
  130. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  131. package/blocks/copilot/components/copilot-panel.tsx +14 -5
  132. package/blocks/copilot/components/copilot-screen.tsx +148 -0
  133. package/blocks/copilot/components/copilot-team-chat.tsx +13 -6
  134. package/blocks/crm/README.md +34 -0
  135. package/blocks/crm/block.json +107 -2
  136. package/blocks/crm/components/companies-screen.tsx +359 -0
  137. package/blocks/crm/components/company-detail.tsx +10 -8
  138. package/blocks/crm/components/company-table.tsx +11 -4
  139. package/blocks/crm/components/contact-detail.tsx +10 -7
  140. package/blocks/crm/components/contact-lookup-link.tsx +44 -0
  141. package/blocks/crm/components/contact-table.tsx +10 -3
  142. package/blocks/crm/components/contacts-screen.tsx +377 -0
  143. package/blocks/crm/components/dedupe-review.tsx +13 -6
  144. package/blocks/crm/components/import-wizard.tsx +7 -9
  145. package/blocks/crm/components/list-select.tsx +20 -0
  146. package/blocks/crm/components/notes-panel.tsx +4 -4
  147. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  148. package/blocks/crm/components/record-form.tsx +6 -4
  149. package/blocks/crm/components/task-list.tsx +4 -4
  150. package/blocks/crm/components/tasks-screen.tsx +399 -0
  151. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  152. package/blocks/crm/hooks/use-custom-fields.ts +6 -1
  153. package/blocks/crm/hooks/use-page-query.ts +9 -1
  154. package/blocks/crm/lib/crm-types.ts +26 -2
  155. package/blocks/crm/lib/read-lists.ts +16 -0
  156. package/blocks/crm/lib/screen-parts.tsx +105 -0
  157. package/blocks/deals/README.md +24 -6
  158. package/blocks/deals/block.json +160 -5
  159. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  160. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  161. package/blocks/deals/components/deal-detail.tsx +25 -13
  162. package/blocks/deals/components/deal-filters.tsx +65 -0
  163. package/blocks/deals/components/deal-line-items.tsx +120 -0
  164. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  165. package/blocks/deals/components/deal-related.tsx +31 -10
  166. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  167. package/blocks/deals/components/deals-screen.tsx +66 -26
  168. package/blocks/deals/components/forecast-view.tsx +4 -4
  169. package/blocks/deals/components/pipeline-board.tsx +8 -6
  170. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  171. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  172. package/blocks/deals/components/stage-editor.tsx +5 -5
  173. package/blocks/deals/hooks/use-pipeline-deals.ts +21 -4
  174. package/blocks/deals/hooks/use-pipelines.ts +1 -1
  175. package/blocks/deals/lib/deal-extras.ts +156 -0
  176. package/blocks/dialer/README.md +45 -4
  177. package/blocks/dialer/block.json +84 -3
  178. package/blocks/dialer/components/call-contact-button.tsx +269 -0
  179. package/blocks/dialer/components/call-log-screen.tsx +208 -0
  180. package/blocks/dialer/components/call-log.tsx +23 -7
  181. package/blocks/dialer/components/call-queue.tsx +4 -4
  182. package/blocks/dialer/components/dialer-screen.tsx +361 -0
  183. package/blocks/dialer/components/dialer-session-detail.tsx +153 -0
  184. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  185. package/blocks/dialer/components/live-calls-table.tsx +7 -3
  186. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  187. package/blocks/dialer/components/session-start-form.tsx +61 -12
  188. package/blocks/dialer/components/softphone.tsx +4 -2
  189. package/blocks/enrichment/README.md +29 -7
  190. package/blocks/enrichment/block.json +49 -9
  191. package/blocks/enrichment/components/bulk-enrich-panel.tsx +83 -29
  192. package/blocks/enrichment/components/email-verify-badge.tsx +31 -11
  193. package/blocks/enrichment/components/enrich-button.tsx +32 -9
  194. package/blocks/enrichment/components/enrichment-job-detail.tsx +78 -0
  195. package/blocks/enrichment/components/enrichment-screen.tsx +341 -0
  196. package/blocks/enrichment/components/waterfall-config.tsx +66 -17
  197. package/blocks/enrichment/lib/enrichment-types.ts +39 -0
  198. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  199. package/blocks/enrichment/recipes/waterfall-pipeline.ts +3 -2
  200. package/blocks/forms/README.md +8 -0
  201. package/blocks/forms/block.json +139 -18
  202. package/blocks/forms/components/form-activity.tsx +71 -0
  203. package/blocks/forms/components/form-backfill.tsx +108 -0
  204. package/blocks/forms/components/form-builder.tsx +4 -2
  205. package/blocks/forms/components/form-embed.tsx +4 -2
  206. package/blocks/forms/components/form-groups.tsx +151 -0
  207. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  208. package/blocks/forms/components/form-route-history.tsx +72 -0
  209. package/blocks/forms/components/form-saved-views.tsx +81 -0
  210. package/blocks/forms/components/forms-screen.tsx +56 -19
  211. package/blocks/forms/components/more-rows.tsx +30 -0
  212. package/blocks/forms/components/submissions-export.tsx +67 -0
  213. package/blocks/forms/components/submissions-table.tsx +17 -6
  214. package/blocks/forms/lib/forms-extra.ts +67 -0
  215. package/blocks/inbox/README.md +32 -3
  216. package/blocks/inbox/block.json +29 -2
  217. package/blocks/inbox/components/inbox-list.tsx +7 -3
  218. package/blocks/inbox/components/inbox-screen.tsx +294 -27
  219. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  220. package/blocks/inbox/components/reply-composer.tsx +65 -9
  221. package/blocks/inbox/components/tag-and-assign.tsx +4 -2
  222. package/blocks/inbox/components/thread-badge.tsx +2 -2
  223. package/blocks/inbox/components/thread-view.tsx +1 -1
  224. package/blocks/inbox/hooks/use-inbox-thread.ts +1 -1
  225. package/blocks/inbox/hooks/use-inbox-threads.ts +1 -0
  226. package/blocks/inbox/lib/inbox-types.ts +47 -1
  227. package/blocks/inbox/recipes/01-triage-and-reply.ts +12 -2
  228. package/blocks/integrations/README.md +63 -1
  229. package/blocks/integrations/block.json +234 -15
  230. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  231. package/blocks/integrations/components/connection-health.tsx +59 -0
  232. package/blocks/integrations/components/crm-detail.tsx +119 -0
  233. package/blocks/integrations/components/crm-records.tsx +103 -0
  234. package/blocks/integrations/components/field-mappings.tsx +61 -0
  235. package/blocks/integrations/components/integration-list.tsx +13 -8
  236. package/blocks/integrations/components/integrations-screen.tsx +152 -0
  237. package/blocks/integrations/components/migration-panel.tsx +133 -0
  238. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  239. package/blocks/integrations/components/sync-status.tsx +11 -9
  240. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  241. package/blocks/integrations/components/webhook-detail.tsx +179 -0
  242. package/blocks/integrations/components/webhook-manager.tsx +43 -25
  243. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  244. package/blocks/integrations/lib/crm-utils.ts +39 -0
  245. package/blocks/integrations/lib/facts.tsx +13 -0
  246. package/blocks/integrations/lib/integrations-utils.ts +11 -1
  247. package/blocks/integrations/lib/load-more.tsx +141 -0
  248. package/blocks/intent/README.md +14 -0
  249. package/blocks/intent/block.json +27 -1
  250. package/blocks/intent/components/account-intent-score.tsx +5 -5
  251. package/blocks/intent/components/intent-feed.tsx +13 -10
  252. package/blocks/intent/components/intent-keyword-manager.tsx +16 -12
  253. package/blocks/intent/components/intent-screen.tsx +265 -0
  254. package/blocks/intent/components/keyword-detail.tsx +124 -0
  255. package/blocks/intent/lib/intent-utils.ts +4 -4
  256. package/blocks/lifecycle/block.json +13 -8
  257. package/blocks/lifecycle/components/account-related.tsx +8 -1
  258. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  259. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  260. package/blocks/lifecycle/components/lifecycle-board.tsx +3 -1
  261. package/blocks/lifecycle/components/lifecycle-screen.tsx +3 -1
  262. package/blocks/lifecycle/lib/lifecycle.ts +1 -1
  263. package/blocks/lists/README.md +20 -1
  264. package/blocks/lists/block.json +69 -3
  265. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  266. package/blocks/lists/components/add-to-list-button.tsx +20 -9
  267. package/blocks/lists/components/list-picker.tsx +17 -3
  268. package/blocks/lists/components/list-table.tsx +12 -6
  269. package/blocks/lists/components/lists-screen.tsx +346 -0
  270. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  271. package/blocks/lists/components/suppression-manager.tsx +33 -8
  272. package/blocks/lists/components/suppressions-screen.tsx +264 -0
  273. package/blocks/mailboxes/README.md +17 -0
  274. package/blocks/mailboxes/block.json +69 -17
  275. package/blocks/mailboxes/components/connect-mailbox.tsx +32 -21
  276. package/blocks/mailboxes/components/domain-detail.tsx +148 -0
  277. package/blocks/mailboxes/components/domain-purchase.tsx +20 -14
  278. package/blocks/mailboxes/components/mailbox-detail.tsx +157 -0
  279. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  280. package/blocks/mailboxes/components/mailboxes-screen.tsx +228 -0
  281. package/blocks/mailboxes/components/warmup-health.tsx +24 -22
  282. package/blocks/mailboxes/lib/mailboxes-utils.ts +26 -5
  283. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  284. package/blocks/newsletter/README.md +12 -0
  285. package/blocks/newsletter/block.json +157 -24
  286. package/blocks/newsletter/components/issue-scheduler.tsx +4 -3
  287. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  288. package/blocks/newsletter/components/more-rows.tsx +30 -0
  289. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  290. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  291. package/blocks/newsletter/components/newsletter-screen.tsx +39 -14
  292. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  293. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  294. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  295. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  296. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  297. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  298. package/blocks/nurture/block.json +28 -14
  299. package/blocks/nurture/components/nurture-builder.tsx +13 -3
  300. package/blocks/nurture/components/nurture-enroll.tsx +12 -10
  301. package/blocks/nurture/components/nurture-form.tsx +27 -10
  302. package/blocks/nurture/components/nurture-screen.tsx +26 -11
  303. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  304. package/blocks/nurture/lib/nurture-types.ts +1 -1
  305. package/blocks/objects/README.md +18 -1
  306. package/blocks/objects/block.json +42 -21
  307. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  308. package/blocks/objects/components/object-not-found.tsx +3 -1
  309. package/blocks/objects/components/object-record-detail.tsx +10 -2
  310. package/blocks/objects/components/object-schema-editor.tsx +4 -2
  311. package/blocks/objects/components/object-table.tsx +13 -3
  312. package/blocks/objects/components/objects-screen.tsx +147 -0
  313. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  314. package/blocks/objects/lib/object-types.ts +1 -1
  315. package/blocks/pages/README.md +14 -0
  316. package/blocks/pages/block.json +140 -23
  317. package/blocks/pages/components/brand-kit-editor.tsx +5 -2
  318. package/blocks/pages/components/content-library.tsx +9 -4
  319. package/blocks/pages/components/more-rows.tsx +30 -0
  320. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  321. package/blocks/pages/components/page-analytics.tsx +176 -0
  322. package/blocks/pages/components/page-clone.tsx +99 -0
  323. package/blocks/pages/components/page-domains.tsx +167 -0
  324. package/blocks/pages/components/page-editor.tsx +1 -1
  325. package/blocks/pages/components/page-generation-status.tsx +48 -0
  326. package/blocks/pages/components/page-list.tsx +2 -1
  327. package/blocks/pages/components/page-tracking.tsx +137 -0
  328. package/blocks/pages/components/pages-screen.tsx +57 -16
  329. package/blocks/pages/components/seo-report.tsx +6 -1
  330. package/blocks/pages/lib/studio.ts +7 -0
  331. package/blocks/phone-numbers/README.md +74 -0
  332. package/blocks/phone-numbers/block.json +262 -0
  333. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  334. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  335. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  336. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  337. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  338. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  339. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  340. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  341. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  342. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  343. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  344. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  345. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  346. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  347. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  348. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  349. package/blocks/pickers/README.md +23 -0
  350. package/blocks/pickers/block.json +103 -0
  351. package/blocks/pickers/components/company-picker.tsx +90 -0
  352. package/blocks/pickers/components/contact-picker.tsx +131 -0
  353. package/blocks/pickers/components/member-picker.tsx +99 -0
  354. package/blocks/pickers/components/search-picker.tsx +98 -0
  355. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  356. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  357. package/blocks/pickers/lib/use-debounced.ts +13 -0
  358. package/blocks/quotes/README.md +13 -0
  359. package/blocks/quotes/block.json +65 -13
  360. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  361. package/blocks/quotes/components/product-picker.tsx +3 -1
  362. package/blocks/quotes/components/quote-builder.tsx +1 -1
  363. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  364. package/blocks/quotes/components/quote-list.tsx +5 -3
  365. package/blocks/quotes/components/quote-related.tsx +8 -4
  366. package/blocks/quotes/components/quote-viewer.tsx +7 -2
  367. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  368. package/blocks/radar/README.md +10 -0
  369. package/blocks/radar/block.json +41 -1
  370. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  371. package/blocks/radar/components/competitor-detail.tsx +117 -0
  372. package/blocks/radar/components/competitor-tracker.tsx +3 -1
  373. package/blocks/radar/components/radar-overview.tsx +4 -4
  374. package/blocks/radar/components/radar-report.tsx +4 -2
  375. package/blocks/radar/components/radar-screen.tsx +214 -0
  376. package/blocks/radar/components/report-detail.tsx +104 -0
  377. package/blocks/search/README.md +10 -0
  378. package/blocks/search/block.json +16 -1
  379. package/blocks/search/components/filter-builder.tsx +3 -3
  380. package/blocks/search/components/prospect-results.tsx +5 -3
  381. package/blocks/search/components/prospect-search-screen.tsx +235 -0
  382. package/blocks/search/components/saved-searches.tsx +4 -1
  383. package/blocks/search/hooks/use-filter-options.ts +20 -6
  384. package/blocks/search/lib/search-types.ts +2 -0
  385. package/blocks/sequencer/README.md +55 -9
  386. package/blocks/sequencer/block.json +126 -19
  387. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  388. package/blocks/sequencer/components/enroll-dialog.tsx +11 -5
  389. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  390. package/blocks/sequencer/components/sequence-builder.tsx +224 -60
  391. package/blocks/sequencer/components/sequence-detail.tsx +261 -0
  392. package/blocks/sequencer/components/sequence-enrollments.tsx +116 -0
  393. package/blocks/sequencer/components/sequence-list.tsx +34 -9
  394. package/blocks/sequencer/components/sequence-stats.tsx +11 -10
  395. package/blocks/sequencer/components/sequence-steps-panel.tsx +103 -0
  396. package/blocks/sequencer/components/sequences-screen.tsx +162 -0
  397. package/blocks/sequencer/components/step-editor.tsx +14 -12
  398. package/blocks/sequencer/lib/sequencer-utils.ts +133 -10
  399. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +33 -5
  400. package/blocks/sequencer/recipes/launch-and-enroll.ts +19 -2
  401. package/blocks/social-ads/README.md +19 -0
  402. package/blocks/social-ads/block.json +233 -1
  403. package/blocks/social-ads/components/ad-campaign-list.tsx +9 -5
  404. package/blocks/social-ads/components/ads-screen.tsx +152 -0
  405. package/blocks/social-ads/components/audience-sync-detail.tsx +194 -0
  406. package/blocks/social-ads/components/audience-sync.tsx +17 -9
  407. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  408. package/blocks/social-ads/components/campaign-detail.tsx +138 -0
  409. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  410. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  411. package/blocks/social-ads/components/creative-detail.tsx +155 -0
  412. package/blocks/social-ads/components/creative-gallery.tsx +35 -7
  413. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  414. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  415. package/blocks/social-ads/components/post-composer.tsx +7 -3
  416. package/blocks/social-ads/components/post-detail.tsx +163 -0
  417. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  418. package/blocks/social-ads/components/social-performance.tsx +80 -0
  419. package/blocks/social-ads/components/social-post-list.tsx +11 -3
  420. package/blocks/social-ads/components/social-screen.tsx +187 -0
  421. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  422. package/blocks/surveys/README.md +91 -0
  423. package/blocks/surveys/block.json +299 -0
  424. package/blocks/surveys/components/account-nps.tsx +69 -0
  425. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  426. package/blocks/surveys/components/org-summary.tsx +82 -0
  427. package/blocks/surveys/components/sending-domains.tsx +113 -0
  428. package/blocks/surveys/components/survey-form.tsx +126 -0
  429. package/blocks/surveys/components/survey-responses.tsx +145 -0
  430. package/blocks/surveys/components/survey-results.tsx +72 -0
  431. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  432. package/blocks/surveys/components/survey-send.tsx +162 -0
  433. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  434. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  435. package/blocks/surveys/lib/surveys-types.ts +228 -0
  436. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  437. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  438. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  439. package/blocks/visitors/README.md +10 -0
  440. package/blocks/visitors/block.json +25 -1
  441. package/blocks/visitors/components/snippet-install.tsx +21 -10
  442. package/blocks/visitors/components/traffic-chart.tsx +4 -4
  443. package/blocks/visitors/components/visitor-company-card.tsx +26 -16
  444. package/blocks/visitors/components/visitor-detail.tsx +105 -0
  445. package/blocks/visitors/components/visitor-feed.tsx +16 -7
  446. package/blocks/visitors/components/visitors-screen.tsx +166 -0
  447. package/blocks/webchat/README.md +10 -0
  448. package/blocks/webchat/block.json +21 -7
  449. package/blocks/webchat/components/chat-widget-config.tsx +8 -5
  450. package/blocks/webchat/components/conversation-list.tsx +8 -4
  451. package/blocks/webchat/components/webchat-screen.tsx +245 -0
  452. package/blocks/workbench/README.md +11 -0
  453. package/blocks/workbench/block.json +48 -1
  454. package/blocks/workbench/components/column-recipe-editor.tsx +5 -3
  455. package/blocks/workbench/components/run-progress.tsx +1 -1
  456. package/blocks/workbench/components/workbench-grid.tsx +10 -4
  457. package/blocks/workbench/components/workbench-row-detail.tsx +104 -0
  458. package/blocks/workbench/components/workbench-screen.tsx +202 -0
  459. package/blocks/workbench/components/workbench-sources.tsx +194 -0
  460. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  461. package/blocks/workflows/README.md +13 -0
  462. package/blocks/workflows/block.json +28 -9
  463. package/blocks/workflows/components/run-history.tsx +31 -9
  464. package/blocks/workflows/components/trigger-config.tsx +3 -1
  465. package/blocks/workflows/components/workflow-editor.tsx +4 -2
  466. package/blocks/workflows/components/workflow-list.tsx +13 -6
  467. package/blocks/workflows/components/workflows-screen.tsx +193 -0
  468. package/dist/cli.js +3109 -1977
  469. package/dist/errors.d.mts +34 -0
  470. package/dist/errors.d.ts +34 -0
  471. package/dist/errors.js +46 -0
  472. package/dist/errors.js.map +1 -0
  473. package/dist/errors.mjs +21 -0
  474. package/dist/errors.mjs.map +1 -0
  475. package/dist/index.browser.js +11 -5
  476. package/dist/index.browser.js.map +1 -1
  477. package/dist/index.browser.mjs +11 -5
  478. package/dist/index.browser.mjs.map +1 -1
  479. package/dist/index.d.mts +164 -25
  480. package/dist/index.d.ts +164 -25
  481. package/dist/index.js +11 -5
  482. package/dist/index.js.map +1 -1
  483. package/dist/index.mjs +11 -5
  484. package/dist/index.mjs.map +1 -1
  485. package/dist/react.browser.js +10 -4
  486. package/dist/react.browser.js.map +1 -1
  487. package/dist/react.browser.mjs +10 -4
  488. package/dist/react.browser.mjs.map +1 -1
  489. package/dist/react.d.mts +142 -5
  490. package/dist/react.d.ts +142 -5
  491. package/dist/react.js +10 -4
  492. package/dist/react.js.map +1 -1
  493. package/dist/react.mjs +10 -4
  494. package/dist/react.mjs.map +1 -1
  495. package/dist/types/blocks/add.d.ts +11 -4
  496. package/dist/types/blocks/brand.d.ts +122 -0
  497. package/dist/types/blocks/compose.d.ts +53 -0
  498. package/dist/types/blocks/create.d.ts +27 -20
  499. package/dist/types/blocks/doctor.d.ts +6 -1
  500. package/dist/types/blocks/flows.d.ts +7 -1
  501. package/dist/types/blocks/navfile.d.ts +35 -0
  502. package/dist/types/blocks/orgBrand.d.ts +34 -0
  503. package/dist/types/blocks/paths.d.ts +4 -0
  504. package/dist/types/blocks/prefixes.d.ts +37 -0
  505. package/dist/types/blocks/registry.d.ts +17 -0
  506. package/dist/types/blocks/screens.d.ts +53 -0
  507. package/dist/types/cli.d.ts +5 -0
  508. package/dist/types/errors.d.ts +33 -0
  509. package/dist/types/http.d.ts +2 -18
  510. package/dist/types/version.d.ts +2 -2
  511. package/docs/agents.md +15 -4
  512. package/docs/api/README.md +2 -2
  513. package/docs/api/by-path.md +1 -0
  514. package/docs/api/desk.d.ts +98 -2
  515. package/docs/api/desk.md +123 -1
  516. package/docs/api/inbox.d.ts +10 -2
  517. package/docs/api/inbox.md +10 -2
  518. package/docs/api/seo.d.ts +19 -1
  519. package/docs/api/seo.md +24 -1
  520. package/package.json +16 -2
  521. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  522. package/stacks/6sense/pages/contacts.tsx.tmpl +19 -0
  523. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  524. package/stacks/6sense.json +55 -17
  525. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  526. package/stacks/apollo/pages/companies.tsx.tmpl +33 -0
  527. package/stacks/apollo/pages/contacts.tsx.tmpl +51 -0
  528. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  529. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  530. package/stacks/apollo/pages/lists.tsx.tmpl +12 -0
  531. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  532. package/stacks/apollo/pages/sequences.tsx.tmpl +12 -0
  533. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  534. package/stacks/apollo.json +147 -37
  535. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  536. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  537. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  538. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  539. package/stacks/calendly.json +64 -0
  540. package/stacks/clay/pages/companies.tsx.tmpl +31 -0
  541. package/stacks/clay/pages/contacts.tsx.tmpl +31 -0
  542. package/stacks/clay/pages/lists.tsx.tmpl +12 -0
  543. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  544. package/stacks/clay.json +73 -27
  545. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  546. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  547. package/stacks/gong/pages/contacts.tsx.tmpl +32 -0
  548. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  549. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  550. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  551. package/stacks/gong.json +98 -35
  552. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  553. package/stacks/hootsuite/pages/contacts.tsx.tmpl +28 -0
  554. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  555. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  556. package/stacks/hootsuite.json +104 -0
  557. package/stacks/hubspot/pages/companies.tsx.tmpl +26 -0
  558. package/stacks/hubspot/pages/contacts.tsx.tmpl +45 -0
  559. package/stacks/hubspot/pages/deals.tsx.tmpl +20 -0
  560. package/stacks/hubspot/pages/inbox.tsx.tmpl +103 -0
  561. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  562. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  563. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  564. package/stacks/hubspot/pages/quotes.tsx.tmpl +11 -0
  565. package/stacks/hubspot/pages/sequences.tsx.tmpl +12 -0
  566. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  567. package/stacks/hubspot.json +181 -64
  568. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  569. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  570. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  571. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  572. package/stacks/intercom.json +52 -20
  573. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  574. package/stacks/mailchimp/pages/contacts.tsx.tmpl +28 -0
  575. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  576. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  577. package/stacks/mailchimp.json +68 -34
  578. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  579. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  580. package/stacks/outreach/pages/contacts.tsx.tmpl +46 -0
  581. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  582. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  583. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  584. package/stacks/outreach/pages/sequences.tsx.tmpl +12 -0
  585. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  586. package/stacks/outreach.json +106 -25
  587. package/stacks/pipedrive/pages/companies.tsx.tmpl +23 -0
  588. package/stacks/pipedrive/pages/contacts.tsx.tmpl +27 -0
  589. package/stacks/pipedrive/pages/deals.tsx.tmpl +20 -0
  590. package/stacks/pipedrive/pages/inbox.tsx.tmpl +103 -0
  591. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  592. package/stacks/pipedrive/pages/quotes.tsx.tmpl +11 -0
  593. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  594. package/stacks/pipedrive.json +117 -0
  595. package/stacks/salesforce/pages/companies.tsx.tmpl +26 -0
  596. package/stacks/salesforce/pages/contacts.tsx.tmpl +32 -0
  597. package/stacks/salesforce/pages/deals.tsx.tmpl +20 -0
  598. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  599. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  600. package/stacks/salesforce/pages/quotes.tsx.tmpl +11 -0
  601. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  602. package/stacks/salesforce.json +85 -31
  603. package/stacks/zoominfo/pages/companies.tsx.tmpl +31 -0
  604. package/stacks/zoominfo/pages/contacts.tsx.tmpl +31 -0
  605. package/stacks/zoominfo/pages/lists.tsx.tmpl +12 -0
  606. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  607. package/stacks/zoominfo.json +115 -0
  608. package/styles/styles.css +1 -1
  609. package/templates/next-app/README.md.tmpl +40 -9
  610. package/templates/next-app/app/globals.css.tmpl +349 -64
  611. package/templates/next-app/app/layout.tsx.tmpl +7 -2
  612. package/templates/next-app/components/app-shell.tsx.tmpl +104 -88
  613. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
  614. package/templates/next-app/dot-gitignore.tmpl +2 -0
  615. package/templates/next-app/package.json.tmpl +1 -1
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ /** NameDialog: a small dialog that asks for one name (the copy of an agent, a template). Used by the duplicate and save-as-template buttons. SDK operations: none. */
4
+ import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label } from "@graph8/sdk/ui";
5
+ import { ApiError } from "@/g8/components/api-error";
6
+
7
+ export interface NameDialogProps {
8
+ open: boolean;
9
+ title: string;
10
+ description: string;
11
+ label: string;
12
+ value: string;
13
+ onChange: (value: string) => void;
14
+ confirm: string;
15
+ busy: boolean;
16
+ error: unknown;
17
+ onCancel: () => void;
18
+ onConfirm: () => void;
19
+ }
20
+
21
+ export function NameDialog({ open, title, description, label, value, onChange, confirm, busy, error, onCancel, onConfirm }: NameDialogProps) {
22
+ return (
23
+ <Dialog open={open} onOpenChange={(o) => !o && !busy && onCancel()}>
24
+ <DialogContent>
25
+ <DialogHeader><DialogTitle>{title}</DialogTitle><DialogDescription>{description}</DialogDescription></DialogHeader>
26
+ <div className="flex flex-col gap-1"><Label htmlFor="agent-action-name">{label}</Label><Input id="agent-action-name" value={value} onChange={(e) => onChange(e.target.value)} /></div>
27
+ {error ? <ApiError error={error} title="That did not work" /> : null}
28
+ <DialogFooter><Button variant="ghost" disabled={busy} onClick={onCancel}>Cancel</Button><Button loading={busy} disabled={!value.trim()} onClick={onConfirm}>{confirm}</Button></DialogFooter>
29
+ </DialogContent>
30
+ </Dialog>
31
+ );
32
+ }
@@ -0,0 +1,46 @@
1
+ "use client";
2
+
3
+ /**
4
+ * ReceptionistButton: set up the workspace receptionist (an agent that answers inbound calls) and switch it on or off. There is no call that
5
+ * reads whether it is on, so Turn on and Turn off are explicit and safe to repeat. Turning it on lets it answer real callers, so on and off
6
+ * go through ConfirmSpendDialog (external).
7
+ *
8
+ * SDK operations, in order:
9
+ * 1. ensureReceptionistAgent POST /voice/agents/receptionist (write) creates it when missing
10
+ * 2. toggleReceptionistAgent PATCH /voice/agents/receptionist?enabled (write, confirmed as external)
11
+ */
12
+ import { useState } from "react";
13
+ import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@graph8/sdk/ui";
14
+ import { ApiError } from "@/g8/components/api-error";
15
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
16
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
17
+ import { g8Patch, g8Post } from "@/g8/lib/g8-client";
18
+
19
+ export function ReceptionistButton() {
20
+ const [open, setOpen] = useState(false);
21
+ const [pending, setPending] = useState<boolean | null>(null);
22
+ const [result, setResult] = useState<string | null>(null);
23
+ const ensure = useG8Mutation(() => g8Post("/voice/agents/receptionist", {}), { onSuccess: () => setResult("The receptionist is set up. Turn it on when it is ready to take calls.") });
24
+ const toggle = useG8Mutation((enabled: boolean) => g8Patch("/voice/agents/receptionist", {}, { query: { enabled } }), { onSuccess: (_r, on) => { setPending(null); setResult(on ? "The receptionist is on." : "The receptionist is off.") }, onError: () => setPending(null) });
25
+ return (
26
+ <>
27
+ <Button variant="outline" onClick={() => { setResult(null); ensure.reset(); toggle.reset(); setOpen(true); }}>Receptionist</Button>
28
+ <Dialog open={open} onOpenChange={setOpen}>
29
+ <DialogContent>
30
+ <DialogHeader><DialogTitle>Receptionist</DialogTitle><DialogDescription>An agent that answers your inbound calls and routes them. Set it up once, then switch it on or off.</DialogDescription></DialogHeader>
31
+ {result ? <p role="status" className="text-sm">{result}</p> : null}
32
+ {ensure.error ? <ApiError error={ensure.error} title="Could not set up the receptionist" /> : null}
33
+ {toggle.error ? <ApiError error={toggle.error} title="Could not change the receptionist" /> : null}
34
+ <DialogFooter className="gap-2">
35
+ <Button variant="outline" loading={ensure.loading} onClick={() => void ensure.mutate(undefined)}>Set up</Button>
36
+ <Button variant="outline" onClick={() => setPending(false)}>Turn off</Button>
37
+ <Button onClick={() => setPending(true)}>Turn on</Button>
38
+ </DialogFooter>
39
+ </DialogContent>
40
+ </Dialog>
41
+ <ConfirmSpendDialog open={pending != null} tier="external" title={pending ? "Turn the receptionist on" : "Turn the receptionist off"}
42
+ description={pending ? "The receptionist starts answering real callers on your numbers." : "Inbound calls stop going to the receptionist."}
43
+ confirmLabel={pending ? "Turn on" : "Turn off"} confirming={toggle.loading} onConfirm={() => pending != null && void toggle.mutate(pending)} onCancel={() => setPending(null)} />
44
+ </>
45
+ );
46
+ }
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ /**
4
+ * SaveAsTemplateButton: keep an agent as a reusable template of this workspace; anyone can start a new agent from it (FromTemplateButton).
5
+ *
6
+ * SDK operations:
7
+ * saveAgentAsTemplate POST /voice/agents/{agent_id}/save-as-template?template_name (write)
8
+ */
9
+ import { useState } from "react";
10
+ import { Button } from "@graph8/sdk/ui";
11
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
12
+ import { g8Post } from "@/g8/lib/g8-client";
13
+ import type { AgentSummary } from "../lib/agent-types";
14
+ import { NameDialog } from "./name-dialog";
15
+
16
+ export interface SaveAsTemplateButtonProps {
17
+ agent: Pick<AgentSummary, "agent_id" | "agent_name">;
18
+ variant?: "outline" | "ghost";
19
+ }
20
+
21
+ export function SaveAsTemplateButton({ agent, variant = "outline" }: SaveAsTemplateButtonProps) {
22
+ const [open, setOpen] = useState(false);
23
+ const [name, setName] = useState("");
24
+ const save = useG8Mutation((n: string) => g8Post(`/voice/agents/${encodeURIComponent(agent.agent_id)}/save-as-template`, {}, { query: { template_name: n } }), { onSuccess: () => setOpen(false) });
25
+ return (
26
+ <>
27
+ <Button size="sm" variant={variant} onClick={() => { save.reset(); setName(agent.agent_name); setOpen(true); }}>Save as template</Button>
28
+ <NameDialog open={open} title={`Save ${agent.agent_name} as a template`} description="Anyone in the workspace can start a new agent from it." label="Template name"
29
+ value={name} onChange={setName} confirm="Save template" busy={save.loading} error={save.error} onCancel={() => setOpen(false)} onConfirm={() => void save.mutate(name.trim())} />
30
+ </>
31
+ );
32
+ }
@@ -0,0 +1,147 @@
1
+ "use client";
2
+
3
+ /**
4
+ * SkillEditor: create or edit one skill (a single-purpose action an agent can run) in a side sheet.
5
+ * Edit: the skill is loaded first and the form stays disabled until it has loaded (a blank form never overwrites the real skill);
6
+ * Save sends only the fields you changed (a runtime config goes whole, because voice validates it as a unit).
7
+ * Also: enable or disable, see the {placeholders} the skill asks for, check a template, delete.
8
+ * Create: an LLM skill (model + prompt) or an API skill (endpoint, method, headers, body).
9
+ * Deleting a skill removes it from every agent that uses it, so it is destructive and confirmed.
10
+ *
11
+ * SDK operations, in order:
12
+ * 1. getSkill GET /skills/{action_id} (read)
13
+ * 2. extractVariables GET /skills/{action_id}/variables (read)
14
+ * 3. listModels GET /skills/models (read) LLM model choices
15
+ * 4. validateTemplate POST /skills/validate { template | runtime_type + config } (write, free: warns about double braces)
16
+ * 5. createSkill POST /skills { name, runtime_type, llm_config | api_config } (write)
17
+ * 6. updateSkill PUT /skills/{action_id} { changed fields } (write)
18
+ * 7. deleteSkill DELETE /skills/{action_id} (destructive, ConfirmSpendDialog)
19
+ */
20
+ import { useEffect, useRef, useState } from "react";
21
+ import { Button, Input, Label, Skeleton, Switch, Textarea } from "@graph8/sdk/ui";
22
+ import { ApiError } from "@/g8/components/api-error";
23
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
24
+ import { FormSheet } from "@/g8/components/screen/form-sheet";
25
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
26
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
27
+ import { g8Delete, g8Get, g8Post, g8Put } from "@/g8/lib/g8-client";
28
+ import { bodyOf, listOf } from "../lib/agent-types";
29
+ import { BLANK_DRAFT, HTTP_METHODS, draftOf, newSkillBody, parseHeaders, skillOf, skillPatch } from "../lib/skill-utils";
30
+ import type { SkillDraft } from "../lib/skill-utils";
31
+
32
+ export type SkillEditorTarget = { kind: "edit"; id: string } | { kind: "new"; runtime: "llm" | "api" };
33
+
34
+ export interface SkillEditorProps {
35
+ /** The skill to edit or the kind to create; null closes the sheet. */
36
+ target: SkillEditorTarget | null;
37
+ onClose: () => void;
38
+ /** After a create, save or delete. */
39
+ onChanged?: (event: "created" | "updated" | "deleted", id?: string) => void;
40
+ }
41
+
42
+ export function SkillEditor({ target, onClose, onChanged }: SkillEditorProps) {
43
+ const editId = target?.kind === "edit" ? target.id : "";
44
+ const runtime = target?.kind === "new" ? target.runtime : undefined;
45
+ const [d, setD] = useState<SkillDraft>(BLANK_DRAFT);
46
+ const [deleting, setDeleting] = useState(false);
47
+ const seeded = useRef<string>("");
48
+ const loaded = useG8Query((signal) => g8Get(`/skills/${encodeURIComponent(editId)}`, undefined, { signal }), [editId], { enabled: Boolean(editId) });
49
+ // useG8Query keeps the previous skill's data while the next loads: only the skill fetched for THIS id counts as loaded.
50
+ const got = editId ? skillOf(loaded.data) : undefined;
51
+ const skill = got && got.action_id === editId ? got : undefined;
52
+ const vars = useG8Query((signal) => g8Get(`/skills/${encodeURIComponent(editId)}/variables`, undefined, { signal }), [editId], { enabled: Boolean(editId) && Boolean(skill) });
53
+ const kind = skill?.runtime_type ?? runtime;
54
+ const models = useG8Query((signal) => g8Get("/skills/models", undefined, { signal }), [], { enabled: kind === "llm" });
55
+ const set = (patch: Partial<SkillDraft>) => setD((x) => ({ ...x, ...patch }));
56
+
57
+ // Seed the form from the LOADED skill, once per skill (a refetch must not overwrite what the person is typing).
58
+ useEffect(() => {
59
+ if (target?.kind === "new") { seeded.current = "new"; setD(BLANK_DRAFT); setDeleting(false); return; }
60
+ if (skill && seeded.current !== skill.action_id) { seeded.current = skill.action_id; setD(draftOf(skill)); }
61
+ }, [target, skill]);
62
+ useEffect(() => { if (!target) { seeded.current = ""; } }, [target]);
63
+
64
+ const patch = skill ? skillPatch(skill, d) : {};
65
+ const headersOk = d.headers.trim() === "" || parseHeaders(d.headers) != null;
66
+ const numbersOk = kind !== "llm" || (Number.isFinite(Number(d.temperature)) && Number(d.max_tokens) > 0);
67
+ const filled = d.name.trim() && (kind === "llm" ? d.model && d.prompt_template.trim() : d.endpoint.trim());
68
+ const valid = Boolean(filled) && headersOk && numbersOk;
69
+
70
+ const create = useG8Mutation(async () => {
71
+ const res = await g8Post("/skills", newSkillBody(kind as "llm" | "api", d));
72
+ return skillOf(res)?.action_id;
73
+ }, { onSuccess: (id) => { onChanged?.("created", id); onClose(); } });
74
+ const save = useG8Mutation(() => g8Put(`/skills/${encodeURIComponent(editId)}`, patch), { onSuccess: () => { seeded.current = ""; void loaded.refetch(); void vars.refetch(); onChanged?.("updated", editId); } });
75
+ const remove = useG8Mutation(() => g8Delete(`/skills/${encodeURIComponent(editId)}`), { onSuccess: () => { setDeleting(false); onChanged?.("deleted", editId); onClose(); }, onError: () => setDeleting(false) });
76
+ const check = useG8Mutation(() => g8Post("/skills/validate", kind === "llm" ? { template: d.prompt_template } : { runtime_type: "api", api_config: { endpoint: d.endpoint, body_template: d.body_template, headers: parseHeaders(d.headers) ?? {} } }));
77
+
78
+ const modelRows = listOf<{ id: string; label?: string }>(models.data, "models");
79
+ const checked = bodyOf<{ variables?: string[]; warnings?: string[] }>(check.data);
80
+ const names = (bodyOf<{ variables?: string[] }>(vars.data).variables ?? []) as string[];
81
+ const waiting = Boolean(editId) && !skill;
82
+ const title = target?.kind === "new" ? (runtime === "api" ? "New API skill" : "New LLM skill") : skill?.name ?? "Skill";
83
+
84
+ return (
85
+ <>
86
+ <FormSheet open={target != null} onOpenChange={(o) => !o && onClose()} title={title} width="lg"
87
+ description={target?.kind === "new" ? "Use {variable} placeholders for values the agent fills in when it runs the skill." : "Changes apply to every agent that uses this skill."}
88
+ footer={<>
89
+ {editId ? <Button variant="ghost" className="mr-auto" disabled={!skill} onClick={() => setDeleting(true)}>Delete</Button> : null}
90
+ <Button variant="ghost" onClick={onClose}>Cancel</Button>
91
+ {target?.kind === "new" ? <Button loading={create.loading} disabled={!valid} onClick={() => void create.mutate(undefined)}>Create skill</Button>
92
+ : <Button loading={save.loading} disabled={!skill || Object.keys(patch).length === 0 || !valid} onClick={() => void save.mutate(undefined)}>Save changes</Button>}
93
+ </>}>
94
+ {loaded.error ? <ApiError error={loaded.error} title="Could not load the skill" onRetry={() => void loaded.refetch()} /> : null}
95
+ {waiting && !loaded.error ? <Skeleton className="h-40 w-full" aria-label="Loading skill" /> : null}
96
+ {!waiting ? (
97
+ <fieldset className="flex flex-col gap-3 border-0 p-0" disabled={save.loading || create.loading}>
98
+ <div className="flex flex-col gap-1"><Label htmlFor="sk-name">Name</Label><Input id="sk-name" value={d.name} onChange={(e) => set({ name: e.target.value })} /></div>
99
+ <div className="flex flex-col gap-1"><Label htmlFor="sk-desc">What it does</Label><Input id="sk-desc" value={d.description} onChange={(e) => set({ description: e.target.value })} /></div>
100
+ <div className="flex flex-col gap-1"><Label htmlFor="sk-cat">Category</Label><Input id="sk-cat" value={d.category} onChange={(e) => set({ category: e.target.value })} /></div>
101
+ {editId ? <label className="flex items-center gap-2 text-sm"><Switch checked={d.enabled} onCheckedChange={(v) => set({ enabled: v })} aria-label="Enabled" />Enabled</label> : null}
102
+ {kind === "llm" ? (
103
+ <>
104
+ <div className="flex flex-col gap-1">
105
+ <Label htmlFor="sk-model">Model</Label>
106
+ <select id="sk-model" className="h-9 rounded-md border bg-background px-2 text-sm" value={d.model} onChange={(e) => set({ model: e.target.value })}>
107
+ <option value="">Choose a model</option>
108
+ {d.model && !modelRows.some((m) => m.id === d.model) ? <option value={d.model}>{d.model}</option> : null}
109
+ {modelRows.map((m) => <option key={m.id} value={m.id}>{m.label ?? m.id}</option>)}
110
+ </select>
111
+ </div>
112
+ <div className="flex flex-col gap-1"><Label htmlFor="sk-prompt">Prompt</Label><Textarea id="sk-prompt" rows={8} value={d.prompt_template} onChange={(e) => set({ prompt_template: e.target.value })} /></div>
113
+ <div className="grid grid-cols-2 gap-3">
114
+ <div className="flex flex-col gap-1"><Label htmlFor="sk-temp">Temperature (0 to 2)</Label><Input id="sk-temp" inputMode="decimal" value={d.temperature} onChange={(e) => set({ temperature: e.target.value })} /></div>
115
+ <div className="flex flex-col gap-1"><Label htmlFor="sk-max">Max tokens</Label><Input id="sk-max" inputMode="numeric" value={d.max_tokens} onChange={(e) => set({ max_tokens: e.target.value })} /></div>
116
+ </div>
117
+ </>
118
+ ) : (
119
+ <>
120
+ <div className="grid grid-cols-[7rem_1fr] gap-3">
121
+ <div className="flex flex-col gap-1"><Label htmlFor="sk-method">Method</Label>
122
+ <select id="sk-method" className="h-9 rounded-md border bg-background px-2 text-sm" value={d.method} onChange={(e) => set({ method: e.target.value })}>{HTTP_METHODS.map((m) => <option key={m} value={m}>{m}</option>)}</select></div>
123
+ <div className="flex flex-col gap-1"><Label htmlFor="sk-endpoint">Endpoint</Label><Input id="sk-endpoint" placeholder="https://api.example.com/items/{item_id}" value={d.endpoint} onChange={(e) => set({ endpoint: e.target.value })} /></div>
124
+ </div>
125
+ <div className="flex flex-col gap-1"><Label htmlFor="sk-headers">Headers (JSON)</Label><Textarea id="sk-headers" rows={3} className="font-mono text-xs" value={d.headers} onChange={(e) => set({ headers: e.target.value })} /></div>
126
+ {!headersOk ? <p role="alert" className="text-sm text-error-foreground">Headers must be a JSON object of text values.</p> : null}
127
+ <div className="flex flex-col gap-1"><Label htmlFor="sk-body">Body template</Label><Textarea id="sk-body" rows={4} className="font-mono text-xs" value={d.body_template} onChange={(e) => set({ body_template: e.target.value })} /></div>
128
+ <label className="flex items-center gap-2 text-sm"><Switch checked={d.requires_auth} onCheckedChange={(v) => set({ requires_auth: v })} aria-label="Requires auth" />Requires authentication</label>
129
+ </>
130
+ )}
131
+ {names.length ? <p className="text-xs text-muted-foreground">Asks for: {names.map((n) => `{${n}}`).join(", ")}</p> : null}
132
+ <div className="flex items-center gap-2">
133
+ <Button size="sm" variant="outline" loading={check.loading} disabled={kind === "llm" ? !d.prompt_template.trim() : !d.endpoint.trim()} onClick={() => void check.mutate(undefined)}>Check placeholders</Button>
134
+ {check.data !== undefined ? <span role="status" className="text-xs text-muted-foreground">{(checked.variables ?? []).length} placeholders{(checked.warnings ?? []).length ? `, ${(checked.warnings ?? []).join(" ")}` : ", no warnings"}</span> : null}
135
+ </div>
136
+ {check.error ? <ApiError error={check.error} title="Could not check the template" /> : null}
137
+ {create.error ? <ApiError error={create.error} title="Could not create the skill" /> : null}
138
+ {save.error ? <ApiError error={save.error} title="Could not save the skill" /> : null}
139
+ {remove.error ? <ApiError error={remove.error} title="Could not delete the skill" /> : null}
140
+ </fieldset>
141
+ ) : null}
142
+ </FormSheet>
143
+ <ConfirmSpendDialog open={deleting} tier="destructive" title={`Delete "${skill?.name ?? "this skill"}"`} description="Every agent that uses it loses it, and workflows that call it stop working." affected={1}
144
+ confirmLabel="Delete" confirming={remove.loading} onConfirm={() => void remove.mutate(undefined)} onCancel={() => setDeleting(false)} />
145
+ </>
146
+ );
147
+ }
@@ -0,0 +1,111 @@
1
+ "use client";
2
+
3
+ /**
4
+ * SkillLibrary: every skill in the workspace (your own plus graph8's defaults) in one list. Search by name, filter by kind
5
+ * and by enabled, open one to edit it (SkillEditor), write a new LLM or API skill, or start from a template. A skill that no
6
+ * agent uses yet can be attached from an agent's Skills tab.
7
+ *
8
+ * SDK operations, in order:
9
+ * 1. listSkills GET /skills?runtime_type&enabled&include_system (read) returns every skill, no cap
10
+ * 2. listTemplates GET /skills/templates (read)
11
+ * 3. createFromTemplate POST /skills/from-template { template_id, name } (write)
12
+ * 4. SkillEditor see skill-editor.tsx (getSkill, updateSkill, deleteSkill, createSkill, validateTemplate, extractVariables, listModels)
13
+ */
14
+ import { useMemo, useRef, useState } from "react";
15
+ import { Wand2 } from "lucide-react";
16
+ import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Skeleton } from "@graph8/sdk/ui";
17
+ import { ApiError } from "@/g8/components/api-error";
18
+ import { EmptyState } from "@/g8/components/empty-state";
19
+ import { StatusPill } from "@/g8/components/screen/status-pill";
20
+ import { Toolbar } from "@/g8/components/screen/toolbar";
21
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
22
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
23
+ import { g8Get, g8Post } from "@/g8/lib/g8-client";
24
+ import { listOf } from "../lib/agent-types";
25
+ import type { SkillDetail } from "../lib/skill-utils";
26
+ import { skillOf } from "../lib/skill-utils";
27
+ import { SkillEditor } from "./skill-editor";
28
+ import type { SkillEditorTarget } from "./skill-editor";
29
+
30
+ export interface SkillLibraryProps {
31
+ className?: string;
32
+ }
33
+
34
+ interface Template { template_id?: string; action_id?: string; id?: string; name: string; description?: string | null; runtime_type?: string }
35
+ const tid = (t: Template): string => t.template_id ?? t.action_id ?? t.id ?? "";
36
+ const KINDS = [{ id: "", label: "All" }, { id: "llm", label: "LLM" }, { id: "api", label: "API" }];
37
+ const STATES = [{ id: "", label: "Any state" }, { id: "true", label: "Enabled" }, { id: "false", label: "Disabled" }];
38
+
39
+ function FromTemplate({ open, onClose, onCreated }: { open: boolean; onClose: () => void; onCreated: (id?: string) => void }) {
40
+ const [picked, setPicked] = useState<Template | null>(null);
41
+ const [name, setName] = useState("");
42
+ const made = useRef<{ id?: string } | null>(null);
43
+ const templates = useG8Query((signal) => g8Get("/skills/templates", undefined, { signal }), [], { enabled: open });
44
+ const create = useG8Mutation(async (v: { id: string; name: string }) => {
45
+ if (made.current) return made.current.id;
46
+ const id = skillOf(await g8Post("/skills/from-template", { template_id: v.id, name: v.name }))?.action_id;
47
+ made.current = { id };
48
+ return id;
49
+ }, { onSuccess: (id) => { made.current = null; setPicked(null); onCreated(id); } });
50
+ const rows = listOf<Template>(templates.data, "actions", "templates", "items");
51
+ return (
52
+ <Dialog open={open} onOpenChange={(o) => !o && !create.loading && onClose()}>
53
+ <DialogContent>
54
+ <DialogHeader><DialogTitle>New skill from a template</DialogTitle><DialogDescription>Templates are ready-made skills. Name yours and edit it afterwards.</DialogDescription></DialogHeader>
55
+ {templates.error ? <ApiError error={templates.error} title="Could not load templates" onRetry={() => void templates.refetch()} /> : null}
56
+ {templates.loading && !templates.data ? <Skeleton className="h-16 w-full" /> : null}
57
+ <ul className="max-h-64 divide-y overflow-y-auto rounded-md border">
58
+ {rows.map((t) => (
59
+ <li key={tid(t)}><button type="button" aria-pressed={picked != null && tid(picked) === tid(t)} className={`block w-full px-3 py-2 text-left text-sm ${picked != null && tid(picked) === tid(t) ? "bg-accent" : ""}`} onClick={() => { made.current = null; setPicked(t); setName(t.name); }}>
60
+ <span className="font-medium">{t.name}</span>{t.runtime_type ? <span className="ml-2 text-xs text-muted-foreground">{t.runtime_type}</span> : null}
61
+ {t.description ? <span className="block text-xs text-muted-foreground">{t.description}</span> : null}
62
+ </button></li>
63
+ ))}
64
+ </ul>
65
+ {!templates.loading && !templates.error && rows.length === 0 ? <p className="text-sm text-muted-foreground">There are no templates yet.</p> : null}
66
+ {picked ? <div className="flex flex-col gap-1"><Label htmlFor="skill-from-template">Name</Label><Input id="skill-from-template" value={name} onChange={(e) => setName(e.target.value)} /></div> : null}
67
+ {create.error ? <ApiError error={create.error} title="Could not create the skill" /> : null}
68
+ <DialogFooter><Button variant="ghost" disabled={create.loading} onClick={onClose}>Cancel</Button><Button loading={create.loading} disabled={!picked || !name.trim()} onClick={() => picked && void create.mutate({ id: tid(picked), name: name.trim() })}>Create skill</Button></DialogFooter>
69
+ </DialogContent>
70
+ </Dialog>
71
+ );
72
+ }
73
+
74
+ export function SkillLibrary({ className }: SkillLibraryProps) {
75
+ const [search, setSearch] = useState("");
76
+ const [kind, setKind] = useState("");
77
+ const [state, setState] = useState("");
78
+ const [target, setTarget] = useState<SkillEditorTarget | null>(null);
79
+ const [fromTemplate, setFromTemplate] = useState(false);
80
+ const skills = useG8Query((signal) => g8Get("/skills", { runtime_type: kind || undefined, enabled: state || undefined, include_system: true }, { signal }), [kind, state]);
81
+ const all = listOf<SkillDetail>(skills.data, "actions", "skills", "items");
82
+ const q = search.trim().toLowerCase();
83
+ const rows = useMemo(() => (q ? all.filter((s) => `${s.name} ${s.description ?? ""} ${s.category ?? ""}`.toLowerCase().includes(q)) : all), [all, q]);
84
+
85
+ return (
86
+ <div className={`flex flex-col gap-3 ${className ?? ""}`}>
87
+ <Toolbar search={{ value: search, onChange: setSearch, placeholder: "Search skills" }}
88
+ filters={<><div role="group" aria-label="Skill kind" className="flex gap-1">{KINDS.map((k) => <Button key={k.id || "all"} size="sm" variant={kind === k.id ? "default" : "outline"} onClick={() => setKind(k.id)}>{k.label}</Button>)}</div>
89
+ <select aria-label="Skill state" className="h-8 rounded-md border bg-background px-2 text-sm" value={state} onChange={(e) => setState(e.target.value)}>{STATES.map((s) => <option key={s.id} value={s.id}>{s.label}</option>)}</select></>}
90
+ actions={<><Button size="sm" variant="outline" onClick={() => setFromTemplate(true)}>From a template</Button><Button size="sm" variant="outline" onClick={() => setTarget({ kind: "new", runtime: "api" })}>New API skill</Button><Button size="sm" onClick={() => setTarget({ kind: "new", runtime: "llm" })}>New LLM skill</Button></>} />
91
+ {skills.error ? <ApiError error={skills.error} title="Could not load skills" onRetry={() => void skills.refetch()} /> : null}
92
+ {skills.loading && !skills.data ? <div className="space-y-2">{[0, 1, 2].map((i) => <Skeleton key={i} className="h-12 w-full" />)}</div> : null}
93
+ <ul className="divide-y rounded-md border">
94
+ {rows.map((s) => (
95
+ <li key={s.action_id}>
96
+ <button type="button" data-testid={`skill-${s.action_id}`} className="flex w-full items-center justify-between gap-3 px-3 py-2 text-left text-sm" onClick={() => setTarget({ kind: "edit", id: s.action_id })}>
97
+ <span className="min-w-0"><span className="block truncate font-medium">{s.name}</span>{s.description ? <span className="block truncate text-xs text-muted-foreground">{s.description}</span> : null}</span>
98
+ <span className="flex shrink-0 items-center gap-2">{s.category ? <span className="text-xs text-muted-foreground">{s.category}</span> : null}<StatusPill tone="neutral">{s.runtime_type ?? "skill"}</StatusPill><StatusPill tone={s.enabled === false ? "warning" : "positive"} dot>{s.enabled === false ? "disabled" : "enabled"}</StatusPill></span>
99
+ </button>
100
+ </li>
101
+ ))}
102
+ </ul>
103
+ {!skills.loading && !skills.error && rows.length === 0 ? (
104
+ <EmptyState icon={<Wand2 strokeWidth={1.5} />} title={all.length ? "No skills match" : "No skills yet"} description="A skill is one thing an agent can do: write a summary, call an API."
105
+ action={all.length ? undefined : <Button onClick={() => setTarget({ kind: "new", runtime: "llm" })}>New LLM skill</Button>} />
106
+ ) : null}
107
+ <SkillEditor target={target} onClose={() => setTarget(null)} onChanged={() => void skills.refetch()} />
108
+ <FromTemplate open={fromTemplate} onClose={() => setFromTemplate(false)} onCreated={(id) => { setFromTemplate(false); void skills.refetch(); if (id) setTarget({ kind: "edit", id }); }} />
109
+ </div>
110
+ );
111
+ }
@@ -58,7 +58,7 @@ export interface SkillRow {
58
58
  description?: string | null;
59
59
  runtime_type?: "llm" | "api" | string;
60
60
  category?: string | null;
61
- llm_config?: { prompt?: string; model?: string } | null;
61
+ llm_config?: { prompt_template?: string; model?: string } | null;
62
62
  }
63
63
 
64
64
  export interface AgentRun {
@@ -112,3 +112,9 @@ export function pretty(value: unknown): string {
112
112
  return String(value);
113
113
  }
114
114
  }
115
+
116
+ /** The id of the agent a create or clone answered with, whatever envelope it came in. */
117
+ export function agentIdOf(res: G8Envelope<unknown> | null | undefined): string {
118
+ const b = bodyOf<{ agent?: { agent_id?: string }; agent_id?: string; new_agent_id?: string }>(res);
119
+ return b.agent?.agent_id ?? b.agent_id ?? b.new_agent_id ?? "";
120
+ }
@@ -0,0 +1,13 @@
1
+ "use client";
2
+
3
+ /** Renders an open-shaped API object (health, scorecard, status) as labelled facts without guessing its fields. */
4
+ import { KeyValueList } from "@/g8/components/screen/key-value-list";
5
+
6
+ const label = (k: string): string => { const t = k.replace(/_/g, " "); return t.charAt(0).toUpperCase() + t.slice(1); };
7
+ const show = (v: unknown): string => (v == null ? "" : typeof v === "string" ? v : typeof v === "number" || typeof v === "boolean" ? String(v) : JSON.stringify(v));
8
+
9
+ export function FactList({ data, empty = "Nothing reported yet." }: { data: unknown; empty?: string }) {
10
+ const entries = Object.entries((data && typeof data === "object" && !Array.isArray(data) ? data : {}) as Record<string, unknown>).filter(([, v]) => v != null && v !== "");
11
+ if (entries.length === 0) return <p className="text-sm text-muted-foreground">{empty}</p>;
12
+ return <KeyValueList columns={2} items={entries.map(([k, v]) => ({ label: label(k), value: show(v), wide: typeof v === "object", mono: typeof v === "object" }))} />;
13
+ }
@@ -0,0 +1,49 @@
1
+ "use client";
2
+
3
+ /** Shapes of the company knowledge (the Knowledge Brain) the agents block reads, and the body helpers for authoring. */
4
+
5
+ export interface KnowledgeSummary {
6
+ concept_id: string;
7
+ concept_type: string;
8
+ title: string;
9
+ source_type?: string;
10
+ knowledge_status?: string;
11
+ trust_tier?: string;
12
+ visibility?: string;
13
+ stale?: boolean;
14
+ logical_path?: string;
15
+ }
16
+
17
+ export interface KnowledgeConcept {
18
+ summary: KnowledgeSummary;
19
+ markdown?: string;
20
+ source_refs?: Array<{ resource?: string; title?: string | null; locator?: string | null; checksum?: string | null }>;
21
+ relationships?: string[];
22
+ warnings?: string[];
23
+ capabilities?: Record<string, boolean>;
24
+ }
25
+
26
+ export interface KnowledgeInput {
27
+ title: string;
28
+ concept_type: string;
29
+ body: string;
30
+ }
31
+
32
+ export const LIFECYCLES = ["draft", "stable", "deprecated"] as const;
33
+
34
+ /** Rendered markdown can start with a `---` frontmatter block; the authoring body must not carry it. */
35
+ export function bodyFromMarkdown(markdown: string | undefined): string {
36
+ const text = markdown ?? "";
37
+ const m = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/.exec(text);
38
+ return (m ? text.slice(m[0].length) : text).replace(/^\s+/, "");
39
+ }
40
+
41
+ /** Only authored entries can be edited, amended, verified or deprecated; generated ones come from the product's own records. */
42
+ export function isAuthored(s: KnowledgeSummary | undefined): boolean {
43
+ return Boolean(s?.source_type && /authored/i.test(s.source_type));
44
+ }
45
+
46
+ export function conceptOf(res: unknown): KnowledgeConcept | undefined {
47
+ const d = ((res as { data?: unknown } | null)?.data ?? res) as Partial<KnowledgeConcept> | null;
48
+ return d && d.summary && d.summary.concept_id ? (d as KnowledgeConcept) : undefined;
49
+ }
@@ -0,0 +1,141 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Paging helpers for the agents block, in the same shape as the foundation's paging primitives (a list never stops silently at a
5
+ * cap: it shows "Load more" while the last page was full).
6
+ *
7
+ * useLoadMoreList offset + limit APIs (the next page starts at items.length).
8
+ * useGrowingList limit-only APIs (a bigger limit refetches the list from the start).
9
+ * LoadMore the footer button plus the error of a failed "more" request.
10
+ */
11
+ import { useCallback, useEffect, useRef, useState } from "react";
12
+ import { Button } from "@graph8/sdk/ui";
13
+ import { ApiError } from "@/g8/components/api-error";
14
+
15
+ export interface LoadMoreList<T> {
16
+ items: T[];
17
+ /** The first page is loading (or reloading after the deps changed). */
18
+ loading: boolean;
19
+ loadingMore: boolean;
20
+ error: unknown;
21
+ moreError: unknown;
22
+ hasMore: boolean;
23
+ /** A limit-only API stopped at its own maximum: there are more rows the API will not return. */
24
+ capped?: boolean;
25
+ loadMore: () => void;
26
+ reload: () => void;
27
+ }
28
+
29
+ interface Options {
30
+ pageSize?: number;
31
+ enabled?: boolean;
32
+ /** useGrowingList: the largest `limit` the API accepts. */
33
+ max?: number;
34
+ }
35
+
36
+ /** Offset paging. `fetchPage(offset, limit)` returns that page's rows; a full page means there may be more. */
37
+ export function useLoadMoreList<T>(
38
+ fetchPage: (offset: number, limit: number, signal: AbortSignal) => Promise<T[]>,
39
+ deps: readonly unknown[],
40
+ { pageSize = 50, enabled = true }: Options = {},
41
+ ): LoadMoreList<T> {
42
+ const [items, setItems] = useState<T[]>([]);
43
+ const [loading, setLoading] = useState(enabled);
44
+ const [loadingMore, setLoadingMore] = useState(false);
45
+ const [error, setError] = useState<unknown>(null);
46
+ const [moreError, setMoreError] = useState<unknown>(null);
47
+ const [hasMore, setHasMore] = useState(false);
48
+ const [tick, setTick] = useState(0);
49
+ const fetchRef = useRef(fetchPage);
50
+ fetchRef.current = fetchPage;
51
+ const gen = useRef(0);
52
+ const count = useRef(0);
53
+ const sig = JSON.stringify([...deps, enabled, pageSize]);
54
+ const lastSig = useRef(sig);
55
+
56
+ useEffect(() => {
57
+ if (!enabled) { setLoading(false); return; }
58
+ const mine = ++gen.current;
59
+ const ctl = new AbortController();
60
+ // A reload() keeps showing the rows it has until the fresh first page lands; a changed filter starts from nothing.
61
+ const fresh = lastSig.current !== sig;
62
+ lastSig.current = sig;
63
+ setLoading(true); setError(null); setMoreError(null);
64
+ if (fresh) { setItems([]); setHasMore(false); count.current = 0; }
65
+ // A reload() refetches as many pages as were showing, so a row opened from page 2 does not vanish; a changed filter starts at page 1.
66
+ const want = fresh ? pageSize : Math.max(count.current, pageSize);
67
+ const load = async (): Promise<T[]> => {
68
+ const all: T[] = [];
69
+ let last = pageSize;
70
+ while (all.length < want && last >= pageSize) {
71
+ const page = await fetchRef.current(all.length, pageSize, ctl.signal);
72
+ all.push(...page);
73
+ last = page.length;
74
+ }
75
+ return all;
76
+ };
77
+ load().then((rows) => {
78
+ if (gen.current !== mine) return;
79
+ setItems(rows); count.current = rows.length; setHasMore(rows.length > 0 && rows.length % pageSize === 0); setLoading(false);
80
+ }, (e) => { if (gen.current === mine) { setError(e); setLoading(false); } });
81
+ return () => ctl.abort();
82
+ }, [...deps, enabled, pageSize, tick]); // eslint-disable-line react-hooks/exhaustive-deps
83
+
84
+ const loadMore = useCallback(() => {
85
+ const mine = gen.current;
86
+ setLoadingMore(true); setMoreError(null);
87
+ fetchRef.current(count.current, pageSize, new AbortController().signal).then((rows) => {
88
+ if (gen.current !== mine) return;
89
+ count.current += rows.length;
90
+ setItems((prev) => [...prev, ...rows]); setHasMore(rows.length >= pageSize); setLoadingMore(false);
91
+ }, (e) => { if (gen.current === mine) { setMoreError(e); setLoadingMore(false); } });
92
+ }, [pageSize]);
93
+
94
+ return { items, loading, loadingMore, error, moreError, hasMore, loadMore, reload: () => setTick((t) => t + 1) };
95
+ }
96
+
97
+ /** Limit-only APIs: asks for `step` rows, and "Load more" asks again for `step` more from the start. */
98
+ export function useGrowingList<T>(
99
+ fetchRows: (limit: number, signal: AbortSignal) => Promise<T[]>,
100
+ deps: readonly unknown[],
101
+ { pageSize = 50, enabled = true, max = Infinity }: Options = {},
102
+ ): LoadMoreList<T> {
103
+ const [limit, setLimit] = useState(pageSize);
104
+ const [rows, setRows] = useState<T[]>([]);
105
+ const [loading, setLoading] = useState(enabled);
106
+ const [error, setError] = useState<unknown>(null);
107
+ const [tick, setTick] = useState(0);
108
+ const [growing, setGrowing] = useState(false);
109
+ const fetchRef = useRef(fetchRows);
110
+ fetchRef.current = fetchRows;
111
+ const depKey = JSON.stringify(deps);
112
+ const lastDeps = useRef(depKey);
113
+
114
+ useEffect(() => { if (lastDeps.current !== depKey) { lastDeps.current = depKey; setLimit(pageSize); } }, [depKey, pageSize]);
115
+ useEffect(() => {
116
+ if (!enabled) { setLoading(false); return; }
117
+ let live = true;
118
+ const ctl = new AbortController();
119
+ setLoading(true); setError(null);
120
+ fetchRef.current(limit, ctl.signal).then((r) => { if (live) { setRows(r); setLoading(false); setGrowing(false); } }, (e) => { if (live) { setError(e); setLoading(false); setGrowing(false); } });
121
+ return () => { live = false; ctl.abort(); };
122
+ }, [limit, depKey, enabled, tick]); // eslint-disable-line react-hooks/exhaustive-deps
123
+
124
+ return {
125
+ items: rows, loading: loading && !growing, loadingMore: loading && growing, error: growing ? null : error, moreError: growing ? error : null,
126
+ hasMore: !loading && rows.length >= limit && limit < max, capped: !loading && rows.length >= limit && limit >= max,
127
+ loadMore: () => { setGrowing(true); setLimit((l) => Math.min(max, l + pageSize)); }, reload: () => setTick((t) => t + 1),
128
+ };
129
+ }
130
+
131
+ /** The footer of a paged list. Renders nothing when everything is shown. */
132
+ export function LoadMore<T>({ list, noun = "rows" }: { list: LoadMoreList<T>; noun?: string }) {
133
+ if (!list.hasMore && !list.moreError && !list.capped) return null;
134
+ return (
135
+ <div className="flex flex-col items-center gap-2 py-2">
136
+ {list.moreError ? <ApiError compact error={list.moreError} title={`Could not load more ${noun}`} onRetry={list.loadMore} /> : null}
137
+ {list.capped ? <p className="text-xs text-muted-foreground">Showing the latest {list.items.length} {noun}. The API returns no more than this.</p> : null}
138
+ {list.hasMore && !list.moreError ? <Button size="sm" variant="outline" loading={list.loadingMore} onClick={list.loadMore}>Load more {noun}</Button> : null}
139
+ </div>
140
+ );
141
+ }