@evinvest/uikit 0.30.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +267 -73
- package/dist/{chunk-3IPB4QQJ.js → chunk-35SPIU4U.js} +10 -6
- package/dist/chunk-35SPIU4U.js.map +1 -0
- package/dist/components/{accordion.js → atomic/accordion.js} +3 -3
- package/dist/components/atomic/accordion.js.map +1 -0
- package/dist/components/{alert-dialog.js → atomic/alert-dialog.js} +11 -10
- package/dist/components/atomic/alert-dialog.js.map +1 -0
- package/dist/components/{alert.js → atomic/alert.js} +2 -2
- package/dist/components/atomic/alert.js.map +1 -0
- package/dist/components/atomic/animated-number-columns.js +105 -0
- package/dist/components/atomic/animated-number-columns.js.map +1 -0
- package/dist/components/{avatar.js → atomic/avatar.js} +2 -2
- package/dist/components/atomic/avatar.js.map +1 -0
- package/dist/components/{badge.js → atomic/badge.js} +4 -4
- package/dist/components/atomic/badge.js.map +1 -0
- package/dist/components/{band.js → atomic/band.js} +2 -2
- package/dist/components/atomic/band.js.map +1 -0
- package/dist/components/{breadcrumb.js → atomic/breadcrumb.js} +3 -3
- package/dist/components/atomic/breadcrumb.js.map +1 -0
- package/dist/components/{button-group.js → atomic/button-group.js} +3 -3
- package/dist/components/atomic/button-group.js.map +1 -0
- package/dist/components/{button.js → atomic/button.js} +7 -15
- package/dist/components/atomic/button.js.map +1 -0
- package/dist/components/{calendar.js → atomic/calendar.js} +2 -2
- package/dist/components/atomic/calendar.js.map +1 -0
- package/dist/components/{card.js → atomic/card.js} +2 -2
- package/dist/components/atomic/card.js.map +1 -0
- package/dist/components/{checkbox.js → atomic/checkbox.js} +4 -4
- package/dist/components/atomic/checkbox.js.map +1 -0
- package/dist/components/{collapsible.js → atomic/collapsible.js} +1 -1
- package/dist/components/atomic/collapsible.js.map +1 -0
- package/dist/components/{command.js → atomic/command.js} +9 -7
- package/dist/components/atomic/command.js.map +1 -0
- package/dist/components/{container.js → atomic/container.js} +2 -2
- package/dist/components/atomic/container.js.map +1 -0
- package/dist/components/{context-menu.js → atomic/context-menu.js} +62 -14
- package/dist/components/atomic/context-menu.js.map +1 -0
- package/dist/components/{dialog.js → atomic/dialog.js} +11 -10
- package/dist/components/atomic/dialog.js.map +1 -0
- package/dist/components/{drawer.js → atomic/drawer.js} +10 -8
- package/dist/components/atomic/drawer.js.map +1 -0
- package/dist/components/{dropdown-menu.js → atomic/dropdown-menu.js} +74 -15
- package/dist/components/atomic/dropdown-menu.js.map +1 -0
- package/dist/components/{empty.js → atomic/empty.js} +2 -2
- package/dist/components/atomic/empty.js.map +1 -0
- package/dist/components/atomic/field-context.js.map +1 -0
- package/dist/components/atomic/form-row.js +51 -0
- package/dist/components/atomic/form-row.js.map +1 -0
- package/dist/components/{hover-card.js → atomic/hover-card.js} +8 -8
- package/dist/components/atomic/hover-card.js.map +1 -0
- package/dist/components/{info-tip.js → atomic/info-tip.js} +10 -10
- package/dist/components/atomic/info-tip.js.map +1 -0
- package/dist/components/{input-otp.js → atomic/input-otp.js} +3 -3
- package/dist/components/atomic/input-otp.js.map +1 -0
- package/dist/components/{input.js → atomic/input.js} +3 -3
- package/dist/components/atomic/input.js.map +1 -0
- package/dist/components/{kbd.js → atomic/kbd.js} +2 -2
- package/dist/components/atomic/kbd.js.map +1 -0
- package/dist/components/atomic/key-value.js +68 -0
- package/dist/components/atomic/key-value.js.map +1 -0
- package/dist/components/{label.js → atomic/label.js} +2 -2
- package/dist/components/atomic/label.js.map +1 -0
- package/dist/components/{menubar.js → atomic/menubar.js} +48 -14
- package/dist/components/atomic/menubar.js.map +1 -0
- package/dist/components/{native-select.js → atomic/native-select.js} +6 -7
- package/dist/components/atomic/native-select.js.map +1 -0
- package/dist/components/{navigation-menu.js → atomic/navigation-menu.js} +11 -12
- package/dist/components/atomic/navigation-menu.js.map +1 -0
- package/dist/components/{pagination.js → atomic/pagination.js} +1 -1
- package/dist/components/atomic/pagination.js.map +1 -0
- package/dist/components/{popover.js → atomic/popover.js} +10 -10
- package/dist/components/atomic/popover.js.map +1 -0
- package/dist/components/{progress.js → atomic/progress.js} +2 -2
- package/dist/components/atomic/progress.js.map +1 -0
- package/dist/components/{radio-group.js → atomic/radio-group.js} +4 -4
- package/dist/components/atomic/radio-group.js.map +1 -0
- package/dist/components/{scroll-area.js → atomic/scroll-area.js} +2 -2
- package/dist/components/atomic/scroll-area.js.map +1 -0
- package/dist/components/{section-descriptor.js → atomic/section-descriptor.js} +2 -2
- package/dist/components/atomic/section-descriptor.js.map +1 -0
- package/dist/components/{select-listbox.js → atomic/select-listbox.js} +1 -1
- package/dist/components/atomic/select-listbox.js.map +1 -0
- package/dist/components/{select.js → atomic/select.js} +26 -16
- package/dist/components/atomic/select.js.map +1 -0
- package/dist/components/{separator.js → atomic/separator.js} +2 -2
- package/dist/components/atomic/separator.js.map +1 -0
- package/dist/components/{sheet.js → atomic/sheet.js} +11 -10
- package/dist/components/atomic/sheet.js.map +1 -0
- package/dist/components/{sidebar.js → atomic/sidebar.js} +5 -5
- package/dist/components/atomic/sidebar.js.map +1 -0
- package/dist/components/{skeleton.js → atomic/skeleton.js} +2 -2
- package/dist/components/atomic/skeleton.js.map +1 -0
- package/dist/components/{slider.js → atomic/slider.js} +3 -3
- package/dist/components/atomic/slider.js.map +1 -0
- package/dist/components/{sonner.js → atomic/sonner.js} +2 -2
- package/dist/components/atomic/sonner.js.map +1 -0
- package/dist/components/{spinner.js → atomic/spinner.js} +1 -1
- package/dist/components/atomic/spinner.js.map +1 -0
- package/dist/components/atomic/status-item.js +42 -0
- package/dist/components/atomic/status-item.js.map +1 -0
- package/dist/components/{switch.js → atomic/switch.js} +4 -4
- package/dist/components/atomic/switch.js.map +1 -0
- package/dist/components/{table.js → atomic/table.js} +2 -2
- package/dist/components/atomic/table.js.map +1 -0
- package/dist/components/{tabs.js → atomic/tabs.js} +4 -4
- package/dist/components/atomic/tabs.js.map +1 -0
- package/dist/components/{textarea.js → atomic/textarea.js} +3 -3
- package/dist/components/atomic/textarea.js.map +1 -0
- package/dist/components/{toggle-group.js → atomic/toggle-group.js} +2 -2
- package/dist/components/atomic/toggle-group.js.map +1 -0
- package/dist/components/{toggle.js → atomic/toggle.js} +3 -3
- package/dist/components/atomic/toggle.js.map +1 -0
- package/dist/components/{tooltip.js → atomic/tooltip.js} +8 -8
- package/dist/components/atomic/tooltip.js.map +1 -0
- package/dist/components/atomic/use-count-tween.js +42 -0
- package/dist/components/atomic/use-count-tween.js.map +1 -0
- package/dist/components/derived/animated-number.js +157 -0
- package/dist/components/derived/animated-number.js.map +1 -0
- package/dist/components/{carousel.js → derived/carousel.js} +4 -4
- package/dist/components/derived/carousel.js.map +1 -0
- package/dist/components/derived/command-palette.js +119 -0
- package/dist/components/derived/command-palette.js.map +1 -0
- package/dist/components/derived/confirm-dialog.js +123 -0
- package/dist/components/derived/confirm-dialog.js.map +1 -0
- package/dist/components/{date-time-picker.js → derived/date-time-picker.js} +8 -8
- package/dist/components/derived/date-time-picker.js.map +1 -0
- package/dist/components/{field.js → derived/field.js} +6 -6
- package/dist/components/derived/field.js.map +1 -0
- package/dist/components/{form.js → derived/form.js} +4 -4
- package/dist/components/derived/form.js.map +1 -0
- package/dist/components/derived/icon-button.js +35 -0
- package/dist/components/derived/icon-button.js.map +1 -0
- package/dist/components/{input-group.js → derived/input-group.js} +5 -5
- package/dist/components/derived/input-group.js.map +1 -0
- package/dist/components/{item.js → derived/item.js} +4 -4
- package/dist/components/derived/item.js.map +1 -0
- package/dist/components/derived/popover-form.js +60 -0
- package/dist/components/derived/popover-form.js.map +1 -0
- package/dist/components/derived/settings.js +94 -0
- package/dist/components/derived/settings.js.map +1 -0
- package/dist/components/shell/account-list.js +128 -0
- package/dist/components/shell/account-list.js.map +1 -0
- package/dist/components/shell/account-menu-profile.js +114 -0
- package/dist/components/shell/account-menu-profile.js.map +1 -0
- package/dist/components/shell/account-menu.js +97 -0
- package/dist/components/shell/account-menu.js.map +1 -0
- package/dist/components/shell/account-settings-dialog.js +84 -0
- package/dist/components/shell/account-settings-dialog.js.map +1 -0
- package/dist/components/{app-shell.js → shell/app-shell.js} +10 -1
- package/dist/components/shell/app-shell.js.map +1 -0
- package/dist/components/shell/aside.js +140 -0
- package/dist/components/shell/aside.js.map +1 -0
- package/dist/components/{auth-wall.js → shell/auth-wall.js} +1 -1
- package/dist/components/shell/auth-wall.js.map +1 -0
- package/dist/components/{bottom-tab-bar.js → shell/bottom-tab-bar.js} +1 -1
- package/dist/components/shell/bottom-tab-bar.js.map +1 -0
- package/dist/components/shell/chat-composer.js +141 -0
- package/dist/components/shell/chat-composer.js.map +1 -0
- package/dist/components/shell/chat-message.js +195 -0
- package/dist/components/shell/chat-message.js.map +1 -0
- package/dist/components/shell/chat-panel.js +137 -0
- package/dist/components/shell/chat-panel.js.map +1 -0
- package/dist/components/shell/chat-stream-text.js +69 -0
- package/dist/components/shell/chat-stream-text.js.map +1 -0
- package/dist/components/shell/chat-typing.js +26 -0
- package/dist/components/shell/chat-typing.js.map +1 -0
- package/dist/components/{footer.js → shell/footer.js} +1 -1
- package/dist/components/shell/footer.js.map +1 -0
- package/dist/components/shell/header-controls.js +149 -0
- package/dist/components/shell/header-controls.js.map +1 -0
- package/dist/components/{list-rows.js → shell/list-rows.js} +1 -1
- package/dist/components/shell/list-rows.js.map +1 -0
- package/dist/components/{logo.js → shell/logo.js} +1 -1
- package/dist/components/shell/logo.js.map +1 -0
- package/dist/components/{mobile-app-bar.js → shell/mobile-app-bar.js} +1 -1
- package/dist/components/shell/mobile-app-bar.js.map +1 -0
- package/dist/components/{nav-badge.js → shell/nav-badge.js} +1 -1
- package/dist/components/shell/nav-badge.js.map +1 -0
- package/dist/components/shell/nav-collapse.js +142 -0
- package/dist/components/shell/nav-collapse.js.map +1 -0
- package/dist/components/shell/nav-link.js.map +1 -0
- package/dist/components/shell/nav-model.js.map +1 -0
- package/dist/components/{page-frame.js → shell/page-frame.js} +3 -6
- package/dist/components/shell/page-frame.js.map +1 -0
- package/dist/components/{resource-error.js → shell/resource-error.js} +4 -4
- package/dist/components/shell/resource-error.js.map +1 -0
- package/dist/components/shell/section-nav.js +139 -0
- package/dist/components/shell/section-nav.js.map +1 -0
- package/dist/components/shell/settled.js.map +1 -0
- package/dist/components/shell/shell-icons.js.map +1 -0
- package/dist/components/{shell-nav.js → shell/shell-nav.js} +81 -39
- package/dist/components/shell/shell-nav.js.map +1 -0
- package/dist/components/shell/smart-header.js +53 -0
- package/dist/components/shell/smart-header.js.map +1 -0
- package/dist/components/{status-screen.js → shell/status-screen.js} +3 -3
- package/dist/components/shell/status-screen.js.map +1 -0
- package/dist/components/{system-banner.js → shell/system-banner.js} +23 -13
- package/dist/components/shell/system-banner.js.map +1 -0
- package/dist/components/{top-bar.js → shell/top-bar.js} +5 -4
- package/dist/components/shell/top-bar.js.map +1 -0
- package/dist/components/shell/use-shell-nav.js.map +1 -0
- package/dist/components/shell/workspace-tab.js +140 -0
- package/dist/components/shell/workspace-tab.js.map +1 -0
- package/dist/generated/accent.js +2 -2
- package/dist/generated/accent.js.map +1 -1
- package/dist/generated/account-list.js +35 -0
- package/dist/generated/account-list.js.map +1 -0
- package/dist/generated/account-settings-dialog.js +27 -0
- package/dist/generated/account-settings-dialog.js.map +1 -0
- package/dist/generated/alert-dialog.js +1 -1
- package/dist/generated/alert-dialog.js.map +1 -1
- package/dist/generated/alert.js +2 -2
- package/dist/generated/alert.js.map +1 -1
- package/dist/generated/aside.js +36 -0
- package/dist/generated/aside.js.map +1 -0
- package/dist/generated/badge.js +7 -6
- package/dist/generated/badge.js.map +1 -1
- package/dist/generated/band.js +1 -1
- package/dist/generated/band.js.map +1 -1
- package/dist/generated/breadcrumb.js +2 -2
- package/dist/generated/breadcrumb.js.map +1 -1
- package/dist/generated/button-group.js +2 -2
- package/dist/generated/button-group.js.map +1 -1
- package/dist/generated/button.js +4 -4
- package/dist/generated/button.js.map +1 -1
- package/dist/generated/card.js +6 -6
- package/dist/generated/card.js.map +1 -1
- package/dist/generated/chat-composer.js +21 -0
- package/dist/generated/chat-composer.js.map +1 -0
- package/dist/generated/chat-message.js +86 -0
- package/dist/generated/chat-message.js.map +1 -0
- package/dist/generated/chat-typing.js +9 -0
- package/dist/generated/chat-typing.js.map +1 -0
- package/dist/generated/command-palette.js +17 -0
- package/dist/generated/command-palette.js.map +1 -0
- package/dist/generated/command.js +2 -2
- package/dist/generated/command.js.map +1 -1
- package/dist/generated/confirm-dialog.js +28 -0
- package/dist/generated/confirm-dialog.js.map +1 -0
- package/dist/generated/context-menu.js +1 -1
- package/dist/generated/context-menu.js.map +1 -1
- package/dist/generated/date-time-picker.js +1 -1
- package/dist/generated/date-time-picker.js.map +1 -1
- package/dist/generated/dialog.js +1 -1
- package/dist/generated/dialog.js.map +1 -1
- package/dist/generated/drawer.js +1 -1
- package/dist/generated/drawer.js.map +1 -1
- package/dist/generated/dropdown-menu.js +1 -1
- package/dist/generated/dropdown-menu.js.map +1 -1
- package/dist/generated/form-row.js +17 -0
- package/dist/generated/form-row.js.map +1 -0
- package/dist/generated/header-controls.js +56 -0
- package/dist/generated/header-controls.js.map +1 -0
- package/dist/generated/icon-button.js +13 -0
- package/dist/generated/icon-button.js.map +1 -0
- package/dist/generated/key-value.js +15 -0
- package/dist/generated/key-value.js.map +1 -0
- package/dist/generated/menubar.js +1 -1
- package/dist/generated/menubar.js.map +1 -1
- package/dist/generated/navigation-menu.js +1 -1
- package/dist/generated/navigation-menu.js.map +1 -1
- package/dist/generated/popover-form.js +13 -0
- package/dist/generated/popover-form.js.map +1 -0
- package/dist/generated/section-label.js +10 -0
- package/dist/generated/section-label.js.map +1 -0
- package/dist/generated/select.js +15 -3
- package/dist/generated/select.js.map +1 -1
- package/dist/generated/settings.js +36 -0
- package/dist/generated/settings.js.map +1 -0
- package/dist/generated/sheet.js +1 -1
- package/dist/generated/sheet.js.map +1 -1
- package/dist/generated/slider.js +1 -1
- package/dist/generated/slider.js.map +1 -1
- package/dist/generated/smart-header.js +11 -0
- package/dist/generated/smart-header.js.map +1 -0
- package/dist/generated/sonner.js +4 -4
- package/dist/generated/sonner.js.map +1 -1
- package/dist/generated/status-item.js +17 -0
- package/dist/generated/status-item.js.map +1 -0
- package/dist/generated/system-banner.js +33 -0
- package/dist/generated/system-banner.js.map +1 -0
- package/dist/generated/top-bar.js +9 -0
- package/dist/generated/top-bar.js.map +1 -0
- package/dist/generated/workspace-tab.js +17 -0
- package/dist/generated/workspace-tab.js.map +1 -0
- package/dist/icons/select-chevron.js.map +1 -0
- package/dist/icons/ui-icons.js +77 -0
- package/dist/icons/ui-icons.js.map +1 -0
- package/dist/index.d.ts +852 -156
- package/dist/index.js +96 -76
- package/dist/lib/cn.js +8 -1
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/number-parts.js +90 -0
- package/dist/lib/number-parts.js.map +1 -0
- package/dist/palette-cli.js +1 -1
- package/dist/palette.d.ts +6 -4
- package/dist/palette.js +3 -1
- package/dist/primitives/dismissable-layer.js +76 -4
- package/dist/primitives/dismissable-layer.js.map +1 -1
- package/dist/primitives/focus-scope.js +20 -5
- package/dist/primitives/focus-scope.js.map +1 -1
- package/dist/primitives/use-floating.js +16 -1
- package/dist/primitives/use-floating.js.map +1 -1
- package/dist/primitives/use-reduced-motion.js +20 -0
- package/dist/primitives/use-reduced-motion.js.map +1 -0
- package/package.json +2 -2
- package/styles/ev.css +281 -49
- package/styles/theme.css +335 -39
- package/styles/tokens.css +643 -103
- package/dist/chunk-3IPB4QQJ.js.map +0 -1
- package/dist/components/accordion.js.map +0 -1
- package/dist/components/account-menu.js +0 -70
- package/dist/components/account-menu.js.map +0 -1
- package/dist/components/alert-dialog.js.map +0 -1
- package/dist/components/alert.js.map +0 -1
- package/dist/components/app-shell.js.map +0 -1
- package/dist/components/auth-wall.js.map +0 -1
- package/dist/components/avatar.js.map +0 -1
- package/dist/components/badge.js.map +0 -1
- package/dist/components/band.js.map +0 -1
- package/dist/components/bottom-tab-bar.js.map +0 -1
- package/dist/components/breadcrumb.js.map +0 -1
- package/dist/components/button-group.js.map +0 -1
- package/dist/components/button.js.map +0 -1
- package/dist/components/calendar.js.map +0 -1
- package/dist/components/card.js.map +0 -1
- package/dist/components/carousel.js.map +0 -1
- package/dist/components/checkbox.js.map +0 -1
- package/dist/components/collapsible.js.map +0 -1
- package/dist/components/command.js.map +0 -1
- package/dist/components/container.js.map +0 -1
- package/dist/components/context-menu.js.map +0 -1
- package/dist/components/date-time-picker.js.map +0 -1
- package/dist/components/dialog.js.map +0 -1
- package/dist/components/drawer.js.map +0 -1
- package/dist/components/dropdown-menu.js.map +0 -1
- package/dist/components/empty.js.map +0 -1
- package/dist/components/field-context.js.map +0 -1
- package/dist/components/field.js.map +0 -1
- package/dist/components/footer.js.map +0 -1
- package/dist/components/form.js.map +0 -1
- package/dist/components/hover-card.js.map +0 -1
- package/dist/components/info-tip.js.map +0 -1
- package/dist/components/input-group.js.map +0 -1
- package/dist/components/input-otp.js.map +0 -1
- package/dist/components/input.js.map +0 -1
- package/dist/components/item.js.map +0 -1
- package/dist/components/kbd.js.map +0 -1
- package/dist/components/label.js.map +0 -1
- package/dist/components/list-rows.js.map +0 -1
- package/dist/components/logo.js.map +0 -1
- package/dist/components/menubar.js.map +0 -1
- package/dist/components/mobile-app-bar.js.map +0 -1
- package/dist/components/native-select.js.map +0 -1
- package/dist/components/nav-badge.js.map +0 -1
- package/dist/components/nav-link.js.map +0 -1
- package/dist/components/nav-model.js.map +0 -1
- package/dist/components/navigation-menu.js.map +0 -1
- package/dist/components/page-frame.js.map +0 -1
- package/dist/components/pagination.js.map +0 -1
- package/dist/components/popover.js.map +0 -1
- package/dist/components/progress.js.map +0 -1
- package/dist/components/radio-group.js.map +0 -1
- package/dist/components/resource-error.js.map +0 -1
- package/dist/components/scroll-area.js.map +0 -1
- package/dist/components/section-descriptor.js.map +0 -1
- package/dist/components/section-nav.js +0 -86
- package/dist/components/section-nav.js.map +0 -1
- package/dist/components/select-chevron.js.map +0 -1
- package/dist/components/select-listbox.js.map +0 -1
- package/dist/components/select.js.map +0 -1
- package/dist/components/separator.js.map +0 -1
- package/dist/components/settled.js.map +0 -1
- package/dist/components/sheet.js.map +0 -1
- package/dist/components/shell-icons.js.map +0 -1
- package/dist/components/shell-nav.js.map +0 -1
- package/dist/components/sidebar.js.map +0 -1
- package/dist/components/skeleton.js.map +0 -1
- package/dist/components/slider.js.map +0 -1
- package/dist/components/sonner.js.map +0 -1
- package/dist/components/spinner.js.map +0 -1
- package/dist/components/status-screen.js.map +0 -1
- package/dist/components/switch.js.map +0 -1
- package/dist/components/system-banner.js.map +0 -1
- package/dist/components/table.js.map +0 -1
- package/dist/components/tabs.js.map +0 -1
- package/dist/components/terminal.js +0 -206
- package/dist/components/terminal.js.map +0 -1
- package/dist/components/textarea.js.map +0 -1
- package/dist/components/toggle-group.js.map +0 -1
- package/dist/components/toggle.js.map +0 -1
- package/dist/components/tooltip.js.map +0 -1
- package/dist/components/top-bar.js.map +0 -1
- package/dist/components/use-shell-nav.js.map +0 -1
- package/dist/generated/terminal.js +0 -70
- package/dist/generated/terminal.js.map +0 -1
- /package/dist/components/{field-context.js → atomic/field-context.js} +0 -0
- /package/dist/components/{nav-link.js → shell/nav-link.js} +0 -0
- /package/dist/components/{nav-model.js → shell/nav-model.js} +0 -0
- /package/dist/components/{settled.js → shell/settled.js} +0 -0
- /package/dist/components/{shell-icons.js → shell/shell-icons.js} +0 -0
- /package/dist/components/{use-shell-nav.js → shell/use-shell-nav.js} +0 -0
- /package/dist/{components → icons}/select-chevron.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/chat-composer.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport {\n CHAT_COMPOSER_ATTACHMENTS,\n CHAT_COMPOSER_BASE,\n CHAT_COMPOSER_FIELD,\n CHAT_COMPOSER_HINT,\n CHAT_COMPOSER_IDLE,\n CHAT_COMPOSER_SPACER,\n CHAT_COMPOSER_STREAMING,\n CHAT_COMPOSER_TOOLBAR,\n MODEL_CHIP_BASE,\n} from \"../../generated/chat-composer\";\nimport { cn } from \"../../lib/cn\";\nimport { useControllableState } from \"../../primitives/use-controllable-state\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuTrigger,\n} from \"../atomic/dropdown-menu\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { Textarea } from \"../atomic/textarea\";\nimport { UiIcon } from \"../../icons/ui-icons\";\n\nexport interface ModelOption {\n id: string;\n label: string;\n}\n\nexport interface ModelChipProps {\n models: readonly ModelOption[];\n value: string;\n onValueChange: (id: string) => void;\n /** Names the menu. Default `\"Model\"`. */\n \"aria-label\"?: string;\n className?: string;\n}\n\n/** The small bordered button naming the model; opens a radio menu of the options. */\nexport function ModelChip({ models, value, onValueChange, \"aria-label\": ariaLabel = \"Model\", className }: ModelChipProps) {\n const current = models.find((m) => m.id === value);\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n aria-label={`${ariaLabel}: ${current?.label ?? value}`}\n data-slot=\"model-chip\"\n className={cn(MODEL_CHIP_BASE, className)}\n >\n {current?.label ?? value}\n <UiIcon name=\"chevron-down\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuRadioGroup value={value} onValueChange={onValueChange}>\n {models.map((m) => (\n <DropdownMenuRadioItem key={m.id} value={m.id}>\n {m.label}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nexport interface ChatComposerLabels {\n /** Names the message field. Default `\"Message\"`. */\n field?: string;\n send?: string;\n stop?: string;\n attach?: string;\n}\n\nexport interface ChatComposerProps extends Omit<React.ComponentProps<\"div\">, \"onChange\" | \"defaultValue\"> {\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n /** Enter or the send button, with the trimmed text. The field clears afterwards. */\n onSend: (text: string) => void;\n /** The assistant is answering: the button becomes Stop. Enter still queues a follow-up. */\n streaming?: boolean;\n onStop?: () => void;\n placeholder?: string;\n /** Shows the attach button. */\n onAttach?: () => void;\n /** A row of `ChatAttachment`s above the field. */\n attachments?: React.ReactNode;\n /** Model chips and other toolbar controls, after the attach button. */\n tools?: React.ReactNode;\n /** A caption in the toolbar: \"Enter to send · Shift+Enter for a new line\". */\n hint?: React.ReactNode;\n disabled?: boolean;\n labels?: ChatComposerLabels;\n}\n\nexport function ChatComposer({\n value,\n defaultValue = \"\",\n onValueChange,\n onSend,\n streaming = false,\n onStop,\n placeholder = \"Ask the assistant…\",\n onAttach,\n attachments,\n tools,\n hint,\n disabled = false,\n labels = {},\n className,\n ...props\n}: ChatComposerProps) {\n const [text, setText] = useControllableState<string>({\n ...(value !== undefined ? { value } : {}),\n defaultValue,\n ...(onValueChange ? { onChange: onValueChange } : {}),\n });\n const trimmed = text.trim();\n const root = React.useRef<HTMLDivElement>(null);\n\n const send = () => {\n if (trimmed === \"\" || disabled) return;\n onSend(trimmed);\n setText(\"\");\n };\n\n return (\n <div\n ref={root}\n data-slot=\"chat-composer\"\n data-state={streaming ? \"streaming\" : \"default\"}\n className={cn(\n CHAT_COMPOSER_BASE,\n streaming ? CHAT_COMPOSER_STREAMING : CHAT_COMPOSER_IDLE,\n className,\n )}\n {...props}\n >\n {attachments != null && <div className={CHAT_COMPOSER_ATTACHMENTS}>{attachments}</div>}\n <Textarea\n rows={1}\n value={text}\n disabled={disabled}\n placeholder={placeholder}\n aria-label={labels.field ?? \"Message\"}\n onChange={(e) => setText(e.target.value)}\n onKeyDown={(e) => {\n // Enter during IME composition confirms the candidate; it must not send. Safari reports the\n // confirming Enter after compositionend, with isComposing already false and keyCode 229.\n if (e.key === \"Enter\" && !e.shiftKey && !e.nativeEvent.isComposing && e.keyCode !== 229) {\n e.preventDefault();\n send();\n }\n }}\n className={CHAT_COMPOSER_FIELD}\n />\n <div className={CHAT_COMPOSER_TOOLBAR}>\n {onAttach && (\n <IconButton aria-label={labels.attach ?? \"Attach file\"} size=\"sm\" onClick={onAttach} disabled={disabled}>\n <UiIcon name=\"plus\" />\n </IconButton>\n )}\n {tools}\n <span className={CHAT_COMPOSER_SPACER} />\n {hint != null && <span className={CHAT_COMPOSER_HINT}>{hint}</span>}\n {streaming ? (\n <IconButton\n variant=\"outline\"\n aria-label={labels.stop ?? \"Stop\"}\n onClick={() => {\n onStop?.();\n // The Stop button turns into Send, so focus would otherwise stay on a swapped-out element.\n root.current?.querySelector(\"textarea\")?.focus();\n }}\n >\n <UiIcon name=\"stop\" />\n </IconButton>\n ) : (\n <IconButton\n variant=\"outline\"\n aria-label={labels.send ?? \"Send\"}\n onClick={send}\n disabled={disabled || trimmed === \"\"}\n >\n <UiIcon name=\"arrow-up\" />\n </IconButton>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
|
+
import {
|
|
6
|
+
CHAT_ATTACHMENT_BASE,
|
|
7
|
+
CHAT_ATTACHMENT_BODY,
|
|
8
|
+
CHAT_ATTACHMENT_ICON,
|
|
9
|
+
CHAT_ATTACHMENT_META,
|
|
10
|
+
CHAT_ATTACHMENT_NAME,
|
|
11
|
+
CHAT_ATTACHMENT_PROGRESS,
|
|
12
|
+
CHAT_ATTACHMENT_PROGRESS_FILL,
|
|
13
|
+
CHAT_ATTACHMENT_ROW,
|
|
14
|
+
CHAT_BUBBLE_BASE,
|
|
15
|
+
CHAT_MARKER_DOT,
|
|
16
|
+
CHAT_MARKER_RULE,
|
|
17
|
+
CHAT_MARKER_SEPARATOR,
|
|
18
|
+
CHAT_MARKER_SEPARATOR_LABEL,
|
|
19
|
+
CHAT_MARKER_STATUS,
|
|
20
|
+
CHAT_MARKER_TOOL,
|
|
21
|
+
CHAT_MARKER_TOOL_BODY,
|
|
22
|
+
CHAT_MARKER_TOOL_BORDER,
|
|
23
|
+
CHAT_MARKER_TOOL_CHECK,
|
|
24
|
+
CHAT_MARKER_TOOL_DETAIL,
|
|
25
|
+
CHAT_MARKER_TOOL_ERROR_BORDER,
|
|
26
|
+
CHAT_MARKER_TOOL_ICON,
|
|
27
|
+
CHAT_MARKER_TOOL_META,
|
|
28
|
+
CHAT_MARKER_TOOL_TITLE,
|
|
29
|
+
CHAT_MESSAGE_AVATAR,
|
|
30
|
+
CHAT_MESSAGE_AVATAR_TILE,
|
|
31
|
+
CHAT_MESSAGE_BASE,
|
|
32
|
+
CHAT_MESSAGE_BODY,
|
|
33
|
+
CHAT_MESSAGE_BODY_ASSISTANT,
|
|
34
|
+
CHAT_MESSAGE_BODY_USER,
|
|
35
|
+
CHAT_MESSAGE_HEADER,
|
|
36
|
+
CHAT_MESSAGE_NAME,
|
|
37
|
+
CHAT_MESSAGE_TIME,
|
|
38
|
+
CHAT_MESSAGE_USER,
|
|
39
|
+
chatBubbleVariantClasses,
|
|
40
|
+
chatStatusDotClasses,
|
|
41
|
+
chatToolMetaClasses
|
|
42
|
+
} from "../../generated/chat-message.js";
|
|
43
|
+
import { ChatStreamText } from "./chat-stream-text.js";
|
|
44
|
+
import { IconButton } from "../derived/icon-button.js";
|
|
45
|
+
import { UiIcon } from "../../icons/ui-icons.js";
|
|
46
|
+
function ChatBubble({ variant = "assistant", streaming = false, className, children, ...props }) {
|
|
47
|
+
const [live, setLive] = React.useState(streaming);
|
|
48
|
+
if (streaming && !live) setLive(true);
|
|
49
|
+
return /* @__PURE__ */ jsx(
|
|
50
|
+
"div",
|
|
51
|
+
{
|
|
52
|
+
"data-slot": "chat-bubble",
|
|
53
|
+
"data-variant": variant,
|
|
54
|
+
"data-streaming": streaming ? "" : void 0,
|
|
55
|
+
"data-caret": live ? "" : void 0,
|
|
56
|
+
className: cn(CHAT_BUBBLE_BASE, chatBubbleVariantClasses[variant], className),
|
|
57
|
+
...props,
|
|
58
|
+
children: live && typeof children === "string" ? /* @__PURE__ */ jsx(ChatStreamText, { text: children, streaming }) : children
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
function ChatMessage({ role, name, time, avatar, compact = false, className, children, ...props }) {
|
|
63
|
+
const user = role === "user";
|
|
64
|
+
return /* @__PURE__ */ jsxs(
|
|
65
|
+
"div",
|
|
66
|
+
{
|
|
67
|
+
"data-slot": "chat-message",
|
|
68
|
+
"data-role": role,
|
|
69
|
+
"data-enter": "rise",
|
|
70
|
+
className: cn(CHAT_MESSAGE_BASE, user && CHAT_MESSAGE_USER, className),
|
|
71
|
+
...props,
|
|
72
|
+
children: [
|
|
73
|
+
!user && !compact && /* @__PURE__ */ jsx("div", { className: CHAT_MESSAGE_AVATAR, children: avatar ?? /* @__PURE__ */ jsx("span", { className: CHAT_MESSAGE_AVATAR_TILE, children: /* @__PURE__ */ jsx(UiIcon, { name: "sparkles" }) }) }),
|
|
74
|
+
/* @__PURE__ */ jsxs("div", { className: cn(CHAT_MESSAGE_BODY, user ? CHAT_MESSAGE_BODY_USER : CHAT_MESSAGE_BODY_ASSISTANT), children: [
|
|
75
|
+
!compact && (name != null || time != null) && /* @__PURE__ */ jsxs("div", { className: CHAT_MESSAGE_HEADER, children: [
|
|
76
|
+
name != null && /* @__PURE__ */ jsx("span", { className: CHAT_MESSAGE_NAME, children: name }),
|
|
77
|
+
time != null && /* @__PURE__ */ jsx("span", { className: CHAT_MESSAGE_TIME, children: time })
|
|
78
|
+
] }),
|
|
79
|
+
children
|
|
80
|
+
] })
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
function ChatMarker(allProps) {
|
|
86
|
+
if (allProps.type === "separator") {
|
|
87
|
+
const { type: _type2, className: className2, children: children2, ...rest2 } = allProps;
|
|
88
|
+
return /* @__PURE__ */ jsxs("div", { "data-slot": "chat-marker", "data-type": "separator", className: cn(CHAT_MARKER_SEPARATOR, className2), ...rest2, children: [
|
|
89
|
+
/* @__PURE__ */ jsx("span", { className: CHAT_MARKER_RULE }),
|
|
90
|
+
/* @__PURE__ */ jsx("span", { className: CHAT_MARKER_SEPARATOR_LABEL, children: children2 }),
|
|
91
|
+
/* @__PURE__ */ jsx("span", { className: CHAT_MARKER_RULE })
|
|
92
|
+
] });
|
|
93
|
+
}
|
|
94
|
+
if (allProps.type === "status") {
|
|
95
|
+
const { type: _type2, state: state2 = "done", className: className2, children: children2, ...rest2 } = allProps;
|
|
96
|
+
return /* @__PURE__ */ jsxs(
|
|
97
|
+
"div",
|
|
98
|
+
{
|
|
99
|
+
"data-slot": "chat-marker",
|
|
100
|
+
"data-type": "status",
|
|
101
|
+
"data-state": state2,
|
|
102
|
+
role: state2 === "running" ? "status" : void 0,
|
|
103
|
+
className: cn(CHAT_MARKER_STATUS, className2),
|
|
104
|
+
...rest2,
|
|
105
|
+
children: [
|
|
106
|
+
/* @__PURE__ */ jsx(
|
|
107
|
+
"span",
|
|
108
|
+
{
|
|
109
|
+
"aria-hidden": true,
|
|
110
|
+
className: cn(CHAT_MARKER_DOT, chatStatusDotClasses[state2])
|
|
111
|
+
}
|
|
112
|
+
),
|
|
113
|
+
children2
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const { type: _type, state = "done", title, detail, meta, className, children, ...rest } = allProps;
|
|
119
|
+
return /* @__PURE__ */ jsxs(
|
|
120
|
+
"div",
|
|
121
|
+
{
|
|
122
|
+
"data-slot": "chat-marker",
|
|
123
|
+
"data-type": "tool",
|
|
124
|
+
"data-state": state,
|
|
125
|
+
className: cn(
|
|
126
|
+
CHAT_MARKER_TOOL,
|
|
127
|
+
state === "error" ? CHAT_MARKER_TOOL_ERROR_BORDER : CHAT_MARKER_TOOL_BORDER,
|
|
128
|
+
className
|
|
129
|
+
),
|
|
130
|
+
...rest,
|
|
131
|
+
children: [
|
|
132
|
+
/* @__PURE__ */ jsx(UiIcon, { name: state === "error" ? "alert" : "sparkles", className: CHAT_MARKER_TOOL_ICON }),
|
|
133
|
+
/* @__PURE__ */ jsxs("div", { className: CHAT_MARKER_TOOL_BODY, children: [
|
|
134
|
+
/* @__PURE__ */ jsx("span", { className: CHAT_MARKER_TOOL_TITLE, children: title }),
|
|
135
|
+
detail != null && /* @__PURE__ */ jsx("span", { className: CHAT_MARKER_TOOL_DETAIL, children: detail }),
|
|
136
|
+
children
|
|
137
|
+
] }),
|
|
138
|
+
meta != null && /* @__PURE__ */ jsx("span", { className: cn(CHAT_MARKER_TOOL_META, chatToolMetaClasses[state]), children: meta }),
|
|
139
|
+
state === "done" && /* @__PURE__ */ jsx(UiIcon, { name: "check", className: CHAT_MARKER_TOOL_CHECK })
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
function ChatAttachment({
|
|
145
|
+
type = "file",
|
|
146
|
+
state = "ready",
|
|
147
|
+
name,
|
|
148
|
+
meta,
|
|
149
|
+
progress = 0,
|
|
150
|
+
onRemove,
|
|
151
|
+
removeLabel,
|
|
152
|
+
className,
|
|
153
|
+
...props
|
|
154
|
+
}) {
|
|
155
|
+
const pct = Math.min(100, Math.max(0, Math.round(progress)));
|
|
156
|
+
return /* @__PURE__ */ jsxs(
|
|
157
|
+
"div",
|
|
158
|
+
{
|
|
159
|
+
"data-slot": "chat-attachment",
|
|
160
|
+
"data-type": type,
|
|
161
|
+
"data-state": state,
|
|
162
|
+
className: cn(CHAT_ATTACHMENT_BASE, className),
|
|
163
|
+
...props,
|
|
164
|
+
children: [
|
|
165
|
+
/* @__PURE__ */ jsxs("div", { className: CHAT_ATTACHMENT_ROW, children: [
|
|
166
|
+
/* @__PURE__ */ jsx("span", { className: CHAT_ATTACHMENT_ICON, children: /* @__PURE__ */ jsx(UiIcon, { name: type === "image" ? "image" : "file" }) }),
|
|
167
|
+
/* @__PURE__ */ jsxs("div", { className: CHAT_ATTACHMENT_BODY, children: [
|
|
168
|
+
/* @__PURE__ */ jsx("span", { className: CHAT_ATTACHMENT_NAME, children: name }),
|
|
169
|
+
/* @__PURE__ */ jsx("span", { className: CHAT_ATTACHMENT_META, children: meta ?? (state === "uploading" ? `Uploading\u2026 ${pct}%` : null) })
|
|
170
|
+
] }),
|
|
171
|
+
onRemove && /* @__PURE__ */ jsx(IconButton, { "aria-label": removeLabel ?? `Remove ${name}`, size: "sm", onClick: onRemove, children: /* @__PURE__ */ jsx(UiIcon, { name: "x" }) })
|
|
172
|
+
] }),
|
|
173
|
+
state === "uploading" && /* @__PURE__ */ jsx(
|
|
174
|
+
"div",
|
|
175
|
+
{
|
|
176
|
+
role: "progressbar",
|
|
177
|
+
"aria-label": name,
|
|
178
|
+
"aria-valuemin": 0,
|
|
179
|
+
"aria-valuemax": 100,
|
|
180
|
+
"aria-valuenow": pct,
|
|
181
|
+
className: CHAT_ATTACHMENT_PROGRESS,
|
|
182
|
+
children: /* @__PURE__ */ jsx("div", { className: CHAT_ATTACHMENT_PROGRESS_FILL, style: { width: `${pct}%` } })
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
export {
|
|
190
|
+
ChatAttachment,
|
|
191
|
+
ChatBubble,
|
|
192
|
+
ChatMarker,
|
|
193
|
+
ChatMessage
|
|
194
|
+
};
|
|
195
|
+
//# sourceMappingURL=chat-message.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/chat-message.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport {\n CHAT_ATTACHMENT_BASE,\n CHAT_ATTACHMENT_BODY,\n CHAT_ATTACHMENT_ICON,\n CHAT_ATTACHMENT_META,\n CHAT_ATTACHMENT_NAME,\n CHAT_ATTACHMENT_PROGRESS,\n CHAT_ATTACHMENT_PROGRESS_FILL,\n CHAT_ATTACHMENT_ROW,\n CHAT_BUBBLE_BASE,\n CHAT_MARKER_DOT,\n CHAT_MARKER_RULE,\n CHAT_MARKER_SEPARATOR,\n CHAT_MARKER_SEPARATOR_LABEL,\n CHAT_MARKER_STATUS,\n CHAT_MARKER_TOOL,\n CHAT_MARKER_TOOL_BODY,\n CHAT_MARKER_TOOL_BORDER,\n CHAT_MARKER_TOOL_CHECK,\n CHAT_MARKER_TOOL_DETAIL,\n CHAT_MARKER_TOOL_ERROR_BORDER,\n CHAT_MARKER_TOOL_ICON,\n CHAT_MARKER_TOOL_META,\n CHAT_MARKER_TOOL_TITLE,\n CHAT_MESSAGE_AVATAR,\n CHAT_MESSAGE_AVATAR_TILE,\n CHAT_MESSAGE_BASE,\n CHAT_MESSAGE_BODY,\n CHAT_MESSAGE_BODY_ASSISTANT,\n CHAT_MESSAGE_BODY_USER,\n CHAT_MESSAGE_HEADER,\n CHAT_MESSAGE_NAME,\n CHAT_MESSAGE_TIME,\n CHAT_MESSAGE_USER,\n chatBubbleVariantClasses,\n chatStatusDotClasses,\n chatToolMetaClasses,\n} from \"../../generated/chat-message\";\nimport type { ChatBubbleVariant, ChatStatusState, ChatToolState } from \"../../generated/chat-message\";\nimport { ChatStreamText } from \"./chat-stream-text\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { UiIcon } from \"../../icons/ui-icons\";\n\nexport type { ChatBubbleVariant };\n\nexport interface ChatBubbleProps extends React.ComponentProps<\"div\"> {\n variant?: ChatBubbleVariant;\n /**\n * Still receiving text: a soft caret pulses after it, and string children\n * fade in word by word (`ChatStreamText`). Stays set until the text is complete.\n */\n streaming?: boolean;\n}\n\n/** One block of message text; `assistant` has no box so long answers read as a page. */\nexport function ChatBubble({ variant = \"assistant\", streaming = false, className, children, ...props }: ChatBubbleProps) {\n // Once a bubble has streamed it keeps its word wrappers and its caret slot, so\n // the last words finish landing and the caret fades out instead of vanishing.\n const [live, setLive] = React.useState(streaming);\n if (streaming && !live) setLive(true);\n return (\n <div\n data-slot=\"chat-bubble\"\n data-variant={variant}\n data-streaming={streaming ? \"\" : undefined}\n data-caret={live ? \"\" : undefined}\n className={cn(CHAT_BUBBLE_BASE, chatBubbleVariantClasses[variant], className)}\n {...props}\n >\n {live && typeof children === \"string\" ? <ChatStreamText text={children} streaming={streaming} /> : children}\n </div>\n );\n}\n\nexport interface ChatMessageProps extends Omit<React.ComponentProps<\"div\">, \"role\"> {\n role: \"assistant\" | \"user\";\n /** \"Assistant\", \"You\". */\n name?: React.ReactNode;\n /** Already formatted: \"13:04\". */\n time?: React.ReactNode;\n /** The assistant's tile; a default sparkle tile when omitted. */\n avatar?: React.ReactNode;\n /** Drops the name row and the avatar, for a narrow dock. */\n compact?: boolean;\n}\n\n/** A turn: who spoke, when, then the bubbles and markers. Fades in and rises once when it mounts. */\nexport function ChatMessage({ role, name, time, avatar, compact = false, className, children, ...props }: ChatMessageProps) {\n const user = role === \"user\";\n return (\n <div\n data-slot=\"chat-message\"\n data-role={role}\n data-enter=\"rise\"\n className={cn(CHAT_MESSAGE_BASE, user && CHAT_MESSAGE_USER, className)}\n {...props}\n >\n {!user && !compact && (\n <div className={CHAT_MESSAGE_AVATAR}>\n {avatar ?? (\n <span className={CHAT_MESSAGE_AVATAR_TILE}>\n <UiIcon name=\"sparkles\" />\n </span>\n )}\n </div>\n )}\n <div className={cn(CHAT_MESSAGE_BODY, user ? CHAT_MESSAGE_BODY_USER : CHAT_MESSAGE_BODY_ASSISTANT)}>\n {!compact && (name != null || time != null) && (\n <div className={CHAT_MESSAGE_HEADER}>\n {name != null && <span className={CHAT_MESSAGE_NAME}>{name}</span>}\n {time != null && <span className={CHAT_MESSAGE_TIME}>{time}</span>}\n </div>\n )}\n {children}\n </div>\n </div>\n );\n}\n\nexport type ChatMarkerProps = Omit<React.ComponentProps<\"div\">, \"title\"> &\n (\n | { type: \"status\"; state?: ChatStatusState }\n | {\n type: \"tool\";\n state?: ChatToolState;\n /** The tool's name. */\n title: React.ReactNode;\n detail?: React.ReactNode;\n /** The right-hand note: \"Running…\", \"0.4s\", \"Failed · 1.2s\". */\n meta?: React.ReactNode;\n }\n | { type: \"separator\" }\n );\n\n/** The quiet lines between messages: progress, a tool call, or a day break. */\nexport function ChatMarker(allProps: ChatMarkerProps) {\n if (allProps.type === \"separator\") {\n const { type: _type, className, children, ...rest } = allProps;\n return (\n <div data-slot=\"chat-marker\" data-type=\"separator\" className={cn(CHAT_MARKER_SEPARATOR, className)} {...rest}>\n <span className={CHAT_MARKER_RULE} />\n <span className={CHAT_MARKER_SEPARATOR_LABEL}>{children}</span>\n <span className={CHAT_MARKER_RULE} />\n </div>\n );\n }\n if (allProps.type === \"status\") {\n const { type: _type, state = \"done\", className, children, ...rest } = allProps;\n return (\n <div\n data-slot=\"chat-marker\"\n data-type=\"status\"\n data-state={state}\n role={state === \"running\" ? \"status\" : undefined}\n className={cn(CHAT_MARKER_STATUS, className)}\n {...rest}\n >\n <span\n aria-hidden\n className={cn(CHAT_MARKER_DOT, chatStatusDotClasses[state])}\n />\n {children}\n </div>\n );\n }\n const { type: _type, state = \"done\", title, detail, meta, className, children, ...rest } = allProps;\n return (\n <div\n data-slot=\"chat-marker\"\n data-type=\"tool\"\n data-state={state}\n className={cn(\n CHAT_MARKER_TOOL,\n state === \"error\" ? CHAT_MARKER_TOOL_ERROR_BORDER : CHAT_MARKER_TOOL_BORDER,\n className,\n )}\n {...rest}\n >\n <UiIcon name={state === \"error\" ? \"alert\" : \"sparkles\"} className={CHAT_MARKER_TOOL_ICON} />\n <div className={CHAT_MARKER_TOOL_BODY}>\n <span className={CHAT_MARKER_TOOL_TITLE}>{title}</span>\n {detail != null && <span className={CHAT_MARKER_TOOL_DETAIL}>{detail}</span>}\n {children}\n </div>\n {meta != null && (\n <span className={cn(CHAT_MARKER_TOOL_META, chatToolMetaClasses[state])}>{meta}\n </span>\n )}\n {state === \"done\" && <UiIcon name=\"check\" className={CHAT_MARKER_TOOL_CHECK} />}\n </div>\n );\n}\n\nexport interface ChatAttachmentProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n type?: \"file\" | \"image\";\n state?: \"ready\" | \"uploading\";\n name: string;\n /** \"PDF · 1.2 MB\"; while uploading, defaults to the percentage. */\n meta?: React.ReactNode;\n /** 0 to 100, while uploading. */\n progress?: number;\n /** Shows the remove button. */\n onRemove?: () => void;\n /** Default `\"Remove {name}\"`. */\n removeLabel?: string;\n}\n\n/** A card for a file in a message or in the composer. */\nexport function ChatAttachment({\n type = \"file\",\n state = \"ready\",\n name,\n meta,\n progress = 0,\n onRemove,\n removeLabel,\n className,\n ...props\n}: ChatAttachmentProps) {\n const pct = Math.min(100, Math.max(0, Math.round(progress)));\n return (\n <div\n data-slot=\"chat-attachment\"\n data-type={type}\n data-state={state}\n className={cn(CHAT_ATTACHMENT_BASE, className)}\n {...props}\n >\n <div className={CHAT_ATTACHMENT_ROW}>\n <span className={CHAT_ATTACHMENT_ICON}>\n <UiIcon name={type === \"image\" ? \"image\" : \"file\"} />\n </span>\n <div className={CHAT_ATTACHMENT_BODY}>\n <span className={CHAT_ATTACHMENT_NAME}>{name}</span>\n <span className={CHAT_ATTACHMENT_META}>{meta ?? (state === \"uploading\" ? `Uploading… ${pct}%` : null)}</span>\n </div>\n {onRemove && (\n <IconButton aria-label={removeLabel ?? `Remove ${name}`} size=\"sm\" onClick={onRemove}>\n <UiIcon name=\"x\" />\n </IconButton>\n )}\n </div>\n {state === \"uploading\" && (\n <div\n role=\"progressbar\"\n aria-label={name}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={pct}\n className={CHAT_ATTACHMENT_PROGRESS}\n >\n <div className={CHAT_ATTACHMENT_PROGRESS_FILL} style={{ width: `${pct}%` }} />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["_type","className","children","rest","state"]}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
|
+
import { AsideHeader } from "./aside.js";
|
|
6
|
+
import { IconButton } from "../derived/icon-button.js";
|
|
7
|
+
import { UiIcon } from "../../icons/ui-icons.js";
|
|
8
|
+
const PIN_SLACK = 24;
|
|
9
|
+
function ChatPanel({ children, empty, composer, streaming = false, labels = {}, className, ...props }) {
|
|
10
|
+
const scroller = React.useRef(null);
|
|
11
|
+
const pinned = React.useRef(true);
|
|
12
|
+
const [away, setAway] = React.useState(false);
|
|
13
|
+
const [scrolled, setScrolled] = React.useState(false);
|
|
14
|
+
const isEmpty = React.Children.toArray(children).length === 0 && empty != null;
|
|
15
|
+
const toEnd = () => {
|
|
16
|
+
const el = scroller.current;
|
|
17
|
+
if (el) el.scrollTop = el.scrollHeight;
|
|
18
|
+
};
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
const el = scroller.current;
|
|
21
|
+
if (!el) return;
|
|
22
|
+
const follow = () => {
|
|
23
|
+
if (pinned.current) toEnd();
|
|
24
|
+
};
|
|
25
|
+
const mo = new MutationObserver(follow);
|
|
26
|
+
mo.observe(el, { childList: true, subtree: true, characterData: true });
|
|
27
|
+
const ro = typeof ResizeObserver === "function" ? new ResizeObserver(follow) : null;
|
|
28
|
+
ro?.observe(el);
|
|
29
|
+
toEnd();
|
|
30
|
+
return () => {
|
|
31
|
+
mo.disconnect();
|
|
32
|
+
ro?.disconnect();
|
|
33
|
+
};
|
|
34
|
+
}, [isEmpty]);
|
|
35
|
+
return /* @__PURE__ */ jsxs("div", { "data-slot": "chat-panel", className: cn("flex min-h-0 flex-1 flex-col", className), ...props, children: [
|
|
36
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
|
|
37
|
+
isEmpty ? /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col justify-center px-4", children: empty }) : /* @__PURE__ */ jsx(
|
|
38
|
+
"div",
|
|
39
|
+
{
|
|
40
|
+
ref: scroller,
|
|
41
|
+
role: "log",
|
|
42
|
+
"aria-live": "polite",
|
|
43
|
+
"aria-busy": streaming,
|
|
44
|
+
"aria-label": labels.log ?? "Conversation",
|
|
45
|
+
tabIndex: 0,
|
|
46
|
+
"data-slot": "chat-log",
|
|
47
|
+
"data-scrolled": scrolled ? "" : void 0,
|
|
48
|
+
className: "flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
|
|
49
|
+
onScroll: (e) => {
|
|
50
|
+
const el = e.currentTarget;
|
|
51
|
+
const atEnd = el.scrollHeight - el.scrollTop - el.clientHeight <= PIN_SLACK;
|
|
52
|
+
pinned.current = atEnd;
|
|
53
|
+
setAway(!atEnd);
|
|
54
|
+
setScrolled(el.scrollTop > 0);
|
|
55
|
+
},
|
|
56
|
+
children
|
|
57
|
+
}
|
|
58
|
+
),
|
|
59
|
+
!isEmpty && // Stays mounted so it can fade; hidden from the tree and the tab order until it matters.
|
|
60
|
+
/* @__PURE__ */ jsx(
|
|
61
|
+
IconButton,
|
|
62
|
+
{
|
|
63
|
+
variant: "outline",
|
|
64
|
+
"aria-label": labels.jump ?? "Jump to latest",
|
|
65
|
+
"data-chat-jump": away ? "visible" : "hidden",
|
|
66
|
+
"aria-hidden": !away,
|
|
67
|
+
tabIndex: away ? 0 : -1,
|
|
68
|
+
disabled: !away,
|
|
69
|
+
className: "absolute bottom-3 left-1/2 -translate-x-1/2",
|
|
70
|
+
onClick: () => {
|
|
71
|
+
pinned.current = true;
|
|
72
|
+
setAway(false);
|
|
73
|
+
toEnd();
|
|
74
|
+
scroller.current?.focus();
|
|
75
|
+
},
|
|
76
|
+
children: /* @__PURE__ */ jsx(UiIcon, { name: "chevron-down" })
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
] }),
|
|
80
|
+
composer != null && /* @__PURE__ */ jsx("div", { className: "shrink-0 px-4 pb-4", children: composer })
|
|
81
|
+
] });
|
|
82
|
+
}
|
|
83
|
+
function ChatDock({
|
|
84
|
+
title,
|
|
85
|
+
expanded,
|
|
86
|
+
defaultExpanded = true,
|
|
87
|
+
onExpandedChange,
|
|
88
|
+
actions,
|
|
89
|
+
prompt = "Ask the assistant\u2026",
|
|
90
|
+
children,
|
|
91
|
+
empty,
|
|
92
|
+
composer,
|
|
93
|
+
streaming = false,
|
|
94
|
+
labels = {},
|
|
95
|
+
className,
|
|
96
|
+
...props
|
|
97
|
+
}) {
|
|
98
|
+
const [inner, setInner] = React.useState(defaultExpanded);
|
|
99
|
+
const open = expanded ?? inner;
|
|
100
|
+
const root = React.useRef(null);
|
|
101
|
+
const moved = React.useRef(false);
|
|
102
|
+
const set = (next) => {
|
|
103
|
+
moved.current = true;
|
|
104
|
+
setInner(next);
|
|
105
|
+
onExpandedChange?.(next);
|
|
106
|
+
};
|
|
107
|
+
React.useEffect(() => {
|
|
108
|
+
if (!moved.current) return;
|
|
109
|
+
moved.current = false;
|
|
110
|
+
const target = open ? root.current?.querySelector("textarea") ?? root.current?.querySelector("[role=log]") : root.current?.querySelector("button");
|
|
111
|
+
target?.focus();
|
|
112
|
+
}, [open]);
|
|
113
|
+
if (!open) {
|
|
114
|
+
return /* @__PURE__ */ jsx("section", { ref: root, "data-slot": "chat-dock", "data-state": "collapsed", className: cn("bg-card", className), ...props, children: /* @__PURE__ */ jsxs(
|
|
115
|
+
"button",
|
|
116
|
+
{
|
|
117
|
+
type: "button",
|
|
118
|
+
"aria-label": labels.open ?? "Open assistant",
|
|
119
|
+
onClick: () => set(true),
|
|
120
|
+
className: "flex h-11 w-full items-center gap-2 border-t border-border px-4 text-left text-body text-ink-soft outline-none hover:bg-hover focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
|
|
121
|
+
children: [
|
|
122
|
+
/* @__PURE__ */ jsx(UiIcon, { name: "sparkles" }),
|
|
123
|
+
prompt
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
) });
|
|
127
|
+
}
|
|
128
|
+
return /* @__PURE__ */ jsxs("section", { ref: root, "data-slot": "chat-dock", "data-state": "expanded", className: cn("flex h-full min-h-0 flex-col bg-card", className), ...props, children: [
|
|
129
|
+
/* @__PURE__ */ jsx(AsideHeader, { title, actions, onClose: () => set(false), labels: { close: labels.collapse ?? "Collapse" } }),
|
|
130
|
+
/* @__PURE__ */ jsx(ChatPanel, { empty, composer, streaming, labels, children })
|
|
131
|
+
] });
|
|
132
|
+
}
|
|
133
|
+
export {
|
|
134
|
+
ChatDock,
|
|
135
|
+
ChatPanel
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=chat-panel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/chat-panel.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport { AsideHeader } from \"./aside\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { UiIcon } from \"../../icons/ui-icons\";\n\n// Within this many pixels of the end still counts as \"at the end\": sub-pixel\n// scroll positions and a growing last line would otherwise unpin the reader.\nconst PIN_SLACK = 24;\n\nexport interface ChatPanelLabels {\n /** The scroll-to-end button. Default `\"Jump to latest\"`. */\n jump?: string;\n /** Names the log region. Default `\"Conversation\"`. */\n log?: string;\n}\n\nexport interface ChatPanelProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n /** `ChatMessage`s and `ChatMarker`s, oldest first. */\n children?: React.ReactNode;\n /** Shown instead of the log while there is nothing to read. */\n empty?: React.ReactNode;\n /** The composer, under the log. */\n composer?: React.ReactNode;\n /**\n * The last turn is still arriving. The log is marked busy, so a screen reader\n * waits for the finished text instead of announcing every chunk.\n */\n streaming?: boolean;\n labels?: ChatPanelLabels;\n}\n\n/**\n * The conversation: a scrolling log that follows new content while the reader\n * is at the end, and lets go the moment they scroll up. The jump button brings\n * them back. The log is a polite live region, so new turns are announced\n * without taking focus.\n */\nexport function ChatPanel({ children, empty, composer, streaming = false, labels = {}, className, ...props }: ChatPanelProps) {\n const scroller = React.useRef<HTMLDivElement>(null);\n const pinned = React.useRef(true);\n const [away, setAway] = React.useState(false);\n const [scrolled, setScrolled] = React.useState(false);\n // `count` would call `false` a child, and `{list.length > 0 && …}` is common.\n const isEmpty = React.Children.toArray(children).length === 0 && empty != null;\n\n const toEnd = () => {\n const el = scroller.current;\n if (el) el.scrollTop = el.scrollHeight;\n };\n\n // Streaming grows the last node's text without adding nodes, so watch characters too.\n React.useEffect(() => {\n const el = scroller.current;\n if (!el) return;\n const follow = () => {\n if (pinned.current) toEnd();\n };\n const mo = new MutationObserver(follow);\n mo.observe(el, { childList: true, subtree: true, characterData: true });\n const ro = typeof ResizeObserver === \"function\" ? new ResizeObserver(follow) : null;\n ro?.observe(el);\n toEnd();\n return () => {\n mo.disconnect();\n ro?.disconnect();\n };\n }, [isEmpty]);\n\n return (\n <div data-slot=\"chat-panel\" className={cn(\"flex min-h-0 flex-1 flex-col\", className)} {...props}>\n <div className=\"relative flex min-h-0 flex-1 flex-col\">\n {isEmpty ? (\n <div className=\"flex flex-1 flex-col justify-center px-4\">{empty}</div>\n ) : (\n <div\n ref={scroller}\n role=\"log\"\n aria-live=\"polite\"\n aria-busy={streaming}\n aria-label={labels.log ?? \"Conversation\"}\n tabIndex={0}\n data-slot=\"chat-log\"\n data-scrolled={scrolled ? \"\" : undefined}\n className=\"flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring\"\n onScroll={(e) => {\n const el = e.currentTarget;\n const atEnd = el.scrollHeight - el.scrollTop - el.clientHeight <= PIN_SLACK;\n pinned.current = atEnd;\n setAway(!atEnd);\n setScrolled(el.scrollTop > 0);\n }}\n >\n {children}\n </div>\n )}\n {!isEmpty && (\n // Stays mounted so it can fade; hidden from the tree and the tab order until it matters.\n <IconButton\n variant=\"outline\"\n aria-label={labels.jump ?? \"Jump to latest\"}\n data-chat-jump={away ? \"visible\" : \"hidden\"}\n aria-hidden={!away}\n tabIndex={away ? 0 : -1}\n disabled={!away}\n className=\"absolute bottom-3 left-1/2 -translate-x-1/2\"\n onClick={() => {\n pinned.current = true;\n setAway(false);\n toEnd();\n // The button unmounts with `away`; keep focus in the conversation.\n scroller.current?.focus();\n }}\n >\n <UiIcon name=\"chevron-down\" />\n </IconButton>\n )}\n </div>\n {composer != null && <div className=\"shrink-0 px-4 pb-4\">{composer}</div>}\n </div>\n );\n}\n\nexport interface ChatDockLabels extends ChatPanelLabels {\n /** The launcher bar's name when collapsed. Default `\"Open assistant\"`. */\n open?: string;\n /** Default `\"Collapse\"`. */\n collapse?: string;\n}\n\nexport interface ChatDockProps extends Omit<React.ComponentProps<\"section\">, \"title\" | \"children\"> {\n title: React.ReactNode;\n expanded?: boolean;\n defaultExpanded?: boolean;\n onExpandedChange?: (expanded: boolean) => void;\n /** Header buttons before collapse: history. */\n actions?: React.ReactNode;\n /** The launcher bar's text when collapsed. */\n prompt?: string;\n children?: React.ReactNode;\n empty?: React.ReactNode;\n composer?: React.ReactNode;\n streaming?: boolean;\n labels?: ChatDockLabels;\n}\n\n/** The assistant docked in a column: a launcher bar when collapsed, header + conversation + composer when open. */\nexport function ChatDock({\n title,\n expanded,\n defaultExpanded = true,\n onExpandedChange,\n actions,\n prompt = \"Ask the assistant…\",\n children,\n empty,\n composer,\n streaming = false,\n labels = {},\n className,\n ...props\n}: ChatDockProps) {\n const [inner, setInner] = React.useState(defaultExpanded);\n const open = expanded ?? inner;\n const root = React.useRef<HTMLElement>(null);\n const moved = React.useRef(false);\n const set = (next: boolean) => {\n moved.current = true;\n setInner(next);\n onExpandedChange?.(next);\n };\n // The button that was pressed unmounts with its state; hand focus to the same job in the other one.\n React.useEffect(() => {\n if (!moved.current) return;\n moved.current = false;\n const target = open\n ? (root.current?.querySelector<HTMLElement>(\"textarea\") ?? root.current?.querySelector<HTMLElement>(\"[role=log]\"))\n : root.current?.querySelector<HTMLElement>(\"button\");\n target?.focus();\n }, [open]);\n if (!open) {\n return (\n <section ref={root} data-slot=\"chat-dock\" data-state=\"collapsed\" className={cn(\"bg-card\", className)} {...props}>\n <button\n type=\"button\"\n aria-label={labels.open ?? \"Open assistant\"}\n onClick={() => set(true)}\n className=\"flex h-11 w-full items-center gap-2 border-t border-border px-4 text-left text-body text-ink-soft outline-none hover:bg-hover focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring\"\n >\n <UiIcon name=\"sparkles\" />\n {prompt}\n </button>\n </section>\n );\n }\n return (\n <section ref={root} data-slot=\"chat-dock\" data-state=\"expanded\" className={cn(\"flex h-full min-h-0 flex-col bg-card\", className)} {...props}>\n <AsideHeader title={title} actions={actions} onClose={() => set(false)} labels={{ close: labels.collapse ?? \"Collapse\" }} />\n <ChatPanel empty={empty} composer={composer} streaming={streaming} labels={labels}>\n {children}\n </ChatPanel>\n </section>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
const STAGGER_STEP_MS = 28;
|
|
5
|
+
const STAGGER_CAP = 6;
|
|
6
|
+
const SETTLE_FALLBACK_MS = 900;
|
|
7
|
+
function split(text) {
|
|
8
|
+
const lead = /^\s*/.exec(text)?.[0] ?? "";
|
|
9
|
+
const units = [];
|
|
10
|
+
for (const m of text.slice(lead.length).matchAll(/(\S+)(\s*)/g)) units.push({ word: m[1] ?? "", gap: m[2] ?? "" });
|
|
11
|
+
return { lead, units };
|
|
12
|
+
}
|
|
13
|
+
function delaysFor(from, to) {
|
|
14
|
+
return Array.from({ length: to - from }, (_, j) => Math.min(j, STAGGER_CAP) * STAGGER_STEP_MS);
|
|
15
|
+
}
|
|
16
|
+
function ChatStreamText({ text, streaming = false, ...props }) {
|
|
17
|
+
const parsed = split(text);
|
|
18
|
+
const [progress, setProgress] = React.useState(() => ({
|
|
19
|
+
text,
|
|
20
|
+
settled: streaming ? 0 : parsed.units.length,
|
|
21
|
+
delays: delaysFor(0, parsed.units.length)
|
|
22
|
+
}));
|
|
23
|
+
let cur = progress;
|
|
24
|
+
if (text !== progress.text) {
|
|
25
|
+
const appended = text.startsWith(progress.text);
|
|
26
|
+
const known = appended ? split(progress.text).units.length : 0;
|
|
27
|
+
cur = {
|
|
28
|
+
text,
|
|
29
|
+
settled: appended ? progress.settled : 0,
|
|
30
|
+
delays: [...appended ? progress.delays.slice(0, known) : [], ...delaysFor(known, parsed.units.length)]
|
|
31
|
+
};
|
|
32
|
+
setProgress(cur);
|
|
33
|
+
}
|
|
34
|
+
const settle = React.useCallback((upTo) => {
|
|
35
|
+
setProgress((p) => upTo > p.settled ? { ...p, settled: upTo } : p);
|
|
36
|
+
}, []);
|
|
37
|
+
const total = parsed.units.length;
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
if (streaming) return;
|
|
40
|
+
const id = setTimeout(() => settle(total), SETTLE_FALLBACK_MS);
|
|
41
|
+
return () => clearTimeout(id);
|
|
42
|
+
}, [streaming, total, settle]);
|
|
43
|
+
const settledText = parsed.lead + parsed.units.slice(0, cur.settled).map((u) => u.word + u.gap).join("");
|
|
44
|
+
return /* @__PURE__ */ jsxs(
|
|
45
|
+
"span",
|
|
46
|
+
{
|
|
47
|
+
"data-slot": "chat-stream-text",
|
|
48
|
+
onAnimationEnd: (e) => {
|
|
49
|
+
const i = Number(e.target.dataset["word"]);
|
|
50
|
+
if (Number.isInteger(i)) settle(i + 1);
|
|
51
|
+
},
|
|
52
|
+
...props,
|
|
53
|
+
children: [
|
|
54
|
+
settledText,
|
|
55
|
+
parsed.units.slice(cur.settled).map((u, k) => {
|
|
56
|
+
const i = cur.settled + k;
|
|
57
|
+
return /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
58
|
+
/* @__PURE__ */ jsx("span", { "data-slot": "chat-word", "data-word": i, style: { animationDelay: `${cur.delays[i] ?? 0}ms` }, children: u.word }),
|
|
59
|
+
u.gap
|
|
60
|
+
] }, i);
|
|
61
|
+
})
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
ChatStreamText
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=chat-stream-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/chat-stream-text.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\n// Words in one chunk start this far apart, up to STAGGER_CAP of them, so a burst\n// of text reads as a ripple instead of landing at once.\nconst STAGGER_STEP_MS = 28;\nconst STAGGER_CAP = 6;\n// Safety net for when `animationend` never fires (hidden tab, display: none).\nconst SETTLE_FALLBACK_MS = 900;\n\ninterface Unit {\n word: string;\n /** The whitespace that follows the word, kept outside the animated span. */\n gap: string;\n}\n\nfunction split(text: string): { lead: string; units: Unit[] } {\n const lead = /^\\s*/.exec(text)?.[0] ?? \"\";\n const units: Unit[] = [];\n for (const m of text.slice(lead.length).matchAll(/(\\S+)(\\s*)/g)) units.push({ word: m[1] ?? \"\", gap: m[2] ?? \"\" });\n return { lead, units };\n}\n\ninterface Progress {\n text: string;\n /** Units before this index are plain text; the rest are animating. */\n settled: number;\n /** Start delay per unit, fixed when the unit appears so re-renders never restart it. */\n delays: number[];\n}\n\nfunction delaysFor(from: number, to: number): number[] {\n return Array.from({ length: to - from }, (_, j) => Math.min(j, STAGGER_CAP) * STAGGER_STEP_MS);\n}\n\nexport interface ChatStreamTextProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n /** The text so far. Pass the whole growing string; only the appended tail animates. */\n text: string;\n /**\n * The text is still growing. A string that is already complete when this\n * mounts as `false` shows at once, with no entrance.\n */\n streaming?: boolean;\n}\n\n/**\n * Text that arrives word by word. New words fade in from a soft blur and settle\n * (motion.css); words already on screen are never touched again. Once a word has\n * landed it folds into one plain text node, so a long reply costs a handful of\n * elements, not thousands. The text content stays exactly `text`.\n */\nexport function ChatStreamText({ text, streaming = false, ...props }: ChatStreamTextProps) {\n const parsed = split(text);\n const [progress, setProgress] = React.useState<Progress>(() => ({\n text,\n settled: streaming ? 0 : parsed.units.length,\n delays: delaysFor(0, parsed.units.length),\n }));\n\n let cur = progress;\n if (text !== progress.text) {\n // Anything but an append (edit, regenerate) starts over rather than patching words in place.\n const appended = text.startsWith(progress.text);\n const known = appended ? split(progress.text).units.length : 0;\n cur = {\n text,\n settled: appended ? progress.settled : 0,\n delays: [...(appended ? progress.delays.slice(0, known) : []), ...delaysFor(known, parsed.units.length)],\n };\n setProgress(cur);\n }\n\n const settle = React.useCallback((upTo: number) => {\n setProgress((p) => (upTo > p.settled ? { ...p, settled: upTo } : p));\n }, []);\n\n const total = parsed.units.length;\n React.useEffect(() => {\n if (streaming) return;\n const id = setTimeout(() => settle(total), SETTLE_FALLBACK_MS);\n return () => clearTimeout(id);\n }, [streaming, total, settle]);\n\n const settledText = parsed.lead + parsed.units.slice(0, cur.settled).map((u) => u.word + u.gap).join(\"\");\n return (\n <span\n data-slot=\"chat-stream-text\"\n onAnimationEnd={(e) => {\n const i = Number((e.target as HTMLElement).dataset[\"word\"]);\n if (Number.isInteger(i)) settle(i + 1);\n }}\n {...props}\n >\n {settledText}\n {parsed.units.slice(cur.settled).map((u, k) => {\n const i = cur.settled + k;\n return (\n <React.Fragment key={i}>\n <span data-slot=\"chat-word\" data-word={i} style={{ animationDelay: `${cur.delays[i] ?? 0}ms` }}>\n {u.word}\n </span>\n {u.gap}\n </React.Fragment>\n );\n })}\n </span>\n );\n}\n"],"mappings":";AAkGU,SACE,KADF;AAhGV,YAAY,WAAW;AAIvB,MAAM,kBAAkB;AACxB,MAAM,cAAc;AAEpB,MAAM,qBAAqB;AAQ3B,SAAS,MAAM,MAA+C;AAC5D,QAAM,OAAO,OAAO,KAAK,IAAI,IAAI,CAAC,KAAK;AACvC,QAAM,QAAgB,CAAC;AACvB,aAAW,KAAK,KAAK,MAAM,KAAK,MAAM,EAAE,SAAS,aAAa,EAAG,OAAM,KAAK,EAAE,MAAM,EAAE,CAAC,KAAK,IAAI,KAAK,EAAE,CAAC,KAAK,GAAG,CAAC;AACjH,SAAO,EAAE,MAAM,MAAM;AACvB;AAUA,SAAS,UAAU,MAAc,IAAsB;AACrD,SAAO,MAAM,KAAK,EAAE,QAAQ,KAAK,KAAK,GAAG,CAAC,GAAG,MAAM,KAAK,IAAI,GAAG,WAAW,IAAI,eAAe;AAC/F;AAkBO,SAAS,eAAe,EAAE,MAAM,YAAY,OAAO,GAAG,MAAM,GAAwB;AACzF,QAAM,SAAS,MAAM,IAAI;AACzB,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAmB,OAAO;AAAA,IAC9D;AAAA,IACA,SAAS,YAAY,IAAI,OAAO,MAAM;AAAA,IACtC,QAAQ,UAAU,GAAG,OAAO,MAAM,MAAM;AAAA,EAC1C,EAAE;AAEF,MAAI,MAAM;AACV,MAAI,SAAS,SAAS,MAAM;AAE1B,UAAM,WAAW,KAAK,WAAW,SAAS,IAAI;AAC9C,UAAM,QAAQ,WAAW,MAAM,SAAS,IAAI,EAAE,MAAM,SAAS;AAC7D,UAAM;AAAA,MACJ;AAAA,MACA,SAAS,WAAW,SAAS,UAAU;AAAA,MACvC,QAAQ,CAAC,GAAI,WAAW,SAAS,OAAO,MAAM,GAAG,KAAK,IAAI,CAAC,GAAI,GAAG,UAAU,OAAO,OAAO,MAAM,MAAM,CAAC;AAAA,IACzG;AACA,gBAAY,GAAG;AAAA,EACjB;AAEA,QAAM,SAAS,MAAM,YAAY,CAAC,SAAiB;AACjD,gBAAY,CAAC,MAAO,OAAO,EAAE,UAAU,EAAE,GAAG,GAAG,SAAS,KAAK,IAAI,CAAE;AAAA,EACrE,GAAG,CAAC,CAAC;AAEL,QAAM,QAAQ,OAAO,MAAM;AAC3B,QAAM,UAAU,MAAM;AACpB,QAAI,UAAW;AACf,UAAM,KAAK,WAAW,MAAM,OAAO,KAAK,GAAG,kBAAkB;AAC7D,WAAO,MAAM,aAAa,EAAE;AAAA,EAC9B,GAAG,CAAC,WAAW,OAAO,MAAM,CAAC;AAE7B,QAAM,cAAc,OAAO,OAAO,OAAO,MAAM,MAAM,GAAG,IAAI,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE;AACvG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAgB,CAAC,MAAM;AACrB,cAAM,IAAI,OAAQ,EAAE,OAAuB,QAAQ,MAAM,CAAC;AAC1D,YAAI,OAAO,UAAU,CAAC,EAAG,QAAO,IAAI,CAAC;AAAA,MACvC;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,QACA,OAAO,MAAM,MAAM,IAAI,OAAO,EAAE,IAAI,CAAC,GAAG,MAAM;AAC7C,gBAAM,IAAI,IAAI,UAAU;AACxB,iBACE,qBAAC,MAAM,UAAN,EACC;AAAA,gCAAC,UAAK,aAAU,aAAY,aAAW,GAAG,OAAO,EAAE,gBAAgB,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,GAC1F,YAAE,MACL;AAAA,YACC,EAAE;AAAA,eAJgB,CAKrB;AAAA,QAEJ,CAAC;AAAA;AAAA;AAAA,EACH;AAEJ;","names":[]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { CHAT_TYPING_BASE, CHAT_TYPING_DOT, CHAT_TYPING_STILL } from "../../generated/chat-typing.js";
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
|
+
function ChatTyping({ label = "Assistant is thinking", className, ...props }) {
|
|
6
|
+
return /* @__PURE__ */ jsxs(
|
|
7
|
+
"span",
|
|
8
|
+
{
|
|
9
|
+
role: "status",
|
|
10
|
+
"aria-label": label,
|
|
11
|
+
"data-slot": "chat-typing",
|
|
12
|
+
className: cn(CHAT_TYPING_BASE, className),
|
|
13
|
+
...props,
|
|
14
|
+
children: [
|
|
15
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, "data-dot": true, className: CHAT_TYPING_DOT }),
|
|
16
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, "data-dot": true, className: CHAT_TYPING_DOT }),
|
|
17
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, "data-dot": true, className: CHAT_TYPING_DOT }),
|
|
18
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, "data-still": true, className: CHAT_TYPING_STILL, children: "\u2026" })
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
ChatTyping
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=chat-typing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/chat-typing.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { CHAT_TYPING_BASE, CHAT_TYPING_DOT, CHAT_TYPING_STILL } from \"../../generated/chat-typing\";\nimport { cn } from \"../../lib/cn\";\n\nexport interface ChatTypingProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n /** Names the state for assistive tech. Default `\"Assistant is thinking\"`. */\n label?: string;\n}\n\n/**\n * The wait before the first token: three dots whose opacity ripples. Under\n * reduced motion CSS swaps them for a still \"…\". It is a status region, so a\n * screen reader hears the label once when it appears.\n */\nexport function ChatTyping({ label = \"Assistant is thinking\", className, ...props }: ChatTypingProps) {\n return (\n <span\n role=\"status\"\n aria-label={label}\n data-slot=\"chat-typing\"\n className={cn(CHAT_TYPING_BASE, className)}\n {...props}\n >\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-still className={CHAT_TYPING_STILL}>\n …\n </span>\n </span>\n );\n}\n"],"mappings":"AAgBI,SAOE,KAPF;AAhBJ,YAAY,WAAW;AACvB,SAAS,kBAAkB,iBAAiB,yBAAyB;AACrE,SAAS,UAAU;AAYZ,SAAS,WAAW,EAAE,QAAQ,yBAAyB,WAAW,GAAG,MAAM,GAAoB;AACpG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,aAAU;AAAA,MACV,WAAW,GAAG,kBAAkB,SAAS;AAAA,MACxC,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,cAAU,MAAC,WAAW,mBAAmB,oBAE3D;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import { Container } from "
|
|
3
|
+
import { Container } from "../atomic/container.js";
|
|
4
4
|
const HEADING = "font-mono text-xs text-ink uppercase tracking-widest mb-6";
|
|
5
5
|
function Footer({
|
|
6
6
|
lockup,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/footer.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Container } from \"../atomic/container\";\n\n/**\n * A 12-col footer grid — brand 3 | sitemap groups 2 each | offices 3 |\n * newsletter 2 — with the legal links and copyright line. On mobile the sitemap\n * columns sit side by side. The kit brings no content of its own: every string,\n * the mark and the lock-up are the caller's, and each column renders only when\n * it has something in it. `children` render right after the `<footer>` tag —\n * the slot for app-side extras like a build-version easter egg.\n *\n * The lock-up is either composed from `mark` + `brand` + `tagline`, or handed\n * over whole as `lockup` when a brand's arrangement is not that one.\n *\n * `linkComponent` lets Next hosts pass `next/link`; everyone else gets `<a>`.\n */\nexport interface FooterLink {\n label: string;\n href: string;\n}\n\nexport interface FooterLinkGroup {\n heading: string;\n links: readonly FooterLink[];\n}\n\nexport interface FooterOffice {\n name: string;\n address: string;\n}\n\nexport interface FooterProps {\n /** The whole lock-up; replaces the one `mark` + `brand` + `tagline` compose. */\n lockup?: React.ReactNode;\n /** The wordmark beside the mark, set in the display family. */\n brand?: string;\n /** The mark beside the brand name. Sized by the caller. */\n mark?: React.ReactNode;\n tagline?: string;\n description?: React.ReactNode;\n copyright?: React.ReactNode;\n nav?: readonly FooterLinkGroup[];\n offices?: readonly FooterOffice[];\n officesHeading?: string;\n legalLinks?: readonly FooterLink[];\n /** The newsletter form; the newsletter column renders only when present. */\n newsletter?: React.ReactNode;\n newsletterHeading?: string;\n newsletterBlurb?: string;\n version?: string;\n commitHref?: string;\n linkComponent?: React.ElementType;\n children?: React.ReactNode;\n}\n\nconst HEADING = \"font-mono text-xs text-ink uppercase tracking-widest mb-6\";\n\nexport function Footer({\n lockup,\n brand,\n mark,\n tagline,\n description,\n copyright,\n nav = [],\n offices = [],\n officesHeading = \"Offices\",\n legalLinks = [],\n newsletter,\n newsletterHeading = \"Newsletter\",\n newsletterBlurb = \"\",\n version,\n commitHref,\n linkComponent,\n children,\n}: FooterProps) {\n const L = linkComponent ?? \"a\";\n const composed = brand !== undefined || mark !== undefined;\n const hasBrandColumn = lockup !== undefined || composed || description !== undefined || legalLinks.length > 0;\n\n return (\n <footer data-slot=\"footer\" className=\"bg-background border-t border-ink/10 py-16\">\n {children}\n <Container>\n <div className=\"grid grid-cols-2 gap-x-8 gap-y-8 lg:grid-cols-12 mb-12\">\n {hasBrandColumn && (\n <div className=\"col-span-2 lg:col-span-3\">\n {lockup !== undefined ? (\n <div data-slot=\"footer-lockup\" className=\"mb-6\">{lockup}</div>\n ) : (\n composed && <FooterLockup brand={brand} mark={mark} tagline={tagline} />\n )}\n {description !== undefined && (\n <p className=\"text-ink/40 text-xs font-light max-w-sm leading-relaxed mb-6\">\n {description}\n </p>\n )}\n {legalLinks.length > 0 && (\n <div className=\"flex gap-4 text-xs font-mono text-primary-ink\">\n {legalLinks.map((link, i) => (\n <React.Fragment key={link.label}>\n {i > 0 && <span className=\"text-ink/20\">|</span>}\n <L href={link.href} className=\"hover:underline\">\n {link.label}\n </L>\n </React.Fragment>\n ))}\n </div>\n )}\n </div>\n )}\n\n {nav.map((group) => (\n <nav key={group.heading} aria-label={`Footer ${group.heading} links`} className=\"lg:col-span-2\">\n <h4 className={HEADING}>{group.heading}</h4>\n <ul className=\"space-y-3\">\n {group.links.map((link) => (\n <li key={link.href}>\n <L\n href={link.href}\n className=\"text-xs font-light text-ink/70 hover:text-primary-ink transition-colors\"\n >\n {link.label}\n </L>\n </li>\n ))}\n </ul>\n </nav>\n ))}\n\n {offices.length > 0 && (\n <div className=\"col-span-2 lg:col-span-3\">\n <h4 className={HEADING}>{officesHeading}</h4>\n <ul className=\"space-y-4 text-xs text-ink/70 font-light leading-relaxed\">\n {offices.map((office) => (\n <li key={office.name}>\n <strong className=\"text-ink block font-mono text-[10px] uppercase tracking-wider mb-1\">\n {office.name}\n </strong>\n {office.address}\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {newsletter && (\n <div className=\"col-span-2 lg:col-span-2\">\n <h4 className={HEADING}>{newsletterHeading}</h4>\n {/* What the source's <Tier tier=\"alt\"><Text> emits: alt body size\n + the info variant, then the mb-4 the caller adds. */}\n <p className=\"text-sm sm:text-xs font-light leading-relaxed text-ink/70 mb-4\">\n {newsletterBlurb}\n </p>\n {newsletter}\n </div>\n )}\n </div>\n\n {(copyright !== undefined || version !== undefined) && (\n <div className=\"border-t border-ink/10 pt-8 text-[10px] font-mono text-ink/40\">\n <p>\n {copyright}\n {version && (\n <>\n {copyright !== undefined && \" \"}\n <a href={commitHref} className=\"text-ink/30\">\n {version}\n </a>\n </>\n )}\n </p>\n </div>\n )}\n </Container>\n </footer>\n );\n}\n\nfunction FooterLockup({\n brand,\n mark,\n tagline,\n}: {\n brand: string | undefined;\n mark: React.ReactNode;\n tagline: string | undefined;\n}) {\n return (\n <div data-slot=\"footer-lockup\" className=\"flex items-center gap-3 mb-6\">\n {mark}\n {(brand !== undefined || tagline !== undefined) && (\n <div className=\"flex flex-col\">\n {brand !== undefined && (\n <span className=\"font-serif font-bold text-base tracking-wider text-ink\">{brand}</span>\n )}\n {tagline !== undefined && (\n <span className=\"text-[8px] font-mono tracking-[0.3em] text-primary-ink uppercase\">\n {tagline}\n </span>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|