makersclaw 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +180 -0
- package/package.json +3 -2
- package/r/annotate.json +62 -0
- package/r/approval-request.json +52 -0
- package/r/audio-player.json +74 -0
- package/r/auth-form.json +55 -0
- package/r/board.json +10 -6
- package/r/calendar-view.json +105 -0
- package/r/cart.json +62 -0
- package/r/chart.json +83 -0
- package/r/chat.json +95 -0
- package/r/code-editor.json +94 -0
- package/r/comment-box.json +64 -0
- package/r/contact-section.json +53 -0
- package/r/core.json +205 -8
- package/r/csv-import.json +63 -0
- package/r/data-table.json +69 -0
- package/r/diagram.json +74 -0
- package/r/doc-editor.json +82 -0
- package/r/doc-page.json +61 -0
- package/r/email-render.json +73 -0
- package/r/faq.json +50 -0
- package/r/file-upload.json +54 -0
- package/r/flow-canvas.json +93 -0
- package/r/footer.json +52 -0
- package/r/gantt.json +70 -0
- package/r/job-panel.json +12 -7
- package/r/json-editor.json +66 -0
- package/r/map.json +89 -0
- package/r/menubar.json +52 -0
- package/r/mind-map.json +67 -0
- package/r/navigation-menu.json +52 -0
- package/r/order-detail.json +54 -0
- package/r/org-chart.json +66 -0
- package/r/page-builder.json +105 -0
- package/r/payment-methods.json +51 -0
- package/r/pdf-document.json +79 -0
- package/r/pdf-viewer.json +88 -0
- package/r/product-detail.json +76 -0
- package/r/product-grid.json +65 -0
- package/r/record-form.json +67 -0
- package/r/record-notes.json +55 -0
- package/r/registry.json +3411 -29
- package/r/search-dialog.json +51 -0
- package/r/settings-list.json +66 -0
- package/r/setup-checklist.json +51 -0
- package/r/share-dialog.json +52 -0
- package/r/sidebar.json +69 -0
- package/r/signature-pad.json +72 -0
- package/r/sketch-canvas.json +99 -0
- package/r/spreadsheet.json +90 -0
- package/r/stat-cards.json +66 -0
- package/r/task-list.json +63 -0
- package/r/task-model.json +98 -0
- package/r/testimonials.json +52 -0
- package/r/transaction-list.json +53 -0
- package/r/whiteboard.json +66 -0
- package/registry/blocks/annotate/README.md +179 -0
- package/registry/blocks/annotate/annotate-data.js +250 -0
- package/registry/blocks/annotate/annotate.jsx +646 -0
- package/registry/blocks/annotate/block.json +10 -0
- package/registry/blocks/annotate/demo.jsx +135 -0
- package/registry/blocks/annotate/index.js +15 -0
- package/registry/blocks/approval-request/README.md +116 -0
- package/registry/blocks/approval-request/approval-request.jsx +386 -0
- package/registry/blocks/approval-request/block.json +10 -0
- package/registry/blocks/approval-request/demo.jsx +127 -0
- package/registry/blocks/approval-request/index.js +4 -0
- package/registry/blocks/audio-player/README.md +167 -0
- package/registry/blocks/audio-player/audio-player.jsx +623 -0
- package/registry/blocks/audio-player/block.json +14 -0
- package/registry/blocks/audio-player/colors.js +109 -0
- package/registry/blocks/audio-player/demo.jsx +209 -0
- package/registry/blocks/audio-player/index.js +6 -0
- package/registry/blocks/audio-player/time.js +67 -0
- package/registry/blocks/audio-player/wavesurfer.js +9 -0
- package/registry/blocks/auth-form/README.md +126 -0
- package/registry/blocks/auth-form/auth-form.jsx +568 -0
- package/registry/blocks/auth-form/block.json +10 -0
- package/registry/blocks/auth-form/demo.jsx +131 -0
- package/registry/blocks/auth-form/index.js +3 -0
- package/registry/blocks/board/README.md +55 -5
- package/registry/blocks/board/block.json +2 -2
- package/registry/blocks/board/board.jsx +230 -86
- package/registry/blocks/board/demo.jsx +103 -24
- package/registry/blocks/calendar-view/README.md +180 -0
- package/registry/blocks/calendar-view/agenda.jsx +147 -0
- package/registry/blocks/calendar-view/block.json +76 -0
- package/registry/blocks/calendar-view/calendar-model.js +442 -0
- package/registry/blocks/calendar-view/calendar.jsx +627 -0
- package/registry/blocks/calendar-view/demo.jsx +195 -0
- package/registry/blocks/calendar-view/draft.jsx +49 -0
- package/registry/blocks/calendar-view/index.js +39 -0
- package/registry/blocks/calendar-view/month.jsx +346 -0
- package/registry/blocks/calendar-view/paint.js +69 -0
- package/registry/blocks/calendar-view/time-grid.jsx +527 -0
- package/registry/blocks/calendar-view/tray.jsx +87 -0
- package/registry/blocks/cart/README.md +146 -0
- package/registry/blocks/cart/block.json +10 -0
- package/registry/blocks/cart/cart.jsx +597 -0
- package/registry/blocks/cart/demo.jsx +98 -0
- package/registry/blocks/cart/index.js +4 -0
- package/registry/blocks/cart/money.js +28 -0
- package/registry/blocks/chart/README.md +316 -0
- package/registry/blocks/chart/block.json +10 -0
- package/registry/blocks/chart/cartesian.jsx +642 -0
- package/registry/blocks/chart/core.jsx +262 -0
- package/registry/blocks/chart/demo.jsx +357 -0
- package/registry/blocks/chart/heatmap.jsx +336 -0
- package/registry/blocks/chart/index.js +8 -0
- package/registry/blocks/chart/pie.jsx +245 -0
- package/registry/blocks/chart/sparkline.jsx +88 -0
- package/registry/blocks/chat/README.md +273 -0
- package/registry/blocks/chat/attachment.jsx +95 -0
- package/registry/blocks/chat/block.json +10 -0
- package/registry/blocks/chat/chat.jsx +132 -0
- package/registry/blocks/chat/composer.jsx +364 -0
- package/registry/blocks/chat/demo.jsx +578 -0
- package/registry/blocks/chat/header.jsx +34 -0
- package/registry/blocks/chat/index.js +9 -0
- package/registry/blocks/chat/message.jsx +450 -0
- package/registry/blocks/chat/scroller.jsx +247 -0
- package/registry/blocks/chat/text.jsx +104 -0
- package/registry/blocks/code-editor/README.md +210 -0
- package/registry/blocks/code-editor/block.json +22 -0
- package/registry/blocks/code-editor/code-editor.jsx +534 -0
- package/registry/blocks/code-editor/codemirror.js +38 -0
- package/registry/blocks/code-editor/demo.jsx +205 -0
- package/registry/blocks/code-editor/index.js +8 -0
- package/registry/blocks/code-editor/json.js +317 -0
- package/registry/blocks/code-editor/sql.js +136 -0
- package/registry/blocks/code-editor/text.js +101 -0
- package/registry/blocks/code-editor/theme.js +186 -0
- package/registry/blocks/comment-box/README.md +123 -0
- package/registry/blocks/comment-box/block.json +10 -0
- package/registry/blocks/comment-box/comment-body.jsx +217 -0
- package/registry/blocks/comment-box/comment-box.jsx +198 -0
- package/registry/blocks/comment-box/comment-time.jsx +77 -0
- package/registry/blocks/comment-box/demo.jsx +141 -0
- package/registry/blocks/comment-box/index.js +8 -0
- package/registry/blocks/contact-section/README.md +152 -0
- package/registry/blocks/contact-section/block.json +11 -0
- package/registry/blocks/contact-section/contact-section.jsx +509 -0
- package/registry/blocks/contact-section/demo.jsx +93 -0
- package/registry/blocks/contact-section/index.js +4 -0
- package/registry/blocks/csv-import/README.md +133 -0
- package/registry/blocks/csv-import/block.json +13 -0
- package/registry/blocks/csv-import/csv-import.jsx +479 -0
- package/registry/blocks/csv-import/demo.jsx +106 -0
- package/registry/blocks/csv-import/import.js +200 -0
- package/registry/blocks/csv-import/index.js +7 -0
- package/registry/blocks/data-table/README.md +386 -0
- package/registry/blocks/data-table/block.json +10 -0
- package/registry/blocks/data-table/data-table.jsx +1164 -0
- package/registry/blocks/data-table/demo.jsx +588 -0
- package/registry/blocks/data-table/index.js +5 -0
- package/registry/blocks/data-table/model.js +169 -0
- package/registry/blocks/diagram/README.md +108 -0
- package/registry/blocks/diagram/block.json +13 -0
- package/registry/blocks/diagram/demo.jsx +113 -0
- package/registry/blocks/diagram/diagram.jsx +481 -0
- package/registry/blocks/diagram/index.js +7 -0
- package/registry/blocks/diagram/mermaid.js +52 -0
- package/registry/blocks/diagram/palette.js +313 -0
- package/registry/blocks/diagram/source.js +98 -0
- package/registry/blocks/doc-editor/README.md +215 -0
- package/registry/blocks/doc-editor/block.json +15 -0
- package/registry/blocks/doc-editor/demo.jsx +225 -0
- package/registry/blocks/doc-editor/doc-editor.jsx +291 -0
- package/registry/blocks/doc-editor/doc.js +309 -0
- package/registry/blocks/doc-editor/index.js +7 -0
- package/registry/blocks/doc-editor/schema.jsx +56 -0
- package/registry/blocks/doc-editor/shadcn-classes.js +110 -0
- package/registry/blocks/doc-editor/theme.js +82 -0
- package/registry/blocks/doc-page/README.md +119 -0
- package/registry/blocks/doc-page/autosave.js +104 -0
- package/registry/blocks/doc-page/block.json +10 -0
- package/registry/blocks/doc-page/demo.jsx +137 -0
- package/registry/blocks/doc-page/doc-page.jsx +276 -0
- package/registry/blocks/doc-page/index.js +4 -0
- package/registry/blocks/email-render/README.md +151 -0
- package/registry/blocks/email-render/block.json +14 -0
- package/registry/blocks/email-render/demo.jsx +153 -0
- package/registry/blocks/email-render/doc-email.jsx +210 -0
- package/registry/blocks/email-render/email.jsx +221 -0
- package/registry/blocks/email-render/index.js +10 -0
- package/registry/blocks/email-render/render.js +50 -0
- package/registry/blocks/email-render/theme.js +215 -0
- package/registry/blocks/faq/README.md +101 -0
- package/registry/blocks/faq/block.json +11 -0
- package/registry/blocks/faq/demo.jsx +89 -0
- package/registry/blocks/faq/faq.jsx +220 -0
- package/registry/blocks/faq/index.js +4 -0
- package/registry/blocks/file-upload/README.md +138 -0
- package/registry/blocks/file-upload/block.json +10 -0
- package/registry/blocks/file-upload/demo.jsx +204 -0
- package/registry/blocks/file-upload/file-upload.jsx +635 -0
- package/registry/blocks/file-upload/index.js +4 -0
- package/registry/blocks/flow-canvas/README.md +420 -0
- package/registry/blocks/flow-canvas/block.json +13 -0
- package/registry/blocks/flow-canvas/board.js +819 -0
- package/registry/blocks/flow-canvas/chrome.jsx +649 -0
- package/registry/blocks/flow-canvas/context.jsx +35 -0
- package/registry/blocks/flow-canvas/demo.jsx +249 -0
- package/registry/blocks/flow-canvas/edges.jsx +107 -0
- package/registry/blocks/flow-canvas/flow-canvas.jsx +1329 -0
- package/registry/blocks/flow-canvas/index.js +41 -0
- package/registry/blocks/flow-canvas/nodes.jsx +392 -0
- package/registry/blocks/flow-canvas/theme.js +174 -0
- package/registry/blocks/footer/README.md +133 -0
- package/registry/blocks/footer/block.json +11 -0
- package/registry/blocks/footer/demo.jsx +119 -0
- package/registry/blocks/footer/footer.jsx +406 -0
- package/registry/blocks/footer/index.js +4 -0
- package/registry/blocks/gantt/README.md +220 -0
- package/registry/blocks/gantt/block.json +10 -0
- package/registry/blocks/gantt/demo.jsx +148 -0
- package/registry/blocks/gantt/gantt.jsx +923 -0
- package/registry/blocks/gantt/index.js +7 -0
- package/registry/blocks/gantt/rows.jsx +468 -0
- package/registry/blocks/gantt/schedule.js +278 -0
- package/registry/blocks/job-panel/README.md +80 -0
- package/registry/blocks/job-panel/block.json +3 -3
- package/registry/blocks/job-panel/demo.jsx +230 -3
- package/registry/blocks/job-panel/job-panel.jsx +169 -3
- package/registry/blocks/json-editor/README.md +133 -0
- package/registry/blocks/json-editor/block.json +10 -0
- package/registry/blocks/json-editor/demo.jsx +92 -0
- package/registry/blocks/json-editor/index.js +7 -0
- package/registry/blocks/json-editor/json-editor.jsx +305 -0
- package/registry/blocks/json-editor/json-tree.jsx +393 -0
- package/registry/blocks/json-editor/tree.js +225 -0
- package/registry/blocks/map/README.md +221 -0
- package/registry/blocks/map/block.json +14 -0
- package/registry/blocks/map/demo.jsx +201 -0
- package/registry/blocks/map/geo.js +198 -0
- package/registry/blocks/map/index.js +8 -0
- package/registry/blocks/map/list.jsx +128 -0
- package/registry/blocks/map/map-view.jsx +715 -0
- package/registry/blocks/map/maplibre.js +30 -0
- package/registry/blocks/map/marks.jsx +146 -0
- package/registry/blocks/map/theme.js +142 -0
- package/registry/blocks/menubar/README.md +109 -0
- package/registry/blocks/menubar/block.json +10 -0
- package/registry/blocks/menubar/demo.jsx +128 -0
- package/registry/blocks/menubar/index.js +4 -0
- package/registry/blocks/menubar/menubar.jsx +199 -0
- package/registry/blocks/mind-map/README.md +206 -0
- package/registry/blocks/mind-map/block.json +10 -0
- package/registry/blocks/mind-map/demo.jsx +103 -0
- package/registry/blocks/mind-map/index.js +31 -0
- package/registry/blocks/mind-map/mind-map.jsx +1125 -0
- package/registry/blocks/mind-map/topic.jsx +143 -0
- package/registry/blocks/mind-map/tree.js +645 -0
- package/registry/blocks/navigation-menu/README.md +124 -0
- package/registry/blocks/navigation-menu/block.json +10 -0
- package/registry/blocks/navigation-menu/demo.jsx +84 -0
- package/registry/blocks/navigation-menu/index.js +4 -0
- package/registry/blocks/navigation-menu/navigation-menu.jsx +448 -0
- package/registry/blocks/order-detail/README.md +109 -0
- package/registry/blocks/order-detail/block.json +10 -0
- package/registry/blocks/order-detail/demo.jsx +88 -0
- package/registry/blocks/order-detail/index.js +4 -0
- package/registry/blocks/order-detail/order-detail.jsx +354 -0
- package/registry/blocks/org-chart/README.md +145 -0
- package/registry/blocks/org-chart/block.json +10 -0
- package/registry/blocks/org-chart/demo.jsx +94 -0
- package/registry/blocks/org-chart/index.js +7 -0
- package/registry/blocks/org-chart/org-chart.jsx +631 -0
- package/registry/blocks/org-chart/org.js +245 -0
- package/registry/blocks/org-chart/person.jsx +96 -0
- package/registry/blocks/page-builder/README.md +257 -0
- package/registry/blocks/page-builder/block.json +14 -0
- package/registry/blocks/page-builder/demo.jsx +145 -0
- package/registry/blocks/page-builder/grapes.js +18 -0
- package/registry/blocks/page-builder/index.js +11 -0
- package/registry/blocks/page-builder/page-builder.jsx +704 -0
- package/registry/blocks/page-builder/page-view.jsx +33 -0
- package/registry/blocks/page-builder/page.js +118 -0
- package/registry/blocks/page-builder/panels.jsx +588 -0
- package/registry/blocks/page-builder/sections.js +209 -0
- package/registry/blocks/page-builder/theme.js +87 -0
- package/registry/blocks/page-builder/thumbs.jsx +189 -0
- package/registry/blocks/page-builder/tokens.js +329 -0
- package/registry/blocks/payment-methods/README.md +104 -0
- package/registry/blocks/payment-methods/block.json +10 -0
- package/registry/blocks/payment-methods/demo.jsx +71 -0
- package/registry/blocks/payment-methods/index.js +4 -0
- package/registry/blocks/payment-methods/payment-methods.jsx +302 -0
- package/registry/blocks/pdf-document/README.md +213 -0
- package/registry/blocks/pdf-document/block.json +13 -0
- package/registry/blocks/pdf-document/demo.jsx +136 -0
- package/registry/blocks/pdf-document/index.js +33 -0
- package/registry/blocks/pdf-document/money.js +83 -0
- package/registry/blocks/pdf-document/pdf-document.jsx +144 -0
- package/registry/blocks/pdf-document/primitives.jsx +271 -0
- package/registry/blocks/pdf-document/quote.jsx +179 -0
- package/registry/blocks/pdf-document/theme.js +193 -0
- package/registry/blocks/pdf-viewer/README.md +191 -0
- package/registry/blocks/pdf-viewer/block.json +14 -0
- package/registry/blocks/pdf-viewer/demo.jsx +252 -0
- package/registry/blocks/pdf-viewer/helpers.js +270 -0
- package/registry/blocks/pdf-viewer/index.js +8 -0
- package/registry/blocks/pdf-viewer/notes.jsx +195 -0
- package/registry/blocks/pdf-viewer/pdf-viewer.jsx +1368 -0
- package/registry/blocks/pdf-viewer/pdfjs.js +37 -0
- package/registry/blocks/pdf-viewer/ssr-shim-end.js +6 -0
- package/registry/blocks/pdf-viewer/ssr-shim.js +44 -0
- package/registry/blocks/product-detail/README.md +217 -0
- package/registry/blocks/product-detail/block.json +10 -0
- package/registry/blocks/product-detail/demo.jsx +233 -0
- package/registry/blocks/product-detail/index.js +7 -0
- package/registry/blocks/product-detail/price.jsx +115 -0
- package/registry/blocks/product-detail/product-detail.jsx +348 -0
- package/registry/blocks/product-detail/product-media.jsx +102 -0
- package/registry/blocks/product-detail/product-options.jsx +144 -0
- package/registry/blocks/product-grid/README.md +162 -0
- package/registry/blocks/product-grid/block.json +10 -0
- package/registry/blocks/product-grid/demo.jsx +255 -0
- package/registry/blocks/product-grid/index.js +5 -0
- package/registry/blocks/product-grid/product-card.jsx +232 -0
- package/registry/blocks/product-grid/product-grid.jsx +235 -0
- package/registry/blocks/record-form/README.md +196 -0
- package/registry/blocks/record-form/block.json +10 -0
- package/registry/blocks/record-form/demo.jsx +525 -0
- package/registry/blocks/record-form/index.js +4 -0
- package/registry/blocks/record-form/record-fields.jsx +457 -0
- package/registry/blocks/record-form/record-form.jsx +415 -0
- package/registry/blocks/record-form/rules.js +81 -0
- package/registry/blocks/record-notes/README.md +139 -0
- package/registry/blocks/record-notes/block.json +10 -0
- package/registry/blocks/record-notes/demo.jsx +179 -0
- package/registry/blocks/record-notes/index.js +4 -0
- package/registry/blocks/record-notes/record-notes.jsx +266 -0
- package/registry/blocks/search-dialog/README.md +108 -0
- package/registry/blocks/search-dialog/block.json +10 -0
- package/registry/blocks/search-dialog/demo.jsx +116 -0
- package/registry/blocks/search-dialog/index.js +3 -0
- package/registry/blocks/search-dialog/search-dialog.jsx +235 -0
- package/registry/blocks/settings-list/README.md +171 -0
- package/registry/blocks/settings-list/block.json +10 -0
- package/registry/blocks/settings-list/demo.jsx +333 -0
- package/registry/blocks/settings-list/index.js +4 -0
- package/registry/blocks/settings-list/row-state.jsx +93 -0
- package/registry/blocks/settings-list/settings-list.jsx +349 -0
- package/registry/blocks/settings-list/settings-matrix.jsx +118 -0
- package/registry/blocks/setup-checklist/README.md +142 -0
- package/registry/blocks/setup-checklist/block.json +10 -0
- package/registry/blocks/setup-checklist/demo.jsx +159 -0
- package/registry/blocks/setup-checklist/index.js +4 -0
- package/registry/blocks/setup-checklist/setup-checklist.jsx +446 -0
- package/registry/blocks/share-dialog/README.md +113 -0
- package/registry/blocks/share-dialog/block.json +10 -0
- package/registry/blocks/share-dialog/demo.jsx +140 -0
- package/registry/blocks/share-dialog/index.js +3 -0
- package/registry/blocks/share-dialog/share-dialog.jsx +384 -0
- package/registry/blocks/sidebar/README.md +190 -0
- package/registry/blocks/sidebar/block.json +43 -0
- package/registry/blocks/sidebar/demo.jsx +577 -0
- package/registry/blocks/sidebar/index.js +4 -0
- package/registry/blocks/sidebar/sidebar-panel.jsx +149 -0
- package/registry/blocks/sidebar/sidebar.jsx +864 -0
- package/registry/blocks/signature-pad/README.md +137 -0
- package/registry/blocks/signature-pad/block.json +13 -0
- package/registry/blocks/signature-pad/colors.js +115 -0
- package/registry/blocks/signature-pad/demo.jsx +94 -0
- package/registry/blocks/signature-pad/index.js +6 -0
- package/registry/blocks/signature-pad/pad.js +7 -0
- package/registry/blocks/signature-pad/signature-pad.jsx +434 -0
- package/registry/blocks/signature-pad/signature.js +68 -0
- package/registry/blocks/sketch-canvas/README.md +280 -0
- package/registry/blocks/sketch-canvas/block.json +13 -0
- package/registry/blocks/sketch-canvas/demo.jsx +159 -0
- package/registry/blocks/sketch-canvas/excalidraw.js +40 -0
- package/registry/blocks/sketch-canvas/index.js +9 -0
- package/registry/blocks/sketch-canvas/palette.js +226 -0
- package/registry/blocks/sketch-canvas/sketch-canvas.jsx +1188 -0
- package/registry/blocks/sketch-canvas/sketch.js +179 -0
- package/registry/blocks/sketch-canvas/ssr-shim-end.js +6 -0
- package/registry/blocks/sketch-canvas/ssr-shim.js +45 -0
- package/registry/blocks/sketch-canvas/stamps.js +104 -0
- package/registry/blocks/sketch-canvas/theme.js +131 -0
- package/registry/blocks/spreadsheet/README.md +307 -0
- package/registry/blocks/spreadsheet/block.json +13 -0
- package/registry/blocks/spreadsheet/cells.jsx +455 -0
- package/registry/blocks/spreadsheet/chrome.jsx +325 -0
- package/registry/blocks/spreadsheet/demo.jsx +177 -0
- package/registry/blocks/spreadsheet/grid.js +387 -0
- package/registry/blocks/spreadsheet/index.js +34 -0
- package/registry/blocks/spreadsheet/spreadsheet.jsx +1732 -0
- package/registry/blocks/spreadsheet/types.js +473 -0
- package/registry/blocks/stat-cards/README.md +173 -0
- package/registry/blocks/stat-cards/block.json +10 -0
- package/registry/blocks/stat-cards/demo.jsx +257 -0
- package/registry/blocks/stat-cards/index.js +4 -0
- package/registry/blocks/stat-cards/stat-cards.jsx +307 -0
- package/registry/blocks/stat-cards/usage.jsx +243 -0
- package/registry/blocks/task-list/README.md +176 -0
- package/registry/blocks/task-list/block.json +10 -0
- package/registry/blocks/task-list/demo.jsx +134 -0
- package/registry/blocks/task-list/index.js +5 -0
- package/registry/blocks/task-list/task-list.jsx +907 -0
- package/registry/blocks/task-model/README.md +455 -0
- package/registry/blocks/task-model/block.json +167 -0
- package/registry/blocks/task-model/changes.js +89 -0
- package/registry/blocks/task-model/dates.js +371 -0
- package/registry/blocks/task-model/demo.jsx +215 -0
- package/registry/blocks/task-model/dnd.jsx +240 -0
- package/registry/blocks/task-model/index.js +97 -0
- package/registry/blocks/task-model/model.js +919 -0
- package/registry/blocks/task-model/parts.jsx +449 -0
- package/registry/blocks/task-model/toolbar.jsx +271 -0
- package/registry/blocks/testimonials/README.md +139 -0
- package/registry/blocks/testimonials/block.json +11 -0
- package/registry/blocks/testimonials/demo.jsx +137 -0
- package/registry/blocks/testimonials/index.js +4 -0
- package/registry/blocks/testimonials/testimonials.jsx +430 -0
- package/registry/blocks/transaction-list/README.md +126 -0
- package/registry/blocks/transaction-list/block.json +10 -0
- package/registry/blocks/transaction-list/demo.jsx +79 -0
- package/registry/blocks/transaction-list/index.js +4 -0
- package/registry/blocks/transaction-list/transaction-list.jsx +383 -0
- package/registry/blocks/whiteboard/README.md +179 -0
- package/registry/blocks/whiteboard/block.json +10 -0
- package/registry/blocks/whiteboard/demo.jsx +169 -0
- package/registry/blocks/whiteboard/index.js +6 -0
- package/registry/blocks/whiteboard/templates.js +150 -0
- package/registry/blocks/whiteboard/whiteboard.jsx +721 -0
- package/src/install.mjs +18 -0
- package/src/shadcn.mjs +8 -0
- package/templates/next/files/README.md +145 -11
- package/templates/next/files/app/globals.css +55 -4
- package/templates/next/files/check/run.mjs +3 -3
- package/templates/next/files/components/ui/avatar.jsx +141 -0
- package/templates/next/files/components/ui/breadcrumb.jsx +149 -0
- package/templates/next/files/components/ui/button-group.jsx +70 -0
- package/templates/next/files/components/ui/button.jsx +132 -12
- package/templates/next/files/components/ui/calendar.jsx +609 -0
- package/templates/next/files/components/ui/carousel.jsx +281 -0
- package/templates/next/files/components/ui/collapsible.jsx +208 -0
- package/templates/next/files/components/ui/combobox.jsx +608 -0
- package/templates/next/files/components/ui/command.jsx +577 -0
- package/templates/next/files/components/ui/context-menu.jsx +191 -0
- package/templates/next/files/components/ui/date-picker.jsx +360 -0
- package/templates/next/files/components/ui/distribution.jsx +117 -0
- package/templates/next/files/components/ui/feedback.jsx +143 -0
- package/templates/next/files/components/ui/field.jsx +115 -3
- package/templates/next/files/components/ui/file-input.jsx +234 -0
- package/templates/next/files/components/ui/icon.jsx +130 -8
- package/templates/next/files/components/ui/image.jsx +143 -0
- package/templates/next/files/components/ui/index.js +47 -2
- package/templates/next/files/components/ui/input-group.jsx +213 -0
- package/templates/next/files/components/ui/input-otp.jsx +170 -0
- package/templates/next/files/components/ui/item.jsx +206 -0
- package/templates/next/files/components/ui/menu.jsx +454 -82
- package/templates/next/files/components/ui/notice.jsx +126 -14
- package/templates/next/files/components/ui/pagination.jsx +156 -0
- package/templates/next/files/components/ui/popover.jsx +284 -0
- package/templates/next/files/components/ui/radio.jsx +193 -0
- package/templates/next/files/components/ui/rating.jsx +131 -0
- package/templates/next/files/components/ui/resizable.jsx +338 -0
- package/templates/next/files/components/ui/scroll-area.jsx +123 -0
- package/templates/next/files/components/ui/separator.jsx +119 -0
- package/templates/next/files/components/ui/sheet.jsx +96 -11
- package/templates/next/files/components/ui/slider.jsx +174 -0
- package/templates/next/files/components/ui/stat.jsx +574 -0
- package/templates/next/files/components/ui/steps.jsx +338 -0
- package/templates/next/files/components/ui/swatch.jsx +33 -0
- package/templates/next/files/components/ui/table.jsx +130 -0
- package/templates/next/files/components/ui/toast.jsx +440 -0
- package/templates/next/files/components/ui/toggle.jsx +195 -0
- package/templates/next/files/components/ui/tooltip.jsx +270 -0
- package/templates/next/template.json +264 -8
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
4
|
+
import { Avatar, Button, Collapsible, IconButton, Icon, Menu, Spinner, Textarea, Toggle, Tooltip, cx } from "../../ui";
|
|
5
|
+
import { Attachment } from "./attachment";
|
|
6
|
+
import { ChatText } from "./text";
|
|
7
|
+
|
|
8
|
+
// Message and Bubble — one turn of a conversation. A message is its
|
|
9
|
+
// author (avatar, name, time — shown once for a run of messages from the
|
|
10
|
+
// same author), its words in a Bubble, its files, its state (sending,
|
|
11
|
+
// streaming, failed with Retry) and the actions that appear on hover or
|
|
12
|
+
// focus (copy, retry, a Menu of the surface's own). An assistant reply can
|
|
13
|
+
// carry its reasoning (folded under "Thought for 4s") and take a thumbs up
|
|
14
|
+
// or down; a user message can be edited in place.
|
|
15
|
+
|
|
16
|
+
/** The text of a message: `content` when it is a string, else the text
|
|
17
|
+
* parts of `parts` / `content` ([{ type: "text", text }]), joined. */
|
|
18
|
+
export function textOf(message) {
|
|
19
|
+
const c = message?.content;
|
|
20
|
+
if (typeof c === "string") return c;
|
|
21
|
+
const parts = Array.isArray(c) ? c : Array.isArray(message?.parts) ? message.parts : [];
|
|
22
|
+
return parts
|
|
23
|
+
.filter((p) => p && (p.type === "text" || typeof p === "string"))
|
|
24
|
+
.map((p) => (typeof p === "string" ? p : p.text ?? ""))
|
|
25
|
+
.join("");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function toDate(value) {
|
|
29
|
+
if (value == null || value === "") return null;
|
|
30
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
31
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function formatTime(date) {
|
|
35
|
+
return date.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The container for a message's words.
|
|
40
|
+
* from user · assistant — user bubbles are filled and sit at the end
|
|
41
|
+
* variant bubble · plain (assistant text on the page, no box)
|
|
42
|
+
* first / last its place in a run from one author: the corner on the
|
|
43
|
+
* author's side tightens between messages of one run
|
|
44
|
+
* text a string, drawn by the safe renderer; or pass children
|
|
45
|
+
*/
|
|
46
|
+
export function Bubble({ from = "assistant", variant = "bubble", first = true, last = true, text, streaming, className, children }) {
|
|
47
|
+
const user = from === "user";
|
|
48
|
+
const plain = !user && variant === "plain";
|
|
49
|
+
const caret = streaming ? (
|
|
50
|
+
<span aria-hidden="true" className="ml-0.5 inline-block h-[1em] w-1.5 translate-y-0.5 animate-pulse rounded-sm bg-current opacity-60" />
|
|
51
|
+
) : null;
|
|
52
|
+
const body = text !== undefined ? <ChatText text={text} tone={user ? "primary" : undefined} trailing={caret} /> : children;
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
className={cx(
|
|
56
|
+
"min-w-0 max-w-full text-sm leading-relaxed",
|
|
57
|
+
plain
|
|
58
|
+
? "py-0.5"
|
|
59
|
+
: cx(
|
|
60
|
+
"rounded-2xl px-3.5 py-2.5",
|
|
61
|
+
user ? "bg-primary text-primary-foreground" : "bg-muted text-foreground",
|
|
62
|
+
user ? [!first && "rounded-tr-md", !last && "rounded-br-md"] : [!first && "rounded-tl-md", !last && "rounded-bl-md"],
|
|
63
|
+
),
|
|
64
|
+
className,
|
|
65
|
+
)}
|
|
66
|
+
>
|
|
67
|
+
{body}
|
|
68
|
+
{text === undefined ? caret : null}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function formatSeconds(s) {
|
|
74
|
+
const n = Math.max(0, Math.round(Number(s) || 0));
|
|
75
|
+
return n < 60 ? `${n}s` : `${Math.floor(n / 60)}m ${n % 60}s`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The folding reasoning over an assistant reply: "Thinking…" while it
|
|
79
|
+
* streams with no answer yet, then "Thought for 4s". Closed by default. */
|
|
80
|
+
function Reasoning({ text, seconds, thinking }) {
|
|
81
|
+
const [open, setOpen] = useState(false);
|
|
82
|
+
const title = thinking ? "Thinking…" : seconds ? `Thought for ${formatSeconds(seconds)}` : "Reasoning";
|
|
83
|
+
return (
|
|
84
|
+
<Collapsible
|
|
85
|
+
open={open}
|
|
86
|
+
onOpenChange={setOpen}
|
|
87
|
+
chevron="right"
|
|
88
|
+
title={<span className={cx("font-normal text-muted-foreground", thinking && "animate-pulse motion-reduce:animate-none")}>{title}</span>}
|
|
89
|
+
className="w-full max-w-prose"
|
|
90
|
+
>
|
|
91
|
+
<div className="border-l-2 border-border pl-3 text-xs leading-relaxed text-muted-foreground">
|
|
92
|
+
{text ? <ChatText text={text} /> : <span>Nothing to show yet.</span>}
|
|
93
|
+
</div>
|
|
94
|
+
</Collapsible>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** A sent message's words, back in a field: Enter (or Save) saves through
|
|
99
|
+
* `onSave(text)`, Esc (or Cancel) leaves it as it was. A failed save keeps
|
|
100
|
+
* the field open with the reason under it. */
|
|
101
|
+
function EditBox({ initial, onSave, onCancel }) {
|
|
102
|
+
const id = useId();
|
|
103
|
+
const [text, setText] = useState(initial);
|
|
104
|
+
const [saving, setSaving] = useState(false);
|
|
105
|
+
const [error, setError] = useState(null);
|
|
106
|
+
|
|
107
|
+
async function save() {
|
|
108
|
+
const next = text.trim();
|
|
109
|
+
if (!next || saving) return;
|
|
110
|
+
if (next === initial.trim()) return onCancel();
|
|
111
|
+
setSaving(true);
|
|
112
|
+
setError(null);
|
|
113
|
+
let r;
|
|
114
|
+
try {
|
|
115
|
+
r = await onSave(next);
|
|
116
|
+
} catch (e) {
|
|
117
|
+
r = { ok: false, error: e?.message ?? String(e) };
|
|
118
|
+
}
|
|
119
|
+
setSaving(false);
|
|
120
|
+
if (r && r.ok === false) setError(r.error ?? "The change was not saved");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div className="grid w-[32rem] max-w-full gap-2">
|
|
125
|
+
<label htmlFor={id} className="sr-only">
|
|
126
|
+
Edit message
|
|
127
|
+
</label>
|
|
128
|
+
<Textarea
|
|
129
|
+
id={id}
|
|
130
|
+
autoFocus
|
|
131
|
+
autoGrow
|
|
132
|
+
minRows={2}
|
|
133
|
+
maxRows={10}
|
|
134
|
+
value={text}
|
|
135
|
+
invalid={Boolean(error)}
|
|
136
|
+
aria-describedby={error ? `${id}-error` : undefined}
|
|
137
|
+
onChange={(e) => setText(e.target.value)}
|
|
138
|
+
onKeyDown={(e) => {
|
|
139
|
+
if (e.key === "Escape") {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
onCancel();
|
|
142
|
+
} else if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
save();
|
|
145
|
+
}
|
|
146
|
+
}}
|
|
147
|
+
/>
|
|
148
|
+
{error ? (
|
|
149
|
+
<p id={`${id}-error`} role="alert" className="text-xs text-destructive">
|
|
150
|
+
{error}
|
|
151
|
+
</p>
|
|
152
|
+
) : null}
|
|
153
|
+
<div className="flex justify-end gap-2">
|
|
154
|
+
<Button size="sm" variant="quiet" onClick={onCancel} disabled={saving}>
|
|
155
|
+
Cancel
|
|
156
|
+
</Button>
|
|
157
|
+
<Button size="sm" variant="primary" onClick={save} loading={saving} disabled={!text.trim()}>
|
|
158
|
+
Save
|
|
159
|
+
</Button>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function Thinking({ label = "Thinking" }) {
|
|
166
|
+
return (
|
|
167
|
+
<span role="status" className="inline-flex h-5 items-center gap-1" aria-label={label}>
|
|
168
|
+
{[0, 150, 300].map((d) => (
|
|
169
|
+
<span key={d} aria-hidden="true" className="size-1.5 animate-pulse rounded-full bg-muted-foreground" style={{ animationDelay: `${d}ms` }} />
|
|
170
|
+
))}
|
|
171
|
+
</span>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* One message.
|
|
177
|
+
* message { id, role, content | parts, createdAt?, status?, error?,
|
|
178
|
+
* author?: { name, src }, attachments?: [{ id?, name, size?, type?, url? }] }
|
|
179
|
+
* status: sending · streaming · failed · (anything else: sent)
|
|
180
|
+
* first / last its place in a run from one author (MessageScroller
|
|
181
|
+
* works these out); `first` shows the avatar, name and time
|
|
182
|
+
* latest the newest reply: its actions stay in view, not only on hover
|
|
183
|
+
* message.reasoning (string) · message.reasoningDuration (seconds) — the
|
|
184
|
+
* folding reasoning over an assistant reply
|
|
185
|
+
* message.feedback "up" · "down" — the thumbs already chosen
|
|
186
|
+
* onFeedback(message, "up" | "down" | null) thumbs on assistant replies;
|
|
187
|
+
* may return { ok: false, error }: the choice goes back
|
|
188
|
+
* onEdit(message, text) an edit button on user messages; may return
|
|
189
|
+
* { ok: false, error }: the field stays open with the reason
|
|
190
|
+
* editing / onEditingChange(next) control the edit (ChatThread does,
|
|
191
|
+
* for ArrowUp in an empty composer)
|
|
192
|
+
*/
|
|
193
|
+
export function Message({
|
|
194
|
+
message,
|
|
195
|
+
first = true,
|
|
196
|
+
last = true,
|
|
197
|
+
latest = false,
|
|
198
|
+
variant = "plain",
|
|
199
|
+
assistantName = "Assistant",
|
|
200
|
+
assistantAvatar,
|
|
201
|
+
userName = "You",
|
|
202
|
+
userAvatar,
|
|
203
|
+
showUserAvatar = false,
|
|
204
|
+
onRetry,
|
|
205
|
+
onRegenerate,
|
|
206
|
+
onCopy,
|
|
207
|
+
actionsOf,
|
|
208
|
+
onFeedback,
|
|
209
|
+
onEdit,
|
|
210
|
+
editing: editingProp,
|
|
211
|
+
onEditingChange,
|
|
212
|
+
formatTime: timeOf = formatTime,
|
|
213
|
+
className,
|
|
214
|
+
}) {
|
|
215
|
+
const [copied, setCopied] = useState(false);
|
|
216
|
+
const copiedTimer = useRef(0);
|
|
217
|
+
useEffect(() => () => clearTimeout(copiedTimer.current), []);
|
|
218
|
+
const [ownEditing, setOwnEditing] = useState(false);
|
|
219
|
+
const editing = editingProp ?? ownEditing;
|
|
220
|
+
const setEditing = (next) => {
|
|
221
|
+
if (editingProp === undefined) setOwnEditing(next);
|
|
222
|
+
onEditingChange?.(next);
|
|
223
|
+
};
|
|
224
|
+
// The thumbs: shown at once, taken back if the save fails.
|
|
225
|
+
const [choice, setChoice] = useState(undefined);
|
|
226
|
+
const [feedbackError, setFeedbackError] = useState(null);
|
|
227
|
+
const [feedbackBusy, setFeedbackBusy] = useState(false);
|
|
228
|
+
const given = message?.feedback ?? null;
|
|
229
|
+
useEffect(() => setChoice(undefined), [given]);
|
|
230
|
+
|
|
231
|
+
if (!message) return null;
|
|
232
|
+
const role = message.role === "user" ? "user" : message.role === "system" ? "system" : "assistant";
|
|
233
|
+
const text = textOf(message);
|
|
234
|
+
const status = message.status;
|
|
235
|
+
const streaming = status === "streaming";
|
|
236
|
+
const failed = status === "failed";
|
|
237
|
+
const sending = status === "sending";
|
|
238
|
+
const date = toDate(message.createdAt);
|
|
239
|
+
|
|
240
|
+
if (role === "system") {
|
|
241
|
+
return (
|
|
242
|
+
<div className={cx("flex justify-center px-2 py-1", className)}>
|
|
243
|
+
<ChatText text={text} className="max-w-prose text-balance text-center text-xs text-muted-foreground" />
|
|
244
|
+
</div>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const user = role === "user";
|
|
249
|
+
const name = message.author?.name ?? (user ? userName : assistantName);
|
|
250
|
+
const src = message.author?.src ?? (user ? userAvatar : assistantAvatar);
|
|
251
|
+
const showAvatar = !user || showUserAvatar;
|
|
252
|
+
const files = (message.attachments ?? []).filter(Boolean);
|
|
253
|
+
|
|
254
|
+
async function copy() {
|
|
255
|
+
try {
|
|
256
|
+
if (onCopy) await onCopy(message, text);
|
|
257
|
+
else await navigator.clipboard.writeText(text);
|
|
258
|
+
setCopied(true);
|
|
259
|
+
clearTimeout(copiedTimer.current);
|
|
260
|
+
copiedTimer.current = setTimeout(() => setCopied(false), 1500);
|
|
261
|
+
} catch {
|
|
262
|
+
// No clipboard (an insecure origin): nothing to say beyond not ticking.
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const feedback = choice !== undefined ? choice : given;
|
|
267
|
+
async function giveFeedback(v) {
|
|
268
|
+
const before = feedback;
|
|
269
|
+
const next = before === v ? null : v;
|
|
270
|
+
setChoice(next);
|
|
271
|
+
setFeedbackError(null);
|
|
272
|
+
setFeedbackBusy(true);
|
|
273
|
+
let r;
|
|
274
|
+
try {
|
|
275
|
+
r = await onFeedback(message, next);
|
|
276
|
+
} catch (e) {
|
|
277
|
+
r = { ok: false, error: e?.message ?? String(e) };
|
|
278
|
+
}
|
|
279
|
+
setFeedbackBusy(false);
|
|
280
|
+
if (r && r.ok === false) {
|
|
281
|
+
setChoice(before);
|
|
282
|
+
setFeedbackError(r.error ?? "Your feedback was not saved");
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const reasoning = typeof message.reasoning === "string" ? message.reasoning : null;
|
|
287
|
+
const thinking = streaming && !text;
|
|
288
|
+
|
|
289
|
+
const extra = actionsOf?.(message) ?? [];
|
|
290
|
+
const canRegenerate = !user && onRegenerate && !streaming;
|
|
291
|
+
const hasActions = !sending && !streaming && !failed;
|
|
292
|
+
|
|
293
|
+
// The actions sit under the words on touch, where they always show.
|
|
294
|
+
const toolbar =
|
|
295
|
+
hasActions && (text || extra.length) ? (
|
|
296
|
+
<div
|
|
297
|
+
className={cx(
|
|
298
|
+
"-mx-1 flex items-center gap-0.5 transition-opacity",
|
|
299
|
+
latest && !user
|
|
300
|
+
? "opacity-100"
|
|
301
|
+
: "pointer-events-none opacity-0 group-hover/message:pointer-events-auto group-hover/message:opacity-100 group-focus-within/message:pointer-events-auto group-focus-within/message:opacity-100 [@media(hover:none)]:pointer-events-auto [@media(hover:none)]:opacity-100",
|
|
302
|
+
// On a pointer that hovers, the actions float instead of holding
|
|
303
|
+
// a row open under every message: beside a user's bubble, over
|
|
304
|
+
// the top corner of an assistant's (the newest reply keeps its row).
|
|
305
|
+
user
|
|
306
|
+
? "flex-row-reverse [@media(hover:hover)]:absolute [@media(hover:hover)]:right-full [@media(hover:hover)]:top-0 [@media(hover:hover)]:mx-0 [@media(hover:hover)]:mr-1"
|
|
307
|
+
: !latest &&
|
|
308
|
+
"[@media(hover:hover)]:absolute [@media(hover:hover)]:-top-3 [@media(hover:hover)]:right-0 [@media(hover:hover)]:z-10 [@media(hover:hover)]:mx-0 [@media(hover:hover)]:rounded-md [@media(hover:hover)]:border [@media(hover:hover)]:border-border [@media(hover:hover)]:bg-card [@media(hover:hover)]:p-0.5 [@media(hover:hover)]:shadow-sm",
|
|
309
|
+
)}
|
|
310
|
+
>
|
|
311
|
+
{text ? (
|
|
312
|
+
<Tooltip label={copied ? "Copied" : "Copy"}>
|
|
313
|
+
<IconButton icon={copied ? "check" : "copy"} label={copied ? "Copied" : "Copy message"} size="sm" onClick={copy} />
|
|
314
|
+
</Tooltip>
|
|
315
|
+
) : null}
|
|
316
|
+
{canRegenerate ? (
|
|
317
|
+
<Tooltip label="Regenerate">
|
|
318
|
+
<IconButton icon="refresh" label="Regenerate reply" size="sm" onClick={() => onRegenerate(message)} />
|
|
319
|
+
</Tooltip>
|
|
320
|
+
) : null}
|
|
321
|
+
{!user && onFeedback && text ? (
|
|
322
|
+
<>
|
|
323
|
+
<Toggle
|
|
324
|
+
variant="quiet"
|
|
325
|
+
size="sm"
|
|
326
|
+
icon="thumbs-up"
|
|
327
|
+
label="Good reply"
|
|
328
|
+
pressed={feedback === "up"}
|
|
329
|
+
disabled={feedbackBusy}
|
|
330
|
+
onPressedChange={() => giveFeedback("up")}
|
|
331
|
+
/>
|
|
332
|
+
<Toggle
|
|
333
|
+
variant="quiet"
|
|
334
|
+
size="sm"
|
|
335
|
+
icon="thumbs-down"
|
|
336
|
+
label="Bad reply"
|
|
337
|
+
pressed={feedback === "down"}
|
|
338
|
+
disabled={feedbackBusy}
|
|
339
|
+
onPressedChange={() => giveFeedback("down")}
|
|
340
|
+
/>
|
|
341
|
+
</>
|
|
342
|
+
) : null}
|
|
343
|
+
{user && onEdit && text ? (
|
|
344
|
+
<Tooltip label="Edit">
|
|
345
|
+
<IconButton icon="pencil" label="Edit message" size="sm" onClick={() => setEditing(true)} />
|
|
346
|
+
</Tooltip>
|
|
347
|
+
) : null}
|
|
348
|
+
<Menu items={extra} label="More actions for this message" align={user ? "end" : "start"} />
|
|
349
|
+
</div>
|
|
350
|
+
) : null;
|
|
351
|
+
|
|
352
|
+
return (
|
|
353
|
+
<article
|
|
354
|
+
aria-label={`Message from ${name}`}
|
|
355
|
+
data-role={role}
|
|
356
|
+
data-status={status || undefined}
|
|
357
|
+
className={cx("group/message relative flex gap-2.5", user && "flex-row-reverse", first ? "mt-4" : "mt-1", className)}
|
|
358
|
+
>
|
|
359
|
+
{showAvatar ? (
|
|
360
|
+
<div className="w-8 shrink-0">
|
|
361
|
+
{first ? <Avatar name={name} src={src} size="sm" shape={user ? "round" : "square"} /> : null}
|
|
362
|
+
</div>
|
|
363
|
+
) : null}
|
|
364
|
+
<div
|
|
365
|
+
className={cx(
|
|
366
|
+
"flex min-w-0 flex-col gap-1",
|
|
367
|
+
user ? "max-w-[85%] items-end" : variant === "plain" ? "flex-1 items-start" : "max-w-[85%] items-start",
|
|
368
|
+
)}
|
|
369
|
+
>
|
|
370
|
+
{first ? (
|
|
371
|
+
<p className={cx("flex items-baseline gap-2 text-xs", user && "flex-row-reverse")}>
|
|
372
|
+
<span className="font-medium text-foreground">{name}</span>
|
|
373
|
+
{date ? (
|
|
374
|
+
<time dateTime={date.toISOString()} suppressHydrationWarning className="text-muted-foreground">
|
|
375
|
+
{timeOf(date)}
|
|
376
|
+
</time>
|
|
377
|
+
) : null}
|
|
378
|
+
</p>
|
|
379
|
+
) : null}
|
|
380
|
+
|
|
381
|
+
{!user && (reasoning || (thinking && reasoning !== null)) ? (
|
|
382
|
+
<Reasoning text={reasoning} seconds={message.reasoningDuration} thinking={thinking} />
|
|
383
|
+
) : null}
|
|
384
|
+
|
|
385
|
+
{editing && user && onEdit ? (
|
|
386
|
+
<EditBox
|
|
387
|
+
initial={text}
|
|
388
|
+
onCancel={() => setEditing(false)}
|
|
389
|
+
onSave={async (next) => {
|
|
390
|
+
const r = await onEdit(message, next);
|
|
391
|
+
if (!(r && r.ok === false)) setEditing(false);
|
|
392
|
+
return r;
|
|
393
|
+
}}
|
|
394
|
+
/>
|
|
395
|
+
) : text || (streaming && reasoning === null) ? (
|
|
396
|
+
<div className={cx("flex max-w-full flex-col gap-1", user ? "relative items-end" : "items-start")}>
|
|
397
|
+
<Bubble
|
|
398
|
+
from={role}
|
|
399
|
+
variant={variant}
|
|
400
|
+
first={first}
|
|
401
|
+
last={last}
|
|
402
|
+
text={text || undefined}
|
|
403
|
+
streaming={streaming && Boolean(text)}
|
|
404
|
+
className={cx(failed && "opacity-70", sending && "opacity-80")}
|
|
405
|
+
>
|
|
406
|
+
{text ? null : <Thinking />}
|
|
407
|
+
</Bubble>
|
|
408
|
+
{toolbar}
|
|
409
|
+
</div>
|
|
410
|
+
) : null}
|
|
411
|
+
|
|
412
|
+
{feedbackError ? (
|
|
413
|
+
<p role="alert" className="text-xs text-destructive">
|
|
414
|
+
{feedbackError}
|
|
415
|
+
</p>
|
|
416
|
+
) : null}
|
|
417
|
+
|
|
418
|
+
{files.length ? (
|
|
419
|
+
<ul className={cx("flex max-w-full flex-wrap gap-2", user && "justify-end")}>
|
|
420
|
+
{files.map((f, i) => (
|
|
421
|
+
<li key={f.id ?? `${f.name}-${i}`} className="min-w-0 max-w-full">
|
|
422
|
+
<Attachment name={f.name} size={f.size} type={f.type} url={f.url} href={f.href} error={f.error} />
|
|
423
|
+
</li>
|
|
424
|
+
))}
|
|
425
|
+
</ul>
|
|
426
|
+
) : null}
|
|
427
|
+
|
|
428
|
+
{sending ? (
|
|
429
|
+
<p className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
430
|
+
<Spinner size="sm" label={null} className="size-3" />
|
|
431
|
+
Sending…
|
|
432
|
+
</p>
|
|
433
|
+
) : null}
|
|
434
|
+
|
|
435
|
+
{failed ? (
|
|
436
|
+
<div role="alert" className="flex flex-wrap items-center gap-x-2 text-xs text-destructive">
|
|
437
|
+
<Icon name="warning" size={14} />
|
|
438
|
+
<span>{message.error ? `Not sent: ${message.error}` : "Not sent"}</span>
|
|
439
|
+
{onRetry ? (
|
|
440
|
+
<Button variant="link" size="sm" className="min-h-9 text-xs" onClick={() => onRetry(message)}>
|
|
441
|
+
Retry
|
|
442
|
+
</Button>
|
|
443
|
+
) : null}
|
|
444
|
+
</div>
|
|
445
|
+
) : null}
|
|
446
|
+
|
|
447
|
+
</div>
|
|
448
|
+
</article>
|
|
449
|
+
);
|
|
450
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
import { Button, ScrollArea, Separator, Spinner, cx } from "../../ui";
|
|
5
|
+
import { Message, textOf, toDate } from "./message";
|
|
6
|
+
|
|
7
|
+
// MessageScroller — the conversation's scrolling list. It keeps to the
|
|
8
|
+
// bottom while messages arrive and stream, unless the reader has scrolled
|
|
9
|
+
// up: then it stays where they are and offers "Jump to latest". It puts a
|
|
10
|
+
// day between messages from different days, works out which messages run
|
|
11
|
+
// together from one author, loads older messages when the reader reaches
|
|
12
|
+
// the top (keeping their place), and says each finished assistant reply
|
|
13
|
+
// once to a screen reader.
|
|
14
|
+
|
|
15
|
+
const NEAR_BOTTOM_PX = 64;
|
|
16
|
+
const NEAR_TOP_PX = 120;
|
|
17
|
+
const RUN_GAP_MS = 5 * 60 * 1000;
|
|
18
|
+
|
|
19
|
+
// useLayoutEffect warns on the server; the scroll work only matters in a browser.
|
|
20
|
+
const useIsoLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
21
|
+
|
|
22
|
+
// Days are the reader's own, but the server does not know their time
|
|
23
|
+
// zone: the first render (server and hydration alike) groups by UTC, and
|
|
24
|
+
// the browser regroups by local days once it is running. Same markup on
|
|
25
|
+
// both sides, so no hydration mismatch.
|
|
26
|
+
function dayKey(d, local) {
|
|
27
|
+
return local ? `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}` : d.toISOString().slice(0, 10);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function formatDay(date, now = new Date()) {
|
|
31
|
+
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
32
|
+
const that = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
33
|
+
const days = Math.round((today - that) / 86400000);
|
|
34
|
+
if (days === 0) return "Today";
|
|
35
|
+
if (days === 1) return "Yesterday";
|
|
36
|
+
return date.toLocaleDateString(undefined, {
|
|
37
|
+
weekday: "short",
|
|
38
|
+
day: "numeric",
|
|
39
|
+
month: "short",
|
|
40
|
+
year: date.getFullYear() === now.getFullYear() ? undefined : "numeric",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function authorKey(m) {
|
|
45
|
+
return `${m.role}|${m.author?.name ?? ""}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Messages with their day separators and run positions worked out. */
|
|
49
|
+
function layout(messages, idOf, local) {
|
|
50
|
+
const out = [];
|
|
51
|
+
let prev = null;
|
|
52
|
+
let prevDay = null;
|
|
53
|
+
messages.forEach((m, i) => {
|
|
54
|
+
const date = toDate(m.createdAt);
|
|
55
|
+
const day = date ? dayKey(date, local) : prevDay;
|
|
56
|
+
if (date && day !== prevDay) out.push({ type: "day", key: `day-${day}`, date });
|
|
57
|
+
const prevDate = prev ? toDate(prev.createdAt) : null;
|
|
58
|
+
const joins =
|
|
59
|
+
prev &&
|
|
60
|
+
m.role !== "system" &&
|
|
61
|
+
authorKey(prev) === authorKey(m) &&
|
|
62
|
+
day === prevDay &&
|
|
63
|
+
!(date && prevDate && date - prevDate > RUN_GAP_MS);
|
|
64
|
+
if (joins) out[out.length - 1].last = false;
|
|
65
|
+
out.push({ type: "message", key: idOf(m, i), message: m, first: !joins, last: true });
|
|
66
|
+
prev = m;
|
|
67
|
+
prevDay = day;
|
|
68
|
+
});
|
|
69
|
+
// The newest reply keeps its actions in view: it is the one people copy
|
|
70
|
+
// or regenerate.
|
|
71
|
+
for (let i = out.length - 1; i >= 0; i--) {
|
|
72
|
+
if (out[i].type === "message" && out[i].message.role === "assistant") {
|
|
73
|
+
out[i].latest = true;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function MessageScroller({
|
|
81
|
+
messages,
|
|
82
|
+
renderMessage,
|
|
83
|
+
idOf = (m, i) => String(m.id ?? i),
|
|
84
|
+
onLoadMore,
|
|
85
|
+
hasMore = false,
|
|
86
|
+
loadingMore = false,
|
|
87
|
+
loadMoreLabel = "Load earlier messages",
|
|
88
|
+
jumpLabel = "Jump to latest",
|
|
89
|
+
emptyState = null,
|
|
90
|
+
formatDay: dayOf = formatDay,
|
|
91
|
+
announce = (m) => `${m.author?.name ?? "Assistant"} replied: ${textOf(m).slice(0, 280)}`,
|
|
92
|
+
label = "Conversation",
|
|
93
|
+
className,
|
|
94
|
+
children,
|
|
95
|
+
}) {
|
|
96
|
+
const scroller = useRef(null);
|
|
97
|
+
const atBottom = useRef(true);
|
|
98
|
+
const anchor = useRef(null);
|
|
99
|
+
const settled = useRef(new Map());
|
|
100
|
+
const primed = useRef(false);
|
|
101
|
+
const [away, setAway] = useState(false);
|
|
102
|
+
const [unseen, setUnseen] = useState(false);
|
|
103
|
+
const [said, setSaid] = useState("");
|
|
104
|
+
const [local, setLocal] = useState(false);
|
|
105
|
+
useEffect(() => setLocal(true), []);
|
|
106
|
+
|
|
107
|
+
const list = messages ?? [];
|
|
108
|
+
const rows = layout(list, idOf, local);
|
|
109
|
+
const lastMsg = list[list.length - 1];
|
|
110
|
+
const firstId = list.length ? idOf(list[0], 0) : null;
|
|
111
|
+
// What changes when the conversation grows at the bottom: the last
|
|
112
|
+
// message, its status, and how much of it has streamed in.
|
|
113
|
+
const tail = lastMsg ? `${idOf(lastMsg, list.length - 1)}|${lastMsg.status ?? ""}|${textOf(lastMsg).length}` : "";
|
|
114
|
+
|
|
115
|
+
function toBottom(smooth) {
|
|
116
|
+
const el = scroller.current;
|
|
117
|
+
if (!el) return;
|
|
118
|
+
el.scrollTo({ top: el.scrollHeight, behavior: smooth ? "smooth" : "auto" });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function onScroll() {
|
|
122
|
+
const el = scroller.current;
|
|
123
|
+
if (!el) return;
|
|
124
|
+
const bottom = el.scrollHeight - el.scrollTop - el.clientHeight <= NEAR_BOTTOM_PX;
|
|
125
|
+
atBottom.current = bottom;
|
|
126
|
+
setAway(!bottom);
|
|
127
|
+
if (bottom) setUnseen(false);
|
|
128
|
+
if (el.scrollTop <= NEAR_TOP_PX && hasMore && !loadingMore && onLoadMore) loadMore();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function loadMore() {
|
|
132
|
+
const el = scroller.current;
|
|
133
|
+
if (el) anchor.current = { id: firstId, height: el.scrollHeight, top: el.scrollTop };
|
|
134
|
+
onLoadMore?.();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Older messages landed above: keep the reader on the message they were reading.
|
|
138
|
+
useIsoLayoutEffect(() => {
|
|
139
|
+
const el = scroller.current;
|
|
140
|
+
const a = anchor.current;
|
|
141
|
+
if (!el || !a || a.id === firstId) return;
|
|
142
|
+
el.scrollTop = a.top + (el.scrollHeight - a.height);
|
|
143
|
+
anchor.current = null;
|
|
144
|
+
}, [firstId]);
|
|
145
|
+
|
|
146
|
+
// Newer messages: follow them if the reader was at the bottom, or if the
|
|
147
|
+
// newest is their own; otherwise flag that there is something below.
|
|
148
|
+
useIsoLayoutEffect(() => {
|
|
149
|
+
if (!lastMsg) {
|
|
150
|
+
atBottom.current = true;
|
|
151
|
+
setAway(false);
|
|
152
|
+
setUnseen(false);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (!primed.current) {
|
|
156
|
+
primed.current = true;
|
|
157
|
+
toBottom(false);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (atBottom.current || lastMsg.role === "user") {
|
|
161
|
+
toBottom(false);
|
|
162
|
+
atBottom.current = true;
|
|
163
|
+
setAway(false);
|
|
164
|
+
setUnseen(false);
|
|
165
|
+
} else {
|
|
166
|
+
setUnseen(true);
|
|
167
|
+
}
|
|
168
|
+
}, [tail]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
169
|
+
|
|
170
|
+
// Say a reply once, when it is whole — never token by token.
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
const seen = settled.current;
|
|
173
|
+
const first = seen.size === 0;
|
|
174
|
+
list.forEach((m, i) => {
|
|
175
|
+
const id = idOf(m, i);
|
|
176
|
+
const done = m.status !== "streaming" && m.status !== "sending";
|
|
177
|
+
const was = seen.get(id);
|
|
178
|
+
seen.set(id, done);
|
|
179
|
+
if (!first && m.role === "assistant" && done && was !== true && textOf(m)) setSaid(announce(m));
|
|
180
|
+
});
|
|
181
|
+
}, [tail]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
182
|
+
|
|
183
|
+
const draw = renderMessage ?? ((m, ctx) => <Message message={m} first={ctx.first} last={ctx.last} latest={ctx.latest} />);
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<div className={cx("relative flex min-h-0 flex-col", className)}>
|
|
187
|
+
<ScrollArea
|
|
188
|
+
ref={scroller}
|
|
189
|
+
onScroll={onScroll}
|
|
190
|
+
label={label}
|
|
191
|
+
aria-busy={loadingMore || undefined}
|
|
192
|
+
className="flex-1"
|
|
193
|
+
>
|
|
194
|
+
<div className="flex min-h-full flex-col justify-end px-3 py-4 sm:px-4">
|
|
195
|
+
{hasMore || loadingMore ? (
|
|
196
|
+
<div className="flex justify-center pb-3">
|
|
197
|
+
{loadingMore ? (
|
|
198
|
+
<span className="flex min-h-9 items-center gap-2 text-xs text-muted-foreground">
|
|
199
|
+
<Spinner size="sm" label={null} />
|
|
200
|
+
Loading earlier messages…
|
|
201
|
+
</span>
|
|
202
|
+
) : (
|
|
203
|
+
<Button variant="quiet" size="sm" onClick={loadMore}>
|
|
204
|
+
{loadMoreLabel}
|
|
205
|
+
</Button>
|
|
206
|
+
)}
|
|
207
|
+
</div>
|
|
208
|
+
) : null}
|
|
209
|
+
{list.length === 0 && !loadingMore ? emptyState : null}
|
|
210
|
+
<ol className="flex flex-col [&>li:first-child>*]:mt-0">
|
|
211
|
+
{rows.map((row) =>
|
|
212
|
+
row.type === "day" ? (
|
|
213
|
+
<li key={row.key} className="mt-6 first:mt-0">
|
|
214
|
+
<Separator label={<span suppressHydrationWarning>{dayOf(row.date)}</span>} />
|
|
215
|
+
</li>
|
|
216
|
+
) : (
|
|
217
|
+
<li key={row.key} className="flex flex-col">
|
|
218
|
+
{draw(row.message, { first: row.first, last: row.last, latest: Boolean(row.latest) })}
|
|
219
|
+
</li>
|
|
220
|
+
),
|
|
221
|
+
)}
|
|
222
|
+
</ol>
|
|
223
|
+
{children}
|
|
224
|
+
</div>
|
|
225
|
+
</ScrollArea>
|
|
226
|
+
{away ? (
|
|
227
|
+
<div className="pointer-events-none absolute inset-x-0 bottom-3 flex justify-center">
|
|
228
|
+
<Button
|
|
229
|
+
variant="secondary"
|
|
230
|
+
size="sm"
|
|
231
|
+
icon="chevron-down"
|
|
232
|
+
className="pointer-events-auto rounded-full shadow-md"
|
|
233
|
+
onClick={() => {
|
|
234
|
+
toBottom(true);
|
|
235
|
+
setUnseen(false);
|
|
236
|
+
}}
|
|
237
|
+
>
|
|
238
|
+
{unseen ? "New messages" : jumpLabel}
|
|
239
|
+
</Button>
|
|
240
|
+
</div>
|
|
241
|
+
) : null}
|
|
242
|
+
<p aria-live="polite" className="sr-only">
|
|
243
|
+
{said}
|
|
244
|
+
</p>
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
}
|