makersclaw 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/package.json +1 -1
- package/r/activity-log.json +52 -0
- package/r/agent-changes.json +51 -0
- package/r/agent-chat.json +53 -0
- package/r/agent-column.json +85 -0
- package/r/agent-dock.json +57 -0
- package/r/agent-kit.json +49 -0
- package/r/agent-marks.json +49 -0
- package/r/agent-pane.json +93 -0
- package/r/ai-action.json +56 -0
- package/r/annotate.json +3 -3
- package/r/approval-request.json +2 -2
- package/r/approval.json +54 -0
- package/r/ask-bar.json +50 -0
- package/r/audio-player.json +2 -2
- package/r/auth-form.json +2 -2
- package/r/automation-editor.json +66 -0
- package/r/automation-list.json +57 -0
- package/r/automation-model.json +118 -0
- package/r/automations-screen.json +71 -0
- package/r/board.json +2 -2
- package/r/calendar-view.json +3 -3
- package/r/cart.json +2 -2
- package/r/chart.json +2 -2
- package/r/chat-pane.json +84 -0
- package/r/chat.json +2 -2
- package/r/code-editor.json +2 -2
- package/r/comment-box.json +3 -3
- package/r/connect-card.json +51 -0
- package/r/contact-section.json +2 -2
- package/r/core.json +13 -7
- package/r/cost.json +51 -0
- package/r/csv-import.json +3 -3
- package/r/data-picker.json +60 -0
- package/r/data-table.json +2 -2
- package/r/diagram.json +2 -2
- package/r/doc-editor.json +2 -2
- package/r/doc-page.json +3 -3
- package/r/email-render.json +2 -2
- package/r/faq.json +2 -2
- package/r/file-upload.json +2 -2
- package/r/first-run.json +63 -0
- package/r/fix-note.json +57 -0
- package/r/flow-canvas.json +2 -2
- package/r/flow-editor.json +79 -0
- package/r/footer.json +2 -2
- package/r/gantt.json +3 -3
- package/r/job-panel.json +2 -2
- package/r/json-editor.json +3 -3
- package/r/library-shelf.json +56 -0
- package/r/map.json +2 -2
- package/r/margin-notes.json +67 -0
- package/r/menubar.json +2 -2
- package/r/mind-map.json +3 -3
- package/r/navigation-menu.json +2 -2
- package/r/needs-you.json +55 -0
- package/r/order-detail.json +2 -2
- package/r/org-chart.json +3 -3
- package/r/pace-bar.json +69 -0
- package/r/page-builder.json +2 -2
- package/r/payment-methods.json +2 -2
- package/r/pdf-document.json +2 -2
- package/r/pdf-viewer.json +2 -2
- package/r/product-detail.json +2 -2
- package/r/product-grid.json +3 -3
- package/r/record-form.json +2 -2
- package/r/record-grid.json +52 -0
- package/r/record-notes.json +4 -4
- package/r/registry.json +4086 -1512
- package/r/run-history.json +52 -0
- package/r/run-view.json +80 -0
- package/r/schedule-card.json +54 -0
- package/r/schedule-engine.json +79 -0
- package/r/schedule-gantt.json +83 -0
- package/r/schedule-ruler.json +64 -0
- package/r/search-dialog.json +2 -2
- package/r/settings-list.json +2 -2
- package/r/setup-checklist.json +2 -2
- package/r/share-dialog.json +2 -2
- package/r/sidebar.json +2 -2
- package/r/signature-pad.json +2 -2
- package/r/sketch-canvas.json +2 -2
- package/r/source-receipt.json +52 -0
- package/r/spreadsheet.json +3 -3
- package/r/stat-cards.json +3 -3
- package/r/step-picker.json +53 -0
- package/r/step-settings.json +72 -0
- package/r/suggestion.json +58 -0
- package/r/sum-bar.json +64 -0
- package/r/task-list.json +3 -3
- package/r/task-model.json +2 -2
- package/r/testimonials.json +2 -2
- package/r/tier-map.json +67 -0
- package/r/time-grid.json +73 -0
- package/r/time-strip.json +64 -0
- package/r/top-bar.json +66 -0
- package/r/transaction-list.json +2 -2
- package/r/trigger-picker.json +61 -0
- package/r/whiteboard.json +3 -3
- package/r/workflow-view.json +55 -0
- package/registry/blocks/activity-log/README.md +112 -0
- package/registry/blocks/activity-log/activity-log.jsx +351 -0
- package/registry/blocks/activity-log/block.json +33 -0
- package/registry/blocks/activity-log/demo.jsx +48 -0
- package/registry/blocks/activity-log/index.js +4 -0
- package/registry/blocks/agent-changes/README.md +94 -0
- package/registry/blocks/agent-changes/agent-changes.jsx +118 -0
- package/registry/blocks/agent-changes/block.json +28 -0
- package/registry/blocks/agent-changes/demo.jsx +39 -0
- package/registry/blocks/agent-changes/index.js +3 -0
- package/registry/blocks/agent-chat/README.md +145 -0
- package/registry/blocks/agent-chat/agent-chat.jsx +415 -0
- package/registry/blocks/agent-chat/block.json +30 -0
- package/registry/blocks/agent-chat/demo.jsx +165 -0
- package/registry/blocks/agent-chat/index.js +3 -0
- package/registry/blocks/agent-column/README.md +197 -0
- package/registry/blocks/agent-column/agent-column.jsx +492 -0
- package/registry/blocks/agent-column/block.json +67 -0
- package/registry/blocks/agent-column/column-picker.jsx +163 -0
- package/registry/blocks/agent-column/demo.jsx +50 -0
- package/registry/blocks/agent-column/index.js +7 -0
- package/registry/blocks/agent-column/model.js +164 -0
- package/registry/blocks/agent-column/receipt.jsx +483 -0
- package/registry/blocks/agent-column/spreadsheet-type.jsx +24 -0
- package/registry/blocks/agent-dock/README.md +210 -0
- package/registry/blocks/agent-dock/agent-dock.jsx +356 -0
- package/registry/blocks/agent-dock/block.json +47 -0
- package/registry/blocks/agent-dock/demo.jsx +153 -0
- package/registry/blocks/agent-dock/dock-model.js +118 -0
- package/registry/blocks/agent-dock/index.js +5 -0
- package/registry/blocks/agent-kit/README.md +73 -0
- package/registry/blocks/agent-kit/agent-kit.jsx +86 -0
- package/registry/blocks/agent-kit/block.json +10 -0
- package/registry/blocks/agent-kit/demo.jsx +35 -0
- package/registry/blocks/agent-kit/index.js +3 -0
- package/registry/blocks/agent-marks/README.md +80 -0
- package/registry/blocks/agent-marks/agent-marks.jsx +133 -0
- package/registry/blocks/agent-marks/block.json +27 -0
- package/registry/blocks/agent-marks/demo.jsx +49 -0
- package/registry/blocks/agent-marks/index.js +3 -0
- package/registry/blocks/agent-pane/README.md +225 -0
- package/registry/blocks/agent-pane/agent-pane.jsx +244 -0
- package/registry/blocks/agent-pane/block.json +41 -0
- package/registry/blocks/agent-pane/composer.jsx +6 -0
- package/registry/blocks/agent-pane/demo.jsx +233 -0
- package/registry/blocks/agent-pane/dock-context.js +10 -0
- package/registry/blocks/agent-pane/index.js +4 -0
- package/registry/blocks/agent-pane/prose.jsx +84 -0
- package/registry/blocks/agent-pane/questions.jsx +134 -0
- package/registry/blocks/agent-pane/transcript.jsx +268 -0
- package/registry/blocks/agent-pane/work.jsx +368 -0
- package/registry/blocks/ai-action/README.md +94 -0
- package/registry/blocks/ai-action/ai-action.jsx +188 -0
- package/registry/blocks/ai-action/block.json +34 -0
- package/registry/blocks/ai-action/demo.jsx +93 -0
- package/registry/blocks/ai-action/index.js +3 -0
- package/registry/blocks/approval/README.md +145 -0
- package/registry/blocks/approval/approval.jsx +163 -0
- package/registry/blocks/approval/block.json +31 -0
- package/registry/blocks/approval/demo.jsx +121 -0
- package/registry/blocks/approval/index.js +3 -0
- package/registry/blocks/ask-bar/README.md +105 -0
- package/registry/blocks/ask-bar/ask-bar.jsx +166 -0
- package/registry/blocks/ask-bar/block.json +26 -0
- package/registry/blocks/ask-bar/demo.jsx +78 -0
- package/registry/blocks/ask-bar/index.js +3 -0
- package/registry/blocks/automation-editor/README.md +291 -0
- package/registry/blocks/automation-editor/automation-editor.jsx +656 -0
- package/registry/blocks/automation-editor/block.json +39 -0
- package/registry/blocks/automation-editor/demo.jsx +284 -0
- package/registry/blocks/automation-editor/index.js +4 -0
- package/registry/blocks/automation-editor/run-inputs.jsx +82 -0
- package/registry/blocks/automation-list/README.md +129 -0
- package/registry/blocks/automation-list/automation-list.jsx +476 -0
- package/registry/blocks/automation-list/block.json +35 -0
- package/registry/blocks/automation-list/demo.jsx +67 -0
- package/registry/blocks/automation-list/index.js +3 -0
- package/registry/blocks/automation-model/README.md +109 -0
- package/registry/blocks/automation-model/block.json +135 -0
- package/registry/blocks/automation-model/catalog.js +256 -0
- package/registry/blocks/automation-model/check.js +161 -0
- package/registry/blocks/automation-model/demo.jsx +42 -0
- package/registry/blocks/automation-model/edit.js +262 -0
- package/registry/blocks/automation-model/fixes.js +287 -0
- package/registry/blocks/automation-model/glyph.jsx +13 -0
- package/registry/blocks/automation-model/graph.js +410 -0
- package/registry/blocks/automation-model/index.js +33 -0
- package/registry/blocks/automation-model/overlay.js +175 -0
- package/registry/blocks/automation-model/parts.jsx +94 -0
- package/registry/blocks/automation-model/schedule.js +53 -0
- package/registry/blocks/automation-model/templates.js +128 -0
- package/registry/blocks/automation-model/values.js +29 -0
- package/registry/blocks/automation-model/words.js +439 -0
- package/registry/blocks/automations-screen/README.md +207 -0
- package/registry/blocks/automations-screen/automations-screen.jsx +452 -0
- package/registry/blocks/automations-screen/block.json +47 -0
- package/registry/blocks/automations-screen/demo.jsx +364 -0
- package/registry/blocks/automations-screen/index.js +4 -0
- package/registry/blocks/automations-screen/screen-model.js +129 -0
- package/registry/blocks/chat-pane/README.md +143 -0
- package/registry/blocks/chat-pane/block.json +48 -0
- package/registry/blocks/chat-pane/chat-attach.js +166 -0
- package/registry/blocks/chat-pane/chat-composer.jsx +195 -0
- package/registry/blocks/chat-pane/chat-context.js +17 -0
- package/registry/blocks/chat-pane/chat-model.js +43 -0
- package/registry/blocks/chat-pane/chat-pane.jsx +398 -0
- package/registry/blocks/chat-pane/chats-list.jsx +52 -0
- package/registry/blocks/chat-pane/demo.jsx +103 -0
- package/registry/blocks/chat-pane/index.js +9 -0
- package/registry/blocks/connect-card/README.md +99 -0
- package/registry/blocks/connect-card/block.json +10 -0
- package/registry/blocks/connect-card/connect-card.jsx +103 -0
- package/registry/blocks/connect-card/demo.jsx +49 -0
- package/registry/blocks/connect-card/index.js +3 -0
- package/registry/blocks/cost/README.md +115 -0
- package/registry/blocks/cost/block.json +10 -0
- package/registry/blocks/cost/cost.jsx +176 -0
- package/registry/blocks/cost/demo.jsx +66 -0
- package/registry/blocks/cost/index.js +3 -0
- package/registry/blocks/data-picker/README.md +81 -0
- package/registry/blocks/data-picker/block.json +38 -0
- package/registry/blocks/data-picker/data-picker.jsx +210 -0
- package/registry/blocks/data-picker/demo.jsx +34 -0
- package/registry/blocks/data-picker/fields.js +89 -0
- package/registry/blocks/data-picker/index.js +4 -0
- package/registry/blocks/data-table/demo.jsx +1 -1
- package/registry/blocks/first-run/README.md +118 -0
- package/registry/blocks/first-run/block.json +11 -0
- package/registry/blocks/first-run/demo.jsx +88 -0
- package/registry/blocks/first-run/first-run-model.js +51 -0
- package/registry/blocks/first-run/first-run.jsx +156 -0
- package/registry/blocks/first-run/index.js +5 -0
- package/registry/blocks/first-run/probe-rules.js +26 -0
- package/registry/blocks/fix-note/README.md +79 -0
- package/registry/blocks/fix-note/block.json +35 -0
- package/registry/blocks/fix-note/demo.jsx +30 -0
- package/registry/blocks/fix-note/fix-note.jsx +235 -0
- package/registry/blocks/fix-note/index.js +3 -0
- package/registry/blocks/flow-editor/README.md +108 -0
- package/registry/blocks/flow-editor/block.json +44 -0
- package/registry/blocks/flow-editor/demo.jsx +115 -0
- package/registry/blocks/flow-editor/flow-canvas.jsx +568 -0
- package/registry/blocks/flow-editor/flow-editor.jsx +154 -0
- package/registry/blocks/flow-editor/index.js +6 -0
- package/registry/blocks/flow-editor/minimap.jsx +78 -0
- package/registry/blocks/flow-editor/outline.jsx +134 -0
- package/registry/blocks/library-shelf/README.md +117 -0
- package/registry/blocks/library-shelf/block.json +28 -0
- package/registry/blocks/library-shelf/demo.jsx +68 -0
- package/registry/blocks/library-shelf/index.js +3 -0
- package/registry/blocks/library-shelf/library-shelf.jsx +211 -0
- package/registry/blocks/library-shelf/probe-rules.js +15 -0
- package/registry/blocks/margin-notes/README.md +222 -0
- package/registry/blocks/margin-notes/block.json +10 -0
- package/registry/blocks/margin-notes/demo.jsx +144 -0
- package/registry/blocks/margin-notes/index.js +20 -0
- package/registry/blocks/margin-notes/margin-model.js +224 -0
- package/registry/blocks/margin-notes/margin-notes.jsx +465 -0
- package/registry/blocks/needs-you/README.md +177 -0
- package/registry/blocks/needs-you/block.json +34 -0
- package/registry/blocks/needs-you/demo.jsx +143 -0
- package/registry/blocks/needs-you/index.js +3 -0
- package/registry/blocks/needs-you/needs-you.jsx +441 -0
- package/registry/blocks/pace-bar/README.md +230 -0
- package/registry/blocks/pace-bar/block.json +10 -0
- package/registry/blocks/pace-bar/demo.jsx +255 -0
- package/registry/blocks/pace-bar/index.js +22 -0
- package/registry/blocks/pace-bar/pace-bar.jsx +461 -0
- package/registry/blocks/pace-bar/pace-model.js +288 -0
- package/registry/blocks/record-grid/README.md +100 -0
- package/registry/blocks/record-grid/block.json +28 -0
- package/registry/blocks/record-grid/demo.jsx +43 -0
- package/registry/blocks/record-grid/index.js +3 -0
- package/registry/blocks/record-grid/record-grid.jsx +251 -0
- package/registry/blocks/run-history/README.md +138 -0
- package/registry/blocks/run-history/block.json +10 -0
- package/registry/blocks/run-history/demo.jsx +74 -0
- package/registry/blocks/run-history/index.js +3 -0
- package/registry/blocks/run-history/run-history.jsx +218 -0
- package/registry/blocks/run-view/README.md +174 -0
- package/registry/blocks/run-view/block.json +50 -0
- package/registry/blocks/run-view/demo.jsx +286 -0
- package/registry/blocks/run-view/index.js +5 -0
- package/registry/blocks/run-view/run-controls.jsx +266 -0
- package/registry/blocks/run-view/run-view.jsx +236 -0
- package/registry/blocks/run-view/runs-list.jsx +99 -0
- package/registry/blocks/schedule-card/README.md +121 -0
- package/registry/blocks/schedule-card/block.json +10 -0
- package/registry/blocks/schedule-card/demo.jsx +82 -0
- package/registry/blocks/schedule-card/index.js +3 -0
- package/registry/blocks/schedule-card/schedule-card.jsx +136 -0
- package/registry/blocks/schedule-engine/README.md +137 -0
- package/registry/blocks/schedule-engine/block.json +10 -0
- package/registry/blocks/schedule-engine/demo.jsx +82 -0
- package/registry/blocks/schedule-engine/engine.js +769 -0
- package/registry/blocks/schedule-engine/index.js +9 -0
- package/registry/blocks/schedule-engine/ops.js +71 -0
- package/registry/blocks/schedule-engine/place.js +294 -0
- package/registry/blocks/schedule-engine/tree.js +79 -0
- package/registry/blocks/schedule-engine/waits.js +80 -0
- package/registry/blocks/schedule-gantt/README.md +159 -0
- package/registry/blocks/schedule-gantt/block.json +10 -0
- package/registry/blocks/schedule-gantt/demo.jsx +223 -0
- package/registry/blocks/schedule-gantt/gantt-model.js +219 -0
- package/registry/blocks/schedule-gantt/geometry.js +176 -0
- package/registry/blocks/schedule-gantt/index.js +26 -0
- package/registry/blocks/schedule-gantt/schedule-gantt.jsx +882 -0
- package/registry/blocks/schedule-gantt/sheet.jsx +372 -0
- package/registry/blocks/schedule-gantt/track.jsx +190 -0
- package/registry/blocks/schedule-ruler/README.md +148 -0
- package/registry/blocks/schedule-ruler/block.json +10 -0
- package/registry/blocks/schedule-ruler/demo.jsx +181 -0
- package/registry/blocks/schedule-ruler/index.js +21 -0
- package/registry/blocks/schedule-ruler/ruler-model.js +239 -0
- package/registry/blocks/schedule-ruler/schedule-ruler.jsx +548 -0
- package/registry/blocks/source-receipt/README.md +97 -0
- package/registry/blocks/source-receipt/block.json +30 -0
- package/registry/blocks/source-receipt/demo.jsx +71 -0
- package/registry/blocks/source-receipt/index.js +3 -0
- package/registry/blocks/source-receipt/source-receipt.jsx +107 -0
- package/registry/blocks/spreadsheet/spreadsheet.jsx +15 -1
- package/registry/blocks/step-picker/README.md +78 -0
- package/registry/blocks/step-picker/block.json +31 -0
- package/registry/blocks/step-picker/demo.jsx +23 -0
- package/registry/blocks/step-picker/index.js +3 -0
- package/registry/blocks/step-picker/step-picker.jsx +144 -0
- package/registry/blocks/step-settings/README.md +100 -0
- package/registry/blocks/step-settings/block.json +44 -0
- package/registry/blocks/step-settings/demo.jsx +93 -0
- package/registry/blocks/step-settings/fields.jsx +58 -0
- package/registry/blocks/step-settings/flow-settings.jsx +115 -0
- package/registry/blocks/step-settings/index.js +5 -0
- package/registry/blocks/step-settings/step-settings.jsx +290 -0
- package/registry/blocks/suggestion/README.md +171 -0
- package/registry/blocks/suggestion/block.json +40 -0
- package/registry/blocks/suggestion/demo.jsx +91 -0
- package/registry/blocks/suggestion/index.js +3 -0
- package/registry/blocks/suggestion/suggestion.jsx +436 -0
- package/registry/blocks/sum-bar/README.md +122 -0
- package/registry/blocks/sum-bar/block.json +10 -0
- package/registry/blocks/sum-bar/demo.jsx +110 -0
- package/registry/blocks/sum-bar/index.js +5 -0
- package/registry/blocks/sum-bar/sum-bar.jsx +209 -0
- package/registry/blocks/sum-bar/sum-model.js +126 -0
- package/registry/blocks/tier-map/README.md +217 -0
- package/registry/blocks/tier-map/block.json +10 -0
- package/registry/blocks/tier-map/demo.jsx +222 -0
- package/registry/blocks/tier-map/index.js +24 -0
- package/registry/blocks/tier-map/tier-map.jsx +574 -0
- package/registry/blocks/tier-map/tier-model.js +318 -0
- package/registry/blocks/time-grid/README.md +305 -0
- package/registry/blocks/time-grid/block.json +10 -0
- package/registry/blocks/time-grid/demo.jsx +279 -0
- package/registry/blocks/time-grid/grid-model.js +322 -0
- package/registry/blocks/time-grid/index.js +25 -0
- package/registry/blocks/time-grid/time-grid.jsx +1325 -0
- package/registry/blocks/time-strip/README.md +141 -0
- package/registry/blocks/time-strip/block.json +10 -0
- package/registry/blocks/time-strip/demo.jsx +129 -0
- package/registry/blocks/time-strip/index.js +19 -0
- package/registry/blocks/time-strip/strip-model.js +178 -0
- package/registry/blocks/time-strip/time-strip.jsx +260 -0
- package/registry/blocks/top-bar/README.md +149 -0
- package/registry/blocks/top-bar/block.json +35 -0
- package/registry/blocks/top-bar/demo.jsx +78 -0
- package/registry/blocks/top-bar/index.js +5 -0
- package/registry/blocks/top-bar/probe-rules.js +74 -0
- package/registry/blocks/top-bar/top-bar-model.js +152 -0
- package/registry/blocks/top-bar/top-bar.jsx +244 -0
- package/registry/blocks/trigger-picker/README.md +98 -0
- package/registry/blocks/trigger-picker/block.json +38 -0
- package/registry/blocks/trigger-picker/demo.jsx +59 -0
- package/registry/blocks/trigger-picker/index.js +3 -0
- package/registry/blocks/trigger-picker/trigger-picker.jsx +374 -0
- package/registry/blocks/workflow-view/README.md +123 -0
- package/registry/blocks/workflow-view/block.json +10 -0
- package/registry/blocks/workflow-view/demo.jsx +90 -0
- package/registry/blocks/workflow-view/index.js +3 -0
- package/registry/blocks/workflow-view/workflow-view.jsx +203 -0
- package/src/install.mjs +77 -16
- package/src/registry.mjs +3 -0
- package/templates/next/browser/lib/data.js +6 -1
- package/templates/next/files/README.md +627 -24
- package/templates/next/files/check/automation-format.ts +2103 -0
- package/templates/next/files/check/automation-package.ts +142 -0
- package/templates/next/files/check/run.mjs +968 -9
- package/templates/next/files/components/ui/field.jsx +17 -4
- package/templates/next/files/components/ui/icon.jsx +13 -0
- package/templates/next/files/components/ui/index.js +2 -1
- package/templates/next/files/components/ui/layout.jsx +77 -2
- package/templates/next/files/components/ui/notice.jsx +70 -0
- package/templates/next/files/components/ui/page.jsx +124 -0
- package/templates/next/files/components/ui/stat.jsx +22 -7
- package/templates/next/files/components/ui/toast.jsx +6 -3
- package/templates/next/files/lib/automations.js +660 -0
- package/templates/next/files/lib/claw.js +241 -5
- package/templates/next/files/lib/door.js +92 -0
- package/templates/next/files/lib/hooks.js +1627 -8
- package/templates/next/files/lib/platform.js +529 -21
- package/templates/next/template.json +13 -3
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// AutomationsScreen over a pretend platform — the gallery's and the smoke
|
|
4
|
+
// test's surface. Not installed into apps. The platform's hooks are written
|
|
5
|
+
// here, in memory, and handed to the block as `hooks`, exactly as an app hands
|
|
6
|
+
// its lib/hooks. A scenario picker draws each state: many automations, one,
|
|
7
|
+
// none (the first-run screen), still loading, the list failing to load, an
|
|
8
|
+
// older platform without the engine, an app whose job is still its old
|
|
9
|
+
// schedule (read-only), and an automation that will not open. A width picker
|
|
10
|
+
// shows the page at 1280, 800, 368 and 320 px of the block's own width.
|
|
11
|
+
//
|
|
12
|
+
// Try: open a row (Back returns to the list with its search kept), Test run
|
|
13
|
+
// then Back from the run, Runs then a run then Back (it returns to Runs),
|
|
14
|
+
// the app's own tab on Morning read, Delete a row (a toast with Undo),
|
|
15
|
+
// New automation from a starting point, the switch on a row. The line under
|
|
16
|
+
// the pickers is the page address the place would travel in.
|
|
17
|
+
import { useMemo, useState, useSyncExternalStore } from "react";
|
|
18
|
+
import { AppLayout, Button, ButtonGroup, Notice } from "../components/ui";
|
|
19
|
+
import { AutomationsScreen, paramsOfPlace, placeFromParams } from "../components/blocks/automations-screen";
|
|
20
|
+
|
|
21
|
+
const MORNING = {
|
|
22
|
+
format: 1,
|
|
23
|
+
key: "morning-read",
|
|
24
|
+
name: "Morning read",
|
|
25
|
+
does: "Reads your mail, saves who wrote, drafts the follow-ups that are owed.",
|
|
26
|
+
trigger: { type: "schedule", cron: "0 7 * * 1-5", tz: "team" },
|
|
27
|
+
inputs: {},
|
|
28
|
+
settings: { overlap: "skip", on_failure: "retry_then_agent", only_if_changed: false },
|
|
29
|
+
steps: [
|
|
30
|
+
{ id: "mail", kind: "tool", label: "Read the mail since the last read", needs: { category: "mail" }, tool: "list_messages", input: { since: "{{ trigger.at }}", limit: 200 } },
|
|
31
|
+
{ id: "touches", kind: "action", label: "Save who wrote", action: "record_touches", input: { touches: "{{ steps.mail.output.messages }}" } },
|
|
32
|
+
{ id: "write", kind: "llm", label: "Write the follow-up", tier: "smart", prompt: "Write a short follow-up to the last person who wrote." },
|
|
33
|
+
{ id: "tell", kind: "notify", message: "Morning read done." },
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
const simple = (key, name, trigger, message = "Done.") => ({ format: 1, key, name, trigger, inputs: {}, settings: { overlap: "skip", on_failure: "retry_then_agent", only_if_changed: false }, steps: [{ id: "tell", kind: "notify", message }] });
|
|
37
|
+
const FRIDAY = simple("friday-review", "Friday review", { type: "schedule", cron: "0 16 * * 5", tz: "team" }, "Friday review is ready.");
|
|
38
|
+
const LONG = simple("long", "Every Monday morning, collect every deal that has gone quiet for more than three weeks, draft a short note to each owner and leave all of it for the team lead to read before the stand-up", { type: "schedule", cron: "0 9 * * 1", tz: "team" });
|
|
39
|
+
|
|
40
|
+
const ACTIONS = [
|
|
41
|
+
{ name: "record_touches", description: "Save who wrote to whom" },
|
|
42
|
+
{ name: "suggest", description: "Leave a suggestion on Today" },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const T0 = "2026-10-09T11:00:00.000Z";
|
|
46
|
+
const auto = (id, def, over = {}) => ({ id, def, liveDef: def, status: "off", live: 1, draft: 1, source: "member", versions: [{ version: 1, kind: "member", created_at: T0 }], runs: [], suggestions: [], passed: null, ...over });
|
|
47
|
+
const SCENARIOS = {
|
|
48
|
+
many: () => ({
|
|
49
|
+
a1: auto("a1", MORNING, { status: "on", source: "package", versions: [{ version: 1, kind: "shipped", created_at: T0 }], runs: ["r1"], passed: 1 }),
|
|
50
|
+
a2: auto("a2", FRIDAY, { status: "on", runs: ["r2"], passed: 1 }),
|
|
51
|
+
a3: auto("a3", simple("weekly-signups", "Weekly signups", { type: "schedule", cron: "0 9 * * 1", tz: "team" })),
|
|
52
|
+
a4: auto("a4", simple("a-draft", "A draft I started", { type: "manual" }), { status: "draft", live: null }),
|
|
53
|
+
a5: auto("a5", LONG, { status: "on", passed: 1 }),
|
|
54
|
+
}),
|
|
55
|
+
one: () => ({ a1: auto("a1", MORNING, { status: "on", source: "package", versions: [{ version: 1, kind: "shipped", created_at: T0 }], runs: ["r1"], passed: 1 }) }),
|
|
56
|
+
none: () => ({}),
|
|
57
|
+
loading: () => ({}),
|
|
58
|
+
failed: () => ({}),
|
|
59
|
+
unavailable: () => ({}),
|
|
60
|
+
readonly: () => ({ a1: auto("a1", simple("morning", "Morning read", { type: "schedule", cron: "0 7 * * 1-5", tz: "team" }), { status: "on", source: "legacy" }), a2: auto("a2", simple("weekly-review", "Weekly look back", { type: "schedule", cron: "0 16 * * 5", tz: "team" }), { source: "legacy" }) }),
|
|
61
|
+
unopenable: () => ({ a1: auto("a1", MORNING, { status: "on" }), a2: auto("a2", FRIDAY) }),
|
|
62
|
+
};
|
|
63
|
+
const SCENARIO_LABELS = { many: "Many", one: "One", none: "None", loading: "Loading", failed: "Failed to load", unavailable: "No engine", readonly: "Read-only", unopenable: "Will not open" };
|
|
64
|
+
|
|
65
|
+
const row = (step_id, over = {}) => ({ path: step_id, step_id, kind: "notify", attempt: 1, status: "done", started_at: T0, ended_at: T0, credits: 0, error: null, ...over });
|
|
66
|
+
const PAST = {
|
|
67
|
+
r1: { id: "r1", status: "done", version: 1, trigger_type: "schedule", is_test: false, created_at: T0, started_at: T0, ended_at: "2026-10-09T11:01:00.000Z", credits: 4.2, cap_credits: 900, result: null, error: null, steps: [row("mail", { kind: "tool" }), row("touches", { kind: "action" }), row("write", { kind: "llm", credits: 4.2 }), row("tell")] },
|
|
68
|
+
r2: { id: "r2", status: "failed", version: 1, trigger_type: "schedule", is_test: false, created_at: T0, started_at: T0, ended_at: T0, credits: 0.8, result: null, error: { words: "The note could not be left." }, steps: [row("tell", { status: "failed", error: { code: "x", words: "The note could not be left." } })] },
|
|
69
|
+
};
|
|
70
|
+
const flat = (steps) => steps.flatMap((s) => [s, ...(s.then ?? []).flatMap((x) => flat([x])), ...(s.else ?? []).flatMap((x) => flat([x])), ...flat(s.steps ?? [])]);
|
|
71
|
+
|
|
72
|
+
/** A platform in memory: the three hooks and two doors the block asks for. */
|
|
73
|
+
function createPlatform(scenario) {
|
|
74
|
+
const state = { autos: SCENARIOS[scenario](), runs: { ...(scenario === "many" || scenario === "one" ? PAST : {}) }, deleted: {}, n: 0, tick: 0 };
|
|
75
|
+
const subs = new Set();
|
|
76
|
+
const bump = () => {
|
|
77
|
+
state.tick += 1;
|
|
78
|
+
subs.forEach((f) => f());
|
|
79
|
+
};
|
|
80
|
+
const useTick = () => useSyncExternalStore((f) => (subs.add(f), () => subs.delete(f)), () => state.tick, () => state.tick);
|
|
81
|
+
const rowOf = (a) => {
|
|
82
|
+
const last = a.runs.map((id) => state.runs[id]).filter(Boolean)[0];
|
|
83
|
+
return {
|
|
84
|
+
id: a.id,
|
|
85
|
+
key: a.def.key,
|
|
86
|
+
name: a.def.name,
|
|
87
|
+
status: a.status,
|
|
88
|
+
live_version: a.live,
|
|
89
|
+
draft_version: a.draft,
|
|
90
|
+
trigger: a.def.trigger,
|
|
91
|
+
tz: "America/New_York",
|
|
92
|
+
next_run_at: a.status === "on" && a.def.trigger.type === "schedule" ? "2026-10-12T11:00:00.000Z" : null,
|
|
93
|
+
last_run: last ? { status: last.status, started_at: last.created_at, credits: last.credits } : null,
|
|
94
|
+
active_runs: 0,
|
|
95
|
+
owner_kind: "app",
|
|
96
|
+
app_id: "crm",
|
|
97
|
+
edited: a.versions.some((v) => v.kind === "member"),
|
|
98
|
+
source: a.source,
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
const testView = (a, version) => (version === null || version === undefined ? null : { version, exempt: a.versions.some((v) => v.version === version && v.kind === "shipped"), passed: a.passed === version, last: null });
|
|
102
|
+
const needsTest = (a, version, opts) => !opts?.skipTest && !testView(a, version)?.exempt && a.passed !== version;
|
|
103
|
+
const REFUSED = { ok: false, status: 409, code: "needs_test", error: "Run a test of this version first. It reads your real data and changes nothing." };
|
|
104
|
+
const wait = (ms = 250) => new Promise((r) => setTimeout(r, ms));
|
|
105
|
+
|
|
106
|
+
const useAutomations = () => {
|
|
107
|
+
useTick();
|
|
108
|
+
if (scenario === "unavailable") return { automations: [], triggers: null, starts: [], describe: async () => ({ ok: false, code: "unavailable" }), loading: false, error: null, unavailable: true, reload() {}, setStatus: async () => ({ ok: false }), create: async () => ({ ok: false }) };
|
|
109
|
+
const loading = scenario === "loading";
|
|
110
|
+
const error = scenario === "failed" ? "not reachable" : null;
|
|
111
|
+
return {
|
|
112
|
+
automations: loading || error ? [] : Object.values(state.autos).map(rowOf),
|
|
113
|
+
triggers: null,
|
|
114
|
+
starts: [],
|
|
115
|
+
describe: async (ask) => {
|
|
116
|
+
await wait(600);
|
|
117
|
+
return ask.length < 24 ? { ok: false, code: "needs_answer", error: "I need a little more.", questions: ["Who should it tell?", "How often should it run?"] } : { ok: true, id: "a1" };
|
|
118
|
+
},
|
|
119
|
+
describing: false,
|
|
120
|
+
loading,
|
|
121
|
+
error,
|
|
122
|
+
code: undefined,
|
|
123
|
+
unavailable: false,
|
|
124
|
+
reload: bump,
|
|
125
|
+
setStatus: async (id, on) => {
|
|
126
|
+
await wait(150);
|
|
127
|
+
state.autos[id] = { ...state.autos[id], status: on ? "on" : "off" };
|
|
128
|
+
bump();
|
|
129
|
+
return { ok: true, status: 200, data: {} };
|
|
130
|
+
},
|
|
131
|
+
create: async (def) => {
|
|
132
|
+
const id = `n${++state.n}`;
|
|
133
|
+
state.autos[id] = auto(id, def, { status: "draft", live: null });
|
|
134
|
+
bump();
|
|
135
|
+
return { ok: true, status: 200, data: { id } };
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const useAutomation = (id) => {
|
|
141
|
+
useTick();
|
|
142
|
+
const a = state.autos[id];
|
|
143
|
+
if (scenario === "loading") return { automation: null, definition: null, versions: [], runs: [], suggestions: [], tests: null, hook: null, loading: true, error: null, unavailable: false, busy: null, reload: bump };
|
|
144
|
+
const dead = scenario === "unopenable" || !a;
|
|
145
|
+
if (dead) return { automation: null, definition: null, versions: [], runs: [], suggestions: [], tests: null, hook: null, loading: false, error: "That did not open.", code: a ? undefined : "not_found", unavailable: false, busy: null, reload: bump };
|
|
146
|
+
const set = (patch) => {
|
|
147
|
+
state.autos[id] = { ...state.autos[id], ...patch };
|
|
148
|
+
bump();
|
|
149
|
+
};
|
|
150
|
+
return {
|
|
151
|
+
automation: { ...rowOf(a), tz: "America/New_York" },
|
|
152
|
+
live: a.live ? { version: a.live, definition: a.liveDef } : null,
|
|
153
|
+
draft: a.draft !== a.live ? { version: a.draft, definition: a.def } : null,
|
|
154
|
+
definition: a.def,
|
|
155
|
+
versions: [...a.versions].reverse(),
|
|
156
|
+
estimate: { credits: 3, credits_max: 12, steps: [] },
|
|
157
|
+
tests: { live: testView(a, a.live), draft: a.draft !== a.live ? testView(a, a.draft) : null },
|
|
158
|
+
runs: a.runs.map((r) => state.runs[r]).filter(Boolean),
|
|
159
|
+
suggestions: a.suggestions,
|
|
160
|
+
keepSuggestion: async () => ({ ok: true }),
|
|
161
|
+
dropSuggestion: async () => ({ ok: true }),
|
|
162
|
+
hook: null,
|
|
163
|
+
rotateHook: async () => ({ ok: true, hook: null }),
|
|
164
|
+
busy: null,
|
|
165
|
+
save: async (def, how) => {
|
|
166
|
+
await wait(150);
|
|
167
|
+
const latest = a.versions.length;
|
|
168
|
+
const base = how && typeof how === "object" ? how.baseVersion : latest;
|
|
169
|
+
if (base !== latest) return { ok: false, status: 409, code: "changed", latest, error: "Someone saved a newer version while you were editing." };
|
|
170
|
+
set({ def, draft: latest + 1, versions: [...a.versions, { version: latest + 1, kind: "member", created_at: new Date().toISOString() }] });
|
|
171
|
+
return { ok: true, status: 200, data: { version: latest + 1 } };
|
|
172
|
+
},
|
|
173
|
+
keep: async (o) => {
|
|
174
|
+
if (needsTest(a, a.draft, o)) return REFUSED;
|
|
175
|
+
set({ live: a.draft, liveDef: a.def });
|
|
176
|
+
return { ok: true, status: 200, data: {} };
|
|
177
|
+
},
|
|
178
|
+
setLive: async (on, o) => {
|
|
179
|
+
if (on && needsTest(a, a.live ?? a.draft, o)) return REFUSED;
|
|
180
|
+
set({ status: on ? "on" : "off" });
|
|
181
|
+
return { ok: true, status: 200, data: {} };
|
|
182
|
+
},
|
|
183
|
+
test: async (given = {}) => {
|
|
184
|
+
await wait(300);
|
|
185
|
+
const dry = !(given && given.dry === false);
|
|
186
|
+
const id2 = `t${++state.n}`;
|
|
187
|
+
const t = new Date().toISOString();
|
|
188
|
+
const standIn = new Set(["action", "notify"]);
|
|
189
|
+
const steps = flat(a.def.steps).map((s) => {
|
|
190
|
+
const would = dry && standIn.has(s.kind);
|
|
191
|
+
return row(s.id, { kind: s.kind, started_at: t, ended_at: t, credits: s.kind === "llm" ? 3 : 0, ...(would ? { simulated: true, output: { simulated: true, would: { kind: s.kind, effect: s.kind === "notify" ? "note" : "write", words: s.kind === "notify" ? "Would leave a note." : `Would run ${s.action}.` } } } : { output: { ok: true } }) });
|
|
192
|
+
});
|
|
193
|
+
const woulds = steps.filter((x) => x.simulated);
|
|
194
|
+
state.runs[id2] = { id: id2, status: "done", dry, version: a.draft, trigger_type: "manual", is_test: dry, created_at: t, started_at: t, ended_at: t, credits: 3, result: dry ? { dry: { simulated: woulds.length, would: woulds.map((x) => ({ step: x.step_id, path: x.path, kind: x.kind, effect: x.output.would.effect, words: x.output.would.words })), unchecked: [], sampled: [] } } : null, error: null, steps, automation_id: id };
|
|
195
|
+
set({ runs: [id2, ...a.runs], passed: a.draft });
|
|
196
|
+
return { ok: true, status: 200, data: { run_id: id2, test: dry, dry, version: a.draft } };
|
|
197
|
+
},
|
|
198
|
+
loading: false,
|
|
199
|
+
error: null,
|
|
200
|
+
code: undefined,
|
|
201
|
+
unavailable: false,
|
|
202
|
+
reload: bump,
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const useAutomationRun = (runId) => {
|
|
207
|
+
useTick();
|
|
208
|
+
const r = runId ? state.runs[runId] : null;
|
|
209
|
+
const none = { run: null, steps: [], stepsVisible: false, automation: null, definition: null, fixes: [], detail: null, control: async () => ({ ok: true }), keepFix: async () => ({ ok: true }), dropFix: async () => ({ ok: true }), busy: null, loading: false, error: null, unavailable: false, reload: bump };
|
|
210
|
+
if (!r) return runId ? { ...none, error: "gone" } : none;
|
|
211
|
+
return { ...none, run: r, steps: r.steps, stepsVisible: true, detail: { run: r, automation: { name: "" }, steps: r.steps, steps_visible: true, fixes: [] } };
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const deleteAutomation = async (id) => {
|
|
215
|
+
await wait(200);
|
|
216
|
+
const a = state.autos[id];
|
|
217
|
+
if (!a) return { ok: false, status: 404, code: "not_found", error: "That automation is not there any more." };
|
|
218
|
+
if (a.source === "package" || a.source === "legacy") return { ok: false, status: 409, code: a.source, error: "This one came with the app. Switch it off in the app's settings instead." };
|
|
219
|
+
state.deleted[id] = a;
|
|
220
|
+
delete state.autos[id];
|
|
221
|
+
bump();
|
|
222
|
+
return { ok: true, status: 200, data: { ok: true, id, name: a.def.name, was_status: a.status, followers: id === "a2" ? [{ id: "a3", key: "weekly-signups", name: "Weekly signups" }] : [], undo: true } };
|
|
223
|
+
};
|
|
224
|
+
const restoreAutomation = async (id) => {
|
|
225
|
+
await wait(150);
|
|
226
|
+
if (!state.deleted[id]) return { ok: false, status: 404, code: "not_found", error: "It could not be brought back." };
|
|
227
|
+
state.autos[id] = state.deleted[id];
|
|
228
|
+
delete state.deleted[id];
|
|
229
|
+
bump();
|
|
230
|
+
return { ok: true, status: 200, data: { ok: true, id } };
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
return { useAutomations, useAutomation, useAutomationRun, deleteAutomation, restoreAutomation };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const WIDTHS = [1280, 800, 368, 320];
|
|
237
|
+
const CHARGE = { many: { used: 4180, of: 10000 }, one: { used: 0, of: 10000 } };
|
|
238
|
+
|
|
239
|
+
function Page({ scenario }) {
|
|
240
|
+
const hooks = useMemo(() => createPlatform(scenario), [scenario]);
|
|
241
|
+
const [place, setPlace] = useState(() => placeFromParams(null));
|
|
242
|
+
const [asked, setAsked] = useState(null);
|
|
243
|
+
return (
|
|
244
|
+
<div className="flex h-full min-h-0 flex-col">
|
|
245
|
+
<p className="m-0 shrink-0 border-b border-border bg-muted px-3 py-1.5 text-xs text-muted-foreground" role="status">
|
|
246
|
+
Page address: <b className="font-semibold text-foreground">{`?${new URLSearchParams({ view: "automations", ...paramsOfPlace(place) })}`}</b>
|
|
247
|
+
{asked ? ` · Describe it handed on: “${asked}”` : ""}
|
|
248
|
+
</p>
|
|
249
|
+
<div className="flex min-h-0 flex-1 flex-col">
|
|
250
|
+
<AutomationsScreen
|
|
251
|
+
key={scenario}
|
|
252
|
+
hooks={hooks}
|
|
253
|
+
place={place}
|
|
254
|
+
onPlace={setPlace}
|
|
255
|
+
actions={ACTIONS}
|
|
256
|
+
title="Automations"
|
|
257
|
+
intro="What the CRM does on its own. Open one to see its steps, change them, test it and switch it on."
|
|
258
|
+
allowance={CHARGE[scenario] ? { ...CHARGE[scenario], words: CHARGE[scenario].used > 0 ? "4,180 of 10,000 credits this week" : "10,000 credits a week" } : null}
|
|
259
|
+
starts={[
|
|
260
|
+
{
|
|
261
|
+
id: "quiet-monday",
|
|
262
|
+
name: "Look at who has gone quiet every Monday",
|
|
263
|
+
blurb: "The agent drafts a short follow-up for the people on open deals who have gone quiet. Nothing is sent.",
|
|
264
|
+
glyph: "agent",
|
|
265
|
+
needs: null,
|
|
266
|
+
build: (taken) => ({ ...simple(taken.has("quiet-monday") ? "quiet-monday-2" : "quiet-monday", "Who has gone quiet", { type: "schedule", cron: "30 8 * * 1", tz: "team" }, "Drafted the follow-ups."), does: "Drafts follow-ups for people who have gone quiet." }),
|
|
267
|
+
},
|
|
268
|
+
]}
|
|
269
|
+
onDescribe={scenario === "one" ? (ask) => (setAsked(ask), Promise.resolve({ ok: true })) : "platform"}
|
|
270
|
+
describePrice=""
|
|
271
|
+
onRequest={
|
|
272
|
+
<section aria-labelledby="demo-on-request" className="grid gap-2">
|
|
273
|
+
<h2 id="demo-on-request" className="m-0 text-[13px] font-semibold">
|
|
274
|
+
On request
|
|
275
|
+
</h2>
|
|
276
|
+
<ul className="m-0 grid list-none rounded-md border border-border p-0">
|
|
277
|
+
{[
|
|
278
|
+
["The first read", "Once, when a mailbox connects: the last 30 days of mail and meetings."],
|
|
279
|
+
["Fill in", "When someone presses Fill in on a person, a company or a list's column."],
|
|
280
|
+
].map(([name, when]) => (
|
|
281
|
+
<li key={name} className="grid gap-0.5 border-b border-border px-3.5 py-2.5 text-[13px] last:border-b-0">
|
|
282
|
+
<b className="font-semibold">{name}</b>
|
|
283
|
+
<span className="text-muted-foreground">{when}</span>
|
|
284
|
+
</li>
|
|
285
|
+
))}
|
|
286
|
+
</ul>
|
|
287
|
+
</section>
|
|
288
|
+
}
|
|
289
|
+
extras={({ automations }) => <p className="m-0 text-xs text-muted-foreground">{automations.length} of the app's own automations are drawn above.</p>}
|
|
290
|
+
extraTabs={[
|
|
291
|
+
{
|
|
292
|
+
key: "reads",
|
|
293
|
+
label: "What it reads",
|
|
294
|
+
when: (a) => a.key === "morning-read",
|
|
295
|
+
render: () => (
|
|
296
|
+
<div className="grid max-w-3xl gap-1.5 p-5 text-[13px]">
|
|
297
|
+
<b className="font-semibold">Mail</b>
|
|
298
|
+
<p className="m-0 text-muted-foreground">For each email: who wrote to whom, when, the subject and one line. Never the full text.</p>
|
|
299
|
+
</div>
|
|
300
|
+
),
|
|
301
|
+
},
|
|
302
|
+
]}
|
|
303
|
+
aboveOne={({ automation, runId }) => (automation.key === "morning-read" && !runId ? <Notice tone="info">It needs a mailbox. Connect one on the desk.</Notice> : null)}
|
|
304
|
+
afterRun={() => <p className="m-0 shrink-0 border-t border-border px-4 py-2 text-xs text-muted-foreground">The app's own account of this run sits here.</p>}
|
|
305
|
+
frame={({ crumb, label, fill, children }) => (
|
|
306
|
+
<AppLayout
|
|
307
|
+
height="parent"
|
|
308
|
+
contentLabel={label}
|
|
309
|
+
contentMode={fill ? "fill" : "scroll"}
|
|
310
|
+
header={
|
|
311
|
+
<nav aria-label="Where you are" className="flex min-h-11 min-w-0 items-center gap-1 px-3 text-sm">
|
|
312
|
+
{crumb.map((c, i) =>
|
|
313
|
+
c.onPress ? (
|
|
314
|
+
<span key={c.label} className="flex items-center gap-1">
|
|
315
|
+
<Button size="sm" variant="quiet" onClick={c.onPress}>
|
|
316
|
+
{c.label}
|
|
317
|
+
</Button>
|
|
318
|
+
<span aria-hidden="true" className="text-muted-foreground">
|
|
319
|
+
›
|
|
320
|
+
</span>
|
|
321
|
+
</span>
|
|
322
|
+
) : (
|
|
323
|
+
<b key={`${c.label}-${i}`} className="min-w-0 truncate font-semibold">
|
|
324
|
+
{c.label}
|
|
325
|
+
</b>
|
|
326
|
+
),
|
|
327
|
+
)}
|
|
328
|
+
</nav>
|
|
329
|
+
}
|
|
330
|
+
>
|
|
331
|
+
{children}
|
|
332
|
+
</AppLayout>
|
|
333
|
+
)}
|
|
334
|
+
/>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export default function AutomationsScreenDemo() {
|
|
341
|
+
const [width, setWidth] = useState(1280);
|
|
342
|
+
const [scenario, setScenario] = useState("many");
|
|
343
|
+
return (
|
|
344
|
+
<div className="grid gap-4 p-4">
|
|
345
|
+
<ButtonGroup aria-label="The state">
|
|
346
|
+
{Object.entries(SCENARIO_LABELS).map(([key, label]) => (
|
|
347
|
+
<Button key={key} size="sm" variant={key === scenario ? "primary" : "secondary"} onClick={() => setScenario(key)}>
|
|
348
|
+
{label}
|
|
349
|
+
</Button>
|
|
350
|
+
))}
|
|
351
|
+
</ButtonGroup>
|
|
352
|
+
<ButtonGroup aria-label="The block's width">
|
|
353
|
+
{WIDTHS.map((w) => (
|
|
354
|
+
<Button key={w} size="sm" variant={w === width ? "primary" : "secondary"} onClick={() => setWidth(w)}>
|
|
355
|
+
{w} px
|
|
356
|
+
</Button>
|
|
357
|
+
))}
|
|
358
|
+
</ButtonGroup>
|
|
359
|
+
<div className="flex h-[720px] w-full flex-col overflow-hidden rounded-lg border border-border" style={{ maxWidth: width }}>
|
|
360
|
+
<Page key={scenario} scenario={scenario} />
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
);
|
|
364
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// The AutomationsScreen block — `makersclaw add automations-screen`. Fixed:
|
|
2
|
+
// compose it through its props (README.md beside this file).
|
|
3
|
+
export { AutomationsScreen } from "./automations-screen";
|
|
4
|
+
export { deletedWords, normalizePlace, paramsOfPlace, placeFromParams } from "./screen-model";
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// The pure part of AutomationsScreen: where the member is (the place), how
|
|
2
|
+
// the place travels in a URL, where Back goes, what a delete says, which
|
|
3
|
+
// starting points are offered, which tabs an automation has. No React, no
|
|
4
|
+
// network: a test runs every function here.
|
|
5
|
+
|
|
6
|
+
/** The `automation` value of a screen that makes a new one. */
|
|
7
|
+
export const NEW = "new";
|
|
8
|
+
/** The two places inside one automation that every automation has. */
|
|
9
|
+
export const FLOW = "flow";
|
|
10
|
+
export const RUNS = "runs";
|
|
11
|
+
|
|
12
|
+
const text = (v) => (typeof v === "string" && v.trim() ? v.trim() : null);
|
|
13
|
+
const list = (v) => (Array.isArray(v) ? v : []);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A place, made well-formed. The list is `{ automation: null }`. One
|
|
17
|
+
* automation is `{ automation: <id>, tab, run }`: `tab` is `flow` (default),
|
|
18
|
+
* `runs`, or the key of an extra tab; `run` is a run's id. A new one is
|
|
19
|
+
* `{ automation: "new", start }`, `start` being the starting point's id.
|
|
20
|
+
*/
|
|
21
|
+
export function normalizePlace(p) {
|
|
22
|
+
const o = p && typeof p === "object" ? p : {};
|
|
23
|
+
const automation = text(o.automation);
|
|
24
|
+
if (!automation) return { automation: null, tab: FLOW, run: null, start: null };
|
|
25
|
+
if (automation === NEW) return { automation: NEW, tab: FLOW, run: null, start: text(o.start) };
|
|
26
|
+
return { automation, tab: text(o.tab) ?? FLOW, run: text(o.run), start: null };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Which screen a place is: list · new · one · run. */
|
|
30
|
+
export function whereOf(place) {
|
|
31
|
+
const p = normalizePlace(place);
|
|
32
|
+
if (!p.automation) return "list";
|
|
33
|
+
if (p.automation === NEW) return "new";
|
|
34
|
+
return p.run ? "run" : "one";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const samePlace = (a, b) => JSON.stringify(normalizePlace(a)) === JSON.stringify(normalizePlace(b));
|
|
38
|
+
|
|
39
|
+
/** A place from the page's query string: `placeFromParams(useSearchParams())`,
|
|
40
|
+
* a URLSearchParams, or a function name → value. The names are `automation`,
|
|
41
|
+
* `tab`, `run`, `start`. */
|
|
42
|
+
export function placeFromParams(params) {
|
|
43
|
+
const get = typeof params === "function" ? params : (name) => (params && typeof params.get === "function" ? params.get(name) : null);
|
|
44
|
+
return normalizePlace({ automation: get("automation"), tab: get("tab"), run: get("run"), start: get("start") });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The query parameters of a place, only what is set: the list is `{}`. Spread
|
|
48
|
+
* them into your own (`{ view: "automations", ...paramsOfPlace(place) }`). */
|
|
49
|
+
export function paramsOfPlace(place) {
|
|
50
|
+
const p = normalizePlace(place);
|
|
51
|
+
if (!p.automation) return {};
|
|
52
|
+
return {
|
|
53
|
+
automation: p.automation,
|
|
54
|
+
...(p.tab !== FLOW && !p.run ? { tab: p.tab } : {}),
|
|
55
|
+
...(p.run ? { run: p.run } : {}),
|
|
56
|
+
...(p.start ? { start: p.start } : {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Where the member came from when a run opened: remembered so that Back from
|
|
62
|
+
* a run returns to the tab it was opened from (the Runs list), not always to
|
|
63
|
+
* the flow. `from` and `to` are the places before and after a move; `origin`
|
|
64
|
+
* is what was remembered. Choosing another run from inside a run keeps it.
|
|
65
|
+
*/
|
|
66
|
+
export function originAfter(from, to, origin = null) {
|
|
67
|
+
const a = normalizePlace(from);
|
|
68
|
+
const b = normalizePlace(to);
|
|
69
|
+
if (!b.run) return null;
|
|
70
|
+
if (a.run) return origin;
|
|
71
|
+
return a.automation === b.automation ? a : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The place Back from a run goes to: the tab the run was opened from, else the flow. */
|
|
75
|
+
export function backFrom(place, origin = null) {
|
|
76
|
+
const p = normalizePlace(place);
|
|
77
|
+
const o = origin ? normalizePlace(origin) : null;
|
|
78
|
+
if (o && o.automation === p.automation && !o.run && o.tab !== FLOW) return { automation: p.automation, tab: o.tab, run: null, start: null };
|
|
79
|
+
return { automation: p.automation, tab: FLOW, run: null, start: null };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The words after a delete: a title, what followed it (the automations that
|
|
84
|
+
* started when it finished and now never will), and whether Undo is offered.
|
|
85
|
+
* `done` is the platform's answer `{ id, name, followers, undo }`.
|
|
86
|
+
*/
|
|
87
|
+
export function deletedWords(done, fallbackName = "") {
|
|
88
|
+
const name = text(done?.name) ?? text(fallbackName);
|
|
89
|
+
const followers = list(done?.followers).filter((f) => f && text(f.name));
|
|
90
|
+
return {
|
|
91
|
+
title: name ? `Deleted “${name}”.` : "Deleted.",
|
|
92
|
+
detail: followers.length
|
|
93
|
+
? `${followers.length} ${followers.length === 1 ? "automation" : "automations"} followed it and will no longer start: ${followers.map((f) => f.name).join(", ")}.`
|
|
94
|
+
: "",
|
|
95
|
+
undo: Boolean(done?.undo) && Boolean(text(done?.id)),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The starting points offered under New: the app's own first, then the platform's
|
|
100
|
+
* (already turned into the list's shape), else the built-in ones. One of each id. */
|
|
101
|
+
export function combineStarts(app, platform, builtIn) {
|
|
102
|
+
const seen = new Set();
|
|
103
|
+
const out = [];
|
|
104
|
+
for (const s of [...list(app), ...(list(platform).length ? list(platform) : list(builtIn))]) {
|
|
105
|
+
if (!s || typeof s.id !== "string" || seen.has(s.id)) continue;
|
|
106
|
+
seen.add(s.id);
|
|
107
|
+
out.push(s);
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The tabs of one automation: Flow, Runs, then the app's own that apply to it. */
|
|
113
|
+
export function tabsOf(extras, automation) {
|
|
114
|
+
const more = [];
|
|
115
|
+
for (const t of list(extras)) {
|
|
116
|
+
if (!t || typeof t.key !== "string" || t.key === FLOW || t.key === RUNS || typeof t.label !== "string") continue;
|
|
117
|
+
if (more.some((x) => x.key === t.key)) continue;
|
|
118
|
+
if (typeof t.when === "function" && !t.when(automation)) continue;
|
|
119
|
+
more.push(t);
|
|
120
|
+
}
|
|
121
|
+
return [{ key: FLOW, label: "Flow" }, { key: RUNS, label: "Runs" }, ...more.map((t) => ({ key: t.key, label: t.label }))];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** What `hooks` must hold. A string naming what is missing, else null. */
|
|
125
|
+
export function hooksProblem(hooks) {
|
|
126
|
+
if (!hooks || typeof hooks !== "object") return "AutomationsScreen needs hooks={{ useAutomations, useAutomation, useAutomationRun }} (from lib/hooks).";
|
|
127
|
+
const missing = ["useAutomations", "useAutomation", "useAutomationRun"].filter((k) => typeof hooks[k] !== "function");
|
|
128
|
+
return missing.length ? `AutomationsScreen: hooks is missing ${missing.join(", ")}.` : null;
|
|
129
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# ChatPane
|
|
2
|
+
|
|
3
|
+
The desk's chat pane, as the app's own, in the app's theme (0.8.1). The
|
|
4
|
+
founder, after reviewing the app agent's message box next to the
|
|
5
|
+
Automations window's: "add claw's ChatPane as a block in the makersclaw
|
|
6
|
+
template". This is that port: claw's `web/src/os/windows/artifact/ChatPane`
|
|
7
|
+
and the composer of its `EveChat`, in JSX, with the kit's `Icon` and
|
|
8
|
+
`Button` and the app's tokens, talking to the agent through the app's own
|
|
9
|
+
hook (`useAgentPane`, `lib/hooks.js`) instead of `eve/client`.
|
|
10
|
+
|
|
11
|
+
It is the **chrome around a conversation**:
|
|
12
|
+
|
|
13
|
+
- **The head**: the title, **New chat**, **Chats** (the past chats drop over
|
|
14
|
+
the conversation, newest first, with a dot on one that needs you or is
|
|
15
|
+
working), and a close.
|
|
16
|
+
- **The empty state**: before the first ask, one line, the recent chats
|
|
17
|
+
("No chats yet. Your first message starts one." when there are none), and
|
|
18
|
+
the app's suggestions.
|
|
19
|
+
- **The conversation**: its lines are the children. It stays at the newest
|
|
20
|
+
words until the member scrolls up, then offers a jump to latest.
|
|
21
|
+
- **The bar** between the conversation and the box: what waits on the member,
|
|
22
|
+
or that the agent is working.
|
|
23
|
+
- **The message box**: the desk's composer. **One rounded box.** The words
|
|
24
|
+
on top, full width, growing with what is typed. Under them a row: **+**
|
|
25
|
+
(attach) at the left, and at the right the **microphone** and a **round
|
|
26
|
+
arrow** to send. **While a turn runs, a round stop takes the arrow's
|
|
27
|
+
place** (once the member types, the arrow comes back: a message sent then
|
|
28
|
+
waits its turn, as before). A quiet `hint` can sit under the box.
|
|
29
|
+
- **Files** by the + button, by paste and by a drop anywhere on the pane (up
|
|
30
|
+
to 4 files and 3 MB together); **dictation** where the browser has speech
|
|
31
|
+
recognition; the box **closed** while something waits on the member, with
|
|
32
|
+
the reason in its placeholder.
|
|
33
|
+
- **A starting line** from the dock: `openAgent({ ask })` (blocks/agent-dock).
|
|
34
|
+
The pane sends it as the member's first words in a new chat, or puts it
|
|
35
|
+
in the box. See "Have the agent do it" below.
|
|
36
|
+
|
|
37
|
+
```jsx
|
|
38
|
+
import { ChatPane } from "../components/blocks/chat-pane"; // a Makersclaw app ("./blocks/chat-pane" from components/)
|
|
39
|
+
import { ChatPane } from "@/components/makersclaw/blocks/chat-pane"; // a shadcn project
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
A client component. It needs the kit and nothing else; it is not tied to the
|
|
43
|
+
agent's lines. **For the agent's conversation use `agent-pane`**, which
|
|
44
|
+
draws the plan, the work folded into "Worked through N steps", the
|
|
45
|
+
questions, the yes and what the agent made, inside this pane. Use `ChatPane`
|
|
46
|
+
itself for lines of your own in the desk's frame.
|
|
47
|
+
|
|
48
|
+
## What was ported, and what was not
|
|
49
|
+
|
|
50
|
+
Ported: the head (New chat, Chats), the past chats list and its marks, the
|
|
51
|
+
empty state, the scroll that follows, the composer (its layout, the
|
|
52
|
+
attachments, the dictation, the send and stop), the closed box, and the
|
|
53
|
+
hand-off seam in a form the app needs (the starting line).
|
|
54
|
+
|
|
55
|
+
Not ported, because the app has no such thing: the desk's **rail** beside the
|
|
56
|
+
chat (an app's agent lives in the dock, a column; the Chats overlay is the
|
|
57
|
+
column mode's), the **binding** (headers, scope, the session pointer: the
|
|
58
|
+
app's `useAgentPane` holds those), **`openSession`/`pendingLine`** (the desk's
|
|
59
|
+
hand-off of a request into the agent's chat; the app's starting line does
|
|
60
|
+
that job), **`chatMarks` "In Makersclaw"** (an ask made in Makersclaw shows
|
|
61
|
+
as a pointer in the conversation instead) and the wallet gate (the platform
|
|
62
|
+
refuses a turn with no credits and says so).
|
|
63
|
+
|
|
64
|
+
## Props
|
|
65
|
+
|
|
66
|
+
- `chat` — what `useAgentPane()` returns. Required. The pane calls its
|
|
67
|
+
`send(text, { files })`, `stop()`, `newChat()` and `openSession(id)` and
|
|
68
|
+
reads `sessionId`, `request`, `chats`, `view`/`turns`/`pending`/`state`
|
|
69
|
+
(to follow the newest words), `sameConversation`, `sending`, `loading`,
|
|
70
|
+
`error`, `notice`, `unavailable`.
|
|
71
|
+
- `title` — the head's words, and the box's label. Default `"Ask the agent"`.
|
|
72
|
+
- `suggestions` — asks that fit the moment, as chips before the first ask.
|
|
73
|
+
- `empty` — one line before the first ask.
|
|
74
|
+
- `placeholder` — the box's words before the first ask. Once there is a
|
|
75
|
+
conversation it says "Ask a follow-up…" unless `chat.sameConversation` is
|
|
76
|
+
false.
|
|
77
|
+
- `closedWords` — the box's words while `blocked` ("Answer above first").
|
|
78
|
+
- `hint` — a quiet line under the box ("Enter sends · Shift+Enter is a new
|
|
79
|
+
line").
|
|
80
|
+
- `recent` — past chats listed before the first ask. Default `5`; `0` none.
|
|
81
|
+
- `bare` — no head, inside a `Sheet` or a `Card` that has its own.
|
|
82
|
+
- `fill` — default `true`: the pane takes its container's height and the
|
|
83
|
+
conversation scrolls inside it. `false`: compact, a capped height.
|
|
84
|
+
- `onClose` — a close in the head. Inside an `agent-dock` it is the dock's.
|
|
85
|
+
- `started` — there is a conversation to draw (else the empty state).
|
|
86
|
+
- `busy` — a turn runs: the arrow is a stop while the box is empty.
|
|
87
|
+
- `blocked` — something waits on the member: the whole box is closed.
|
|
88
|
+
- `bar` — a node between the conversation and the box.
|
|
89
|
+
- `children` — the conversation's lines.
|
|
90
|
+
- `className` — layout around it. Never colour.
|
|
91
|
+
|
|
92
|
+
`ChatComposer` is also exported, for a box of your own around the same
|
|
93
|
+
frame: `value`, `onChange`, `onSubmit`, `onStop`, `onAttach`, `files`,
|
|
94
|
+
`onRemoveFile`, `busy`, `canStop`, `stopping`, `blocked`, `sending`,
|
|
95
|
+
`encoding`, `dictation` (`useDictation()`), `placeholder`, `label`, `hint`,
|
|
96
|
+
`textareaRef`, `fileInputRef`. `ChatsList` draws the past chats.
|
|
97
|
+
|
|
98
|
+
## Have the agent do it
|
|
99
|
+
|
|
100
|
+
A button that says the agent will do the work does not open a form. It calls
|
|
101
|
+
`openAgent("Draft a project plan for me")` (blocks/agent-dock). The dock
|
|
102
|
+
opens, tells this pane the line through `ChatDockContext`, and the pane:
|
|
103
|
+
|
|
104
|
+
1. starts a new chat (if one is open; `fresh: false` carries on in it),
|
|
105
|
+
2. sends the line as the member's first words, once,
|
|
106
|
+
3. and tells the dock it took it.
|
|
107
|
+
|
|
108
|
+
With `send: false` it puts the line in the box instead, with the cursor at
|
|
109
|
+
its end (words the member already typed stay, and the line goes under them). A closed box (something waits on the member) always takes the line
|
|
110
|
+
as a prefill. The pure rule is `startStep(...)` in `chat-model.js`.
|
|
111
|
+
|
|
112
|
+
## The model (plain functions)
|
|
113
|
+
|
|
114
|
+
`ago(iso)`, `startStep({ start, handled, sessionId, blocked, canNew, newed })`,
|
|
115
|
+
and the composer's parts: `addFiles`, `filesFromTransfer`, `readFile`,
|
|
116
|
+
`formatBytes`, `MAX_FILES`, `MAX_BYTES`, `useDictation`.
|
|
117
|
+
|
|
118
|
+
## Example
|
|
119
|
+
|
|
120
|
+
```jsx
|
|
121
|
+
"use client";
|
|
122
|
+
import { useAgentPane } from "../lib/hooks";
|
|
123
|
+
import { ChatPane } from "../components/blocks/chat-pane";
|
|
124
|
+
|
|
125
|
+
export default function Notes() {
|
|
126
|
+
const chat = useAgentPane({ key: "notes" });
|
|
127
|
+
const messages = chat.view?.messages ?? [];
|
|
128
|
+
return (
|
|
129
|
+
<ChatPane
|
|
130
|
+
chat={chat}
|
|
131
|
+
title="Ask about your notes"
|
|
132
|
+
suggestions={["Summarise this week"]}
|
|
133
|
+
hint="Enter sends · Shift+Enter is a new line"
|
|
134
|
+
started={messages.length > 0}
|
|
135
|
+
busy={chat.state === "working"}
|
|
136
|
+
>
|
|
137
|
+
{messages.map((m) => (
|
|
138
|
+
<p key={m.id}>{m.text}</p>
|
|
139
|
+
))}
|
|
140
|
+
</ChatPane>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
```
|