tempest-react-sdk 0.39.0 → 0.40.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/dist/_virtual/_rolldown/runtime.cjs +1 -1
- package/dist/access/access-control-context.cjs.map +1 -1
- package/dist/access/use-can.cjs.map +1 -1
- package/dist/app/AppProviders.cjs.map +1 -1
- package/dist/app/AppProviders.js.map +1 -1
- package/dist/audio/use-audio-recorder.cjs.map +1 -1
- package/dist/audio/use-audio.cjs.map +1 -1
- package/dist/audio/use-microphone.cjs.map +1 -1
- package/dist/auth/create-auth-store.cjs.map +1 -1
- package/dist/auth/lazy-with-retry.cjs.map +1 -1
- package/dist/auth/passkey.cjs.map +1 -1
- package/dist/auth/passkey.js.map +1 -1
- package/dist/auth/use-passkey.cjs.map +1 -1
- package/dist/br/BrazilMap.cjs +1 -1
- package/dist/br/BrazilMap.cjs.map +1 -1
- package/dist/br/BrazilMap.js +2 -2
- package/dist/br/BrazilMap.js.map +1 -1
- package/dist/br/BrazilStateCitySelect.cjs.map +1 -1
- package/dist/br/BrazilStateCitySelect.js.map +1 -1
- package/dist/br/BrazilStateMap.cjs +1 -1
- package/dist/br/BrazilStateMap.cjs.map +1 -1
- package/dist/br/BrazilStateMap.js +2 -2
- package/dist/br/BrazilStateMap.js.map +1 -1
- package/dist/br/MapLegend.cjs.map +1 -1
- package/dist/br/MapLegend.js.map +1 -1
- package/dist/br/MapMarkers.cjs.map +1 -1
- package/dist/br/MapMarkers.js.map +1 -1
- package/dist/br/MunicipalitySearch.cjs.map +1 -1
- package/dist/br/MunicipalitySearch.js.map +1 -1
- package/dist/br/PixQRCode.cjs.map +1 -1
- package/dist/br/PixQRCode.js.map +1 -1
- package/dist/br/use-map-hover.cjs.map +1 -1
- package/dist/br/use-map-zoom.cjs.map +1 -1
- package/dist/capture/media-recorder.cjs +1 -1
- package/dist/capture/media-recorder.js +2 -2
- package/dist/capture/use-barcode-scanner.cjs +1 -1
- package/dist/capture/use-barcode-scanner.cjs.map +1 -1
- package/dist/capture/use-barcode-scanner.js +2 -2
- package/dist/capture/use-barcode-scanner.js.map +1 -1
- package/dist/capture/use-screen-capture.cjs.map +1 -1
- package/dist/capture/use-speech-recognition.cjs.map +1 -1
- package/dist/capture/use-torch.cjs.map +1 -1
- package/dist/capture/use-video-recorder.cjs.map +1 -1
- package/dist/charts/AreaChart.cjs.map +1 -1
- package/dist/charts/AreaChart.js.map +1 -1
- package/dist/charts/BarChart.cjs.map +1 -1
- package/dist/charts/BarChart.js.map +1 -1
- package/dist/charts/LineChart.cjs.map +1 -1
- package/dist/charts/LineChart.js.map +1 -1
- package/dist/charts/PieChart.cjs.map +1 -1
- package/dist/charts/PieChart.js.map +1 -1
- package/dist/charts/RadarChart.cjs.map +1 -1
- package/dist/charts/RadarChart.js.map +1 -1
- package/dist/charts/use-chart-colors.cjs.map +1 -1
- package/dist/components/AIChat/AIChat.cjs.map +1 -1
- package/dist/components/AIChat/AIChat.js.map +1 -1
- package/dist/components/AIChat/AIChatComposer.cjs.map +1 -1
- package/dist/components/AIChat/AIChatComposer.js.map +1 -1
- package/dist/components/AIChat/AIChatTurn.cjs.map +1 -1
- package/dist/components/AIChat/AIChatTurn.js.map +1 -1
- package/dist/components/Accordion/Accordion.cjs.map +1 -1
- package/dist/components/Accordion/Accordion.js.map +1 -1
- package/dist/components/Alert/Alert.cjs.map +1 -1
- package/dist/components/Alert/Alert.js.map +1 -1
- package/dist/components/AppBar/AppBar.cjs.map +1 -1
- package/dist/components/AppBar/AppBar.js.map +1 -1
- package/dist/components/AppShell/AppShell.cjs.map +1 -1
- package/dist/components/AppShell/AppShell.js.map +1 -1
- package/dist/components/AudioPlayer/AudioPlayer.cjs.map +1 -1
- package/dist/components/AudioPlayer/AudioPlayer.js.map +1 -1
- package/dist/components/AudioRecorder/AudioRecorder.cjs.map +1 -1
- package/dist/components/AudioRecorder/AudioRecorder.js.map +1 -1
- package/dist/components/Avatar/Avatar.cjs.map +1 -1
- package/dist/components/Avatar/Avatar.js.map +1 -1
- package/dist/components/AvatarGroup/AvatarGroup.cjs.map +1 -1
- package/dist/components/AvatarGroup/AvatarGroup.js.map +1 -1
- package/dist/components/Badge/Badge.cjs.map +1 -1
- package/dist/components/Badge/Badge.js.map +1 -1
- package/dist/components/Banner/Banner.cjs.map +1 -1
- package/dist/components/Banner/Banner.js.map +1 -1
- package/dist/components/BarcodeScanner/BarcodeScanner.cjs.map +1 -1
- package/dist/components/BarcodeScanner/BarcodeScanner.js.map +1 -1
- package/dist/components/BottomNavigation/BottomNavigation.cjs.map +1 -1
- package/dist/components/BottomNavigation/BottomNavigation.js.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
- package/dist/components/Button/Button.cjs.map +1 -1
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Calendar/Calendar.cjs +1 -1
- package/dist/components/Calendar/Calendar.cjs.map +1 -1
- package/dist/components/Calendar/Calendar.js +38 -41
- package/dist/components/Calendar/Calendar.js.map +1 -1
- package/dist/components/Card/Card.cjs.map +1 -1
- package/dist/components/Card/Card.js.map +1 -1
- package/dist/components/Carousel/Carousel.cjs.map +1 -1
- package/dist/components/Carousel/Carousel.js.map +1 -1
- package/dist/components/Chat/Chat.cjs.map +1 -1
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/Chat/ChatComposer.cjs.map +1 -1
- package/dist/components/Chat/ChatComposer.js.map +1 -1
- package/dist/components/Chat/chat-groups.cjs +1 -1
- package/dist/components/Chat/chat-groups.cjs.map +1 -1
- package/dist/components/Chat/chat-groups.js +2 -2
- package/dist/components/Chat/chat-groups.js.map +1 -1
- package/dist/components/Checkbox/Checkbox.cjs.map +1 -1
- package/dist/components/Checkbox/Checkbox.js.map +1 -1
- package/dist/components/ChipInput/ChipInput.cjs.map +1 -1
- package/dist/components/ChipInput/ChipInput.js.map +1 -1
- package/dist/components/ClickOutside/ClickOutside.cjs.map +1 -1
- package/dist/components/CodeBlock/CodeBlock.cjs.map +1 -1
- package/dist/components/CodeBlock/CodeBlock.js.map +1 -1
- package/dist/components/Collapsible/Collapsible.cjs.map +1 -1
- package/dist/components/Collapsible/Collapsible.js.map +1 -1
- package/dist/components/Combobox/Combobox.cjs.map +1 -1
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Command/Command.cjs.map +1 -1
- package/dist/components/Command/Command.js.map +1 -1
- package/dist/components/ConfirmDialog/ConfirmDialog.cjs.map +1 -1
- package/dist/components/ConfirmDialog/ConfirmDialog.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.cjs.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/CopyButton/CopyButton.cjs.map +1 -1
- package/dist/components/CopyButton/CopyButton.js.map +1 -1
- package/dist/components/DataList/DataList.cjs.map +1 -1
- package/dist/components/DataList/DataList.js.map +1 -1
- package/dist/components/DataTable/DataTable.cjs +1 -1
- package/dist/components/DataTable/DataTable.cjs.map +1 -1
- package/dist/components/DataTable/DataTable.js +2 -2
- package/dist/components/DataTable/DataTable.js.map +1 -1
- package/dist/components/DataTable/EditableCell.cjs.map +1 -1
- package/dist/components/DataTable/EditableCell.js.map +1 -1
- package/dist/components/DatePicker/DatePicker.cjs.map +1 -1
- package/dist/components/DateRangePicker/DateRangePicker.cjs.map +1 -1
- package/dist/components/DateRangePicker/DateRangePicker.js.map +1 -1
- package/dist/components/DescriptionList/DescriptionList.cjs.map +1 -1
- package/dist/components/DescriptionList/DescriptionList.js.map +1 -1
- package/dist/components/Divider/Divider.cjs.map +1 -1
- package/dist/components/Divider/Divider.js.map +1 -1
- package/dist/components/Drawer/Drawer.cjs.map +1 -1
- package/dist/components/Drawer/Drawer.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/Dropzone/Dropzone.cjs.map +1 -1
- package/dist/components/Dropzone/Dropzone.js.map +1 -1
- package/dist/components/EmptyState/EmptyState.cjs.map +1 -1
- package/dist/components/EmptyState/EmptyState.js.map +1 -1
- package/dist/components/ErrorState/ErrorState.cjs.map +1 -1
- package/dist/components/ErrorState/ErrorState.js.map +1 -1
- package/dist/components/FileUpload/FileUpload.cjs +1 -1
- package/dist/components/FileUpload/FileUpload.cjs.map +1 -1
- package/dist/components/FileUpload/FileUpload.js +1 -1
- package/dist/components/FileUpload/FileUpload.js.map +1 -1
- package/dist/components/FilterBar/FilterBar.cjs.map +1 -1
- package/dist/components/FilterBar/FilterBar.js.map +1 -1
- package/dist/components/FloatingActionButton/FloatingActionButton.cjs.map +1 -1
- package/dist/components/FloatingActionButton/FloatingActionButton.js.map +1 -1
- package/dist/components/Form/Form.cjs.map +1 -1
- package/dist/components/Form/Form.js.map +1 -1
- package/dist/components/HoverCard/HoverCard.cjs.map +1 -1
- package/dist/components/HoverCard/HoverCard.js.map +1 -1
- package/dist/components/Image/Image.cjs.map +1 -1
- package/dist/components/ImageCropper/ImageCropper.cjs +1 -1
- package/dist/components/ImageCropper/ImageCropper.cjs.map +1 -1
- package/dist/components/ImageCropper/ImageCropper.js +1 -1
- package/dist/components/ImageCropper/ImageCropper.js.map +1 -1
- package/dist/components/Input/Input.cjs.map +1 -1
- package/dist/components/Input/Input.js.map +1 -1
- package/dist/components/InstallBanner/InstallBanner.cjs.map +1 -1
- package/dist/components/InstallBanner/InstallBanner.js.map +1 -1
- package/dist/components/InstallButton/InstallButton.cjs.map +1 -1
- package/dist/components/InstallButton/InstallButton.js.map +1 -1
- package/dist/components/Kanban/Kanban.cjs.map +1 -1
- package/dist/components/Kanban/Kanban.js.map +1 -1
- package/dist/components/Label/Label.cjs.map +1 -1
- package/dist/components/Label/Label.js.map +1 -1
- package/dist/components/Lightbox/Lightbox.cjs.map +1 -1
- package/dist/components/Lightbox/Lightbox.js.map +1 -1
- package/dist/components/ListTile/ListTile.cjs.map +1 -1
- package/dist/components/ListTile/ListTile.js.map +1 -1
- package/dist/components/Markdown/Markdown.cjs +1 -1
- package/dist/components/Markdown/Markdown.cjs.map +1 -1
- package/dist/components/Markdown/Markdown.js +2 -2
- package/dist/components/Markdown/Markdown.js.map +1 -1
- package/dist/components/Masonry/Masonry.cjs.map +1 -1
- package/dist/components/Masonry/Masonry.js.map +1 -1
- package/dist/components/Menubar/Menubar.cjs.map +1 -1
- package/dist/components/Menubar/Menubar.js.map +1 -1
- package/dist/components/Modal/Modal.cjs.map +1 -1
- package/dist/components/Modal/Modal.js.map +1 -1
- package/dist/components/ModalsManager/ModalsManager.cjs.map +1 -1
- package/dist/components/ModalsManager/ModalsManager.js.map +1 -1
- package/dist/components/Money/Money.cjs.map +1 -1
- package/dist/components/Money/Money.js.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.cjs.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.js.map +1 -1
- package/dist/components/NProgress/NProgress.cjs.map +1 -1
- package/dist/components/Navbar/Navbar.cjs.map +1 -1
- package/dist/components/Navbar/Navbar.js.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
- package/dist/components/NavigationRail/NavigationRail.cjs.map +1 -1
- package/dist/components/NavigationRail/NavigationRail.js.map +1 -1
- package/dist/components/NotificationCenter/NotificationCenter.cjs.map +1 -1
- package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -1
- package/dist/components/NotificationCenter/use-notification-inbox.cjs.map +1 -1
- package/dist/components/OfflineIndicator/OfflineIndicator.cjs.map +1 -1
- package/dist/components/OfflineIndicator/OfflineIndicator.js.map +1 -1
- package/dist/components/Page/Page.cjs.map +1 -1
- package/dist/components/Page/Page.js.map +1 -1
- package/dist/components/Pagination/Pagination.cjs.map +1 -1
- package/dist/components/Pagination/Pagination.js.map +1 -1
- package/dist/components/PasswordInput/PasswordInput.cjs.map +1 -1
- package/dist/components/PasswordInput/PasswordInput.js.map +1 -1
- package/dist/components/PinInput/PinInput.cjs.map +1 -1
- package/dist/components/PinInput/PinInput.js.map +1 -1
- package/dist/components/Popover/Popover.cjs.map +1 -1
- package/dist/components/Popover/Popover.js.map +1 -1
- package/dist/components/Portal/Portal.cjs.map +1 -1
- package/dist/components/Progress/Progress.cjs.map +1 -1
- package/dist/components/Progress/Progress.js.map +1 -1
- package/dist/components/QRCode/QRCode.cjs.map +1 -1
- package/dist/components/QRCode/QRCode.js.map +1 -1
- package/dist/components/Radio/Radio.cjs.map +1 -1
- package/dist/components/Radio/Radio.js.map +1 -1
- package/dist/components/RangeSlider/RangeSlider.cjs.map +1 -1
- package/dist/components/RangeSlider/RangeSlider.js.map +1 -1
- package/dist/components/RatingStars/RatingStars.cjs.map +1 -1
- package/dist/components/RatingStars/RatingStars.js.map +1 -1
- package/dist/components/RefreshIndicator/RefreshIndicator.cjs.map +1 -1
- package/dist/components/RefreshIndicator/RefreshIndicator.js.map +1 -1
- package/dist/components/RelativeTime/RelativeTime.cjs.map +1 -1
- package/dist/components/RelativeTime/RelativeTime.js.map +1 -1
- package/dist/components/Resizable/Resizable.cjs.map +1 -1
- package/dist/components/Resizable/Resizable.js.map +1 -1
- package/dist/components/Scheduler/Scheduler.cjs.map +1 -1
- package/dist/components/Scheduler/Scheduler.js.map +1 -1
- package/dist/components/ScrollArea/ScrollArea.cjs.map +1 -1
- package/dist/components/SearchBar/SearchBar.cjs.map +1 -1
- package/dist/components/SearchBar/SearchBar.js.map +1 -1
- package/dist/components/SegmentedControl/SegmentedControl.cjs.map +1 -1
- package/dist/components/SegmentedControl/SegmentedControl.js.map +1 -1
- package/dist/components/Select/Select.cjs.map +1 -1
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/SignaturePad/SignaturePad.cjs.map +1 -1
- package/dist/components/SignaturePad/SignaturePad.js.map +1 -1
- package/dist/components/Slider/Slider.cjs.map +1 -1
- package/dist/components/Slider/Slider.js.map +1 -1
- package/dist/components/Sparkline/Sparkline.cjs.map +1 -1
- package/dist/components/Sparkline/Sparkline.js.map +1 -1
- package/dist/components/Spinner/Spinner.cjs.map +1 -1
- package/dist/components/Spinner/Spinner.js.map +1 -1
- package/dist/components/Stat/Stat.cjs.map +1 -1
- package/dist/components/Stat/Stat.js.map +1 -1
- package/dist/components/Stepper/Stepper.cjs.map +1 -1
- package/dist/components/Stepper/Stepper.js.map +1 -1
- package/dist/components/StepperInput/StepperInput.cjs.map +1 -1
- package/dist/components/StepperInput/StepperInput.js.map +1 -1
- package/dist/components/Switch/Switch.cjs.map +1 -1
- package/dist/components/Switch/Switch.js.map +1 -1
- package/dist/components/SyncStatusBadge/SyncStatusBadge.cjs.map +1 -1
- package/dist/components/SyncStatusBadge/SyncStatusBadge.js.map +1 -1
- package/dist/components/Table/Table.cjs.map +1 -1
- package/dist/components/Table/Table.js.map +1 -1
- package/dist/components/Tabs/Tabs.cjs.map +1 -1
- package/dist/components/Tabs/Tabs.js.map +1 -1
- package/dist/components/Tag/Tag.cjs.map +1 -1
- package/dist/components/Tag/Tag.js.map +1 -1
- package/dist/components/Textarea/Textarea.cjs.map +1 -1
- package/dist/components/Textarea/Textarea.js.map +1 -1
- package/dist/components/TimePicker/TimePicker.cjs.map +1 -1
- package/dist/components/TimePicker/TimePicker.js.map +1 -1
- package/dist/components/Timeline/Timeline.cjs.map +1 -1
- package/dist/components/Timeline/Timeline.js.map +1 -1
- package/dist/components/Toast/ToastProvider.cjs.map +1 -1
- package/dist/components/Toast/ToastProvider.js.map +1 -1
- package/dist/components/Toggle/Toggle.cjs.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.cjs.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
- package/dist/components/Tooltip/Tooltip.cjs.map +1 -1
- package/dist/components/Tooltip/Tooltip.js.map +1 -1
- package/dist/components/Tour/Tour.cjs.map +1 -1
- package/dist/components/Tour/Tour.js.map +1 -1
- package/dist/components/Transfer/Transfer.cjs.map +1 -1
- package/dist/components/Transfer/Transfer.js.map +1 -1
- package/dist/components/TreeView/TreeView.cjs +1 -1
- package/dist/components/TreeView/TreeView.cjs.map +1 -1
- package/dist/components/TreeView/TreeView.js +1 -4
- package/dist/components/TreeView/TreeView.js.map +1 -1
- package/dist/components/UpdatePrompt/UpdatePrompt.cjs.map +1 -1
- package/dist/components/UpdatePrompt/UpdatePrompt.js.map +1 -1
- package/dist/components/VirtualList/VirtualList.cjs.map +1 -1
- package/dist/components/VirtualList/VirtualList.js.map +1 -1
- package/dist/components/VirtualTable/VirtualTable.cjs.map +1 -1
- package/dist/components/VirtualTable/VirtualTable.js.map +1 -1
- package/dist/components/VisuallyHidden/VisuallyHidden.cjs.map +1 -1
- package/dist/components/Wizard/Wizard.cjs.map +1 -1
- package/dist/components/Wizard/Wizard.js.map +1 -1
- package/dist/data/data-provider-context.cjs.map +1 -1
- package/dist/data/use-resource.cjs.map +1 -1
- package/dist/editor/RichTextEditor.cjs.map +1 -1
- package/dist/editor/RichTextEditor.js.map +1 -1
- package/dist/error-boundary/use-error-handler.cjs.map +1 -1
- package/dist/feature-flags/FeatureFlagsProvider.cjs.map +1 -1
- package/dist/forms/FormField.cjs.map +1 -1
- package/dist/forms/masked-inputs.cjs +1 -1
- package/dist/forms/masked-inputs.cjs.map +1 -1
- package/dist/forms/masked-inputs.js +1 -1
- package/dist/forms/masked-inputs.js.map +1 -1
- package/dist/forms/use-viacep.cjs.map +1 -1
- package/dist/forms/use-zod-form.cjs.map +1 -1
- package/dist/forms/zod-resolver.cjs.map +1 -1
- package/dist/forms/zod-resolver.js.map +1 -1
- package/dist/geo/TrajectoryMap.cjs.map +1 -1
- package/dist/geo/TrajectoryMap.js.map +1 -1
- package/dist/geo/leaflet-map.cjs.map +1 -1
- package/dist/geo/leaflet-map.js.map +1 -1
- package/dist/geo/use-position-tracker.cjs.map +1 -1
- package/dist/hooks/use-announce.cjs.map +1 -1
- package/dist/hooks/use-async.cjs.map +1 -1
- package/dist/hooks/use-before-install-prompt.cjs.map +1 -1
- package/dist/hooks/use-breakpoint.cjs.map +1 -1
- package/dist/hooks/use-click-outside.cjs.map +1 -1
- package/dist/hooks/use-client-filter.cjs.map +1 -1
- package/dist/hooks/use-clipboard.cjs.map +1 -1
- package/dist/hooks/use-counter.cjs.map +1 -1
- package/dist/hooks/use-debounce.cjs.map +1 -1
- package/dist/hooks/use-deep-memo.cjs.map +1 -1
- package/dist/hooks/use-disclosure.cjs.map +1 -1
- package/dist/hooks/use-document-visibility.cjs.map +1 -1
- package/dist/hooks/use-event-listener.cjs.map +1 -1
- package/dist/hooks/use-geolocation.cjs.map +1 -1
- package/dist/hooks/use-hover.cjs.map +1 -1
- package/dist/hooks/use-idle.cjs.map +1 -1
- package/dist/hooks/use-install-prompt.cjs +1 -1
- package/dist/hooks/use-install-prompt.cjs.map +1 -1
- package/dist/hooks/use-install-prompt.js +1 -1
- package/dist/hooks/use-install-prompt.js.map +1 -1
- package/dist/hooks/use-intersection-observer.cjs.map +1 -1
- package/dist/hooks/use-interval.cjs.map +1 -1
- package/dist/hooks/use-is-first-render.cjs.map +1 -1
- package/dist/hooks/use-keyboard-shortcut.cjs +1 -1
- package/dist/hooks/use-keyboard-shortcut.cjs.map +1 -1
- package/dist/hooks/use-keyboard-shortcut.js +1 -1
- package/dist/hooks/use-keyboard-shortcut.js.map +1 -1
- package/dist/hooks/use-list-state.cjs.map +1 -1
- package/dist/hooks/use-local-storage.cjs.map +1 -1
- package/dist/hooks/use-long-press-handlers.cjs.map +1 -1
- package/dist/hooks/use-long-press.cjs.map +1 -1
- package/dist/hooks/use-map.cjs.map +1 -1
- package/dist/hooks/use-media-devices.cjs.map +1 -1
- package/dist/hooks/use-media-permission.cjs.map +1 -1
- package/dist/hooks/use-media-query.cjs.map +1 -1
- package/dist/hooks/use-object-url.cjs.map +1 -1
- package/dist/hooks/use-online.cjs.map +1 -1
- package/dist/hooks/use-pagination.cjs.map +1 -1
- package/dist/hooks/use-previous.cjs.map +1 -1
- package/dist/hooks/use-queue.cjs.map +1 -1
- package/dist/hooks/use-resize-observer.cjs.map +1 -1
- package/dist/hooks/use-scroll-overflow.cjs.map +1 -1
- package/dist/hooks/use-service-worker-update.cjs.map +1 -1
- package/dist/hooks/use-set.cjs.map +1 -1
- package/dist/hooks/use-sortable.cjs +1 -1
- package/dist/hooks/use-sortable.cjs.map +1 -1
- package/dist/hooks/use-sortable.js +0 -2
- package/dist/hooks/use-sortable.js.map +1 -1
- package/dist/hooks/use-stable-callback.cjs.map +1 -1
- package/dist/hooks/use-storage-estimate.cjs.map +1 -1
- package/dist/hooks/use-throttle.cjs.map +1 -1
- package/dist/hooks/use-timeout.cjs.map +1 -1
- package/dist/hooks/use-toggle.cjs.map +1 -1
- package/dist/hooks/use-window-size.cjs.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +7 -5
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +1 -1
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/use-poll.cjs.map +1 -1
- package/dist/i18n/I18nProvider.cjs.map +1 -1
- package/dist/icons/Icon.cjs.map +1 -1
- package/dist/icons/IconProvider.cjs.map +1 -1
- package/dist/icons/icon-context.cjs.map +1 -1
- package/dist/icons/use-icon.cjs.map +1 -1
- package/dist/imaging/use-image-processing.cjs.map +1 -1
- package/dist/oauth/GoogleSignIn.cjs.map +1 -1
- package/dist/oauth/GoogleSignIn.js.map +1 -1
- package/dist/oauth/use-oauth-callback.cjs.map +1 -1
- package/dist/offline/use-offline-sync.cjs.map +1 -1
- package/dist/push/use-push-subscription.cjs.map +1 -1
- package/dist/query/QueryProvider.cjs.map +1 -1
- package/dist/query/constants.cjs +1 -1
- package/dist/query/constants.cjs.map +1 -1
- package/dist/query/constants.js +10 -10
- package/dist/query/constants.js.map +1 -1
- package/dist/query/create-query-keys.cjs +1 -1
- package/dist/query/create-query-keys.cjs.map +1 -1
- package/dist/query/create-query-keys.js +1 -1
- package/dist/query/create-query-keys.js.map +1 -1
- package/dist/query/offline-persistence.cjs.map +1 -1
- package/dist/query/use-cursor-query.cjs.map +1 -1
- package/dist/query/use-offline-mutation.cjs.map +1 -1
- package/dist/query/use-paginated-query.cjs.map +1 -1
- package/dist/router/AppRouter.cjs +1 -1
- package/dist/router/AppRouter.cjs.map +1 -1
- package/dist/router/AppRouter.js +7 -2
- package/dist/router/AppRouter.js.map +1 -1
- package/dist/sse/use-event-stream.cjs.map +1 -1
- package/dist/store/create-store.cjs.map +1 -1
- package/dist/tabular/use-tabular-predictor.cjs.map +1 -1
- package/dist/telemetry/TelemetryProvider.cjs.map +1 -1
- package/dist/tempest-react-sdk.d.ts +25 -26
- package/dist/theme/ThemeProvider.cjs.map +1 -1
- package/dist/theme/color.cjs +1 -1
- package/dist/theme/color.cjs.map +1 -1
- package/dist/theme/color.js +1 -1
- package/dist/theme/color.js.map +1 -1
- package/dist/utils/cn.cjs +1 -1
- package/dist/utils/cn.cjs.map +1 -1
- package/dist/utils/cn.js +1 -1
- package/dist/utils/cn.js.map +1 -1
- package/dist/utils/objects.cjs +1 -1
- package/dist/utils/objects.cjs.map +1 -1
- package/dist/utils/objects.js +1 -1
- package/dist/utils/objects.js.map +1 -1
- package/dist/utils/xlsx.cjs +1 -1
- package/dist/utils/xlsx.cjs.map +1 -1
- package/dist/utils/xlsx.js +3 -3
- package/dist/utils/xlsx.js.map +1 -1
- package/dist/vision/core/exceptions.cjs.map +1 -1
- package/dist/vision/core/exceptions.js.map +1 -1
- package/dist/vision/core/metadata.cjs +1 -1
- package/dist/vision/core/metadata.cjs.map +1 -1
- package/dist/vision/core/metadata.js +1 -1
- package/dist/vision/core/metadata.js.map +1 -1
- package/dist/vision/io/image.cjs +1 -1
- package/dist/vision/io/image.cjs.map +1 -1
- package/dist/vision/io/image.js +4 -2
- package/dist/vision/io/image.js.map +1 -1
- package/dist/vision/preprocess/image.cjs +1 -1
- package/dist/vision/preprocess/image.cjs.map +1 -1
- package/dist/vision/preprocess/image.js +4 -2
- package/dist/vision/preprocess/image.js.map +1 -1
- package/dist/vision/tasks/base.cjs +1 -1
- package/dist/vision/tasks/base.cjs.map +1 -1
- package/dist/vision/tasks/base.js +3 -1
- package/dist/vision/tasks/base.js.map +1 -1
- package/dist/vision/tasks/classifier.cjs +1 -1
- package/dist/vision/tasks/classifier.cjs.map +1 -1
- package/dist/vision/tasks/classifier.js +2 -2
- package/dist/vision/tasks/classifier.js.map +1 -1
- package/dist/vision/tasks/detectClassify.cjs +1 -1
- package/dist/vision/tasks/detectClassify.cjs.map +1 -1
- package/dist/vision/tasks/detectClassify.js +16 -15
- package/dist/vision/tasks/detectClassify.js.map +1 -1
- package/dist/vision/use-camera-stream.cjs.map +1 -1
- package/dist/vision/use-live-luminance.cjs.map +1 -1
- package/dist/vite/create-vite-config.cjs +1 -1
- package/dist/vite/create-vite-config.cjs.map +1 -1
- package/dist/vite/create-vite-config.js +3 -3
- package/dist/vite/create-vite-config.js.map +1 -1
- package/dist/vite/tempest-icons.cjs.map +1 -1
- package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
- package/dist/vite/tempest-pwa-icons.cjs.map +1 -1
- package/dist/ws/use-web-socket.cjs +1 -1
- package/dist/ws/use-web-socket.cjs.map +1 -1
- package/dist/ws/use-web-socket.js +5 -3
- package/dist/ws/use-web-socket.js.map +1 -1
- package/package.json +2 -2
- package/template/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detectClassify.cjs","names":[],"sources":["../../../src/vision/tasks/detectClassify.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Run a fused detect→classify pipeline in the browser.\n *\n * The file this loads was built by the Python SDK's `ort_vision_sdk.compose`,\n * and it already contains both models plus the crop-and-resize bridge between\n * them. That matters far more in a tab than on a server: two models mean two\n * `.onnx` downloads, two WASM/WebGPU session initializations, and a per-crop\n * round trip through JavaScript to slice, resize and restack the regions before\n * the second model can see them. A fused pipeline has one download, one session\n * and no round trip — the crops are produced and consumed inside the graph.\n */\n\nimport type * as ort from \"onnxruntime-web\";\n\nimport { FusionError } from \"../core/exceptions\";\nimport { type ModelSource, type OrtSessionOptions, OrtSession } from \"../core/session\";\nimport { SpeedTimer } from \"../core/timing\";\nimport {\n INPUT_IMAGE,\n INPUT_PAD,\n INPUT_SCALE,\n INPUT_SOURCE,\n OUTPUT_BOXES,\n OUTPUT_CLASSES,\n OUTPUT_NUM_DETECTIONS,\n OUTPUT_PROBS,\n OUTPUT_SCORES,\n type FusionSpec,\n readFusionSpec,\n} from \"../fusion\";\nimport { type ImageInput, loadImage } from \"../io/image\";\nimport { type LabelSpec, resolveLabels } from \"../labels\";\nimport { softmax, topK } from \"../postprocess/classification\";\nimport { toCHW, toFloat32, toFloat32Tensor } from \"../preprocess/image\";\nimport { LetterboxPipeline, zeroTensorData } from \"../preprocess/pipeline\";\nimport { Boxes, DetectClassifyResults } from \"../results\";\nimport {\n BoundingBox,\n RGBImage,\n type ClassProbability,\n type ClassificationResult,\n type DetectionResult,\n} from \"../types\";\nimport { VisionTask, requireDetections } from \"./base\";\n\nexport interface DetectClassifyOptions extends OrtSessionOptions {\n /**\n * Class label spec for the **detection** stage — see {@link resolveLabels}.\n * Defaults to the names recorded at fusion time, falling back to the COCO\n * 80-class preset when the fusion recorded none.\n */\n readonly labels?: LabelSpec;\n /**\n * Class label spec for the **classification** stage. Defaults to the recorded\n * names, falling back to generated `class_<id>` names.\n */\n readonly classifierLabels?: LabelSpec;\n /**\n * If `true`, a run that finds nothing throws {@link NoDetectionsError}\n * instead of returning an empty envelope. Default `false`, because looking\n * and finding nothing is a successful inference. Turn it on when an empty\n * result means the surrounding pipeline should stop rather than carry on with\n * zero rows. Can be overridden per `predict` call.\n */\n readonly raiseOnEmpty?: boolean;\n}\n\nexport interface DetectClassifyPredictOptions {\n /**\n * Drop detections scoring below this. The graph's own NMS threshold was fixed\n * at fusion time and cannot be lowered here — this only filters further.\n */\n readonly confThreshold?: number;\n /** If set, keep only detections whose detector `classId` is in this list. */\n readonly classes?: readonly number[];\n /** Truncate each detection's `classification.probabilities` to its top-k entries. */\n readonly topK?: number;\n /** Override the constructor's `raiseOnEmpty` setting for this call. */\n readonly raiseOnEmpty?: boolean;\n}\n\n/**\n * Detector and classifier running as a single ONNX model.\n *\n * Everything the pipeline needs to know about itself — the resolution to\n * letterbox to, whether it wants the full-resolution image as well, whether its\n * classifier output still needs a softmax, the class names of both stages — was\n * written into the file at fusion time and is read back here. Nothing is\n * restated on the JavaScript side, so nothing can drift out of step with the\n * Python side that built it.\n *\n * @example\n * ```typescript\n * const pipeline = await DetectClassify.create(\"/models/pipeline.onnx\");\n * const result = (await pipeline.predict(\"/images/flock.jpg\"))[0];\n * for (const detection of result) {\n * console.log(detection.name, detection.conf, detection.classification?.name);\n * }\n * ```\n */\nexport class DetectClassify extends VisionTask {\n private constructor(\n session: OrtSession,\n private readonly _spec: FusionSpec,\n private readonly _labels: readonly string[],\n private readonly _names: Readonly<Record<number, string>>,\n private readonly _classifierLabels: readonly string[],\n private readonly _classifierNames: Readonly<Record<number, string>>,\n private readonly _raiseOnEmpty: boolean,\n ) {\n super(session);\n }\n\n private _pipelineCache: LetterboxPipeline | null = null;\n\n /**\n * Run the model once on zero-filled inputs, paying one-time costs up front.\n *\n * Worth more here than on a single-stage task: a fused pipeline is two models\n * plus the bridge in one graph, so the first inference compiles shaders for\n * all of it. Calling this while a loading spinner is still up moves that cost\n * somewhere the user is already waiting.\n *\n * @param runs How many warm-up inferences to run. One is enough for WASM;\n * WebGPU sometimes settles on the second.\n */\n async warmup(runs: number = 1): Promise<void> {\n const [width, height] = this._spec.inputSize;\n for (let i = 0; i < runs; i++) {\n const feeds: Record<string, ort.Tensor> = {\n [INPUT_IMAGE]: toFloat32Tensor(zeroTensorData(width, height), [\n 1,\n 3,\n height,\n width,\n ]),\n };\n if (this._spec.needsSourceImage) {\n feeds[INPUT_SOURCE] = toFloat32Tensor(zeroTensorData(width, height), [\n 1,\n 3,\n height,\n width,\n ]);\n feeds[INPUT_SCALE] = toFloat32Tensor(new Float32Array([1]), [1]);\n feeds[INPUT_PAD] = toFloat32Tensor(new Float32Array([0, 0]), [2]);\n }\n await this._session.run(feeds);\n }\n }\n\n /**\n * The fused preprocessing pipeline, built on first use.\n *\n * Lazily, because constructing it allocates canvases: a pipeline built in an\n * environment without a canvas implementation stays constructible, and only\n * fails if it is actually asked to preprocess something.\n */\n private get _pipeline(): LetterboxPipeline {\n if (this._pipelineCache === null) {\n const [width, height] = this._spec.inputSize;\n this._pipelineCache = new LetterboxPipeline(width, height);\n }\n return this._pipelineCache;\n }\n\n /**\n * Load a fused pipeline and resolve both label spaces.\n *\n * @param model The fused `.onnx` — a URL, an `ArrayBuffer`, or bytes.\n * @param options Label overrides plus the usual session options.\n * @throws {@link FusionError} when the model carries no pipeline metadata,\n * i.e. it is a plain detector or classifier rather than something\n * `ort_vision_sdk.compose` produced.\n */\n static async create(\n model: ModelSource,\n options: DetectClassifyOptions = {},\n ): Promise<DetectClassify> {\n const session = await OrtSession.create(model, options);\n const spec = readFusionSpec(session.metadata);\n if (spec === null) {\n throw new FusionError(\n \"This model carries no fused-pipeline metadata, so DetectClassify cannot tell how to \" +\n \"drive it. Build one with ort_vision_sdk.compose.fuse_detect_classify, or load a \" +\n \"plain model with Detector/Classifier instead.\",\n );\n }\n\n const labels = resolveLabels(options.labels ?? spec.detectorNames ?? \"coco\");\n const classifierLabels = resolveLabels(options.classifierLabels ?? spec.classifierNames, {\n numClasses: classifierClasses(session) ?? undefined,\n });\n return new DetectClassify(\n session,\n spec,\n labels,\n indexNames(labels),\n classifierLabels,\n indexNames(classifierLabels),\n options.raiseOnEmpty ?? false,\n );\n }\n\n /** The pipeline configuration recorded in the model at fusion time. */\n get spec(): FusionSpec {\n return this._spec;\n }\n\n /** The `[width, height]` the detection stage runs at. */\n get inputSize(): readonly [number, number] {\n return this._spec.inputSize;\n }\n\n /** Detector class labels indexed by class id. */\n get labels(): readonly string[] {\n return this._labels;\n }\n\n /** Detector class id → class name (matches Ultralytics' `model.names`). */\n get names(): Readonly<Record<number, string>> {\n return this._names;\n }\n\n /** Classifier class labels indexed by class id. */\n get classifierLabels(): readonly string[] {\n return this._classifierLabels;\n }\n\n /** Classifier class id → class name. */\n get classifierNames(): Readonly<Record<number, string>> {\n return this._classifierNames;\n }\n\n /**\n * Alias for {@link predict} — call the pipeline like a torch `nn.Module`.\n *\n * Use as `pipeline.call(img)` since JavaScript class instances are not\n * callable; for direct invocation, prefer `pipeline.predict(img)`.\n */\n async call(\n image: ImageInput,\n options: DetectClassifyPredictOptions = {},\n ): Promise<DetectClassifyResults[]> {\n return this.predict(image, options);\n }\n\n /**\n * Run the pipeline on a single image.\n *\n * The returned envelope carries a {@link Speed} breakdown in `speed`. Its\n * `inference` figure covers detection *and* classification, since the\n * pipeline runs them as one graph and no boundary between them is observable\n * from outside.\n */\n async predict(\n image: ImageInput,\n options: DetectClassifyPredictOptions = {},\n ): Promise<DetectClassifyResults[]> {\n const timer = new SpeedTimer();\n const path = typeof image === \"string\" ? image : null;\n const original = await loadImage(image);\n timer.stage(\"load\");\n const { feeds, scale, padLeft, padTop } = this._preprocess(original);\n timer.stage(\"preprocess\");\n const outputs = await this._session.run(feeds);\n this._pipeline.release();\n timer.stage(\"inference\");\n\n const probsTensor = output(outputs, OUTPUT_PROBS);\n const boxes = floats(outputs, OUTPUT_BOXES);\n const scores = floats(outputs, OUTPUT_SCORES);\n const classes = integers(outputs, OUTPUT_CLASSES);\n const probs = probsTensor.data as Float32Array;\n const reported = integers(outputs, OUTPUT_NUM_DETECTIONS)[0] ?? 0;\n const rows = Math.min(reported, Math.floor(boxes.length / 4));\n const classCount = probsTensor.dims[probsTensor.dims.length - 1] ?? 0;\n\n const allowed = options.classes === undefined ? null : new Set(options.classes);\n const floor = options.confThreshold ?? 0;\n const detections: DetectionResult[] = [];\n for (let row = 0; row < rows; row++) {\n const classId = classes[row] ?? 0;\n const confidence = scores[row] ?? 0;\n if (confidence < floor || (allowed !== null && !allowed.has(classId))) continue;\n\n const bbox = this._toOriginal(boxes, row, { scale, padLeft, padTop, original });\n const cropped = crop(original, bbox);\n detections.push(\n detection(\n classId,\n this._names[classId] ?? `class_${classId}`,\n confidence,\n bbox,\n cropped,\n this._classify(\n probs.subarray(row * classCount, (row + 1) * classCount),\n cropped,\n options.topK,\n ),\n ),\n );\n }\n\n requireDetections(detections.length, {\n raiseOnEmpty: options.raiseOnEmpty ?? this._raiseOnEmpty,\n confThreshold: Math.max(floor, this._spec.confThreshold),\n classes: options.classes,\n path,\n });\n\n const origShape: readonly [number, number] = [original.height, original.width];\n timer.stage(\"postprocess\");\n return [\n new DetectClassifyResults(\n bulkBoxes(detections, origShape),\n detections,\n this._names,\n this._classifierNames,\n original,\n origShape,\n path,\n timer.speed(),\n ),\n ];\n }\n\n /**\n * Letterbox the image and build the graph's feeds.\n *\n * The detector input runs through {@link LetterboxPipeline}, which fuses the\n * resize, the padding and the HWC-to-CHW float conversion into one\n * `drawImage` plus one readback, and reuses its output buffer between frames.\n * That buffer goes straight to ONNX Runtime, so `_pipeline.release()` must not\n * be called until the run resolves.\n *\n * A pipeline fused with `cropSource: \"original\"` also takes the untouched\n * image as a second input, plus the scale and padding of the letterbox — that\n * is what lets the graph undo the letterbox transform internally and crop at\n * native resolution instead of from the downscaled copy. That one is **not**\n * letterboxed by definition, so it does not go through the fused path.\n */\n private _preprocess(image: RGBImage): {\n feeds: Record<string, ort.Tensor>;\n scale: number;\n padLeft: number;\n padTop: number;\n } {\n const [width, height] = this._spec.inputSize;\n const boxed = this._pipeline.run(image);\n const feeds: Record<string, ort.Tensor> = {\n [INPUT_IMAGE]: toFloat32Tensor(boxed.data, [1, 3, height, width]),\n };\n\n if (this._spec.needsSourceImage) {\n feeds[INPUT_SOURCE] = tensorOf(image);\n feeds[INPUT_SCALE] = toFloat32Tensor(new Float32Array([boxed.scale]), [1]);\n feeds[INPUT_PAD] = toFloat32Tensor(\n new Float32Array([boxed.padLeft, boxed.padTop]),\n [2],\n );\n }\n return { feeds, scale: boxed.scale, padLeft: boxed.padLeft, padTop: boxed.padTop };\n }\n\n /**\n * Map one letterboxed xyxy row back onto the original image.\n *\n * The graph always reports boxes in the detector's letterboxed pixel space,\n * whichever crop source it was fused with, so both sources agree here.\n */\n private _toOriginal(\n boxes: Float32Array,\n row: number,\n context: {\n scale: number;\n padLeft: number;\n padTop: number;\n original: RGBImage;\n },\n ): BoundingBox {\n const { scale, padLeft, padTop, original } = context;\n const at = (offset: number): number => boxes[row * 4 + offset] ?? 0;\n const clampX = (value: number): number => Math.min(Math.max(value, 0), original.width);\n const clampY = (value: number): number => Math.min(Math.max(value, 0), original.height);\n return new BoundingBox(\n clampX((at(0) - padLeft) / scale),\n clampY((at(1) - padTop) / scale),\n clampX((at(2) - padLeft) / scale),\n clampY((at(3) - padTop) / scale),\n );\n }\n\n /**\n * Turn one row of the classifier output into a result object.\n *\n * @param row The output row for this detection.\n * @param image The crop the row describes, carried so callers can display\n * what was classified.\n * @param k Optional truncation of the probability list.\n */\n private _classify(\n row: Float32Array,\n image: RGBImage,\n k: number | undefined,\n ): ClassificationResult {\n const scores = this._spec.applySoftmax ? softmax(row) : row;\n const { indices, values } = topK(scores, k ?? null);\n const probabilities: ClassProbability[] = [];\n for (let i = 0; i < indices.length; i++) {\n const classId = indices[i] ?? 0;\n const probability = values[i] ?? 0;\n const className = this._classifierLabels[classId] ?? `class_${classId}`;\n probabilities.push({\n classId,\n className,\n probability,\n cls: classId,\n name: className,\n conf: probability,\n });\n }\n const top = probabilities[0] ?? {\n classId: 0,\n className: \"class_0\",\n probability: 0,\n cls: 0,\n name: \"class_0\",\n conf: 0,\n };\n return {\n classId: top.classId,\n className: top.className,\n confidence: top.probability,\n cls: top.classId,\n name: top.className,\n conf: top.probability,\n image,\n probabilities,\n };\n }\n}\n\n/**\n * Read the classifier stage's class count off the `probs` output shape.\n *\n * @param session The loaded pipeline session.\n * @returns The class count, or `null` when the graph leaves that axis dynamic\n * or declares no `probs` output — in which case label resolution falls back\n * to whatever the fusion recorded.\n */\nfunction classifierClasses(session: OrtSession): number | null {\n const index = session.outputNames.indexOf(OUTPUT_PROBS);\n if (index < 0) return null;\n const shape = session.outputShapes[index];\n if (shape === undefined || shape.length === 0) return null;\n return shape[shape.length - 1] ?? null;\n}\n\n/**\n * Build a class id → name record from an ordered label list.\n *\n * @param labels Labels indexed by class id.\n * @returns The equivalent record.\n */\nfunction indexNames(labels: readonly string[]): Readonly<Record<number, string>> {\n const names: Record<number, string> = {};\n for (let i = 0; i < labels.length; i++) names[i] = labels[i] as string;\n return names;\n}\n\n/**\n * Fetch an output tensor by name.\n *\n * @param outputs The run's results.\n * @param name The output's name in the pipeline contract.\n * @returns The tensor.\n * @throws {@link FusionError} when the graph does not carry that output, which\n * means the file is not a pipeline this version can drive.\n */\nfunction output(outputs: Record<string, ort.Tensor>, name: string): ort.Tensor {\n const tensor = outputs[name];\n if (tensor === undefined) {\n throw new FusionError(`The fused pipeline is missing its '${name}' output.`);\n }\n return tensor;\n}\n\n/**\n * Fetch a float output by name.\n *\n * @param outputs The run's results.\n * @param name The output's name in the pipeline contract.\n * @returns The output's data.\n * @throws {@link FusionError} when the graph does not carry that output.\n */\nfunction floats(outputs: Record<string, ort.Tensor>, name: string): Float32Array {\n return output(outputs, name).data as Float32Array;\n}\n\n/**\n * Fetch an integer output by name, normalizing ORT's 64-bit representation.\n *\n * ONNX Runtime Web returns `int64` tensors as `BigInt64Array`, whose values do\n * not compare or index like numbers. Class ids and detection counts are always\n * small, so widening them to `number` here is lossless and keeps every caller\n * free of `BigInt` handling.\n *\n * @param outputs The run's results.\n * @param name The output's name in the pipeline contract.\n * @returns The output's values as plain numbers.\n * @throws {@link FusionError} when the graph does not carry that output.\n */\nfunction integers(outputs: Record<string, ort.Tensor>, name: string): number[] {\n const data = output(outputs, name).data as BigInt64Array | Int32Array | Float32Array;\n const values: number[] = [];\n for (let i = 0; i < data.length; i++) values.push(Number(data[i]));\n return values;\n}\n\n/**\n * Convert an image to the `[1, 3, H, W]` float32 tensor the graph expects.\n *\n * @param image The image to convert.\n * @returns The batched CHW tensor, scaled to `[0, 1]`.\n */\nfunction tensorOf(image: RGBImage): ort.Tensor {\n const chw = toCHW(toFloat32(image), image.width, image.height, 3);\n return toFloat32Tensor(chw, [1, 3, image.height, image.width]);\n}\n\n/**\n * Cut the box region out of the original image.\n *\n * @param image The source image.\n * @param bbox The box, in original-image pixel coordinates.\n * @returns The cropped region, or a zero-sized image for a box with no area.\n */\nfunction crop(image: RGBImage, bbox: BoundingBox): RGBImage {\n const [rawX1, rawY1, rawX2, rawY2] = bbox.asIntXyxy();\n const x1 = Math.max(0, rawX1);\n const y1 = Math.max(0, rawY1);\n const x2 = Math.min(image.width, rawX2);\n const y2 = Math.min(image.height, rawY2);\n if (x2 <= x1 || y2 <= y1) return new RGBImage(new Uint8Array(0), 0, 0);\n\n const width = x2 - x1;\n const height = y2 - y1;\n const out = new Uint8Array(width * height * 3);\n for (let row = 0; row < height; row++) {\n const offset = ((y1 + row) * image.width + x1) * 3;\n out.set(image.data.subarray(offset, offset + width * 3), row * width * 3);\n }\n return new RGBImage(out, width, height);\n}\n\n/**\n * Assemble one detection, filling the Ultralytics-style aliases.\n *\n * @param classId Detector class index.\n * @param className Detector class name.\n * @param confidence Detection score.\n * @param bbox Box in original-image coordinates.\n * @param croppedImage The region the box covers.\n * @param classification What the classification stage said about that region.\n * @returns The detection object.\n */\nfunction detection(\n classId: number,\n className: string,\n confidence: number,\n bbox: BoundingBox,\n croppedImage: RGBImage,\n classification: ClassificationResult,\n): DetectionResult {\n return {\n classId,\n className,\n confidence,\n bbox,\n cls: classId,\n name: className,\n conf: confidence,\n box: bbox,\n croppedImage,\n classification,\n };\n}\n\n/**\n * Assemble the bulk-array `Boxes` view from per-instance detections.\n *\n * @param detections The surviving detections.\n * @param origShape `[height, width]` of the original image.\n * @returns The bulk view, empty when nothing survived.\n */\nfunction bulkBoxes(\n detections: readonly DetectionResult[],\n origShape: readonly [number, number],\n): Boxes {\n const xyxy = new Float32Array(detections.length * 4);\n const cls = new Int32Array(detections.length);\n const conf = new Float32Array(detections.length);\n detections.forEach((entry, index) => {\n const [x1, y1, x2, y2] = entry.bbox.xyxy;\n xyxy[index * 4] = x1;\n xyxy[index * 4 + 1] = y1;\n xyxy[index * 4 + 2] = x2;\n xyxy[index * 4 + 3] = y2;\n cls[index] = entry.classId;\n conf[index] = entry.confidence;\n });\n return new Boxes(xyxy, cls, conf, origShape);\n}\n"],"mappings":"wYAqGA,IAAa,EAAb,MAAa,UAAuB,EAAA,UAAW,CAGtB,MACA,QACA,OACA,kBACA,iBACA,cAPrB,YACI,EACA,EACA,EACA,EACA,EACA,EACA,EACF,CACE,MAAM,CAAO,EAPI,KAAA,MAAA,EACA,KAAA,QAAA,EACA,KAAA,OAAA,EACA,KAAA,kBAAA,EACA,KAAA,iBAAA,EACA,KAAA,cAAA,CAGrB,CAEA,eAAmD,KAanD,MAAM,OAAO,EAAe,EAAkB,CAC1C,GAAM,CAAC,EAAO,GAAU,KAAK,MAAM,UACnC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,IAAK,CAC3B,IAAM,EAAoC,EACrC,EAAA,aAAc,EAAA,gBAAgB,EAAA,eAAe,EAAO,CAAM,EAAG,CAC1D,EACA,EACA,EACA,CACJ,CAAC,CACL,EACI,KAAK,MAAM,mBACX,EAAM,EAAA,cAAgB,EAAA,gBAAgB,EAAA,eAAe,EAAO,CAAM,EAAG,CACjE,EACA,EACA,EACA,CACJ,CAAC,EACD,EAAM,EAAA,aAAe,EAAA,gBAAgB,IAAI,aAAa,CAAC,CAAC,CAAC,EAAG,CAAC,CAAC,CAAC,EAC/D,EAAM,EAAA,WAAa,EAAA,gBAAgB,IAAI,aAAa,CAAC,EAAG,CAAC,CAAC,EAAG,CAAC,CAAC,CAAC,GAEpE,MAAM,KAAK,SAAS,IAAI,CAAK,CACjC,CACJ,CASA,IAAY,WAA+B,CACvC,GAAI,KAAK,iBAAmB,KAAM,CAC9B,GAAM,CAAC,EAAO,GAAU,KAAK,MAAM,UACnC,KAAK,eAAiB,IAAI,EAAA,kBAAkB,EAAO,CAAM,CAC7D,CACA,OAAO,KAAK,cAChB,CAWA,aAAa,OACT,EACA,EAAiC,CAAC,EACX,CACvB,IAAM,EAAU,MAAM,EAAA,WAAW,OAAO,EAAO,CAAO,EAChD,EAAO,EAAA,eAAe,EAAQ,QAAQ,EAC5C,GAAI,IAAS,KACT,MAAM,IAAI,EAAA,YACN,mNAGJ,EAGJ,IAAM,EAAS,EAAA,cAAc,EAAQ,QAAU,EAAK,eAAiB,MAAM,EACrE,EAAmB,EAAA,cAAc,EAAQ,kBAAoB,EAAK,gBAAiB,CACrF,WAAY,EAAkB,CAAO,GAAK,IAAA,EAC9C,CAAC,EACD,OAAO,IAAI,EACP,EACA,EACA,EACA,EAAW,CAAM,EACjB,EACA,EAAW,CAAgB,EAC3B,EAAQ,cAAgB,EAC5B,CACJ,CAGA,IAAI,MAAmB,CACnB,OAAO,KAAK,KAChB,CAGA,IAAI,WAAuC,CACvC,OAAO,KAAK,MAAM,SACtB,CAGA,IAAI,QAA4B,CAC5B,OAAO,KAAK,OAChB,CAGA,IAAI,OAA0C,CAC1C,OAAO,KAAK,MAChB,CAGA,IAAI,kBAAsC,CACtC,OAAO,KAAK,iBAChB,CAGA,IAAI,iBAAoD,CACpD,OAAO,KAAK,gBAChB,CAQA,MAAM,KACF,EACA,EAAwC,CAAC,EACT,CAChC,OAAO,KAAK,QAAQ,EAAO,CAAO,CACtC,CAUA,MAAM,QACF,EACA,EAAwC,CAAC,EACT,CAChC,IAAM,EAAQ,IAAI,EAAA,WACZ,EAAO,OAAO,GAAU,SAAW,EAAQ,KAC3C,EAAW,MAAM,EAAA,UAAU,CAAK,EACtC,EAAM,MAAM,MAAM,EAClB,GAAM,CAAE,QAAO,QAAO,UAAS,UAAW,KAAK,YAAY,CAAQ,EACnE,EAAM,MAAM,YAAY,EACxB,IAAM,EAAU,MAAM,KAAK,SAAS,IAAI,CAAK,EAC7C,KAAK,UAAU,QAAQ,EACvB,EAAM,MAAM,WAAW,EAEvB,IAAM,EAAc,EAAO,EAAS,EAAA,YAAY,EAC1C,EAAQ,EAAO,EAAS,EAAA,YAAY,EACpC,EAAS,EAAO,EAAS,EAAA,aAAa,EACtC,EAAU,EAAS,EAAS,EAAA,cAAc,EAC1C,EAAQ,EAAY,KACpB,EAAW,EAAS,EAAA,gBAA8B,CAAC,CAAC,IAAM,EAC1D,EAAO,KAAK,IAAI,EAAU,KAAK,MAAM,EAAM,OAAS,CAAC,CAAC,EACtD,EAAa,EAAY,KAAK,EAAY,KAAK,OAAS,IAAM,EAE9D,EAAU,EAAQ,UAAY,IAAA,GAAY,KAAO,IAAI,IAAI,EAAQ,OAAO,EACxE,EAAQ,EAAQ,eAAiB,EACjC,EAAgC,CAAC,EACvC,IAAK,IAAI,EAAM,EAAG,EAAM,EAAM,IAAO,CACjC,IAAM,EAAU,EAAQ,IAAQ,EAC1B,EAAa,EAAO,IAAQ,EAClC,GAAI,EAAa,GAAU,IAAY,MAAQ,CAAC,EAAQ,IAAI,CAAO,EAAI,SAEvE,IAAM,EAAO,KAAK,YAAY,EAAO,EAAK,CAAE,QAAO,UAAS,SAAQ,UAAS,CAAC,EACxE,EAAU,EAAK,EAAU,CAAI,EACnC,EAAW,KACP,EACI,EACA,KAAK,OAAO,IAAY,SAAS,IACjC,EACA,EACA,EACA,KAAK,UACD,EAAM,SAAS,EAAM,GAAa,EAAM,GAAK,CAAU,EACvD,EACA,EAAQ,IACZ,CACJ,CACJ,CACJ,CAEA,EAAA,kBAAkB,EAAW,OAAQ,CACjC,aAAc,EAAQ,cAAgB,KAAK,cAC3C,cAAe,KAAK,IAAI,EAAO,KAAK,MAAM,aAAa,EACvD,QAAS,EAAQ,QACjB,MACJ,CAAC,EAED,IAAM,EAAuC,CAAC,EAAS,OAAQ,EAAS,KAAK,EAE7E,OADA,EAAM,MAAM,aAAa,EAClB,CACH,IAAI,EAAA,sBACA,EAAU,EAAY,CAAS,EAC/B,EACA,KAAK,OACL,KAAK,iBACL,EACA,EACA,EACA,EAAM,MAAM,CAChB,CACJ,CACJ,CAiBA,YAAoB,EAKlB,CACE,GAAM,CAAC,EAAO,GAAU,KAAK,MAAM,UAC7B,EAAQ,KAAK,UAAU,IAAI,CAAK,EAChC,EAAoC,EACrC,EAAA,aAAc,EAAA,gBAAgB,EAAM,KAAM,CAAC,EAAG,EAAG,EAAQ,CAAK,CAAC,CACpE,EAUA,OARI,KAAK,MAAM,mBACX,EAAM,EAAA,cAAgB,EAAS,CAAK,EACpC,EAAM,EAAA,aAAe,EAAA,gBAAgB,IAAI,aAAa,CAAC,EAAM,KAAK,CAAC,EAAG,CAAC,CAAC,CAAC,EACzE,EAAM,EAAA,WAAa,EAAA,gBACf,IAAI,aAAa,CAAC,EAAM,QAAS,EAAM,MAAM,CAAC,EAC9C,CAAC,CAAC,CACN,GAEG,CAAE,QAAO,MAAO,EAAM,MAAO,QAAS,EAAM,QAAS,OAAQ,EAAM,MAAO,CACrF,CAQA,YACI,EACA,EACA,EAMW,CACX,GAAM,CAAE,QAAO,UAAS,SAAQ,YAAa,EACvC,EAAM,GAA2B,EAAM,EAAM,EAAI,IAAW,EAC5D,EAAU,GAA0B,KAAK,IAAI,KAAK,IAAI,EAAO,CAAC,EAAG,EAAS,KAAK,EAC/E,EAAU,GAA0B,KAAK,IAAI,KAAK,IAAI,EAAO,CAAC,EAAG,EAAS,MAAM,EACtF,OAAO,IAAI,EAAA,YACP,GAAQ,EAAG,CAAC,EAAI,GAAW,CAAK,EAChC,GAAQ,EAAG,CAAC,EAAI,GAAU,CAAK,EAC/B,GAAQ,EAAG,CAAC,EAAI,GAAW,CAAK,EAChC,GAAQ,EAAG,CAAC,EAAI,GAAU,CAAK,CACnC,CACJ,CAUA,UACI,EACA,EACA,EACoB,CAEpB,GAAM,CAAE,UAAS,UAAW,EAAA,KADb,KAAK,MAAM,aAAe,EAAA,QAAQ,CAAG,EAAI,EACf,GAAK,IAAI,EAC5C,EAAoC,CAAC,EAC3C,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,OAAQ,IAAK,CACrC,IAAM,EAAU,EAAQ,IAAM,EACxB,EAAc,EAAO,IAAM,EAC3B,EAAY,KAAK,kBAAkB,IAAY,SAAS,IAC9D,EAAc,KAAK,CACf,UACA,YACA,cACA,IAAK,EACL,KAAM,EACN,KAAM,CACV,CAAC,CACL,CACA,IAAM,EAAM,EAAc,IAAM,CAC5B,QAAS,EACT,UAAW,UACX,YAAa,EACb,IAAK,EACL,KAAM,UACN,KAAM,CACV,EACA,MAAO,CACH,QAAS,EAAI,QACb,UAAW,EAAI,UACf,WAAY,EAAI,YAChB,IAAK,EAAI,QACT,KAAM,EAAI,UACV,KAAM,EAAI,YACV,QACA,eACJ,CACJ,CACJ,EAUA,SAAS,EAAkB,EAAoC,CAC3D,IAAM,EAAQ,EAAQ,YAAY,QAAQ,EAAA,YAAY,EACtD,GAAI,EAAQ,EAAG,OAAO,KACtB,IAAM,EAAQ,EAAQ,aAAa,GAEnC,OADI,IAAU,IAAA,IAAa,EAAM,SAAW,EAAU,KAC/C,EAAM,EAAM,OAAS,IAAM,IACtC,CAQA,SAAS,EAAW,EAA6D,CAC7E,IAAM,EAAgC,CAAC,EACvC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAO,OAAQ,IAAK,EAAM,GAAK,EAAO,GAC1D,OAAO,CACX,CAWA,SAAS,EAAO,EAAqC,EAA0B,CAC3E,IAAM,EAAS,EAAQ,GACvB,GAAI,IAAW,IAAA,GACX,MAAM,IAAI,EAAA,YAAY,sCAAsC,EAAK,UAAU,EAE/E,OAAO,CACX,CAUA,SAAS,EAAO,EAAqC,EAA4B,CAC7E,OAAO,EAAO,EAAS,CAAI,CAAC,CAAC,IACjC,CAeA,SAAS,EAAS,EAAqC,EAAwB,CAC3E,IAAM,EAAO,EAAO,EAAS,CAAI,CAAC,CAAC,KAC7B,EAAmB,CAAC,EAC1B,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAQ,IAAK,EAAO,KAAK,OAAO,EAAK,EAAE,CAAC,EACjE,OAAO,CACX,CAQA,SAAS,EAAS,EAA6B,CAE3C,OAAO,EAAA,gBADK,EAAA,MAAM,EAAA,UAAU,CAAK,EAAG,EAAM,MAAO,EAAM,OAAQ,CACxC,EAAK,CAAC,EAAG,EAAG,EAAM,OAAQ,EAAM,KAAK,CAAC,CACjE,CASA,SAAS,EAAK,EAAiB,EAA6B,CACxD,GAAM,CAAC,EAAO,EAAO,EAAO,GAAS,EAAK,UAAU,EAC9C,EAAK,KAAK,IAAI,EAAG,CAAK,EACtB,EAAK,KAAK,IAAI,EAAG,CAAK,EACtB,EAAK,KAAK,IAAI,EAAM,MAAO,CAAK,EAChC,EAAK,KAAK,IAAI,EAAM,OAAQ,CAAK,EACvC,GAAI,GAAM,GAAM,GAAM,EAAI,OAAO,IAAI,EAAA,SAAS,IAAI,WAAe,EAAG,CAAC,EAErE,IAAM,EAAQ,EAAK,EACb,EAAS,EAAK,EACd,EAAM,IAAI,WAAW,EAAQ,EAAS,CAAC,EAC7C,IAAK,IAAI,EAAM,EAAG,EAAM,EAAQ,IAAO,CACnC,IAAM,IAAW,EAAK,GAAO,EAAM,MAAQ,GAAM,EACjD,EAAI,IAAI,EAAM,KAAK,SAAS,EAAQ,EAAS,EAAQ,CAAC,EAAG,EAAM,EAAQ,CAAC,CAC5E,CACA,OAAO,IAAI,EAAA,SAAS,EAAK,EAAO,CAAM,CAC1C,CAaA,SAAS,EACL,EACA,EACA,EACA,EACA,EACA,EACe,CACf,MAAO,CACH,UACA,YACA,aACA,OACA,IAAK,EACL,KAAM,EACN,KAAM,EACN,IAAK,EACL,eACA,gBACJ,CACJ,CASA,SAAS,EACL,EACA,EACK,CACL,IAAM,EAAO,IAAI,aAAa,EAAW,OAAS,CAAC,EAC7C,EAAM,IAAI,WAAW,EAAW,MAAM,EACtC,EAAO,IAAI,aAAa,EAAW,MAAM,EAU/C,OATA,EAAW,SAAS,EAAO,IAAU,CACjC,GAAM,CAAC,EAAI,EAAI,EAAI,GAAM,EAAM,KAAK,KACpC,EAAK,EAAQ,GAAK,EAClB,EAAK,EAAQ,EAAI,GAAK,EACtB,EAAK,EAAQ,EAAI,GAAK,EACtB,EAAK,EAAQ,EAAI,GAAK,EACtB,EAAI,GAAS,EAAM,QACnB,EAAK,GAAS,EAAM,UACxB,CAAC,EACM,IAAI,EAAA,MAAM,EAAM,EAAK,EAAM,CAAS,CAC/C"}
|
|
1
|
+
{"version":3,"file":"detectClassify.cjs","names":[],"sources":["../../../src/vision/tasks/detectClassify.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Run a fused detect→classify pipeline in the browser.\n *\n * The file this loads was built by the Python SDK's `ort_vision_sdk.compose`,\n * and it already contains both models plus the crop-and-resize bridge between\n * them. That matters far more in a tab than on a server: two models mean two\n * `.onnx` downloads, two WASM/WebGPU session initializations, and a per-crop\n * round trip through JavaScript to slice, resize and restack the regions before\n * the second model can see them. A fused pipeline has one download, one session\n * and no round trip — the crops are produced and consumed inside the graph.\n */\n\nimport type * as ort from \"onnxruntime-web\";\n\nimport { FusionError } from \"../core/exceptions\";\nimport { type ModelSource, type OrtSessionOptions, OrtSession } from \"../core/session\";\nimport { SpeedTimer } from \"../core/timing\";\nimport {\n INPUT_IMAGE,\n INPUT_PAD,\n INPUT_SCALE,\n INPUT_SOURCE,\n OUTPUT_BOXES,\n OUTPUT_CLASSES,\n OUTPUT_NUM_DETECTIONS,\n OUTPUT_PROBS,\n OUTPUT_SCORES,\n type FusionSpec,\n readFusionSpec,\n} from \"../fusion\";\nimport { type ImageInput, loadImage } from \"../io/image\";\nimport { type LabelSpec, resolveLabels } from \"../labels\";\nimport { softmax, topK } from \"../postprocess/classification\";\nimport { toCHW, toFloat32, toFloat32Tensor } from \"../preprocess/image\";\nimport { LetterboxPipeline, zeroTensorData } from \"../preprocess/pipeline\";\nimport { Boxes, DetectClassifyResults } from \"../results\";\nimport {\n BoundingBox,\n RGBImage,\n type ClassProbability,\n type ClassificationResult,\n type DetectionResult,\n} from \"../types\";\nimport { VisionTask, requireDetections } from \"./base\";\n\nexport interface DetectClassifyOptions extends OrtSessionOptions {\n /**\n * Class label spec for the **detection** stage — see {@link resolveLabels}.\n * Defaults to the names recorded at fusion time, falling back to the COCO\n * 80-class preset when the fusion recorded none.\n */\n readonly labels?: LabelSpec;\n /**\n * Class label spec for the **classification** stage. Defaults to the recorded\n * names, falling back to generated `class_<id>` names.\n */\n readonly classifierLabels?: LabelSpec;\n /**\n * If `true`, a run that finds nothing throws {@link NoDetectionsError}\n * instead of returning an empty envelope. Default `false`, because looking\n * and finding nothing is a successful inference. Turn it on when an empty\n * result means the surrounding pipeline should stop rather than carry on with\n * zero rows. Can be overridden per `predict` call.\n */\n readonly raiseOnEmpty?: boolean;\n}\n\nexport interface DetectClassifyPredictOptions {\n /**\n * Drop detections scoring below this. The graph's own NMS threshold was fixed\n * at fusion time and cannot be lowered here — this only filters further.\n */\n readonly confThreshold?: number;\n /** If set, keep only detections whose detector `classId` is in this list. */\n readonly classes?: readonly number[];\n /** Truncate each detection's `classification.probabilities` to its top-k entries. */\n readonly topK?: number;\n /** Override the constructor's `raiseOnEmpty` setting for this call. */\n readonly raiseOnEmpty?: boolean;\n}\n\n/**\n * Detector and classifier running as a single ONNX model.\n *\n * Everything the pipeline needs to know about itself — the resolution to\n * letterbox to, whether it wants the full-resolution image as well, whether its\n * classifier output still needs a softmax, the class names of both stages — was\n * written into the file at fusion time and is read back here. Nothing is\n * restated on the JavaScript side, so nothing can drift out of step with the\n * Python side that built it.\n *\n * @example\n * ```typescript\n * const pipeline = await DetectClassify.create(\"/models/pipeline.onnx\");\n * const result = (await pipeline.predict(\"/images/flock.jpg\"))[0];\n * for (const detection of result) {\n * console.log(detection.name, detection.conf, detection.classification?.name);\n * }\n * ```\n */\nexport class DetectClassify extends VisionTask {\n private constructor(\n session: OrtSession,\n private readonly _spec: FusionSpec,\n private readonly _labels: readonly string[],\n private readonly _names: Readonly<Record<number, string>>,\n private readonly _classifierLabels: readonly string[],\n private readonly _classifierNames: Readonly<Record<number, string>>,\n private readonly _raiseOnEmpty: boolean,\n ) {\n super(session);\n }\n\n private _pipelineCache: LetterboxPipeline | null = null;\n\n /**\n * Run the model once on zero-filled inputs, paying one-time costs up front.\n *\n * Worth more here than on a single-stage task: a fused pipeline is two models\n * plus the bridge in one graph, so the first inference compiles shaders for\n * all of it. Calling this while a loading spinner is still up moves that cost\n * somewhere the user is already waiting.\n *\n * @param runs How many warm-up inferences to run. One is enough for WASM;\n * WebGPU sometimes settles on the second.\n */\n async warmup(runs: number = 1): Promise<void> {\n const [width, height] = this._spec.inputSize;\n for (let i = 0; i < runs; i++) {\n const feeds: Record<string, ort.Tensor> = {\n [INPUT_IMAGE]: toFloat32Tensor(zeroTensorData(width, height), [\n 1,\n 3,\n height,\n width,\n ]),\n };\n if (this._spec.needsSourceImage) {\n feeds[INPUT_SOURCE] = toFloat32Tensor(zeroTensorData(width, height), [\n 1,\n 3,\n height,\n width,\n ]);\n feeds[INPUT_SCALE] = toFloat32Tensor(new Float32Array([1]), [1]);\n feeds[INPUT_PAD] = toFloat32Tensor(new Float32Array([0, 0]), [2]);\n }\n await this._session.run(feeds);\n }\n }\n\n /**\n * The fused preprocessing pipeline, built on first use.\n *\n * Lazily, because constructing it allocates canvases: a pipeline built in an\n * environment without a canvas implementation stays constructible, and only\n * fails if it is actually asked to preprocess something.\n */\n private get _pipeline(): LetterboxPipeline {\n if (this._pipelineCache === null) {\n const [width, height] = this._spec.inputSize;\n this._pipelineCache = new LetterboxPipeline(width, height);\n }\n return this._pipelineCache;\n }\n\n /**\n * Load a fused pipeline and resolve both label spaces.\n *\n * @param model The fused `.onnx` — a URL, an `ArrayBuffer`, or bytes.\n * @param options Label overrides plus the usual session options.\n * @throws {@link FusionError} when the model carries no pipeline metadata,\n * i.e. it is a plain detector or classifier rather than something\n * `ort_vision_sdk.compose` produced.\n */\n static async create(\n model: ModelSource,\n options: DetectClassifyOptions = {},\n ): Promise<DetectClassify> {\n const session = await OrtSession.create(model, options);\n const spec = readFusionSpec(session.metadata);\n if (spec === null) {\n throw new FusionError(\n \"This model carries no fused-pipeline metadata, so DetectClassify cannot tell how to \" +\n \"drive it. Build one with ort_vision_sdk.compose.fuse_detect_classify, or load a \" +\n \"plain model with Detector/Classifier instead.\",\n );\n }\n\n const labels = resolveLabels(options.labels ?? spec.detectorNames ?? \"coco\");\n const classifierLabels = resolveLabels(options.classifierLabels ?? spec.classifierNames, {\n numClasses: classifierClasses(session) ?? undefined,\n });\n return new DetectClassify(\n session,\n spec,\n labels,\n indexNames(labels),\n classifierLabels,\n indexNames(classifierLabels),\n options.raiseOnEmpty ?? false,\n );\n }\n\n /** The pipeline configuration recorded in the model at fusion time. */\n get spec(): FusionSpec {\n return this._spec;\n }\n\n /** The `[width, height]` the detection stage runs at. */\n get inputSize(): readonly [number, number] {\n return this._spec.inputSize;\n }\n\n /** Detector class labels indexed by class id. */\n get labels(): readonly string[] {\n return this._labels;\n }\n\n /** Detector class id → class name (matches Ultralytics' `model.names`). */\n get names(): Readonly<Record<number, string>> {\n return this._names;\n }\n\n /** Classifier class labels indexed by class id. */\n get classifierLabels(): readonly string[] {\n return this._classifierLabels;\n }\n\n /** Classifier class id → class name. */\n get classifierNames(): Readonly<Record<number, string>> {\n return this._classifierNames;\n }\n\n /**\n * Alias for {@link predict} — call the pipeline like a torch `nn.Module`.\n *\n * Use as `pipeline.call(img)` since JavaScript class instances are not\n * callable; for direct invocation, prefer `pipeline.predict(img)`.\n */\n async call(\n image: ImageInput,\n options: DetectClassifyPredictOptions = {},\n ): Promise<DetectClassifyResults[]> {\n return this.predict(image, options);\n }\n\n /**\n * Run the pipeline on a single image.\n *\n * The returned envelope carries a {@link Speed} breakdown in `speed`. Its\n * `inference` figure covers detection *and* classification, since the\n * pipeline runs them as one graph and no boundary between them is observable\n * from outside.\n */\n async predict(\n image: ImageInput,\n options: DetectClassifyPredictOptions = {},\n ): Promise<DetectClassifyResults[]> {\n const timer = new SpeedTimer();\n const path = typeof image === \"string\" ? image : null;\n const original = await loadImage(image);\n timer.stage(\"load\");\n const { feeds, scale, padLeft, padTop } = this._preprocess(original);\n timer.stage(\"preprocess\");\n const outputs = await this._session.run(feeds);\n this._pipeline.release();\n timer.stage(\"inference\");\n\n const probsTensor = output(outputs, OUTPUT_PROBS);\n const boxes = floats(outputs, OUTPUT_BOXES);\n const scores = floats(outputs, OUTPUT_SCORES);\n const classes = integers(outputs, OUTPUT_CLASSES);\n const probs = probsTensor.data as Float32Array;\n const reported = integers(outputs, OUTPUT_NUM_DETECTIONS)[0] ?? 0;\n const rows = Math.min(reported, Math.floor(boxes.length / 4));\n const classCount = probsTensor.dims[probsTensor.dims.length - 1] ?? 0;\n\n const allowed = options.classes === undefined ? null : new Set(options.classes);\n const floor = options.confThreshold ?? 0;\n const detections: DetectionResult[] = [];\n for (let row = 0; row < rows; row++) {\n const classId = classes[row] ?? 0;\n const confidence = scores[row] ?? 0;\n if (confidence < floor || (allowed !== null && !allowed.has(classId))) continue;\n\n const bbox = this._toOriginal(boxes, row, { scale, padLeft, padTop, original });\n const cropped = crop(original, bbox);\n detections.push(\n detection(\n classId,\n this._names[classId] ?? `class_${classId}`,\n confidence,\n bbox,\n cropped,\n this._classify(\n probs.subarray(row * classCount, (row + 1) * classCount),\n cropped,\n options.topK,\n ),\n ),\n );\n }\n\n requireDetections(detections.length, {\n raiseOnEmpty: options.raiseOnEmpty ?? this._raiseOnEmpty,\n confThreshold: Math.max(floor, this._spec.confThreshold),\n classes: options.classes,\n path,\n });\n\n const origShape: readonly [number, number] = [original.height, original.width];\n timer.stage(\"postprocess\");\n return [\n new DetectClassifyResults(\n bulkBoxes(detections, origShape),\n detections,\n this._names,\n this._classifierNames,\n original,\n origShape,\n path,\n timer.speed(),\n ),\n ];\n }\n\n /**\n * Letterbox the image and build the graph's feeds.\n *\n * The detector input runs through {@link LetterboxPipeline}, which fuses the\n * resize, the padding and the HWC-to-CHW float conversion into one\n * `drawImage` plus one readback, and reuses its output buffer between frames.\n * That buffer goes straight to ONNX Runtime, so `_pipeline.release()` must not\n * be called until the run resolves.\n *\n * A pipeline fused with `cropSource: \"original\"` also takes the untouched\n * image as a second input, plus the scale and padding of the letterbox — that\n * is what lets the graph undo the letterbox transform internally and crop at\n * native resolution instead of from the downscaled copy. That one is **not**\n * letterboxed by definition, so it does not go through the fused path.\n */\n private _preprocess(image: RGBImage): {\n feeds: Record<string, ort.Tensor>;\n scale: number;\n padLeft: number;\n padTop: number;\n } {\n const [width, height] = this._spec.inputSize;\n const boxed = this._pipeline.run(image);\n const feeds: Record<string, ort.Tensor> = {\n [INPUT_IMAGE]: toFloat32Tensor(boxed.data, [1, 3, height, width]),\n };\n\n if (this._spec.needsSourceImage) {\n feeds[INPUT_SOURCE] = tensorOf(image);\n feeds[INPUT_SCALE] = toFloat32Tensor(new Float32Array([boxed.scale]), [1]);\n feeds[INPUT_PAD] = toFloat32Tensor(\n new Float32Array([boxed.padLeft, boxed.padTop]),\n [2],\n );\n }\n return { feeds, scale: boxed.scale, padLeft: boxed.padLeft, padTop: boxed.padTop };\n }\n\n /**\n * Map one letterboxed xyxy row back onto the original image.\n *\n * The graph always reports boxes in the detector's letterboxed pixel space,\n * whichever crop source it was fused with, so both sources agree here.\n */\n private _toOriginal(\n boxes: Float32Array,\n row: number,\n context: {\n scale: number;\n padLeft: number;\n padTop: number;\n original: RGBImage;\n },\n ): BoundingBox {\n const { scale, padLeft, padTop, original } = context;\n const at = (offset: number): number => boxes[row * 4 + offset] ?? 0;\n const clampX = (value: number): number => Math.min(Math.max(value, 0), original.width);\n const clampY = (value: number): number => Math.min(Math.max(value, 0), original.height);\n return new BoundingBox(\n clampX((at(0) - padLeft) / scale),\n clampY((at(1) - padTop) / scale),\n clampX((at(2) - padLeft) / scale),\n clampY((at(3) - padTop) / scale),\n );\n }\n\n /**\n * Turn one row of the classifier output into a result object.\n *\n * @param row The output row for this detection.\n * @param image The crop the row describes, carried so callers can display\n * what was classified.\n * @param k Optional truncation of the probability list.\n */\n private _classify(\n row: Float32Array,\n image: RGBImage,\n k: number | undefined,\n ): ClassificationResult {\n const scores = this._spec.applySoftmax ? softmax(row) : row;\n const { indices, values } = topK(scores, k ?? null);\n const probabilities: ClassProbability[] = [];\n for (let i = 0; i < indices.length; i++) {\n const classId = indices[i] ?? 0;\n const probability = values[i] ?? 0;\n const className = this._classifierLabels[classId] ?? `class_${classId}`;\n probabilities.push({\n classId,\n className,\n probability,\n cls: classId,\n name: className,\n conf: probability,\n });\n }\n const top = probabilities[0] ?? {\n classId: 0,\n className: \"class_0\",\n probability: 0,\n cls: 0,\n name: \"class_0\",\n conf: 0,\n };\n return {\n classId: top.classId,\n className: top.className,\n confidence: top.probability,\n cls: top.classId,\n name: top.className,\n conf: top.probability,\n image,\n probabilities,\n };\n }\n}\n\n/**\n * Read the classifier stage's class count off the `probs` output shape.\n *\n * @param session The loaded pipeline session.\n * @returns The class count, or `null` when the graph leaves that axis dynamic\n * or declares no `probs` output — in which case label resolution falls back\n * to whatever the fusion recorded.\n */\nfunction classifierClasses(session: OrtSession): number | null {\n const index = session.outputNames.indexOf(OUTPUT_PROBS);\n if (index < 0) return null;\n const shape = session.outputShapes[index];\n if (shape === undefined || shape.length === 0) return null;\n return shape[shape.length - 1] ?? null;\n}\n\n/**\n * Build a class id → name record from an ordered label list.\n *\n * @param labels Labels indexed by class id.\n * @returns The equivalent record.\n */\nfunction indexNames(labels: readonly string[]): Readonly<Record<number, string>> {\n const names: Record<number, string> = {};\n for (let i = 0; i < labels.length; i++) names[i] = labels[i] as string;\n return names;\n}\n\n/**\n * Fetch an output tensor by name.\n *\n * @param outputs The run's results.\n * @param name The output's name in the pipeline contract.\n * @returns The tensor.\n * @throws {@link FusionError} when the graph does not carry that output, which\n * means the file is not a pipeline this version can drive.\n */\nfunction output(outputs: Record<string, ort.Tensor>, name: string): ort.Tensor {\n const tensor = outputs[name];\n if (tensor === undefined) {\n throw new FusionError(`The fused pipeline is missing its '${name}' output.`);\n }\n return tensor;\n}\n\n/**\n * Fetch a float output by name.\n *\n * @param outputs The run's results.\n * @param name The output's name in the pipeline contract.\n * @returns The output's data.\n * @throws {@link FusionError} when the graph does not carry that output.\n */\nfunction floats(outputs: Record<string, ort.Tensor>, name: string): Float32Array {\n return output(outputs, name).data as Float32Array;\n}\n\n/**\n * Fetch an integer output by name, normalizing ORT's 64-bit representation.\n *\n * ONNX Runtime Web returns `int64` tensors as `BigInt64Array`, whose values do\n * not compare or index like numbers. Class ids and detection counts are always\n * small, so widening them to `number` here is lossless and keeps every caller\n * free of `BigInt` handling.\n *\n * @param outputs The run's results.\n * @param name The output's name in the pipeline contract.\n * @returns The output's values as plain numbers.\n * @throws {@link FusionError} when the graph does not carry that output.\n */\nfunction integers(outputs: Record<string, ort.Tensor>, name: string): number[] {\n const data = output(outputs, name).data as BigInt64Array | Int32Array | Float32Array;\n const values: number[] = [];\n for (let i = 0; i < data.length; i++) values.push(Number(data[i]));\n return values;\n}\n\n/**\n * Convert an image to the `[1, 3, H, W]` float32 tensor the graph expects.\n *\n * @param image The image to convert.\n * @returns The batched CHW tensor, scaled to `[0, 1]`.\n */\nfunction tensorOf(image: RGBImage): ort.Tensor {\n const chw = toCHW(toFloat32(image), image.width, image.height, 3);\n return toFloat32Tensor(chw, [1, 3, image.height, image.width]);\n}\n\n/**\n * Cut the box region out of the original image.\n *\n * @param image The source image.\n * @param bbox The box, in original-image pixel coordinates.\n * @returns The cropped region, or a zero-sized image for a box with no area.\n */\nfunction crop(image: RGBImage, bbox: BoundingBox): RGBImage {\n const [rawX1, rawY1, rawX2, rawY2] = bbox.asIntXyxy();\n const x1 = Math.max(0, rawX1);\n const y1 = Math.max(0, rawY1);\n const x2 = Math.min(image.width, rawX2);\n const y2 = Math.min(image.height, rawY2);\n if (x2 <= x1 || y2 <= y1) return new RGBImage(new Uint8Array(0), 0, 0);\n\n const width = x2 - x1;\n const height = y2 - y1;\n const out = new Uint8Array(width * height * 3);\n for (let row = 0; row < height; row++) {\n const offset = ((y1 + row) * image.width + x1) * 3;\n out.set(image.data.subarray(offset, offset + width * 3), row * width * 3);\n }\n return new RGBImage(out, width, height);\n}\n\n/**\n * Assemble one detection, filling the Ultralytics-style aliases.\n *\n * @param classId Detector class index.\n * @param className Detector class name.\n * @param confidence Detection score.\n * @param bbox Box in original-image coordinates.\n * @param croppedImage The region the box covers.\n * @param classification What the classification stage said about that region.\n * @returns The detection object.\n */\nfunction detection(\n classId: number,\n className: string,\n confidence: number,\n bbox: BoundingBox,\n croppedImage: RGBImage,\n classification: ClassificationResult,\n): DetectionResult {\n return {\n classId,\n className,\n confidence,\n bbox,\n cls: classId,\n name: className,\n conf: confidence,\n box: bbox,\n croppedImage,\n classification,\n };\n}\n\n/**\n * Assemble the bulk-array `Boxes` view from per-instance detections.\n *\n * @param detections The surviving detections.\n * @param origShape `[height, width]` of the original image.\n * @returns The bulk view, empty when nothing survived.\n */\nfunction bulkBoxes(\n detections: readonly DetectionResult[],\n origShape: readonly [number, number],\n): Boxes {\n const xyxy = new Float32Array(detections.length * 4);\n const cls = new Int32Array(detections.length);\n const conf = new Float32Array(detections.length);\n detections.forEach((entry, index) => {\n const [x1, y1, x2, y2] = entry.bbox.xyxy;\n xyxy[index * 4] = x1;\n xyxy[index * 4 + 1] = y1;\n xyxy[index * 4 + 2] = x2;\n xyxy[index * 4 + 3] = y2;\n cls[index] = entry.classId;\n conf[index] = entry.confidence;\n });\n return new Boxes(xyxy, cls, conf, origShape);\n}\n"],"mappings":"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"}
|
|
@@ -120,19 +120,19 @@ var D = class n extends T {
|
|
|
120
120
|
return new t(l((c(0) - a) / i), u((c(1) - o) / i), l((c(2) - a) / i), u((c(3) - o) / i));
|
|
121
121
|
}
|
|
122
122
|
_classify(e, t, n) {
|
|
123
|
-
let
|
|
124
|
-
for (let e = 0; e <
|
|
125
|
-
let t =
|
|
126
|
-
|
|
123
|
+
let r = this._spec.applySoftmax ? C(e) : e, { indices: i, values: a } = w(r, n ?? null), o = [];
|
|
124
|
+
for (let e = 0; e < i.length; e++) {
|
|
125
|
+
let t = i[e] ?? 0, n = a[e] ?? 0, r = this._classifierLabels[t] ?? `class_${t}`;
|
|
126
|
+
o.push({
|
|
127
127
|
classId: t,
|
|
128
|
-
className:
|
|
128
|
+
className: r,
|
|
129
129
|
probability: n,
|
|
130
130
|
cls: t,
|
|
131
|
-
name:
|
|
131
|
+
name: r,
|
|
132
132
|
conf: n
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
|
-
let
|
|
135
|
+
let s = o[0] ?? {
|
|
136
136
|
classId: 0,
|
|
137
137
|
className: "class_0",
|
|
138
138
|
probability: 0,
|
|
@@ -141,14 +141,14 @@ var D = class n extends T {
|
|
|
141
141
|
conf: 0
|
|
142
142
|
};
|
|
143
143
|
return {
|
|
144
|
-
classId:
|
|
145
|
-
className:
|
|
146
|
-
confidence:
|
|
147
|
-
cls:
|
|
148
|
-
name:
|
|
149
|
-
conf:
|
|
144
|
+
classId: s.classId,
|
|
145
|
+
className: s.className,
|
|
146
|
+
confidence: s.probability,
|
|
147
|
+
cls: s.classId,
|
|
148
|
+
name: s.className,
|
|
149
|
+
conf: s.probability,
|
|
150
150
|
image: t,
|
|
151
|
-
probabilities:
|
|
151
|
+
probabilities: o
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
154
|
};
|
|
@@ -177,7 +177,8 @@ function M(e, t) {
|
|
|
177
177
|
return r;
|
|
178
178
|
}
|
|
179
179
|
function N(e) {
|
|
180
|
-
|
|
180
|
+
let t = v(y(e), e.width, e.height, 3);
|
|
181
|
+
return b(t, [
|
|
181
182
|
1,
|
|
182
183
|
3,
|
|
183
184
|
e.height,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detectClassify.js","names":[],"sources":["../../../src/vision/tasks/detectClassify.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Run a fused detect→classify pipeline in the browser.\n *\n * The file this loads was built by the Python SDK's `ort_vision_sdk.compose`,\n * and it already contains both models plus the crop-and-resize bridge between\n * them. That matters far more in a tab than on a server: two models mean two\n * `.onnx` downloads, two WASM/WebGPU session initializations, and a per-crop\n * round trip through JavaScript to slice, resize and restack the regions before\n * the second model can see them. A fused pipeline has one download, one session\n * and no round trip — the crops are produced and consumed inside the graph.\n */\n\nimport type * as ort from \"onnxruntime-web\";\n\nimport { FusionError } from \"../core/exceptions\";\nimport { type ModelSource, type OrtSessionOptions, OrtSession } from \"../core/session\";\nimport { SpeedTimer } from \"../core/timing\";\nimport {\n INPUT_IMAGE,\n INPUT_PAD,\n INPUT_SCALE,\n INPUT_SOURCE,\n OUTPUT_BOXES,\n OUTPUT_CLASSES,\n OUTPUT_NUM_DETECTIONS,\n OUTPUT_PROBS,\n OUTPUT_SCORES,\n type FusionSpec,\n readFusionSpec,\n} from \"../fusion\";\nimport { type ImageInput, loadImage } from \"../io/image\";\nimport { type LabelSpec, resolveLabels } from \"../labels\";\nimport { softmax, topK } from \"../postprocess/classification\";\nimport { toCHW, toFloat32, toFloat32Tensor } from \"../preprocess/image\";\nimport { LetterboxPipeline, zeroTensorData } from \"../preprocess/pipeline\";\nimport { Boxes, DetectClassifyResults } from \"../results\";\nimport {\n BoundingBox,\n RGBImage,\n type ClassProbability,\n type ClassificationResult,\n type DetectionResult,\n} from \"../types\";\nimport { VisionTask, requireDetections } from \"./base\";\n\nexport interface DetectClassifyOptions extends OrtSessionOptions {\n /**\n * Class label spec for the **detection** stage — see {@link resolveLabels}.\n * Defaults to the names recorded at fusion time, falling back to the COCO\n * 80-class preset when the fusion recorded none.\n */\n readonly labels?: LabelSpec;\n /**\n * Class label spec for the **classification** stage. Defaults to the recorded\n * names, falling back to generated `class_<id>` names.\n */\n readonly classifierLabels?: LabelSpec;\n /**\n * If `true`, a run that finds nothing throws {@link NoDetectionsError}\n * instead of returning an empty envelope. Default `false`, because looking\n * and finding nothing is a successful inference. Turn it on when an empty\n * result means the surrounding pipeline should stop rather than carry on with\n * zero rows. Can be overridden per `predict` call.\n */\n readonly raiseOnEmpty?: boolean;\n}\n\nexport interface DetectClassifyPredictOptions {\n /**\n * Drop detections scoring below this. The graph's own NMS threshold was fixed\n * at fusion time and cannot be lowered here — this only filters further.\n */\n readonly confThreshold?: number;\n /** If set, keep only detections whose detector `classId` is in this list. */\n readonly classes?: readonly number[];\n /** Truncate each detection's `classification.probabilities` to its top-k entries. */\n readonly topK?: number;\n /** Override the constructor's `raiseOnEmpty` setting for this call. */\n readonly raiseOnEmpty?: boolean;\n}\n\n/**\n * Detector and classifier running as a single ONNX model.\n *\n * Everything the pipeline needs to know about itself — the resolution to\n * letterbox to, whether it wants the full-resolution image as well, whether its\n * classifier output still needs a softmax, the class names of both stages — was\n * written into the file at fusion time and is read back here. Nothing is\n * restated on the JavaScript side, so nothing can drift out of step with the\n * Python side that built it.\n *\n * @example\n * ```typescript\n * const pipeline = await DetectClassify.create(\"/models/pipeline.onnx\");\n * const result = (await pipeline.predict(\"/images/flock.jpg\"))[0];\n * for (const detection of result) {\n * console.log(detection.name, detection.conf, detection.classification?.name);\n * }\n * ```\n */\nexport class DetectClassify extends VisionTask {\n private constructor(\n session: OrtSession,\n private readonly _spec: FusionSpec,\n private readonly _labels: readonly string[],\n private readonly _names: Readonly<Record<number, string>>,\n private readonly _classifierLabels: readonly string[],\n private readonly _classifierNames: Readonly<Record<number, string>>,\n private readonly _raiseOnEmpty: boolean,\n ) {\n super(session);\n }\n\n private _pipelineCache: LetterboxPipeline | null = null;\n\n /**\n * Run the model once on zero-filled inputs, paying one-time costs up front.\n *\n * Worth more here than on a single-stage task: a fused pipeline is two models\n * plus the bridge in one graph, so the first inference compiles shaders for\n * all of it. Calling this while a loading spinner is still up moves that cost\n * somewhere the user is already waiting.\n *\n * @param runs How many warm-up inferences to run. One is enough for WASM;\n * WebGPU sometimes settles on the second.\n */\n async warmup(runs: number = 1): Promise<void> {\n const [width, height] = this._spec.inputSize;\n for (let i = 0; i < runs; i++) {\n const feeds: Record<string, ort.Tensor> = {\n [INPUT_IMAGE]: toFloat32Tensor(zeroTensorData(width, height), [\n 1,\n 3,\n height,\n width,\n ]),\n };\n if (this._spec.needsSourceImage) {\n feeds[INPUT_SOURCE] = toFloat32Tensor(zeroTensorData(width, height), [\n 1,\n 3,\n height,\n width,\n ]);\n feeds[INPUT_SCALE] = toFloat32Tensor(new Float32Array([1]), [1]);\n feeds[INPUT_PAD] = toFloat32Tensor(new Float32Array([0, 0]), [2]);\n }\n await this._session.run(feeds);\n }\n }\n\n /**\n * The fused preprocessing pipeline, built on first use.\n *\n * Lazily, because constructing it allocates canvases: a pipeline built in an\n * environment without a canvas implementation stays constructible, and only\n * fails if it is actually asked to preprocess something.\n */\n private get _pipeline(): LetterboxPipeline {\n if (this._pipelineCache === null) {\n const [width, height] = this._spec.inputSize;\n this._pipelineCache = new LetterboxPipeline(width, height);\n }\n return this._pipelineCache;\n }\n\n /**\n * Load a fused pipeline and resolve both label spaces.\n *\n * @param model The fused `.onnx` — a URL, an `ArrayBuffer`, or bytes.\n * @param options Label overrides plus the usual session options.\n * @throws {@link FusionError} when the model carries no pipeline metadata,\n * i.e. it is a plain detector or classifier rather than something\n * `ort_vision_sdk.compose` produced.\n */\n static async create(\n model: ModelSource,\n options: DetectClassifyOptions = {},\n ): Promise<DetectClassify> {\n const session = await OrtSession.create(model, options);\n const spec = readFusionSpec(session.metadata);\n if (spec === null) {\n throw new FusionError(\n \"This model carries no fused-pipeline metadata, so DetectClassify cannot tell how to \" +\n \"drive it. Build one with ort_vision_sdk.compose.fuse_detect_classify, or load a \" +\n \"plain model with Detector/Classifier instead.\",\n );\n }\n\n const labels = resolveLabels(options.labels ?? spec.detectorNames ?? \"coco\");\n const classifierLabels = resolveLabels(options.classifierLabels ?? spec.classifierNames, {\n numClasses: classifierClasses(session) ?? undefined,\n });\n return new DetectClassify(\n session,\n spec,\n labels,\n indexNames(labels),\n classifierLabels,\n indexNames(classifierLabels),\n options.raiseOnEmpty ?? false,\n );\n }\n\n /** The pipeline configuration recorded in the model at fusion time. */\n get spec(): FusionSpec {\n return this._spec;\n }\n\n /** The `[width, height]` the detection stage runs at. */\n get inputSize(): readonly [number, number] {\n return this._spec.inputSize;\n }\n\n /** Detector class labels indexed by class id. */\n get labels(): readonly string[] {\n return this._labels;\n }\n\n /** Detector class id → class name (matches Ultralytics' `model.names`). */\n get names(): Readonly<Record<number, string>> {\n return this._names;\n }\n\n /** Classifier class labels indexed by class id. */\n get classifierLabels(): readonly string[] {\n return this._classifierLabels;\n }\n\n /** Classifier class id → class name. */\n get classifierNames(): Readonly<Record<number, string>> {\n return this._classifierNames;\n }\n\n /**\n * Alias for {@link predict} — call the pipeline like a torch `nn.Module`.\n *\n * Use as `pipeline.call(img)` since JavaScript class instances are not\n * callable; for direct invocation, prefer `pipeline.predict(img)`.\n */\n async call(\n image: ImageInput,\n options: DetectClassifyPredictOptions = {},\n ): Promise<DetectClassifyResults[]> {\n return this.predict(image, options);\n }\n\n /**\n * Run the pipeline on a single image.\n *\n * The returned envelope carries a {@link Speed} breakdown in `speed`. Its\n * `inference` figure covers detection *and* classification, since the\n * pipeline runs them as one graph and no boundary between them is observable\n * from outside.\n */\n async predict(\n image: ImageInput,\n options: DetectClassifyPredictOptions = {},\n ): Promise<DetectClassifyResults[]> {\n const timer = new SpeedTimer();\n const path = typeof image === \"string\" ? image : null;\n const original = await loadImage(image);\n timer.stage(\"load\");\n const { feeds, scale, padLeft, padTop } = this._preprocess(original);\n timer.stage(\"preprocess\");\n const outputs = await this._session.run(feeds);\n this._pipeline.release();\n timer.stage(\"inference\");\n\n const probsTensor = output(outputs, OUTPUT_PROBS);\n const boxes = floats(outputs, OUTPUT_BOXES);\n const scores = floats(outputs, OUTPUT_SCORES);\n const classes = integers(outputs, OUTPUT_CLASSES);\n const probs = probsTensor.data as Float32Array;\n const reported = integers(outputs, OUTPUT_NUM_DETECTIONS)[0] ?? 0;\n const rows = Math.min(reported, Math.floor(boxes.length / 4));\n const classCount = probsTensor.dims[probsTensor.dims.length - 1] ?? 0;\n\n const allowed = options.classes === undefined ? null : new Set(options.classes);\n const floor = options.confThreshold ?? 0;\n const detections: DetectionResult[] = [];\n for (let row = 0; row < rows; row++) {\n const classId = classes[row] ?? 0;\n const confidence = scores[row] ?? 0;\n if (confidence < floor || (allowed !== null && !allowed.has(classId))) continue;\n\n const bbox = this._toOriginal(boxes, row, { scale, padLeft, padTop, original });\n const cropped = crop(original, bbox);\n detections.push(\n detection(\n classId,\n this._names[classId] ?? `class_${classId}`,\n confidence,\n bbox,\n cropped,\n this._classify(\n probs.subarray(row * classCount, (row + 1) * classCount),\n cropped,\n options.topK,\n ),\n ),\n );\n }\n\n requireDetections(detections.length, {\n raiseOnEmpty: options.raiseOnEmpty ?? this._raiseOnEmpty,\n confThreshold: Math.max(floor, this._spec.confThreshold),\n classes: options.classes,\n path,\n });\n\n const origShape: readonly [number, number] = [original.height, original.width];\n timer.stage(\"postprocess\");\n return [\n new DetectClassifyResults(\n bulkBoxes(detections, origShape),\n detections,\n this._names,\n this._classifierNames,\n original,\n origShape,\n path,\n timer.speed(),\n ),\n ];\n }\n\n /**\n * Letterbox the image and build the graph's feeds.\n *\n * The detector input runs through {@link LetterboxPipeline}, which fuses the\n * resize, the padding and the HWC-to-CHW float conversion into one\n * `drawImage` plus one readback, and reuses its output buffer between frames.\n * That buffer goes straight to ONNX Runtime, so `_pipeline.release()` must not\n * be called until the run resolves.\n *\n * A pipeline fused with `cropSource: \"original\"` also takes the untouched\n * image as a second input, plus the scale and padding of the letterbox — that\n * is what lets the graph undo the letterbox transform internally and crop at\n * native resolution instead of from the downscaled copy. That one is **not**\n * letterboxed by definition, so it does not go through the fused path.\n */\n private _preprocess(image: RGBImage): {\n feeds: Record<string, ort.Tensor>;\n scale: number;\n padLeft: number;\n padTop: number;\n } {\n const [width, height] = this._spec.inputSize;\n const boxed = this._pipeline.run(image);\n const feeds: Record<string, ort.Tensor> = {\n [INPUT_IMAGE]: toFloat32Tensor(boxed.data, [1, 3, height, width]),\n };\n\n if (this._spec.needsSourceImage) {\n feeds[INPUT_SOURCE] = tensorOf(image);\n feeds[INPUT_SCALE] = toFloat32Tensor(new Float32Array([boxed.scale]), [1]);\n feeds[INPUT_PAD] = toFloat32Tensor(\n new Float32Array([boxed.padLeft, boxed.padTop]),\n [2],\n );\n }\n return { feeds, scale: boxed.scale, padLeft: boxed.padLeft, padTop: boxed.padTop };\n }\n\n /**\n * Map one letterboxed xyxy row back onto the original image.\n *\n * The graph always reports boxes in the detector's letterboxed pixel space,\n * whichever crop source it was fused with, so both sources agree here.\n */\n private _toOriginal(\n boxes: Float32Array,\n row: number,\n context: {\n scale: number;\n padLeft: number;\n padTop: number;\n original: RGBImage;\n },\n ): BoundingBox {\n const { scale, padLeft, padTop, original } = context;\n const at = (offset: number): number => boxes[row * 4 + offset] ?? 0;\n const clampX = (value: number): number => Math.min(Math.max(value, 0), original.width);\n const clampY = (value: number): number => Math.min(Math.max(value, 0), original.height);\n return new BoundingBox(\n clampX((at(0) - padLeft) / scale),\n clampY((at(1) - padTop) / scale),\n clampX((at(2) - padLeft) / scale),\n clampY((at(3) - padTop) / scale),\n );\n }\n\n /**\n * Turn one row of the classifier output into a result object.\n *\n * @param row The output row for this detection.\n * @param image The crop the row describes, carried so callers can display\n * what was classified.\n * @param k Optional truncation of the probability list.\n */\n private _classify(\n row: Float32Array,\n image: RGBImage,\n k: number | undefined,\n ): ClassificationResult {\n const scores = this._spec.applySoftmax ? softmax(row) : row;\n const { indices, values } = topK(scores, k ?? null);\n const probabilities: ClassProbability[] = [];\n for (let i = 0; i < indices.length; i++) {\n const classId = indices[i] ?? 0;\n const probability = values[i] ?? 0;\n const className = this._classifierLabels[classId] ?? `class_${classId}`;\n probabilities.push({\n classId,\n className,\n probability,\n cls: classId,\n name: className,\n conf: probability,\n });\n }\n const top = probabilities[0] ?? {\n classId: 0,\n className: \"class_0\",\n probability: 0,\n cls: 0,\n name: \"class_0\",\n conf: 0,\n };\n return {\n classId: top.classId,\n className: top.className,\n confidence: top.probability,\n cls: top.classId,\n name: top.className,\n conf: top.probability,\n image,\n probabilities,\n };\n }\n}\n\n/**\n * Read the classifier stage's class count off the `probs` output shape.\n *\n * @param session The loaded pipeline session.\n * @returns The class count, or `null` when the graph leaves that axis dynamic\n * or declares no `probs` output — in which case label resolution falls back\n * to whatever the fusion recorded.\n */\nfunction classifierClasses(session: OrtSession): number | null {\n const index = session.outputNames.indexOf(OUTPUT_PROBS);\n if (index < 0) return null;\n const shape = session.outputShapes[index];\n if (shape === undefined || shape.length === 0) return null;\n return shape[shape.length - 1] ?? null;\n}\n\n/**\n * Build a class id → name record from an ordered label list.\n *\n * @param labels Labels indexed by class id.\n * @returns The equivalent record.\n */\nfunction indexNames(labels: readonly string[]): Readonly<Record<number, string>> {\n const names: Record<number, string> = {};\n for (let i = 0; i < labels.length; i++) names[i] = labels[i] as string;\n return names;\n}\n\n/**\n * Fetch an output tensor by name.\n *\n * @param outputs The run's results.\n * @param name The output's name in the pipeline contract.\n * @returns The tensor.\n * @throws {@link FusionError} when the graph does not carry that output, which\n * means the file is not a pipeline this version can drive.\n */\nfunction output(outputs: Record<string, ort.Tensor>, name: string): ort.Tensor {\n const tensor = outputs[name];\n if (tensor === undefined) {\n throw new FusionError(`The fused pipeline is missing its '${name}' output.`);\n }\n return tensor;\n}\n\n/**\n * Fetch a float output by name.\n *\n * @param outputs The run's results.\n * @param name The output's name in the pipeline contract.\n * @returns The output's data.\n * @throws {@link FusionError} when the graph does not carry that output.\n */\nfunction floats(outputs: Record<string, ort.Tensor>, name: string): Float32Array {\n return output(outputs, name).data as Float32Array;\n}\n\n/**\n * Fetch an integer output by name, normalizing ORT's 64-bit representation.\n *\n * ONNX Runtime Web returns `int64` tensors as `BigInt64Array`, whose values do\n * not compare or index like numbers. Class ids and detection counts are always\n * small, so widening them to `number` here is lossless and keeps every caller\n * free of `BigInt` handling.\n *\n * @param outputs The run's results.\n * @param name The output's name in the pipeline contract.\n * @returns The output's values as plain numbers.\n * @throws {@link FusionError} when the graph does not carry that output.\n */\nfunction integers(outputs: Record<string, ort.Tensor>, name: string): number[] {\n const data = output(outputs, name).data as BigInt64Array | Int32Array | Float32Array;\n const values: number[] = [];\n for (let i = 0; i < data.length; i++) values.push(Number(data[i]));\n return values;\n}\n\n/**\n * Convert an image to the `[1, 3, H, W]` float32 tensor the graph expects.\n *\n * @param image The image to convert.\n * @returns The batched CHW tensor, scaled to `[0, 1]`.\n */\nfunction tensorOf(image: RGBImage): ort.Tensor {\n const chw = toCHW(toFloat32(image), image.width, image.height, 3);\n return toFloat32Tensor(chw, [1, 3, image.height, image.width]);\n}\n\n/**\n * Cut the box region out of the original image.\n *\n * @param image The source image.\n * @param bbox The box, in original-image pixel coordinates.\n * @returns The cropped region, or a zero-sized image for a box with no area.\n */\nfunction crop(image: RGBImage, bbox: BoundingBox): RGBImage {\n const [rawX1, rawY1, rawX2, rawY2] = bbox.asIntXyxy();\n const x1 = Math.max(0, rawX1);\n const y1 = Math.max(0, rawY1);\n const x2 = Math.min(image.width, rawX2);\n const y2 = Math.min(image.height, rawY2);\n if (x2 <= x1 || y2 <= y1) return new RGBImage(new Uint8Array(0), 0, 0);\n\n const width = x2 - x1;\n const height = y2 - y1;\n const out = new Uint8Array(width * height * 3);\n for (let row = 0; row < height; row++) {\n const offset = ((y1 + row) * image.width + x1) * 3;\n out.set(image.data.subarray(offset, offset + width * 3), row * width * 3);\n }\n return new RGBImage(out, width, height);\n}\n\n/**\n * Assemble one detection, filling the Ultralytics-style aliases.\n *\n * @param classId Detector class index.\n * @param className Detector class name.\n * @param confidence Detection score.\n * @param bbox Box in original-image coordinates.\n * @param croppedImage The region the box covers.\n * @param classification What the classification stage said about that region.\n * @returns The detection object.\n */\nfunction detection(\n classId: number,\n className: string,\n confidence: number,\n bbox: BoundingBox,\n croppedImage: RGBImage,\n classification: ClassificationResult,\n): DetectionResult {\n return {\n classId,\n className,\n confidence,\n bbox,\n cls: classId,\n name: className,\n conf: confidence,\n box: bbox,\n croppedImage,\n classification,\n };\n}\n\n/**\n * Assemble the bulk-array `Boxes` view from per-instance detections.\n *\n * @param detections The surviving detections.\n * @param origShape `[height, width]` of the original image.\n * @returns The bulk view, empty when nothing survived.\n */\nfunction bulkBoxes(\n detections: readonly DetectionResult[],\n origShape: readonly [number, number],\n): Boxes {\n const xyxy = new Float32Array(detections.length * 4);\n const cls = new Int32Array(detections.length);\n const conf = new Float32Array(detections.length);\n detections.forEach((entry, index) => {\n const [x1, y1, x2, y2] = entry.bbox.xyxy;\n xyxy[index * 4] = x1;\n xyxy[index * 4 + 1] = y1;\n xyxy[index * 4 + 2] = x2;\n xyxy[index * 4 + 3] = y2;\n cls[index] = entry.classId;\n conf[index] = entry.confidence;\n });\n return new Boxes(xyxy, cls, conf, origShape);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"detectClassify.js","names":[],"sources":["../../../src/vision/tasks/detectClassify.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Run a fused detect→classify pipeline in the browser.\n *\n * The file this loads was built by the Python SDK's `ort_vision_sdk.compose`,\n * and it already contains both models plus the crop-and-resize bridge between\n * them. That matters far more in a tab than on a server: two models mean two\n * `.onnx` downloads, two WASM/WebGPU session initializations, and a per-crop\n * round trip through JavaScript to slice, resize and restack the regions before\n * the second model can see them. A fused pipeline has one download, one session\n * and no round trip — the crops are produced and consumed inside the graph.\n */\n\nimport type * as ort from \"onnxruntime-web\";\n\nimport { FusionError } from \"../core/exceptions\";\nimport { type ModelSource, type OrtSessionOptions, OrtSession } from \"../core/session\";\nimport { SpeedTimer } from \"../core/timing\";\nimport {\n INPUT_IMAGE,\n INPUT_PAD,\n INPUT_SCALE,\n INPUT_SOURCE,\n OUTPUT_BOXES,\n OUTPUT_CLASSES,\n OUTPUT_NUM_DETECTIONS,\n OUTPUT_PROBS,\n OUTPUT_SCORES,\n type FusionSpec,\n readFusionSpec,\n} from \"../fusion\";\nimport { type ImageInput, loadImage } from \"../io/image\";\nimport { type LabelSpec, resolveLabels } from \"../labels\";\nimport { softmax, topK } from \"../postprocess/classification\";\nimport { toCHW, toFloat32, toFloat32Tensor } from \"../preprocess/image\";\nimport { LetterboxPipeline, zeroTensorData } from \"../preprocess/pipeline\";\nimport { Boxes, DetectClassifyResults } from \"../results\";\nimport {\n BoundingBox,\n RGBImage,\n type ClassProbability,\n type ClassificationResult,\n type DetectionResult,\n} from \"../types\";\nimport { VisionTask, requireDetections } from \"./base\";\n\nexport interface DetectClassifyOptions extends OrtSessionOptions {\n /**\n * Class label spec for the **detection** stage — see {@link resolveLabels}.\n * Defaults to the names recorded at fusion time, falling back to the COCO\n * 80-class preset when the fusion recorded none.\n */\n readonly labels?: LabelSpec;\n /**\n * Class label spec for the **classification** stage. Defaults to the recorded\n * names, falling back to generated `class_<id>` names.\n */\n readonly classifierLabels?: LabelSpec;\n /**\n * If `true`, a run that finds nothing throws {@link NoDetectionsError}\n * instead of returning an empty envelope. Default `false`, because looking\n * and finding nothing is a successful inference. Turn it on when an empty\n * result means the surrounding pipeline should stop rather than carry on with\n * zero rows. Can be overridden per `predict` call.\n */\n readonly raiseOnEmpty?: boolean;\n}\n\nexport interface DetectClassifyPredictOptions {\n /**\n * Drop detections scoring below this. The graph's own NMS threshold was fixed\n * at fusion time and cannot be lowered here — this only filters further.\n */\n readonly confThreshold?: number;\n /** If set, keep only detections whose detector `classId` is in this list. */\n readonly classes?: readonly number[];\n /** Truncate each detection's `classification.probabilities` to its top-k entries. */\n readonly topK?: number;\n /** Override the constructor's `raiseOnEmpty` setting for this call. */\n readonly raiseOnEmpty?: boolean;\n}\n\n/**\n * Detector and classifier running as a single ONNX model.\n *\n * Everything the pipeline needs to know about itself — the resolution to\n * letterbox to, whether it wants the full-resolution image as well, whether its\n * classifier output still needs a softmax, the class names of both stages — was\n * written into the file at fusion time and is read back here. Nothing is\n * restated on the JavaScript side, so nothing can drift out of step with the\n * Python side that built it.\n *\n * @example\n * ```typescript\n * const pipeline = await DetectClassify.create(\"/models/pipeline.onnx\");\n * const result = (await pipeline.predict(\"/images/flock.jpg\"))[0];\n * for (const detection of result) {\n * console.log(detection.name, detection.conf, detection.classification?.name);\n * }\n * ```\n */\nexport class DetectClassify extends VisionTask {\n private constructor(\n session: OrtSession,\n private readonly _spec: FusionSpec,\n private readonly _labels: readonly string[],\n private readonly _names: Readonly<Record<number, string>>,\n private readonly _classifierLabels: readonly string[],\n private readonly _classifierNames: Readonly<Record<number, string>>,\n private readonly _raiseOnEmpty: boolean,\n ) {\n super(session);\n }\n\n private _pipelineCache: LetterboxPipeline | null = null;\n\n /**\n * Run the model once on zero-filled inputs, paying one-time costs up front.\n *\n * Worth more here than on a single-stage task: a fused pipeline is two models\n * plus the bridge in one graph, so the first inference compiles shaders for\n * all of it. Calling this while a loading spinner is still up moves that cost\n * somewhere the user is already waiting.\n *\n * @param runs How many warm-up inferences to run. One is enough for WASM;\n * WebGPU sometimes settles on the second.\n */\n async warmup(runs: number = 1): Promise<void> {\n const [width, height] = this._spec.inputSize;\n for (let i = 0; i < runs; i++) {\n const feeds: Record<string, ort.Tensor> = {\n [INPUT_IMAGE]: toFloat32Tensor(zeroTensorData(width, height), [\n 1,\n 3,\n height,\n width,\n ]),\n };\n if (this._spec.needsSourceImage) {\n feeds[INPUT_SOURCE] = toFloat32Tensor(zeroTensorData(width, height), [\n 1,\n 3,\n height,\n width,\n ]);\n feeds[INPUT_SCALE] = toFloat32Tensor(new Float32Array([1]), [1]);\n feeds[INPUT_PAD] = toFloat32Tensor(new Float32Array([0, 0]), [2]);\n }\n await this._session.run(feeds);\n }\n }\n\n /**\n * The fused preprocessing pipeline, built on first use.\n *\n * Lazily, because constructing it allocates canvases: a pipeline built in an\n * environment without a canvas implementation stays constructible, and only\n * fails if it is actually asked to preprocess something.\n */\n private get _pipeline(): LetterboxPipeline {\n if (this._pipelineCache === null) {\n const [width, height] = this._spec.inputSize;\n this._pipelineCache = new LetterboxPipeline(width, height);\n }\n return this._pipelineCache;\n }\n\n /**\n * Load a fused pipeline and resolve both label spaces.\n *\n * @param model The fused `.onnx` — a URL, an `ArrayBuffer`, or bytes.\n * @param options Label overrides plus the usual session options.\n * @throws {@link FusionError} when the model carries no pipeline metadata,\n * i.e. it is a plain detector or classifier rather than something\n * `ort_vision_sdk.compose` produced.\n */\n static async create(\n model: ModelSource,\n options: DetectClassifyOptions = {},\n ): Promise<DetectClassify> {\n const session = await OrtSession.create(model, options);\n const spec = readFusionSpec(session.metadata);\n if (spec === null) {\n throw new FusionError(\n \"This model carries no fused-pipeline metadata, so DetectClassify cannot tell how to \" +\n \"drive it. Build one with ort_vision_sdk.compose.fuse_detect_classify, or load a \" +\n \"plain model with Detector/Classifier instead.\",\n );\n }\n\n const labels = resolveLabels(options.labels ?? spec.detectorNames ?? \"coco\");\n const classifierLabels = resolveLabels(options.classifierLabels ?? spec.classifierNames, {\n numClasses: classifierClasses(session) ?? undefined,\n });\n return new DetectClassify(\n session,\n spec,\n labels,\n indexNames(labels),\n classifierLabels,\n indexNames(classifierLabels),\n options.raiseOnEmpty ?? false,\n );\n }\n\n /** The pipeline configuration recorded in the model at fusion time. */\n get spec(): FusionSpec {\n return this._spec;\n }\n\n /** The `[width, height]` the detection stage runs at. */\n get inputSize(): readonly [number, number] {\n return this._spec.inputSize;\n }\n\n /** Detector class labels indexed by class id. */\n get labels(): readonly string[] {\n return this._labels;\n }\n\n /** Detector class id → class name (matches Ultralytics' `model.names`). */\n get names(): Readonly<Record<number, string>> {\n return this._names;\n }\n\n /** Classifier class labels indexed by class id. */\n get classifierLabels(): readonly string[] {\n return this._classifierLabels;\n }\n\n /** Classifier class id → class name. */\n get classifierNames(): Readonly<Record<number, string>> {\n return this._classifierNames;\n }\n\n /**\n * Alias for {@link predict} — call the pipeline like a torch `nn.Module`.\n *\n * Use as `pipeline.call(img)` since JavaScript class instances are not\n * callable; for direct invocation, prefer `pipeline.predict(img)`.\n */\n async call(\n image: ImageInput,\n options: DetectClassifyPredictOptions = {},\n ): Promise<DetectClassifyResults[]> {\n return this.predict(image, options);\n }\n\n /**\n * Run the pipeline on a single image.\n *\n * The returned envelope carries a {@link Speed} breakdown in `speed`. Its\n * `inference` figure covers detection *and* classification, since the\n * pipeline runs them as one graph and no boundary between them is observable\n * from outside.\n */\n async predict(\n image: ImageInput,\n options: DetectClassifyPredictOptions = {},\n ): Promise<DetectClassifyResults[]> {\n const timer = new SpeedTimer();\n const path = typeof image === \"string\" ? image : null;\n const original = await loadImage(image);\n timer.stage(\"load\");\n const { feeds, scale, padLeft, padTop } = this._preprocess(original);\n timer.stage(\"preprocess\");\n const outputs = await this._session.run(feeds);\n this._pipeline.release();\n timer.stage(\"inference\");\n\n const probsTensor = output(outputs, OUTPUT_PROBS);\n const boxes = floats(outputs, OUTPUT_BOXES);\n const scores = floats(outputs, OUTPUT_SCORES);\n const classes = integers(outputs, OUTPUT_CLASSES);\n const probs = probsTensor.data as Float32Array;\n const reported = integers(outputs, OUTPUT_NUM_DETECTIONS)[0] ?? 0;\n const rows = Math.min(reported, Math.floor(boxes.length / 4));\n const classCount = probsTensor.dims[probsTensor.dims.length - 1] ?? 0;\n\n const allowed = options.classes === undefined ? null : new Set(options.classes);\n const floor = options.confThreshold ?? 0;\n const detections: DetectionResult[] = [];\n for (let row = 0; row < rows; row++) {\n const classId = classes[row] ?? 0;\n const confidence = scores[row] ?? 0;\n if (confidence < floor || (allowed !== null && !allowed.has(classId))) continue;\n\n const bbox = this._toOriginal(boxes, row, { scale, padLeft, padTop, original });\n const cropped = crop(original, bbox);\n detections.push(\n detection(\n classId,\n this._names[classId] ?? `class_${classId}`,\n confidence,\n bbox,\n cropped,\n this._classify(\n probs.subarray(row * classCount, (row + 1) * classCount),\n cropped,\n options.topK,\n ),\n ),\n );\n }\n\n requireDetections(detections.length, {\n raiseOnEmpty: options.raiseOnEmpty ?? this._raiseOnEmpty,\n confThreshold: Math.max(floor, this._spec.confThreshold),\n classes: options.classes,\n path,\n });\n\n const origShape: readonly [number, number] = [original.height, original.width];\n timer.stage(\"postprocess\");\n return [\n new DetectClassifyResults(\n bulkBoxes(detections, origShape),\n detections,\n this._names,\n this._classifierNames,\n original,\n origShape,\n path,\n timer.speed(),\n ),\n ];\n }\n\n /**\n * Letterbox the image and build the graph's feeds.\n *\n * The detector input runs through {@link LetterboxPipeline}, which fuses the\n * resize, the padding and the HWC-to-CHW float conversion into one\n * `drawImage` plus one readback, and reuses its output buffer between frames.\n * That buffer goes straight to ONNX Runtime, so `_pipeline.release()` must not\n * be called until the run resolves.\n *\n * A pipeline fused with `cropSource: \"original\"` also takes the untouched\n * image as a second input, plus the scale and padding of the letterbox — that\n * is what lets the graph undo the letterbox transform internally and crop at\n * native resolution instead of from the downscaled copy. That one is **not**\n * letterboxed by definition, so it does not go through the fused path.\n */\n private _preprocess(image: RGBImage): {\n feeds: Record<string, ort.Tensor>;\n scale: number;\n padLeft: number;\n padTop: number;\n } {\n const [width, height] = this._spec.inputSize;\n const boxed = this._pipeline.run(image);\n const feeds: Record<string, ort.Tensor> = {\n [INPUT_IMAGE]: toFloat32Tensor(boxed.data, [1, 3, height, width]),\n };\n\n if (this._spec.needsSourceImage) {\n feeds[INPUT_SOURCE] = tensorOf(image);\n feeds[INPUT_SCALE] = toFloat32Tensor(new Float32Array([boxed.scale]), [1]);\n feeds[INPUT_PAD] = toFloat32Tensor(\n new Float32Array([boxed.padLeft, boxed.padTop]),\n [2],\n );\n }\n return { feeds, scale: boxed.scale, padLeft: boxed.padLeft, padTop: boxed.padTop };\n }\n\n /**\n * Map one letterboxed xyxy row back onto the original image.\n *\n * The graph always reports boxes in the detector's letterboxed pixel space,\n * whichever crop source it was fused with, so both sources agree here.\n */\n private _toOriginal(\n boxes: Float32Array,\n row: number,\n context: {\n scale: number;\n padLeft: number;\n padTop: number;\n original: RGBImage;\n },\n ): BoundingBox {\n const { scale, padLeft, padTop, original } = context;\n const at = (offset: number): number => boxes[row * 4 + offset] ?? 0;\n const clampX = (value: number): number => Math.min(Math.max(value, 0), original.width);\n const clampY = (value: number): number => Math.min(Math.max(value, 0), original.height);\n return new BoundingBox(\n clampX((at(0) - padLeft) / scale),\n clampY((at(1) - padTop) / scale),\n clampX((at(2) - padLeft) / scale),\n clampY((at(3) - padTop) / scale),\n );\n }\n\n /**\n * Turn one row of the classifier output into a result object.\n *\n * @param row The output row for this detection.\n * @param image The crop the row describes, carried so callers can display\n * what was classified.\n * @param k Optional truncation of the probability list.\n */\n private _classify(\n row: Float32Array,\n image: RGBImage,\n k: number | undefined,\n ): ClassificationResult {\n const scores = this._spec.applySoftmax ? softmax(row) : row;\n const { indices, values } = topK(scores, k ?? null);\n const probabilities: ClassProbability[] = [];\n for (let i = 0; i < indices.length; i++) {\n const classId = indices[i] ?? 0;\n const probability = values[i] ?? 0;\n const className = this._classifierLabels[classId] ?? `class_${classId}`;\n probabilities.push({\n classId,\n className,\n probability,\n cls: classId,\n name: className,\n conf: probability,\n });\n }\n const top = probabilities[0] ?? {\n classId: 0,\n className: \"class_0\",\n probability: 0,\n cls: 0,\n name: \"class_0\",\n conf: 0,\n };\n return {\n classId: top.classId,\n className: top.className,\n confidence: top.probability,\n cls: top.classId,\n name: top.className,\n conf: top.probability,\n image,\n probabilities,\n };\n }\n}\n\n/**\n * Read the classifier stage's class count off the `probs` output shape.\n *\n * @param session The loaded pipeline session.\n * @returns The class count, or `null` when the graph leaves that axis dynamic\n * or declares no `probs` output — in which case label resolution falls back\n * to whatever the fusion recorded.\n */\nfunction classifierClasses(session: OrtSession): number | null {\n const index = session.outputNames.indexOf(OUTPUT_PROBS);\n if (index < 0) return null;\n const shape = session.outputShapes[index];\n if (shape === undefined || shape.length === 0) return null;\n return shape[shape.length - 1] ?? null;\n}\n\n/**\n * Build a class id → name record from an ordered label list.\n *\n * @param labels Labels indexed by class id.\n * @returns The equivalent record.\n */\nfunction indexNames(labels: readonly string[]): Readonly<Record<number, string>> {\n const names: Record<number, string> = {};\n for (let i = 0; i < labels.length; i++) names[i] = labels[i] as string;\n return names;\n}\n\n/**\n * Fetch an output tensor by name.\n *\n * @param outputs The run's results.\n * @param name The output's name in the pipeline contract.\n * @returns The tensor.\n * @throws {@link FusionError} when the graph does not carry that output, which\n * means the file is not a pipeline this version can drive.\n */\nfunction output(outputs: Record<string, ort.Tensor>, name: string): ort.Tensor {\n const tensor = outputs[name];\n if (tensor === undefined) {\n throw new FusionError(`The fused pipeline is missing its '${name}' output.`);\n }\n return tensor;\n}\n\n/**\n * Fetch a float output by name.\n *\n * @param outputs The run's results.\n * @param name The output's name in the pipeline contract.\n * @returns The output's data.\n * @throws {@link FusionError} when the graph does not carry that output.\n */\nfunction floats(outputs: Record<string, ort.Tensor>, name: string): Float32Array {\n return output(outputs, name).data as Float32Array;\n}\n\n/**\n * Fetch an integer output by name, normalizing ORT's 64-bit representation.\n *\n * ONNX Runtime Web returns `int64` tensors as `BigInt64Array`, whose values do\n * not compare or index like numbers. Class ids and detection counts are always\n * small, so widening them to `number` here is lossless and keeps every caller\n * free of `BigInt` handling.\n *\n * @param outputs The run's results.\n * @param name The output's name in the pipeline contract.\n * @returns The output's values as plain numbers.\n * @throws {@link FusionError} when the graph does not carry that output.\n */\nfunction integers(outputs: Record<string, ort.Tensor>, name: string): number[] {\n const data = output(outputs, name).data as BigInt64Array | Int32Array | Float32Array;\n const values: number[] = [];\n for (let i = 0; i < data.length; i++) values.push(Number(data[i]));\n return values;\n}\n\n/**\n * Convert an image to the `[1, 3, H, W]` float32 tensor the graph expects.\n *\n * @param image The image to convert.\n * @returns The batched CHW tensor, scaled to `[0, 1]`.\n */\nfunction tensorOf(image: RGBImage): ort.Tensor {\n const chw = toCHW(toFloat32(image), image.width, image.height, 3);\n return toFloat32Tensor(chw, [1, 3, image.height, image.width]);\n}\n\n/**\n * Cut the box region out of the original image.\n *\n * @param image The source image.\n * @param bbox The box, in original-image pixel coordinates.\n * @returns The cropped region, or a zero-sized image for a box with no area.\n */\nfunction crop(image: RGBImage, bbox: BoundingBox): RGBImage {\n const [rawX1, rawY1, rawX2, rawY2] = bbox.asIntXyxy();\n const x1 = Math.max(0, rawX1);\n const y1 = Math.max(0, rawY1);\n const x2 = Math.min(image.width, rawX2);\n const y2 = Math.min(image.height, rawY2);\n if (x2 <= x1 || y2 <= y1) return new RGBImage(new Uint8Array(0), 0, 0);\n\n const width = x2 - x1;\n const height = y2 - y1;\n const out = new Uint8Array(width * height * 3);\n for (let row = 0; row < height; row++) {\n const offset = ((y1 + row) * image.width + x1) * 3;\n out.set(image.data.subarray(offset, offset + width * 3), row * width * 3);\n }\n return new RGBImage(out, width, height);\n}\n\n/**\n * Assemble one detection, filling the Ultralytics-style aliases.\n *\n * @param classId Detector class index.\n * @param className Detector class name.\n * @param confidence Detection score.\n * @param bbox Box in original-image coordinates.\n * @param croppedImage The region the box covers.\n * @param classification What the classification stage said about that region.\n * @returns The detection object.\n */\nfunction detection(\n classId: number,\n className: string,\n confidence: number,\n bbox: BoundingBox,\n croppedImage: RGBImage,\n classification: ClassificationResult,\n): DetectionResult {\n return {\n classId,\n className,\n confidence,\n bbox,\n cls: classId,\n name: className,\n conf: confidence,\n box: bbox,\n croppedImage,\n classification,\n };\n}\n\n/**\n * Assemble the bulk-array `Boxes` view from per-instance detections.\n *\n * @param detections The surviving detections.\n * @param origShape `[height, width]` of the original image.\n * @returns The bulk view, empty when nothing survived.\n */\nfunction bulkBoxes(\n detections: readonly DetectionResult[],\n origShape: readonly [number, number],\n): Boxes {\n const xyxy = new Float32Array(detections.length * 4);\n const cls = new Int32Array(detections.length);\n const conf = new Float32Array(detections.length);\n detections.forEach((entry, index) => {\n const [x1, y1, x2, y2] = entry.bbox.xyxy;\n xyxy[index * 4] = x1;\n xyxy[index * 4 + 1] = y1;\n xyxy[index * 4 + 2] = x2;\n xyxy[index * 4 + 3] = y2;\n cls[index] = entry.classId;\n conf[index] = entry.confidence;\n });\n return new Boxes(xyxy, cls, conf, origShape);\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-camera-stream.cjs","names":[],"sources":["../../src/vision/use-camera-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState, type RefObject } from \"react\";\n\nimport {\n classifyMediaError,\n missingCaptureApiError,\n type MediaAccessError,\n} from \"@/audio/media-access\";\n\n/** Lifecycle status of the camera stream. */\nexport type CameraStreamStatus = \"idle\" | \"loading\" | \"ready\" | \"error\";\n\n/** Classified reason a camera stream could not be acquired. */\nexport type CameraStreamErrorKind =\n \"unsupported\" | \"permission-denied\" | \"no-camera\" | \"in-use\" | \"insecure\" | \"unknown\";\n\n/** A classified camera error with a human-readable, English message. */\nexport interface CameraStreamError {\n kind: CameraStreamErrorKind;\n message: string;\n}\n\n/** Options for {@link useCameraStream}. */\nexport interface UseCameraStreamOptions {\n /**\n * Constraints passed to `getUserMedia`. Defaults to the rear\n * (\"environment\") camera at Full-HD ideal resolution with audio off.\n * Read when the stream (re)starts — change it and call `retry()` to apply.\n */\n constraints?: MediaStreamConstraints;\n /**\n * Hold off acquiring the camera until this is `true`. Default `true`.\n *\n * The point is to be able to *not* prompt. A permission prompt costs the user a\n * decision and, if they refuse, costs the app the feature permanently — so a\n * surface that already knows it cannot do its job (a barcode scanner in a browser\n * with no decoder) must not open the camera to then say so. Flipping this to `false`\n * also releases a stream that is already live.\n */\n enabled?: boolean;\n}\n\n/** Value returned by {@link useCameraStream}. */\nexport interface UseCameraStreamApi {\n /** Current lifecycle status. */\n status: CameraStreamStatus;\n /** The classified error, or `null` while not in the `error` status. */\n error: CameraStreamError | null;\n /** Attach to a `<video ref={…} />`. The stream is wired to it once ready. */\n videoRef: RefObject<HTMLVideoElement | null>;\n /**\n * The live stream, or `null`.\n *\n * Exposed for the things that need the **track** rather than the picture — the LED\n * torch (`useTorch`), the real frame size from `getSettings()`, recording it with\n * `useVideoRecorder`. Do not stop it yourself: the hook owns its lifetime and\n * releases it on unmount and on `retry()`.\n */\n stream: MediaStream | null;\n /** Manually re-attempt after an error (e.g. the user changed permissions). */\n retry: () => void;\n}\n\n/** Rear-camera Full-HD defaults used when no `constraints` are supplied. */\nconst DEFAULT_CONSTRAINTS: MediaStreamConstraints = {\n video: {\n facingMode: { ideal: \"environment\" },\n width: { ideal: 1920 },\n height: { ideal: 1080 },\n },\n audio: false,\n};\n\n/**\n * Re-label a {@link MediaAccessError} as a {@link CameraStreamError}.\n *\n * The classification itself lives in one place for the whole SDK\n * (`classifyMediaError`), so a microphone and a camera failure are never explained\n * two different ways. Only the name of one kind differs: this surface shipped\n * `\"no-camera\"` where the shared taxonomy says `\"not-found\"`, and renaming a\n * published union member would break every consumer switching on it.\n *\n * @param error - The shared classification.\n * @returns The same error under this module's kind names.\n */\nfunction toCameraError({ kind, message }: MediaAccessError): CameraStreamError {\n return { kind: kind === \"not-found\" ? \"no-camera\" : kind, message };\n}\n\n/**\n * Map an unknown `getUserMedia` failure into a {@link CameraStreamError}. Secure-context\n * and environment checks run first (they are the reason `getUserMedia` is missing or\n * rejects), then the `DOMException.name` is mapped to a stable `kind`.\n */\nfunction classifyError(err: unknown): CameraStreamError {\n return toCameraError(classifyMediaError(err, \"camera\"));\n}\n\n/**\n * Acquire a `MediaStream` via `getUserMedia`, attach it to a `<video>` element,\n * and expose status/error so the page can render permission and error states.\n * The stream is automatically released on unmount or retry.\n *\n * Defaults to the rear (\"environment\") camera; desktops fall back to whatever\n * single camera they expose. Pass `options.constraints` to override, or\n * `options.enabled: false` to render the surface without prompting for the camera\n * at all.\n *\n * Implementation notes:\n * - Cleanup detaches the stream from a *snapshotted* video node, so it releases\n * the same element it attached to even if the page remounts the `<video>`.\n * - When `getUserMedia` is missing, an insecure context is the usual cause, so\n * the hook prefers that (actionable) error; otherwise it reports `unsupported`.\n * - `video.play()` rejections are swallowed: autoplay may be blocked, but the\n * user gesture that opened the camera usually counts and playback resumes on\n * the next interaction.\n *\n * @param options - optional configuration (see {@link UseCameraStreamOptions}).\n * @returns The stream status, classified error, a `videoRef` to attach, the live\n * `stream` for whatever needs the track itself, and a `retry()` to re-attempt\n * acquisition.\n */\nexport function useCameraStream(options: UseCameraStreamOptions = {}): UseCameraStreamApi {\n const [status, setStatus] = useState<CameraStreamStatus>(\"loading\");\n const [error, setError] = useState<CameraStreamError | null>(null);\n const [live, setLive] = useState<MediaStream | null>(null);\n const [retryToken, setRetryToken] = useState(0);\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const constraintsRef = useRef<MediaStreamConstraints>(\n options.constraints ?? DEFAULT_CONSTRAINTS,\n );\n useEffect(() => {\n constraintsRef.current = options.constraints ?? DEFAULT_CONSTRAINTS;\n }, [options.constraints]);\n\n const enabled = options.enabled ?? true;\n\n useEffect(() => {\n let cancelled = false;\n let stream: MediaStream | null = null;\n let attachedVideo: HTMLVideoElement | null = null;\n\n async function start(): Promise<void> {\n if (!enabled) {\n setStatus(\"idle\");\n setError(null);\n return;\n }\n setStatus(\"loading\");\n setError(null);\n\n if (\n typeof navigator === \"undefined\" ||\n !navigator.mediaDevices ||\n typeof navigator.mediaDevices.getUserMedia !== \"function\"\n ) {\n if (!cancelled) {\n setError(toCameraError(missingCaptureApiError(\"camera\")));\n setStatus(\"error\");\n }\n return;\n }\n\n try {\n stream = await navigator.mediaDevices.getUserMedia(constraintsRef.current);\n if (cancelled) {\n stream.getTracks().forEach((track) => track.stop());\n return;\n }\n const video = videoRef.current;\n if (!video) {\n stream.getTracks().forEach((track) => track.stop());\n return;\n }\n attachedVideo = video;\n video.srcObject = stream;\n setLive(stream);\n await video.play().catch(() => undefined);\n if (!cancelled) setStatus(\"ready\");\n } catch (err) {\n if (!cancelled) {\n setError(classifyError(err));\n setStatus(\"error\");\n }\n }\n }\n\n void start();\n\n return () => {\n cancelled = true;\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n }\n if (attachedVideo) {\n attachedVideo.srcObject = null;\n }\n setLive(null);\n };\n }, [retryToken, enabled]);\n\n return {\n status,\n error,\n videoRef,\n stream: live,\n retry: () => setRetryToken((n) => n + 1),\n };\n}\n"],"mappings":"oEA+DA,IAAM,EAA8C,CAChD,MAAO,CACH,WAAY,CAAE,MAAO,aAAc,EACnC,MAAO,CAAE,MAAO,IAAK,EACrB,OAAQ,CAAE,MAAO,IAAK,CAC1B,EACA,MAAO,EACX,EAcA,SAAS,EAAc,CAAE,OAAM,WAAgD,CAC3E,MAAO,CAAE,KAAM,IAAS,YAAc,YAAc,EAAM,SAAQ,CACtE,CAOA,SAAS,EAAc,EAAiC,CACpD,OAAO,EAAc,EAAA,mBAAmB,EAAK,QAAQ,CAAC,CAC1D,CA0BA,SAAgB,EAAgB,EAAkC,CAAC,EAAuB,CACtF,GAAM,CAAC,EAAQ,IAAA,EAAA,EAAA,SAAA,CAA0C,SAAS,EAC5D,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAA+C,IAAI,EAC3D,CAAC,EAAM,IAAA,EAAA,EAAA,SAAA,CAAwC,IAAI,EACnD,CAAC,EAAY,IAAA,EAAA,EAAA,SAAA,CAA0B,CAAC,EACxC,GAAA,EAAA,EAAA,OAAA,CAA2C,IAAI,EAC/C,GAAA,EAAA,EAAA,OAAA,CACF,EAAQ,aAAe,CAC3B,GACA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAe,QAAU,EAAQ,aAAe,CACpD,EAAG,CAAC,EAAQ,WAAW,CAAC,EAExB,IAAM,EAAU,EAAQ,SAAW,GAkEnC,OAhEA,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAI,EAAY,GACZ,EAA6B,KAC7B,EAAyC,KAE7C,eAAe,GAAuB,CAClC,GAAI,CAAC,EAAS,CACV,EAAU,MAAM,EAChB,EAAS,IAAI,EACb,MACJ,CAIA,GAHA,EAAU,SAAS,EACnB,EAAS,IAAI,EAGT,OAAO,UAAc,KACrB,CAAC,UAAU,cACX,OAAO,UAAU,aAAa,cAAiB,WACjD,CACO,IACD,EAAS,EAAc,EAAA,uBAAuB,QAAQ,CAAC,CAAC,EACxD,EAAU,OAAO,GAErB,MACJ,CAEA,GAAI,CAEA,GADA,EAAS,MAAM,UAAU,aAAa,aAAa,EAAe,OAAO,EACrE,EAAW,CACX,EAAO,UAAU,CAAC,CAAC,QAAS,GAAU,EAAM,KAAK,CAAC,EAClD,MACJ,CACA,IAAM,EAAQ,EAAS,QACvB,GAAI,CAAC,EAAO,CACR,EAAO,UAAU,CAAC,CAAC,QAAS,GAAU,EAAM,KAAK,CAAC,EAClD,MACJ,CACA,EAAgB,EAChB,EAAM,UAAY,EAClB,EAAQ,CAAM,EACd,MAAM,EAAM,KAAK,CAAC,CAAC,UAAY,IAAA,EAAS,EACnC,GAAW,EAAU,OAAO,CACrC,OAAS,EAAK,CACL,IACD,EAAS,EAAc,CAAG,CAAC,EAC3B,EAAU,OAAO,EAEzB,CACJ,CAIA,OAFA,EAAW,MAEE,CACT,EAAY,GACR,GACA,EAAO,UAAU,CAAC,CAAC,QAAS,GAAU,EAAM,KAAK,CAAC,EAElD,IACA,EAAc,UAAY,MAE9B,EAAQ,IAAI,CAChB,CACJ,EAAG,CAAC,EAAY,CAAO,CAAC,EAEjB,CACH,SACA,QACA,WACA,OAAQ,EACR,UAAa,EAAe,GAAM,EAAI,CAAC,CAC3C,CACJ"}
|
|
1
|
+
{"version":3,"file":"use-camera-stream.cjs","names":[],"sources":["../../src/vision/use-camera-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState, type RefObject } from \"react\";\n\nimport {\n classifyMediaError,\n missingCaptureApiError,\n type MediaAccessError,\n} from \"@/audio/media-access\";\n\n/** Lifecycle status of the camera stream. */\nexport type CameraStreamStatus = \"idle\" | \"loading\" | \"ready\" | \"error\";\n\n/** Classified reason a camera stream could not be acquired. */\nexport type CameraStreamErrorKind =\n \"unsupported\" | \"permission-denied\" | \"no-camera\" | \"in-use\" | \"insecure\" | \"unknown\";\n\n/** A classified camera error with a human-readable, English message. */\nexport interface CameraStreamError {\n kind: CameraStreamErrorKind;\n message: string;\n}\n\n/** Options for {@link useCameraStream}. */\nexport interface UseCameraStreamOptions {\n /**\n * Constraints passed to `getUserMedia`. Defaults to the rear\n * (\"environment\") camera at Full-HD ideal resolution with audio off.\n * Read when the stream (re)starts — change it and call `retry()` to apply.\n */\n constraints?: MediaStreamConstraints;\n /**\n * Hold off acquiring the camera until this is `true`. Default `true`.\n *\n * The point is to be able to *not* prompt. A permission prompt costs the user a\n * decision and, if they refuse, costs the app the feature permanently — so a\n * surface that already knows it cannot do its job (a barcode scanner in a browser\n * with no decoder) must not open the camera to then say so. Flipping this to `false`\n * also releases a stream that is already live.\n */\n enabled?: boolean;\n}\n\n/** Value returned by {@link useCameraStream}. */\nexport interface UseCameraStreamApi {\n /** Current lifecycle status. */\n status: CameraStreamStatus;\n /** The classified error, or `null` while not in the `error` status. */\n error: CameraStreamError | null;\n /** Attach to a `<video ref={…} />`. The stream is wired to it once ready. */\n videoRef: RefObject<HTMLVideoElement | null>;\n /**\n * The live stream, or `null`.\n *\n * Exposed for the things that need the **track** rather than the picture — the LED\n * torch (`useTorch`), the real frame size from `getSettings()`, recording it with\n * `useVideoRecorder`. Do not stop it yourself: the hook owns its lifetime and\n * releases it on unmount and on `retry()`.\n */\n stream: MediaStream | null;\n /** Manually re-attempt after an error (e.g. the user changed permissions). */\n retry: () => void;\n}\n\n/** Rear-camera Full-HD defaults used when no `constraints` are supplied. */\nconst DEFAULT_CONSTRAINTS: MediaStreamConstraints = {\n video: {\n facingMode: { ideal: \"environment\" },\n width: { ideal: 1920 },\n height: { ideal: 1080 },\n },\n audio: false,\n};\n\n/**\n * Re-label a {@link MediaAccessError} as a {@link CameraStreamError}.\n *\n * The classification itself lives in one place for the whole SDK\n * (`classifyMediaError`), so a microphone and a camera failure are never explained\n * two different ways. Only the name of one kind differs: this surface shipped\n * `\"no-camera\"` where the shared taxonomy says `\"not-found\"`, and renaming a\n * published union member would break every consumer switching on it.\n *\n * @param error - The shared classification.\n * @returns The same error under this module's kind names.\n */\nfunction toCameraError({ kind, message }: MediaAccessError): CameraStreamError {\n return { kind: kind === \"not-found\" ? \"no-camera\" : kind, message };\n}\n\n/**\n * Map an unknown `getUserMedia` failure into a {@link CameraStreamError}. Secure-context\n * and environment checks run first (they are the reason `getUserMedia` is missing or\n * rejects), then the `DOMException.name` is mapped to a stable `kind`.\n */\nfunction classifyError(err: unknown): CameraStreamError {\n return toCameraError(classifyMediaError(err, \"camera\"));\n}\n\n/**\n * Acquire a `MediaStream` via `getUserMedia`, attach it to a `<video>` element,\n * and expose status/error so the page can render permission and error states.\n * The stream is automatically released on unmount or retry.\n *\n * Defaults to the rear (\"environment\") camera; desktops fall back to whatever\n * single camera they expose. Pass `options.constraints` to override, or\n * `options.enabled: false` to render the surface without prompting for the camera\n * at all.\n *\n * Implementation notes:\n * - Cleanup detaches the stream from a *snapshotted* video node, so it releases\n * the same element it attached to even if the page remounts the `<video>`.\n * - When `getUserMedia` is missing, an insecure context is the usual cause, so\n * the hook prefers that (actionable) error; otherwise it reports `unsupported`.\n * - `video.play()` rejections are swallowed: autoplay may be blocked, but the\n * user gesture that opened the camera usually counts and playback resumes on\n * the next interaction.\n *\n * @param options - optional configuration (see {@link UseCameraStreamOptions}).\n * @returns The stream status, classified error, a `videoRef` to attach, the live\n * `stream` for whatever needs the track itself, and a `retry()` to re-attempt\n * acquisition.\n */\nexport function useCameraStream(options: UseCameraStreamOptions = {}): UseCameraStreamApi {\n const [status, setStatus] = useState<CameraStreamStatus>(\"loading\");\n const [error, setError] = useState<CameraStreamError | null>(null);\n const [live, setLive] = useState<MediaStream | null>(null);\n const [retryToken, setRetryToken] = useState(0);\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const constraintsRef = useRef<MediaStreamConstraints>(\n options.constraints ?? DEFAULT_CONSTRAINTS,\n );\n useEffect(() => {\n constraintsRef.current = options.constraints ?? DEFAULT_CONSTRAINTS;\n }, [options.constraints]);\n\n const enabled = options.enabled ?? true;\n\n useEffect(() => {\n let cancelled = false;\n let stream: MediaStream | null = null;\n let attachedVideo: HTMLVideoElement | null = null;\n\n async function start(): Promise<void> {\n if (!enabled) {\n setStatus(\"idle\");\n setError(null);\n return;\n }\n setStatus(\"loading\");\n setError(null);\n\n if (\n typeof navigator === \"undefined\" ||\n !navigator.mediaDevices ||\n typeof navigator.mediaDevices.getUserMedia !== \"function\"\n ) {\n if (!cancelled) {\n setError(toCameraError(missingCaptureApiError(\"camera\")));\n setStatus(\"error\");\n }\n return;\n }\n\n try {\n stream = await navigator.mediaDevices.getUserMedia(constraintsRef.current);\n if (cancelled) {\n stream.getTracks().forEach((track) => track.stop());\n return;\n }\n const video = videoRef.current;\n if (!video) {\n stream.getTracks().forEach((track) => track.stop());\n return;\n }\n attachedVideo = video;\n video.srcObject = stream;\n setLive(stream);\n await video.play().catch(() => undefined);\n if (!cancelled) setStatus(\"ready\");\n } catch (err) {\n if (!cancelled) {\n setError(classifyError(err));\n setStatus(\"error\");\n }\n }\n }\n\n void start();\n\n return () => {\n cancelled = true;\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n }\n if (attachedVideo) {\n attachedVideo.srcObject = null;\n }\n setLive(null);\n };\n }, [retryToken, enabled]);\n\n return {\n status,\n error,\n videoRef,\n stream: live,\n retry: () => setRetryToken((n) => n + 1),\n };\n}\n"],"mappings":"oEA+DA,IAAM,EAA8C,CAChD,MAAO,CACH,WAAY,CAAE,MAAO,aAAc,EACnC,MAAO,CAAE,MAAO,IAAK,EACrB,OAAQ,CAAE,MAAO,IAAK,CAC1B,EACA,MAAO,EACX,EAcA,SAAS,EAAc,CAAE,OAAM,WAAgD,CAC3E,MAAO,CAAE,KAAM,IAAS,YAAc,YAAc,EAAM,SAAQ,CACtE,CAOA,SAAS,EAAc,EAAiC,CACpD,OAAO,EAAc,EAAA,mBAAmB,EAAK,QAAQ,CAAC,CAC1D,CA0BA,SAAgB,EAAgB,EAAkC,CAAC,EAAuB,CACtF,GAAM,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAA6B,SAAS,EAC5D,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAmC,IAAI,EAC3D,CAAC,EAAM,IAAA,EAAW,EAAA,SAAA,CAA6B,IAAI,EACnD,CAAC,EAAY,IAAA,EAAiB,EAAA,SAAA,CAAS,CAAC,EACxC,GAAA,EAAW,EAAA,OAAA,CAAgC,IAAI,EAC/C,GAAA,EAAiB,EAAA,OAAA,CACnB,EAAQ,aAAe,CAC3B,GACA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAe,QAAU,EAAQ,aAAe,CACpD,EAAG,CAAC,EAAQ,WAAW,CAAC,EAExB,IAAM,EAAU,EAAQ,SAAW,GAkEnC,OAhEA,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAI,EAAY,GACZ,EAA6B,KAC7B,EAAyC,KAE7C,eAAe,GAAuB,CAClC,GAAI,CAAC,EAAS,CACV,EAAU,MAAM,EAChB,EAAS,IAAI,EACb,MACJ,CAIA,GAHA,EAAU,SAAS,EACnB,EAAS,IAAI,EAGT,OAAO,UAAc,KACrB,CAAC,UAAU,cACX,OAAO,UAAU,aAAa,cAAiB,WACjD,CACO,IACD,EAAS,EAAc,EAAA,uBAAuB,QAAQ,CAAC,CAAC,EACxD,EAAU,OAAO,GAErB,MACJ,CAEA,GAAI,CAEA,GADA,EAAS,MAAM,UAAU,aAAa,aAAa,EAAe,OAAO,EACrE,EAAW,CACX,EAAO,UAAU,CAAC,CAAC,QAAS,GAAU,EAAM,KAAK,CAAC,EAClD,MACJ,CACA,IAAM,EAAQ,EAAS,QACvB,GAAI,CAAC,EAAO,CACR,EAAO,UAAU,CAAC,CAAC,QAAS,GAAU,EAAM,KAAK,CAAC,EAClD,MACJ,CACA,EAAgB,EAChB,EAAM,UAAY,EAClB,EAAQ,CAAM,EACd,MAAM,EAAM,KAAK,CAAC,CAAC,UAAY,IAAA,EAAS,EACnC,GAAW,EAAU,OAAO,CACrC,OAAS,EAAK,CACL,IACD,EAAS,EAAc,CAAG,CAAC,EAC3B,EAAU,OAAO,EAEzB,CACJ,CAIA,OAFA,EAAW,MAEE,CACT,EAAY,GACR,GACA,EAAO,UAAU,CAAC,CAAC,QAAS,GAAU,EAAM,KAAK,CAAC,EAElD,IACA,EAAc,UAAY,MAE9B,EAAQ,IAAI,CAChB,CACJ,EAAG,CAAC,EAAY,CAAO,CAAC,EAEjB,CACH,SACA,QACA,WACA,OAAQ,EACR,UAAa,EAAe,GAAM,EAAI,CAAC,CAC3C,CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-live-luminance.cjs","names":[],"sources":["../../src/vision/use-live-luminance.ts"],"sourcesContent":["import { useEffect, useRef, useState, type RefObject } from \"react\";\nimport { computeImageLuminance } from \"./luminance\";\n\n/** Options for {@link useLiveLuminance}. */\nexport interface UseLiveLuminanceOptions {\n /** When `false` the loop is paused (e.g. while a capture is in flight). Default: `true`. */\n enabled?: boolean;\n /** Throttle measurements in milliseconds. Default: `160` (~6 fps), plenty for UX. */\n intervalMs?: number;\n}\n\n/**\n * Sample mean luminance from a `<video>` source on a `requestAnimationFrame`\n * loop and expose the rolling value. Sampling is throttled by `intervalMs` and\n * paused whenever `enabled` is `false` or the video is not ready yet\n * (`readyState < 2` or `videoWidth === 0`).\n *\n * One offscreen canvas is reused across frames to avoid GC pressure. Designed\n * to feed a live brightness bar / border color on a camera page.\n *\n * @param videoRef - ref to the `<video>` element to sample.\n * @param options - optional configuration (see {@link UseLiveLuminanceOptions}).\n * @returns The rolling mean luminance in `0..255` (`0` until the first sample).\n */\nexport function useLiveLuminance(\n videoRef: RefObject<HTMLVideoElement | null>,\n { enabled = true, intervalMs = 160 }: UseLiveLuminanceOptions = {},\n): number {\n const [luminance, setLuminance] = useState(0);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n\n useEffect(() => {\n if (!enabled) return;\n if (typeof window === \"undefined\") return;\n if (!canvasRef.current) canvasRef.current = document.createElement(\"canvas\");\n\n let rafId = 0;\n let lastSampledAt = 0;\n\n const tick = (timestamp: number): void => {\n const video = videoRef.current;\n if (!video || video.readyState < 2 || video.videoWidth === 0) {\n rafId = window.requestAnimationFrame(tick);\n return;\n }\n if (timestamp - lastSampledAt >= intervalMs) {\n lastSampledAt = timestamp;\n setLuminance(computeImageLuminance(video, canvasRef.current ?? undefined));\n }\n rafId = window.requestAnimationFrame(tick);\n };\n\n rafId = window.requestAnimationFrame(tick);\n return () => window.cancelAnimationFrame(rafId);\n }, [videoRef, enabled, intervalMs]);\n\n return luminance;\n}\n"],"mappings":"0DAwBA,SAAgB,EACZ,EACA,CAAE,UAAU,GAAM,aAAa,KAAiC,CAAC,EAC3D,CACN,GAAM,CAAC,EAAW,IAAA,
|
|
1
|
+
{"version":3,"file":"use-live-luminance.cjs","names":[],"sources":["../../src/vision/use-live-luminance.ts"],"sourcesContent":["import { useEffect, useRef, useState, type RefObject } from \"react\";\nimport { computeImageLuminance } from \"./luminance\";\n\n/** Options for {@link useLiveLuminance}. */\nexport interface UseLiveLuminanceOptions {\n /** When `false` the loop is paused (e.g. while a capture is in flight). Default: `true`. */\n enabled?: boolean;\n /** Throttle measurements in milliseconds. Default: `160` (~6 fps), plenty for UX. */\n intervalMs?: number;\n}\n\n/**\n * Sample mean luminance from a `<video>` source on a `requestAnimationFrame`\n * loop and expose the rolling value. Sampling is throttled by `intervalMs` and\n * paused whenever `enabled` is `false` or the video is not ready yet\n * (`readyState < 2` or `videoWidth === 0`).\n *\n * One offscreen canvas is reused across frames to avoid GC pressure. Designed\n * to feed a live brightness bar / border color on a camera page.\n *\n * @param videoRef - ref to the `<video>` element to sample.\n * @param options - optional configuration (see {@link UseLiveLuminanceOptions}).\n * @returns The rolling mean luminance in `0..255` (`0` until the first sample).\n */\nexport function useLiveLuminance(\n videoRef: RefObject<HTMLVideoElement | null>,\n { enabled = true, intervalMs = 160 }: UseLiveLuminanceOptions = {},\n): number {\n const [luminance, setLuminance] = useState(0);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n\n useEffect(() => {\n if (!enabled) return;\n if (typeof window === \"undefined\") return;\n if (!canvasRef.current) canvasRef.current = document.createElement(\"canvas\");\n\n let rafId = 0;\n let lastSampledAt = 0;\n\n const tick = (timestamp: number): void => {\n const video = videoRef.current;\n if (!video || video.readyState < 2 || video.videoWidth === 0) {\n rafId = window.requestAnimationFrame(tick);\n return;\n }\n if (timestamp - lastSampledAt >= intervalMs) {\n lastSampledAt = timestamp;\n setLuminance(computeImageLuminance(video, canvasRef.current ?? undefined));\n }\n rafId = window.requestAnimationFrame(tick);\n };\n\n rafId = window.requestAnimationFrame(tick);\n return () => window.cancelAnimationFrame(rafId);\n }, [videoRef, enabled, intervalMs]);\n\n return luminance;\n}\n"],"mappings":"0DAwBA,SAAgB,EACZ,EACA,CAAE,UAAU,GAAM,aAAa,KAAiC,CAAC,EAC3D,CACN,GAAM,CAAC,EAAW,IAAA,EAAgB,EAAA,SAAA,CAAS,CAAC,EACtC,GAAA,EAAY,EAAA,OAAA,CAAiC,IAAI,EA2BvD,OAzBA,EAAA,EAAA,UAAA,KAAgB,CAEZ,GADI,CAAC,GACD,OAAO,OAAW,IAAa,OACnC,AAAwB,EAAU,UAAU,SAAS,cAAc,QAAQ,EAE3E,IAAI,EAAQ,EACR,EAAgB,EAEd,EAAQ,GAA4B,CACtC,IAAM,EAAQ,EAAS,QACvB,GAAI,CAAC,GAAS,EAAM,WAAa,GAAK,EAAM,aAAe,EAAG,CAC1D,EAAQ,OAAO,sBAAsB,CAAI,EACzC,MACJ,CACI,EAAY,GAAiB,IAC7B,EAAgB,EAChB,EAAa,EAAA,sBAAsB,EAAO,EAAU,SAAW,IAAA,EAAS,CAAC,GAE7E,EAAQ,OAAO,sBAAsB,CAAI,CAC7C,EAGA,MADA,GAAQ,OAAO,sBAAsB,CAAI,MAC5B,OAAO,qBAAqB,CAAK,CAClD,EAAG,CAAC,EAAU,EAAS,CAAU,CAAC,EAE3B,CACX"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./tempest-icons.cjs");let n=require("node:path"),r=require("vite"),i=require("@vitejs/plugin-react");i=e.__toESM(i,1);function a(e){let t={};for(let[n,r]of Object.entries(e))t[n]=typeof r==`string`?{target:r,changeOrigin:!0}:r;return t}function o(e={}){let{srcDir:o=`src`,port:s=5173,host:c=`127.0.0.1`,open:l=!1,proxy:u,alias:d={},plugins:f=[],icons:p=!0,overrides:m={}}=e,h=m,g=p?[t.tempestIcons({dir:o,...typeof p==`object`?p:{}})]:[],_={plugins:[(0,i.default)(),...g,...f],resolve:{alias:{"@":(0,n.resolve)(process.cwd(),o),...d}},server:{port:s,host:c,open:l,...u?{proxy:a(u)}:{}}}
|
|
1
|
+
const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./tempest-icons.cjs");let n=require("node:path"),r=require("vite"),i=require("@vitejs/plugin-react");i=e.__toESM(i,1);function a(e){let t={};for(let[n,r]of Object.entries(e))t[n]=typeof r==`string`?{target:r,changeOrigin:!0}:r;return t}function o(e={}){let{srcDir:o=`src`,port:s=5173,host:c=`127.0.0.1`,open:l=!1,proxy:u,alias:d={},plugins:f=[],icons:p=!0,overrides:m={}}=e,h=m,g=p?[t.tempestIcons({dir:o,...typeof p==`object`?p:{}})]:[],_={plugins:[(0,i.default)(),...g,...f],resolve:{alias:{"@":(0,n.resolve)(process.cwd(),o),...d}},server:{port:s,host:c,open:l,...u?{proxy:a(u)}:{}}},v={..._,...h,plugins:[..._.plugins??[],...h.plugins??[]],resolve:{..._.resolve,...h.resolve},server:{..._.server,...h.server}};return(0,r.defineConfig)(v)}exports.createViteConfig=o;
|
|
2
2
|
//# sourceMappingURL=create-vite-config.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-vite-config.cjs","names":[],"sources":["../../src/vite/create-vite-config.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { defineConfig } from \"vite\";\nimport type { ProxyOptions, UserConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\n\nimport { tempestIcons, type TempestIconsOptions } from \"./tempest-icons\";\n\n/**\n * A Vite proxy entry: either a target URL string (expanded to\n * `{ target, changeOrigin: true }`) or a raw Vite `ProxyOptions` object.\n */\nexport type ProxyEntry = string | Record<string, unknown>;\n\nexport interface CreateViteConfigOptions {\n /**\n * Source directory aliased to `@`, relative to the project root.\n * Default: `\"src\"` (so `@/components/Button` → `<root>/src/components/Button`).\n */\n srcDir?: string;\n /** Dev server port. Default: `5173`. */\n port?: number;\n /** Dev server host. Default: `\"127.0.0.1\"`. */\n host?: string | boolean;\n /** Open the browser on `dev` start. Default: `false`. */\n open?: boolean;\n /**\n * Dev proxy table. String values are expanded to\n * `{ target, changeOrigin: true }`; objects are passed through untouched.\n *\n * @example { \"/api\": \"http://127.0.0.1:8000\" }\n */\n proxy?: Record<string, ProxyEntry>;\n /** Extra path aliases merged on top of the default `@` → src alias. */\n alias?: Record<string, string>;\n /** Vite plugins appended after `@vitejs/plugin-react`. */\n plugins?: unknown[];\n /**\n * The `tempestIcons()` plugin, which generates `virtual:tempest-icons` from the\n * icon slugs your source mentions. On by default — it only ever removes\n * requests. Pass `false` to leave it out, or an options object to configure the\n * scan.\n */\n icons?: boolean | TempestIconsOptions;\n /**\n * Arbitrary Vite config (a `UserConfig` object) deep-merged last, for\n * escape-hatch overrides (build target, define, extra `server` keys, …).\n */\n overrides?: Record<string, unknown>;\n}\n\n/**\n * The resulting Vite config object. Typed loosely so the SDK's published\n * declarations stay free of `vite`'s internal types; assign it straight to a\n * `vite.config.ts` default export.\n */\nexport type TempestViteConfig = Record<string, unknown>;\n\nfunction normalizeProxy(proxy: Record<string, ProxyEntry>): Record<string, ProxyOptions> {\n const out: Record<string, ProxyOptions> = {};\n for (const [path, value] of Object.entries(proxy)) {\n out[path] =\n typeof value === \"string\"\n ? { target: value, changeOrigin: true }\n : (value as ProxyOptions);\n }\n return out;\n}\n\n/**\n * Build a Tempest-flavored Vite config for a React app: the `@vitejs/plugin-react`\n * plugin, the `@` → `src` import alias, and sane dev-server defaults — so a\n * consuming app's `vite.config.ts` is a single call instead of repeated\n * boilerplate. Everything is overridable.\n *\n * Import it from the dedicated Node entry point:\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({\n * proxy: { \"/api\": \"http://127.0.0.1:8000\" },\n * });\n */\nexport function createViteConfig(options: CreateViteConfigOptions = {}): TempestViteConfig {\n const {\n srcDir = \"src\",\n port = 5173,\n host = \"127.0.0.1\",\n open = false,\n proxy,\n alias = {},\n plugins = [],\n icons = true,\n overrides = {},\n } = options;\n\n const overridesConfig = overrides as UserConfig;\n const iconsPlugin = icons\n ? [tempestIcons({ dir: srcDir, ...(typeof icons === \"object\" ? icons : {}) })]\n : [];\n\n const base: UserConfig = {\n plugins: [react(), ...iconsPlugin, ...plugins] as UserConfig[\"plugins\"],\n resolve: {\n alias: {\n \"@\": resolve(process.cwd(), srcDir),\n ...alias,\n },\n },\n server: {\n port,\n host,\n open,\n ...(proxy ? { proxy: normalizeProxy(proxy) } : {}),\n },\n };\n\n const merged: UserConfig = {\n ...base,\n ...overridesConfig,\n plugins: [...(base.plugins ?? []), ...(overridesConfig.plugins ?? [])],\n resolve: { ...base.resolve, ...overridesConfig.resolve },\n server: { ...base.server, ...overridesConfig.server },\n };\n\n return defineConfig(merged) as TempestViteConfig;\n}\n"],"mappings":"sLAyDA,SAAS,EAAe,EAAiE,CACrF,IAAM,EAAoC,CAAC,EAC3C,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,CAAK,EAC5C,EAAI,GACA,OAAO,GAAU,SACX,CAAE,OAAQ,EAAO,aAAc,EAAK,EACnC,EAEf,OAAO,CACX,CAkBA,SAAgB,EAAiB,EAAmC,CAAC,EAAsB,CACvF,GAAM,CACF,SAAS,MACT,OAAO,KACP,OAAO,YACP,OAAO,GACP,QACA,QAAQ,CAAC,EACT,UAAU,CAAC,EACX,QAAQ,GACR,YAAY,CAAC,GACb,EAEE,EAAkB,EAClB,EAAc,EACd,CAAC,EAAA,aAAa,CAAE,IAAK,EAAQ,GAAI,OAAO,GAAU,SAAW,EAAQ,CAAC,CAAG,CAAC,CAAC,EAC3E,CAAC,EAED,EAAmB,CACrB,QAAS,
|
|
1
|
+
{"version":3,"file":"create-vite-config.cjs","names":[],"sources":["../../src/vite/create-vite-config.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { defineConfig } from \"vite\";\nimport type { ProxyOptions, UserConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\n\nimport { tempestIcons, type TempestIconsOptions } from \"./tempest-icons\";\n\n/**\n * A Vite proxy entry: either a target URL string (expanded to\n * `{ target, changeOrigin: true }`) or a raw Vite `ProxyOptions` object.\n */\nexport type ProxyEntry = string | Record<string, unknown>;\n\nexport interface CreateViteConfigOptions {\n /**\n * Source directory aliased to `@`, relative to the project root.\n * Default: `\"src\"` (so `@/components/Button` → `<root>/src/components/Button`).\n */\n srcDir?: string;\n /** Dev server port. Default: `5173`. */\n port?: number;\n /** Dev server host. Default: `\"127.0.0.1\"`. */\n host?: string | boolean;\n /** Open the browser on `dev` start. Default: `false`. */\n open?: boolean;\n /**\n * Dev proxy table. String values are expanded to\n * `{ target, changeOrigin: true }`; objects are passed through untouched.\n *\n * @example { \"/api\": \"http://127.0.0.1:8000\" }\n */\n proxy?: Record<string, ProxyEntry>;\n /** Extra path aliases merged on top of the default `@` → src alias. */\n alias?: Record<string, string>;\n /** Vite plugins appended after `@vitejs/plugin-react`. */\n plugins?: unknown[];\n /**\n * The `tempestIcons()` plugin, which generates `virtual:tempest-icons` from the\n * icon slugs your source mentions. On by default — it only ever removes\n * requests. Pass `false` to leave it out, or an options object to configure the\n * scan.\n */\n icons?: boolean | TempestIconsOptions;\n /**\n * Arbitrary Vite config (a `UserConfig` object) deep-merged last, for\n * escape-hatch overrides (build target, define, extra `server` keys, …).\n */\n overrides?: Record<string, unknown>;\n}\n\n/**\n * The resulting Vite config object. Typed loosely so the SDK's published\n * declarations stay free of `vite`'s internal types; assign it straight to a\n * `vite.config.ts` default export.\n */\nexport type TempestViteConfig = Record<string, unknown>;\n\nfunction normalizeProxy(proxy: Record<string, ProxyEntry>): Record<string, ProxyOptions> {\n const out: Record<string, ProxyOptions> = {};\n for (const [path, value] of Object.entries(proxy)) {\n out[path] =\n typeof value === \"string\"\n ? { target: value, changeOrigin: true }\n : (value as ProxyOptions);\n }\n return out;\n}\n\n/**\n * Build a Tempest-flavored Vite config for a React app: the `@vitejs/plugin-react`\n * plugin, the `@` → `src` import alias, and sane dev-server defaults — so a\n * consuming app's `vite.config.ts` is a single call instead of repeated\n * boilerplate. Everything is overridable.\n *\n * Import it from the dedicated Node entry point:\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({\n * proxy: { \"/api\": \"http://127.0.0.1:8000\" },\n * });\n */\nexport function createViteConfig(options: CreateViteConfigOptions = {}): TempestViteConfig {\n const {\n srcDir = \"src\",\n port = 5173,\n host = \"127.0.0.1\",\n open = false,\n proxy,\n alias = {},\n plugins = [],\n icons = true,\n overrides = {},\n } = options;\n\n const overridesConfig = overrides as UserConfig;\n const iconsPlugin = icons\n ? [tempestIcons({ dir: srcDir, ...(typeof icons === \"object\" ? icons : {}) })]\n : [];\n\n const base: UserConfig = {\n plugins: [react(), ...iconsPlugin, ...plugins] as UserConfig[\"plugins\"],\n resolve: {\n alias: {\n \"@\": resolve(process.cwd(), srcDir),\n ...alias,\n },\n },\n server: {\n port,\n host,\n open,\n ...(proxy ? { proxy: normalizeProxy(proxy) } : {}),\n },\n };\n\n const merged: UserConfig = {\n ...base,\n ...overridesConfig,\n plugins: [...(base.plugins ?? []), ...(overridesConfig.plugins ?? [])],\n resolve: { ...base.resolve, ...overridesConfig.resolve },\n server: { ...base.server, ...overridesConfig.server },\n };\n\n return defineConfig(merged) as TempestViteConfig;\n}\n"],"mappings":"sLAyDA,SAAS,EAAe,EAAiE,CACrF,IAAM,EAAoC,CAAC,EAC3C,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,CAAK,EAC5C,EAAI,GACA,OAAO,GAAU,SACX,CAAE,OAAQ,EAAO,aAAc,EAAK,EACnC,EAEf,OAAO,CACX,CAkBA,SAAgB,EAAiB,EAAmC,CAAC,EAAsB,CACvF,GAAM,CACF,SAAS,MACT,OAAO,KACP,OAAO,YACP,OAAO,GACP,QACA,QAAQ,CAAC,EACT,UAAU,CAAC,EACX,QAAQ,GACR,YAAY,CAAC,GACb,EAEE,EAAkB,EAClB,EAAc,EACd,CAAC,EAAA,aAAa,CAAE,IAAK,EAAQ,GAAI,OAAO,GAAU,SAAW,EAAQ,CAAC,CAAG,CAAC,CAAC,EAC3E,CAAC,EAED,EAAmB,CACrB,QAAS,EAAC,EAAA,EAAA,QAAA,CAAM,EAAG,GAAG,EAAa,GAAG,CAAO,EAC7C,QAAS,CACL,MAAO,CACH,KAAA,EAAK,EAAA,QAAA,CAAQ,QAAQ,IAAI,EAAG,CAAM,EAClC,GAAG,CACP,CACJ,EACA,OAAQ,CACJ,OACA,OACA,OACA,GAAI,EAAQ,CAAE,MAAO,EAAe,CAAK,CAAE,EAAI,CAAC,CACpD,CACJ,EAEM,EAAqB,CACvB,GAAG,EACH,GAAG,EACH,QAAS,CAAC,GAAI,EAAK,SAAW,CAAC,EAAI,GAAI,EAAgB,SAAW,CAAC,CAAE,EACrE,QAAS,CAAE,GAAG,EAAK,QAAS,GAAG,EAAgB,OAAQ,EACvD,OAAQ,CAAE,GAAG,EAAK,OAAQ,GAAG,EAAgB,MAAO,CACxD,EAEA,OAAA,EAAO,EAAA,aAAA,CAAa,CAAM,CAC9B"}
|
|
@@ -31,8 +31,7 @@ function a(a = {}) {
|
|
|
31
31
|
open: l,
|
|
32
32
|
...u ? { proxy: i(u) } : {}
|
|
33
33
|
}
|
|
34
|
-
}
|
|
35
|
-
return n({
|
|
34
|
+
}, v = {
|
|
36
35
|
..._,
|
|
37
36
|
...h,
|
|
38
37
|
plugins: [..._.plugins ?? [], ...h.plugins ?? []],
|
|
@@ -44,7 +43,8 @@ function a(a = {}) {
|
|
|
44
43
|
..._.server,
|
|
45
44
|
...h.server
|
|
46
45
|
}
|
|
47
|
-
}
|
|
46
|
+
};
|
|
47
|
+
return n(v);
|
|
48
48
|
}
|
|
49
49
|
//#endregion
|
|
50
50
|
export { a as createViteConfig };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-vite-config.js","names":[],"sources":["../../src/vite/create-vite-config.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { defineConfig } from \"vite\";\nimport type { ProxyOptions, UserConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\n\nimport { tempestIcons, type TempestIconsOptions } from \"./tempest-icons\";\n\n/**\n * A Vite proxy entry: either a target URL string (expanded to\n * `{ target, changeOrigin: true }`) or a raw Vite `ProxyOptions` object.\n */\nexport type ProxyEntry = string | Record<string, unknown>;\n\nexport interface CreateViteConfigOptions {\n /**\n * Source directory aliased to `@`, relative to the project root.\n * Default: `\"src\"` (so `@/components/Button` → `<root>/src/components/Button`).\n */\n srcDir?: string;\n /** Dev server port. Default: `5173`. */\n port?: number;\n /** Dev server host. Default: `\"127.0.0.1\"`. */\n host?: string | boolean;\n /** Open the browser on `dev` start. Default: `false`. */\n open?: boolean;\n /**\n * Dev proxy table. String values are expanded to\n * `{ target, changeOrigin: true }`; objects are passed through untouched.\n *\n * @example { \"/api\": \"http://127.0.0.1:8000\" }\n */\n proxy?: Record<string, ProxyEntry>;\n /** Extra path aliases merged on top of the default `@` → src alias. */\n alias?: Record<string, string>;\n /** Vite plugins appended after `@vitejs/plugin-react`. */\n plugins?: unknown[];\n /**\n * The `tempestIcons()` plugin, which generates `virtual:tempest-icons` from the\n * icon slugs your source mentions. On by default — it only ever removes\n * requests. Pass `false` to leave it out, or an options object to configure the\n * scan.\n */\n icons?: boolean | TempestIconsOptions;\n /**\n * Arbitrary Vite config (a `UserConfig` object) deep-merged last, for\n * escape-hatch overrides (build target, define, extra `server` keys, …).\n */\n overrides?: Record<string, unknown>;\n}\n\n/**\n * The resulting Vite config object. Typed loosely so the SDK's published\n * declarations stay free of `vite`'s internal types; assign it straight to a\n * `vite.config.ts` default export.\n */\nexport type TempestViteConfig = Record<string, unknown>;\n\nfunction normalizeProxy(proxy: Record<string, ProxyEntry>): Record<string, ProxyOptions> {\n const out: Record<string, ProxyOptions> = {};\n for (const [path, value] of Object.entries(proxy)) {\n out[path] =\n typeof value === \"string\"\n ? { target: value, changeOrigin: true }\n : (value as ProxyOptions);\n }\n return out;\n}\n\n/**\n * Build a Tempest-flavored Vite config for a React app: the `@vitejs/plugin-react`\n * plugin, the `@` → `src` import alias, and sane dev-server defaults — so a\n * consuming app's `vite.config.ts` is a single call instead of repeated\n * boilerplate. Everything is overridable.\n *\n * Import it from the dedicated Node entry point:\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({\n * proxy: { \"/api\": \"http://127.0.0.1:8000\" },\n * });\n */\nexport function createViteConfig(options: CreateViteConfigOptions = {}): TempestViteConfig {\n const {\n srcDir = \"src\",\n port = 5173,\n host = \"127.0.0.1\",\n open = false,\n proxy,\n alias = {},\n plugins = [],\n icons = true,\n overrides = {},\n } = options;\n\n const overridesConfig = overrides as UserConfig;\n const iconsPlugin = icons\n ? [tempestIcons({ dir: srcDir, ...(typeof icons === \"object\" ? icons : {}) })]\n : [];\n\n const base: UserConfig = {\n plugins: [react(), ...iconsPlugin, ...plugins] as UserConfig[\"plugins\"],\n resolve: {\n alias: {\n \"@\": resolve(process.cwd(), srcDir),\n ...alias,\n },\n },\n server: {\n port,\n host,\n open,\n ...(proxy ? { proxy: normalizeProxy(proxy) } : {}),\n },\n };\n\n const merged: UserConfig = {\n ...base,\n ...overridesConfig,\n plugins: [...(base.plugins ?? []), ...(overridesConfig.plugins ?? [])],\n resolve: { ...base.resolve, ...overridesConfig.resolve },\n server: { ...base.server, ...overridesConfig.server },\n };\n\n return defineConfig(merged) as TempestViteConfig;\n}\n"],"mappings":";;;;;AAyDA,SAAS,EAAe,GAAiE;CACrF,IAAM,IAAoC,CAAC;CAC3C,KAAK,IAAM,CAAC,GAAM,MAAU,OAAO,QAAQ,CAAK,GAC5C,EAAI,KACA,OAAO,KAAU,WACX;EAAE,QAAQ;EAAO,cAAc;CAAK,IACnC;CAEf,OAAO;AACX;AAkBA,SAAgB,EAAiB,IAAmC,CAAC,GAAsB;CACvF,IAAM,EACF,YAAS,OACT,UAAO,MACP,UAAO,aACP,UAAO,IACP,UACA,WAAQ,CAAC,GACT,aAAU,CAAC,GACX,WAAQ,IACR,eAAY,CAAC,MACb,GAEE,IAAkB,GAClB,IAAc,IACd,CAAC,EAAa;EAAE,KAAK;EAAQ,GAAI,OAAO,KAAU,WAAW,IAAQ,CAAC;CAAG,CAAC,CAAC,IAC3E,CAAC,GAED,IAAmB;EACrB,SAAS;GAAC,EAAM;GAAG,GAAG;GAAa,GAAG;EAAO;EAC7C,SAAS,EACL,OAAO;GACH,KAAK,EAAQ,QAAQ,IAAI,GAAG,CAAM;GAClC,GAAG;EACP,EACJ;EACA,QAAQ;GACJ;GACA;GACA;GACA,GAAI,IAAQ,EAAE,OAAO,EAAe,CAAK,EAAE,IAAI,CAAC;EACpD;CACJ
|
|
1
|
+
{"version":3,"file":"create-vite-config.js","names":[],"sources":["../../src/vite/create-vite-config.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { defineConfig } from \"vite\";\nimport type { ProxyOptions, UserConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\n\nimport { tempestIcons, type TempestIconsOptions } from \"./tempest-icons\";\n\n/**\n * A Vite proxy entry: either a target URL string (expanded to\n * `{ target, changeOrigin: true }`) or a raw Vite `ProxyOptions` object.\n */\nexport type ProxyEntry = string | Record<string, unknown>;\n\nexport interface CreateViteConfigOptions {\n /**\n * Source directory aliased to `@`, relative to the project root.\n * Default: `\"src\"` (so `@/components/Button` → `<root>/src/components/Button`).\n */\n srcDir?: string;\n /** Dev server port. Default: `5173`. */\n port?: number;\n /** Dev server host. Default: `\"127.0.0.1\"`. */\n host?: string | boolean;\n /** Open the browser on `dev` start. Default: `false`. */\n open?: boolean;\n /**\n * Dev proxy table. String values are expanded to\n * `{ target, changeOrigin: true }`; objects are passed through untouched.\n *\n * @example { \"/api\": \"http://127.0.0.1:8000\" }\n */\n proxy?: Record<string, ProxyEntry>;\n /** Extra path aliases merged on top of the default `@` → src alias. */\n alias?: Record<string, string>;\n /** Vite plugins appended after `@vitejs/plugin-react`. */\n plugins?: unknown[];\n /**\n * The `tempestIcons()` plugin, which generates `virtual:tempest-icons` from the\n * icon slugs your source mentions. On by default — it only ever removes\n * requests. Pass `false` to leave it out, or an options object to configure the\n * scan.\n */\n icons?: boolean | TempestIconsOptions;\n /**\n * Arbitrary Vite config (a `UserConfig` object) deep-merged last, for\n * escape-hatch overrides (build target, define, extra `server` keys, …).\n */\n overrides?: Record<string, unknown>;\n}\n\n/**\n * The resulting Vite config object. Typed loosely so the SDK's published\n * declarations stay free of `vite`'s internal types; assign it straight to a\n * `vite.config.ts` default export.\n */\nexport type TempestViteConfig = Record<string, unknown>;\n\nfunction normalizeProxy(proxy: Record<string, ProxyEntry>): Record<string, ProxyOptions> {\n const out: Record<string, ProxyOptions> = {};\n for (const [path, value] of Object.entries(proxy)) {\n out[path] =\n typeof value === \"string\"\n ? { target: value, changeOrigin: true }\n : (value as ProxyOptions);\n }\n return out;\n}\n\n/**\n * Build a Tempest-flavored Vite config for a React app: the `@vitejs/plugin-react`\n * plugin, the `@` → `src` import alias, and sane dev-server defaults — so a\n * consuming app's `vite.config.ts` is a single call instead of repeated\n * boilerplate. Everything is overridable.\n *\n * Import it from the dedicated Node entry point:\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({\n * proxy: { \"/api\": \"http://127.0.0.1:8000\" },\n * });\n */\nexport function createViteConfig(options: CreateViteConfigOptions = {}): TempestViteConfig {\n const {\n srcDir = \"src\",\n port = 5173,\n host = \"127.0.0.1\",\n open = false,\n proxy,\n alias = {},\n plugins = [],\n icons = true,\n overrides = {},\n } = options;\n\n const overridesConfig = overrides as UserConfig;\n const iconsPlugin = icons\n ? [tempestIcons({ dir: srcDir, ...(typeof icons === \"object\" ? icons : {}) })]\n : [];\n\n const base: UserConfig = {\n plugins: [react(), ...iconsPlugin, ...plugins] as UserConfig[\"plugins\"],\n resolve: {\n alias: {\n \"@\": resolve(process.cwd(), srcDir),\n ...alias,\n },\n },\n server: {\n port,\n host,\n open,\n ...(proxy ? { proxy: normalizeProxy(proxy) } : {}),\n },\n };\n\n const merged: UserConfig = {\n ...base,\n ...overridesConfig,\n plugins: [...(base.plugins ?? []), ...(overridesConfig.plugins ?? [])],\n resolve: { ...base.resolve, ...overridesConfig.resolve },\n server: { ...base.server, ...overridesConfig.server },\n };\n\n return defineConfig(merged) as TempestViteConfig;\n}\n"],"mappings":";;;;;AAyDA,SAAS,EAAe,GAAiE;CACrF,IAAM,IAAoC,CAAC;CAC3C,KAAK,IAAM,CAAC,GAAM,MAAU,OAAO,QAAQ,CAAK,GAC5C,EAAI,KACA,OAAO,KAAU,WACX;EAAE,QAAQ;EAAO,cAAc;CAAK,IACnC;CAEf,OAAO;AACX;AAkBA,SAAgB,EAAiB,IAAmC,CAAC,GAAsB;CACvF,IAAM,EACF,YAAS,OACT,UAAO,MACP,UAAO,aACP,UAAO,IACP,UACA,WAAQ,CAAC,GACT,aAAU,CAAC,GACX,WAAQ,IACR,eAAY,CAAC,MACb,GAEE,IAAkB,GAClB,IAAc,IACd,CAAC,EAAa;EAAE,KAAK;EAAQ,GAAI,OAAO,KAAU,WAAW,IAAQ,CAAC;CAAG,CAAC,CAAC,IAC3E,CAAC,GAED,IAAmB;EACrB,SAAS;GAAC,EAAM;GAAG,GAAG;GAAa,GAAG;EAAO;EAC7C,SAAS,EACL,OAAO;GACH,KAAK,EAAQ,QAAQ,IAAI,GAAG,CAAM;GAClC,GAAG;EACP,EACJ;EACA,QAAQ;GACJ;GACA;GACA;GACA,GAAI,IAAQ,EAAE,OAAO,EAAe,CAAK,EAAE,IAAI,CAAC;EACpD;CACJ,GAEM,IAAqB;EACvB,GAAG;EACH,GAAG;EACH,SAAS,CAAC,GAAI,EAAK,WAAW,CAAC,GAAI,GAAI,EAAgB,WAAW,CAAC,CAAE;EACrE,SAAS;GAAE,GAAG,EAAK;GAAS,GAAG,EAAgB;EAAQ;EACvD,QAAQ;GAAE,GAAG,EAAK;GAAQ,GAAG,EAAgB;EAAO;CACxD;CAEA,OAAO,EAAa,CAAM;AAC9B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tempest-icons.cjs","names":[],"sources":["../../src/vite/tempest-icons.ts"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\n\nimport { iconAliases } from \"@/icons/generated/aliases\";\nimport { iconNames } from \"@/icons/generated/icon-names\";\n\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** The module id an app imports to get its static registry. */\nexport const TEMPEST_ICONS_ID = \"virtual:tempest-icons\";\n\nconst RESOLVED_ID = `\\0${TEMPEST_ICONS_ID}`;\n\n/**\n * Slugs written as a literal `name` prop or a literal `name:` field.\n *\n * Matching the *attribute* rather than any kebab-case string is what keeps the\n * scan from sweeping in unrelated text — a CSS class, a translation key and a\n * route segment all look like slugs otherwise. Everything matched is still\n * validated against lucide's real slug list before it reaches the module.\n */\nconst NAME_PATTERNS = [\n /\\bname\\s*=\\s*[\"']([a-z0-9-]+)[\"']/g,\n /\\bname\\s*=\\s*\\{\\s*[\"']([a-z0-9-]+)[\"']\\s*\\}/g,\n /\\bname\\s*:\\s*[\"']([a-z0-9-]+)[\"']/g,\n];\n\nconst SOURCE_FILE = /\\.[cm]?[jt]sx?$/;\nconst SKIP_DIRS = new Set([\"node_modules\", \"dist\", \"build\", \"coverage\"]);\n\nexport interface TempestIconsOptions {\n /**\n * Directory to scan, relative to the Vite root. Default: `\"src\"`.\n */\n dir?: string;\n /**\n * Extra slugs to include even when the scan cannot see them — a name built by\n * concatenation, or one that only ever arrives from the API but is worth\n * paying for statically because it is on the first screen.\n */\n include?: readonly string[];\n /** Directory names to skip. Default: `node_modules`, `dist`, `build`, `coverage`. */\n skipDirs?: readonly string[];\n}\n\n/**\n * Collect the icon slugs a source file references literally.\n *\n * @param code - File contents.\n * @param known - The set of real lucide slugs, aliases included.\n * @returns The slugs found, validated against `known`.\n */\nexport function scanIconSlugs(code: string, known: ReadonlySet<string>): string[] {\n const found = new Set<string>();\n for (const pattern of NAME_PATTERNS) {\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(code)) !== null) {\n if (known.has(match[1])) found.add(match[1]);\n }\n }\n return [...found];\n}\n\n/**\n * Build the virtual module source for a set of slugs.\n *\n * Aliases resolve to their canonical slug before importing, because only the\n * canonical name is a real lucide export — but the alias key still points at the\n * component, so `<Icon name=\"alert-circle\" />` stays a zero-request lookup.\n *\n * @param slugs - Validated slugs to include.\n * @param aliases - Alias slug → canonical slug.\n * @returns ES module source.\n */\nexport function buildIconsModule(\n slugs: readonly string[],\n aliases: Record<string, string>,\n): string {\n const sorted = [...new Set(slugs)].sort();\n if (!sorted.length) return \"export const staticIcons = {};\\n\";\n\n const componentOf = new Map<string, string>();\n for (const slug of sorted) componentOf.set(slug, pascalCase(aliases[slug] ?? slug));\n\n const named = [...new Set(componentOf.values())]\n .sort()\n .map((name) => (localName(name) === name ? name : `${name} as ${localName(name)}`))\n .join(\", \");\n const entries = sorted\n .map((slug) => ` \"${slug}\": ${localName(componentOf.get(slug) as string)},`)\n .join(\"\\n\");\n return `import { ${named} } from \"lucide-react\";\n\nexport const staticIcons = {\n${entries}\n};\n`;\n}\n\n/** `circle-alert` → `CircleAlert`; `a-arrow-down` → `AArrowDown`. */\nfunction pascalCase(slug: string): string {\n return slug\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n}\n\n/**\n * Export names that would shadow a restricted global if bound directly.\n *\n * Lucide ships an icon literally called `infinity`, whose export is `Infinity`.\n * Binding that at module scope shadows the global, so the import is aliased.\n */\nconst RESERVED_NAMES = new Set([\"Infinity\", \"NaN\", \"undefined\", \"eval\", \"arguments\"]);\n\n/** The local binding to use for a lucide export name. */\nfunction localName(name: string): string {\n return RESERVED_NAMES.has(name) ? `${name}Icon` : name;\n}\n\n/**\n * Walk a directory and collect every source file path.\n *\n * @param dir - Absolute directory to walk.\n * @param skip - Directory names to skip.\n * @returns Absolute file paths.\n */\nasync function collectSourceFiles(dir: string, skip: ReadonlySet<string>): Promise<string[]> {\n const out: string[] = [];\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return out;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (skip.has(entry.name) || entry.name.startsWith(\".\")) continue;\n out.push(...(await collectSourceFiles(join(dir, entry.name), skip)));\n } else if (entry.isFile() && SOURCE_FILE.test(entry.name)) {\n out.push(join(dir, entry.name));\n }\n }\n return out;\n}\n\n/**\n * Generate a static icon registry from the slugs your source mentions.\n *\n * Removes the reason to reach for `lucide-react`'s `DynamicIcon`: the slugs an app\n * writes as literals become ordinary static imports, so the bundler keeps exactly\n * those icons and the browser makes **no** extra request for them. A slug that only\n * exists at runtime still works — `<Icon>` falls back to fetching one shard per\n * initial letter.\n *\n * The scan reads the source tree directly at `buildStart` rather than harvesting\n * modules as they pass through `transform`: relying on transform order would let\n * the virtual module load before every consumer had been seen, and the registry\n * would come out short on a cold dev start — silently, since the missing slugs\n * would still render via the lazy path.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestIcons } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({ plugins: [tempestIcons()] });\n *\n * @example\n * // src/main.tsx\n * import { IconProvider } from \"tempest-react-sdk/icons\";\n * import { staticIcons } from \"virtual:tempest-icons\";\n *\n * <IconProvider registry={staticIcons}>\n * <App />\n * </IconProvider>\n */\nexport function tempestIcons(options: TempestIconsOptions = {}): TempestVitePlugin {\n const { dir = \"src\", include = [], skipDirs } = options;\n const known = new Set<string>(iconNames);\n const skip = new Set<string>(skipDirs ?? SKIP_DIRS);\n const slugs = new Set<string>(include.filter((slug) => known.has(slug)));\n let root = process.cwd();\n\n /**\n * Rescan the source tree, keeping the explicitly included slugs.\n *\n * @tempest-limits empty-catch — the scan races the editor and the file system:\n * a file listed a moment ago can be renamed, deleted, or held by another process\n * by the time it is read. An unreadable file contributes no slugs and the build\n * continues; failing the whole scan would break `vite dev` over a temp file that\n * no longer exists.\n */\n const rescan = async (): Promise<void> => {\n const files = await collectSourceFiles(resolve(root, dir), skip);\n for (const file of files) {\n try {\n const code = await readFile(file, \"utf8\");\n for (const slug of scanIconSlugs(code, known)) slugs.add(slug);\n } catch {\n /* empty */\n }\n }\n };\n\n return {\n name: \"tempest-icons\",\n enforce: \"pre\",\n\n configResolved(config: { root?: string }) {\n root = config.root ?? root;\n },\n\n async buildStart() {\n await rescan();\n },\n\n resolveId(id: string) {\n return id === TEMPEST_ICONS_ID ? RESOLVED_ID : null;\n },\n\n load(id: string) {\n return id === RESOLVED_ID ? buildIconsModule([...slugs], iconAliases) : null;\n },\n\n /**\n * Pick up a slug added while the dev server is running.\n *\n * A newly written `<Icon name=\"save\" />` would otherwise only render via\n * the lazy path until the next restart. Rescanning the changed file and\n * invalidating the virtual module keeps dev and build agreeing on what the\n * static registry contains.\n */\n async handleHotUpdate(ctx: {\n file: string;\n read: () => Promise<string>;\n server: { moduleGraph: { getModuleById: (id: string) => unknown } };\n }) {\n if (!SOURCE_FILE.test(ctx.file)) return;\n const before = slugs.size;\n for (const slug of scanIconSlugs(await ctx.read(), known)) slugs.add(slug);\n if (slugs.size === before) return;\n const mod = ctx.server.moduleGraph.getModuleById(RESOLVED_ID);\n if (mod) return [mod];\n },\n } as TempestVitePlugin;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tempest-icons.cjs","names":[],"sources":["../../src/vite/tempest-icons.ts"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\n\nimport { iconAliases } from \"@/icons/generated/aliases\";\nimport { iconNames } from \"@/icons/generated/icon-names\";\n\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** The module id an app imports to get its static registry. */\nexport const TEMPEST_ICONS_ID = \"virtual:tempest-icons\";\n\nconst RESOLVED_ID = `\\0${TEMPEST_ICONS_ID}`;\n\n/**\n * Slugs written as a literal `name` prop or a literal `name:` field.\n *\n * Matching the *attribute* rather than any kebab-case string is what keeps the\n * scan from sweeping in unrelated text — a CSS class, a translation key and a\n * route segment all look like slugs otherwise. Everything matched is still\n * validated against lucide's real slug list before it reaches the module.\n */\nconst NAME_PATTERNS = [\n /\\bname\\s*=\\s*[\"']([a-z0-9-]+)[\"']/g,\n /\\bname\\s*=\\s*\\{\\s*[\"']([a-z0-9-]+)[\"']\\s*\\}/g,\n /\\bname\\s*:\\s*[\"']([a-z0-9-]+)[\"']/g,\n];\n\nconst SOURCE_FILE = /\\.[cm]?[jt]sx?$/;\nconst SKIP_DIRS = new Set([\"node_modules\", \"dist\", \"build\", \"coverage\"]);\n\nexport interface TempestIconsOptions {\n /**\n * Directory to scan, relative to the Vite root. Default: `\"src\"`.\n */\n dir?: string;\n /**\n * Extra slugs to include even when the scan cannot see them — a name built by\n * concatenation, or one that only ever arrives from the API but is worth\n * paying for statically because it is on the first screen.\n */\n include?: readonly string[];\n /** Directory names to skip. Default: `node_modules`, `dist`, `build`, `coverage`. */\n skipDirs?: readonly string[];\n}\n\n/**\n * Collect the icon slugs a source file references literally.\n *\n * @param code - File contents.\n * @param known - The set of real lucide slugs, aliases included.\n * @returns The slugs found, validated against `known`.\n */\nexport function scanIconSlugs(code: string, known: ReadonlySet<string>): string[] {\n const found = new Set<string>();\n for (const pattern of NAME_PATTERNS) {\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(code)) !== null) {\n if (known.has(match[1])) found.add(match[1]);\n }\n }\n return [...found];\n}\n\n/**\n * Build the virtual module source for a set of slugs.\n *\n * Aliases resolve to their canonical slug before importing, because only the\n * canonical name is a real lucide export — but the alias key still points at the\n * component, so `<Icon name=\"alert-circle\" />` stays a zero-request lookup.\n *\n * @param slugs - Validated slugs to include.\n * @param aliases - Alias slug → canonical slug.\n * @returns ES module source.\n */\nexport function buildIconsModule(\n slugs: readonly string[],\n aliases: Record<string, string>,\n): string {\n const sorted = [...new Set(slugs)].sort();\n if (!sorted.length) return \"export const staticIcons = {};\\n\";\n\n const componentOf = new Map<string, string>();\n for (const slug of sorted) componentOf.set(slug, pascalCase(aliases[slug] ?? slug));\n\n const named = [...new Set(componentOf.values())]\n .sort()\n .map((name) => (localName(name) === name ? name : `${name} as ${localName(name)}`))\n .join(\", \");\n const entries = sorted\n .map((slug) => ` \"${slug}\": ${localName(componentOf.get(slug) as string)},`)\n .join(\"\\n\");\n return `import { ${named} } from \"lucide-react\";\n\nexport const staticIcons = {\n${entries}\n};\n`;\n}\n\n/** `circle-alert` → `CircleAlert`; `a-arrow-down` → `AArrowDown`. */\nfunction pascalCase(slug: string): string {\n return slug\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n}\n\n/**\n * Export names that would shadow a restricted global if bound directly.\n *\n * Lucide ships an icon literally called `infinity`, whose export is `Infinity`.\n * Binding that at module scope shadows the global, so the import is aliased.\n */\nconst RESERVED_NAMES = new Set([\"Infinity\", \"NaN\", \"undefined\", \"eval\", \"arguments\"]);\n\n/** The local binding to use for a lucide export name. */\nfunction localName(name: string): string {\n return RESERVED_NAMES.has(name) ? `${name}Icon` : name;\n}\n\n/**\n * Walk a directory and collect every source file path.\n *\n * @param dir - Absolute directory to walk.\n * @param skip - Directory names to skip.\n * @returns Absolute file paths.\n */\nasync function collectSourceFiles(dir: string, skip: ReadonlySet<string>): Promise<string[]> {\n const out: string[] = [];\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return out;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (skip.has(entry.name) || entry.name.startsWith(\".\")) continue;\n out.push(...(await collectSourceFiles(join(dir, entry.name), skip)));\n } else if (entry.isFile() && SOURCE_FILE.test(entry.name)) {\n out.push(join(dir, entry.name));\n }\n }\n return out;\n}\n\n/**\n * Generate a static icon registry from the slugs your source mentions.\n *\n * Removes the reason to reach for `lucide-react`'s `DynamicIcon`: the slugs an app\n * writes as literals become ordinary static imports, so the bundler keeps exactly\n * those icons and the browser makes **no** extra request for them. A slug that only\n * exists at runtime still works — `<Icon>` falls back to fetching one shard per\n * initial letter.\n *\n * The scan reads the source tree directly at `buildStart` rather than harvesting\n * modules as they pass through `transform`: relying on transform order would let\n * the virtual module load before every consumer had been seen, and the registry\n * would come out short on a cold dev start — silently, since the missing slugs\n * would still render via the lazy path.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestIcons } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({ plugins: [tempestIcons()] });\n *\n * @example\n * // src/main.tsx\n * import { IconProvider } from \"tempest-react-sdk/icons\";\n * import { staticIcons } from \"virtual:tempest-icons\";\n *\n * <IconProvider registry={staticIcons}>\n * <App />\n * </IconProvider>\n */\nexport function tempestIcons(options: TempestIconsOptions = {}): TempestVitePlugin {\n const { dir = \"src\", include = [], skipDirs } = options;\n const known = new Set<string>(iconNames);\n const skip = new Set<string>(skipDirs ?? SKIP_DIRS);\n const slugs = new Set<string>(include.filter((slug) => known.has(slug)));\n let root = process.cwd();\n\n /**\n * Rescan the source tree, keeping the explicitly included slugs.\n *\n * @tempest-limits empty-catch — the scan races the editor and the file system:\n * a file listed a moment ago can be renamed, deleted, or held by another process\n * by the time it is read. An unreadable file contributes no slugs and the build\n * continues; failing the whole scan would break `vite dev` over a temp file that\n * no longer exists.\n */\n const rescan = async (): Promise<void> => {\n const files = await collectSourceFiles(resolve(root, dir), skip);\n for (const file of files) {\n try {\n const code = await readFile(file, \"utf8\");\n for (const slug of scanIconSlugs(code, known)) slugs.add(slug);\n } catch {\n /* empty */\n }\n }\n };\n\n return {\n name: \"tempest-icons\",\n enforce: \"pre\",\n\n configResolved(config: { root?: string }) {\n root = config.root ?? root;\n },\n\n async buildStart() {\n await rescan();\n },\n\n resolveId(id: string) {\n return id === TEMPEST_ICONS_ID ? RESOLVED_ID : null;\n },\n\n load(id: string) {\n return id === RESOLVED_ID ? buildIconsModule([...slugs], iconAliases) : null;\n },\n\n /**\n * Pick up a slug added while the dev server is running.\n *\n * A newly written `<Icon name=\"save\" />` would otherwise only render via\n * the lazy path until the next restart. Rescanning the changed file and\n * invalidating the virtual module keeps dev and build agreeing on what the\n * static registry contains.\n */\n async handleHotUpdate(ctx: {\n file: string;\n read: () => Promise<string>;\n server: { moduleGraph: { getModuleById: (id: string) => unknown } };\n }) {\n if (!SOURCE_FILE.test(ctx.file)) return;\n const before = slugs.size;\n for (const slug of scanIconSlugs(await ctx.read(), known)) slugs.add(slug);\n if (slugs.size === before) return;\n const mod = ctx.server.moduleGraph.getModuleById(RESOLVED_ID);\n if (mod) return [mod];\n },\n } as TempestVitePlugin;\n}\n"],"mappings":"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"}
|