@gntik-ai/blocks 0.0.0-stage → 0.5.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/index.d.ts +3494 -0
- package/dist/index.js +14911 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -3
- package/src/ai/AssistantCardGrid/AssistantCardGrid.test.tsx +32 -0
- package/src/ai/AssistantCardGrid/AssistantCardGrid.tsx +98 -0
- package/src/ai/AssistantCardGrid/block.meta.ts +10 -0
- package/src/ai/AssistantCardGrid/fixtures.ts +72 -0
- package/src/ai/EvaluationScorecard/EvaluationScorecard.test.tsx +39 -0
- package/src/ai/EvaluationScorecard/EvaluationScorecard.tsx +233 -0
- package/src/ai/EvaluationScorecard/block.meta.ts +10 -0
- package/src/ai/EvaluationScorecard/fixtures.ts +79 -0
- package/src/ai/ModelKeysList/ModelKeysList.test.tsx +44 -0
- package/src/ai/ModelKeysList/ModelKeysList.tsx +203 -0
- package/src/ai/ModelKeysList/block.meta.ts +10 -0
- package/src/ai/ModelKeysList/fixtures.ts +44 -0
- package/src/ai/PromptEditor/PromptEditor.test.tsx +97 -0
- package/src/ai/PromptEditor/PromptEditor.tsx +202 -0
- package/src/ai/PromptEditor/block.meta.ts +10 -0
- package/src/ai/PromptEditor/examples/controlled-variables.tsx +23 -0
- package/src/ai/PromptEditor/fixtures.ts +12 -0
- package/src/ai/TokenCostCard/TokenCostCard.test.tsx +36 -0
- package/src/ai/TokenCostCard/TokenCostCard.tsx +130 -0
- package/src/ai/TokenCostCard/block.meta.ts +9 -0
- package/src/ai/TokenCostCard/fixtures.ts +18 -0
- package/src/ai/TraceWaterfall/TraceWaterfall.test.tsx +119 -0
- package/src/ai/TraceWaterfall/TraceWaterfall.tsx +317 -0
- package/src/ai/TraceWaterfall/block.meta.ts +10 -0
- package/src/ai/TraceWaterfall/examples/no-details.tsx +6 -0
- package/src/ai/TraceWaterfall/examples/running.tsx +16 -0
- package/src/ai/TraceWaterfall/fixtures.ts +133 -0
- package/src/ai/TraceWaterfall/span-details.tsx +51 -0
- package/src/ai/index.ts +13 -0
- package/src/auth/ForgotPasswordForm/ForgotPasswordForm.test.tsx +209 -0
- package/src/auth/ForgotPasswordForm/ForgotPasswordForm.tsx +211 -0
- package/src/auth/ForgotPasswordForm/block.meta.ts +10 -0
- package/src/auth/ForgotPasswordForm/examples/UsernameOrEmail.tsx +24 -0
- package/src/auth/MfaChallenge/MfaChallenge.test.tsx +35 -0
- package/src/auth/MfaChallenge/MfaChallenge.tsx +170 -0
- package/src/auth/MfaChallenge/block.meta.ts +9 -0
- package/src/auth/ResetPasswordForm/ResetPasswordForm.test.tsx +27 -0
- package/src/auth/ResetPasswordForm/ResetPasswordForm.tsx +100 -0
- package/src/auth/ResetPasswordForm/block.meta.ts +10 -0
- package/src/auth/SignInForm/SignInForm.test.tsx +222 -0
- package/src/auth/SignInForm/SignInForm.tsx +226 -0
- package/src/auth/SignInForm/block.meta.ts +22 -0
- package/src/auth/SignInForm/examples/Username.tsx +26 -0
- package/src/auth/SignUpForm/SignUpForm.test.tsx +33 -0
- package/src/auth/SignUpForm/SignUpForm.tsx +154 -0
- package/src/auth/SignUpForm/block.meta.ts +9 -0
- package/src/auth/SsoButtons/SsoButtons.test.tsx +23 -0
- package/src/auth/SsoButtons/SsoButtons.tsx +52 -0
- package/src/auth/SsoButtons/block.meta.ts +9 -0
- package/src/auth/SsoButtons/fixtures.ts +15 -0
- package/src/auth/index.ts +15 -0
- package/src/auth/shared.tsx +113 -0
- package/src/billing/CostBreakdown/CostBreakdown.test.tsx +24 -0
- package/src/billing/CostBreakdown/CostBreakdown.tsx +95 -0
- package/src/billing/CostBreakdown/block.meta.ts +10 -0
- package/src/billing/CostBreakdown/fixtures.ts +9 -0
- package/src/billing/InvoiceTable/InvoiceTable.test.tsx +29 -0
- package/src/billing/InvoiceTable/InvoiceTable.tsx +130 -0
- package/src/billing/InvoiceTable/block.meta.ts +10 -0
- package/src/billing/InvoiceTable/fixtures.ts +38 -0
- package/src/billing/PaymentMethodCard/PaymentMethodCard.test.tsx +28 -0
- package/src/billing/PaymentMethodCard/PaymentMethodCard.tsx +103 -0
- package/src/billing/PaymentMethodCard/block.meta.ts +10 -0
- package/src/billing/PaymentMethodCard/fixtures.ts +12 -0
- package/src/billing/PlanCard/PlanCard.baseline.test.tsx +26 -0
- package/src/billing/PlanCard/PlanCard.summary.test.tsx +227 -0
- package/src/billing/PlanCard/PlanCard.test.tsx +27 -0
- package/src/billing/PlanCard/PlanCard.tsx +140 -0
- package/src/billing/PlanCard/PlanSummary.tsx +101 -0
- package/src/billing/PlanCard/README.md +23 -0
- package/src/billing/PlanCard/__snapshots__/PlanCard.baseline.test.tsx.snap +5 -0
- package/src/billing/PlanCard/block.meta.ts +60 -0
- package/src/billing/PlanCard/examples/SummaryActionsOnly.tsx +12 -0
- package/src/billing/PlanCard/examples/SummaryFeaturesOnly.tsx +14 -0
- package/src/billing/PlanCard/examples/SummaryFull.tsx +28 -0
- package/src/billing/PlanCard/examples/SummaryMinimal.tsx +5 -0
- package/src/billing/PlanCard/examples/SummaryPriceOnly.tsx +15 -0
- package/src/billing/PlanCard/examples/SummaryRenewalOnly.tsx +14 -0
- package/src/billing/PlanCard/examples/SummaryUnassigned.tsx +5 -0
- package/src/billing/PlanCard/examples/SummaryUnassignedNoLabel.tsx +5 -0
- package/src/billing/PlanCard/examples/SummaryZeroPrice.tsx +15 -0
- package/src/billing/PlanCard/fixtures.ts +19 -0
- package/src/billing/PlanDefinitionCard/PlanDefinitionCard.test.tsx +111 -0
- package/src/billing/PlanDefinitionCard/PlanDefinitionCard.tsx +86 -0
- package/src/billing/PlanDefinitionCard/README.md +29 -0
- package/src/billing/PlanDefinitionCard/block.meta.ts +46 -0
- package/src/billing/PlanDefinitionCard/examples/Compact.tsx +23 -0
- package/src/billing/PlanDefinitionCard/examples/Full.tsx +23 -0
- package/src/billing/PlanDefinitionCard/fixtures.ts +12 -0
- package/src/billing/QuotaMeters/QuotaMeters.test.tsx +23 -0
- package/src/billing/QuotaMeters/QuotaMeters.tsx +92 -0
- package/src/billing/QuotaMeters/block.meta.ts +10 -0
- package/src/billing/QuotaMeters/fixtures.ts +9 -0
- package/src/billing/SpendVsBudget/SpendVsBudget.test.tsx +24 -0
- package/src/billing/SpendVsBudget/SpendVsBudget.tsx +105 -0
- package/src/billing/SpendVsBudget/block.meta.ts +10 -0
- package/src/billing/SpendVsBudget/fixtures.ts +18 -0
- package/src/billing/format.ts +42 -0
- package/src/billing/index.ts +21 -0
- package/src/builders/CodePanel/CodePanel.test.tsx +280 -0
- package/src/builders/CodePanel/CodePanel.tsx +239 -0
- package/src/builders/CodePanel/block.meta.ts +10 -0
- package/src/builders/CodePanel/examples/controlled.tsx +15 -0
- package/src/builders/CodePanel/examples/diagnostics.tsx +40 -0
- package/src/builders/CodePanel/fixtures.ts +95 -0
- package/src/builders/CodePanel/marker-store.ts +90 -0
- package/src/builders/CodePanel/markers.ts +62 -0
- package/src/builders/FlowBuilder/FlowBuilder.test.tsx +480 -0
- package/src/builders/FlowBuilder/FlowBuilder.tsx +343 -0
- package/src/builders/FlowBuilder/block.meta.ts +20 -0
- package/src/builders/FlowBuilder/connections.ts +67 -0
- package/src/builders/FlowBuilder/decorated-nodes.ts +80 -0
- package/src/builders/FlowBuilder/examples/extensions.tsx +120 -0
- package/src/builders/FlowBuilder/extensions.ts +37 -0
- package/src/builders/FlowBuilder/fixtures.ts +80 -0
- package/src/builders/FlowBuilder/parts.tsx +196 -0
- package/src/builders/FlowBuilder/problems.tsx +40 -0
- package/src/builders/LogViewer/LogViewer.test.tsx +97 -0
- package/src/builders/LogViewer/LogViewer.tsx +253 -0
- package/src/builders/LogViewer/block.meta.ts +10 -0
- package/src/builders/LogViewer/fixtures.ts +49 -0
- package/src/builders/index.ts +15 -0
- package/src/data-display/ActivityFeed/ActivityFeed.test.tsx +62 -0
- package/src/data-display/ActivityFeed/ActivityFeed.tsx +118 -0
- package/src/data-display/ActivityFeed/block.meta.ts +10 -0
- package/src/data-display/ActivityFeed/fixtures.ts +29 -0
- package/src/data-display/ChartCard/ChartCard.ranges.test.tsx +282 -0
- package/src/data-display/ChartCard/ChartCard.test.tsx +54 -0
- package/src/data-display/ChartCard/ChartCard.tsx +218 -0
- package/src/data-display/ChartCard/block.meta.ts +10 -0
- package/src/data-display/ChartCard/examples/custom-ranges.tsx +37 -0
- package/src/data-display/ChartCard/examples/heatmap.tsx +20 -0
- package/src/data-display/ChartCard/examples/toolbar-owned-range.tsx +34 -0
- package/src/data-display/ChartCard/fixtures.ts +82 -0
- package/src/data-display/CommentThread/CommentThread.test.tsx +29 -0
- package/src/data-display/CommentThread/CommentThread.tsx +120 -0
- package/src/data-display/CommentThread/block.meta.ts +10 -0
- package/src/data-display/CommentThread/fixtures.ts +28 -0
- package/src/data-display/DescriptionListCard/DescriptionListCard.test.tsx +28 -0
- package/src/data-display/DescriptionListCard/DescriptionListCard.tsx +120 -0
- package/src/data-display/DescriptionListCard/block.meta.ts +9 -0
- package/src/data-display/DescriptionListCard/fixtures.ts +12 -0
- package/src/data-display/KpiRow/KpiRow.test.tsx +29 -0
- package/src/data-display/KpiRow/KpiRow.tsx +101 -0
- package/src/data-display/KpiRow/block.meta.ts +10 -0
- package/src/data-display/KpiRow/fixtures.ts +34 -0
- package/src/data-display/ResourceCardGrid/ResourceCardGrid.test.tsx +22 -0
- package/src/data-display/ResourceCardGrid/ResourceCardGrid.tsx +98 -0
- package/src/data-display/ResourceCardGrid/block.meta.ts +10 -0
- package/src/data-display/ResourceCardGrid/fixtures.ts +72 -0
- package/src/data-display/StatCard/StatCard.test.tsx +30 -0
- package/src/data-display/StatCard/StatCard.tsx +138 -0
- package/src/data-display/StatCard/block.meta.ts +10 -0
- package/src/data-display/StatCard/fixtures.ts +26 -0
- package/src/data-display/StatusTimeline/StatusTimeline.test.tsx +153 -0
- package/src/data-display/StatusTimeline/StatusTimeline.tsx +114 -0
- package/src/data-display/StatusTimeline/block.meta.ts +10 -0
- package/src/data-display/StatusTimeline/examples/Empty.tsx +6 -0
- package/src/data-display/StatusTimeline/fixtures.ts +62 -0
- package/src/data-display/index.ts +34 -0
- package/src/data-display/shared/TrendDelta.tsx +60 -0
- package/src/feedback/ConfirmDestructive/ConfirmDestructive.test.tsx +47 -0
- package/src/feedback/ConfirmDestructive/ConfirmDestructive.tsx +145 -0
- package/src/feedback/ConfirmDestructive/block.meta.ts +10 -0
- package/src/feedback/EmptyStates/EmptyStates.test.tsx +48 -0
- package/src/feedback/EmptyStates/EmptyStates.tsx +191 -0
- package/src/feedback/EmptyStates/block.meta.ts +10 -0
- package/src/feedback/EmptyStates/fixtures.ts +6 -0
- package/src/feedback/ErrorPanel/ErrorPanel.test.tsx +37 -0
- package/src/feedback/ErrorPanel/ErrorPanel.tsx +146 -0
- package/src/feedback/ErrorPanel/block.meta.ts +10 -0
- package/src/feedback/InlineCallout/InlineCallout.test.tsx +33 -0
- package/src/feedback/InlineCallout/InlineCallout.tsx +81 -0
- package/src/feedback/InlineCallout/block.meta.ts +9 -0
- package/src/feedback/Skeletons/Skeletons.test.tsx +25 -0
- package/src/feedback/Skeletons/Skeletons.tsx +142 -0
- package/src/feedback/Skeletons/block.meta.ts +10 -0
- package/src/feedback/SystemBanner/SystemBanner.test.tsx +29 -0
- package/src/feedback/SystemBanner/SystemBanner.tsx +105 -0
- package/src/feedback/SystemBanner/block.meta.ts +9 -0
- package/src/feedback/index.ts +14 -0
- package/src/feedback/types.ts +13 -0
- package/src/forms/CreateApiKeyDialog/CreateApiKeyDialog.test.tsx +36 -0
- package/src/forms/CreateApiKeyDialog/CreateApiKeyDialog.tsx +197 -0
- package/src/forms/CreateApiKeyDialog/block.meta.ts +10 -0
- package/src/forms/CreateApiKeyDialog/fixtures.ts +31 -0
- package/src/forms/CredentialDisclosureDialog/CredentialDisclosureDialog.test.tsx +339 -0
- package/src/forms/CredentialDisclosureDialog/CredentialDisclosureDialog.tsx +193 -0
- package/src/forms/CredentialDisclosureDialog/block.meta.ts +10 -0
- package/src/forms/CredentialDisclosureDialog/examples/Fresh.tsx +32 -0
- package/src/forms/CredentialDisclosureDialog/examples/ResponseGating.tsx +69 -0
- package/src/forms/CredentialDisclosureDialog/examples/Stored.tsx +32 -0
- package/src/forms/CredentialDisclosureDialog/fixtures.ts +14 -0
- package/src/forms/DangerZone/ConfirmDestructiveDialog.tsx +101 -0
- package/src/forms/DangerZone/DangerZone.test.tsx +28 -0
- package/src/forms/DangerZone/DangerZone.tsx +86 -0
- package/src/forms/DangerZone/block.meta.ts +10 -0
- package/src/forms/DangerZone/fixtures.ts +36 -0
- package/src/forms/DateRangeFilter/DateRangeFilter.test.tsx +26 -0
- package/src/forms/DateRangeFilter/DateRangeFilter.tsx +83 -0
- package/src/forms/DateRangeFilter/block.meta.ts +9 -0
- package/src/forms/DialogWizard/DialogWizard.test.tsx +95 -0
- package/src/forms/DialogWizard/DialogWizard.tsx +39 -0
- package/src/forms/DialogWizard/block.meta.ts +10 -0
- package/src/forms/DialogWizard/examples/CreateResource.tsx +65 -0
- package/src/forms/DialogWizard/fixtures.ts +20 -0
- package/src/forms/FileUploadPanel/FileUploadPanel.test.tsx +43 -0
- package/src/forms/FileUploadPanel/FileUploadPanel.tsx +244 -0
- package/src/forms/FileUploadPanel/block.meta.ts +10 -0
- package/src/forms/FileUploadPanel/fixtures.ts +66 -0
- package/src/forms/FormSection/FormSection.test.tsx +34 -0
- package/src/forms/FormSection/FormSection.tsx +77 -0
- package/src/forms/FormSection/block.meta.ts +9 -0
- package/src/forms/InviteMembersDialog/InviteMembersDialog.test.tsx +37 -0
- package/src/forms/InviteMembersDialog/InviteMembersDialog.tsx +183 -0
- package/src/forms/InviteMembersDialog/block.meta.ts +10 -0
- package/src/forms/InviteMembersDialog/fixtures.ts +14 -0
- package/src/forms/SettingsRow/SettingsRow.test.tsx +50 -0
- package/src/forms/SettingsRow/SettingsRow.tsx +74 -0
- package/src/forms/SettingsRow/block.meta.ts +9 -0
- package/src/forms/ValidationSummary/ValidationSummary.test.tsx +46 -0
- package/src/forms/ValidationSummary/ValidationSummary.tsx +75 -0
- package/src/forms/ValidationSummary/block.meta.ts +9 -0
- package/src/forms/ValidationSummary/fixtures.ts +13 -0
- package/src/forms/index.ts +27 -0
- package/src/index.ts +14 -0
- package/src/marketing/ComparisonTable/ComparisonTable.test.tsx +69 -0
- package/src/marketing/ComparisonTable/ComparisonTable.tsx +245 -0
- package/src/marketing/ComparisonTable/block.meta.ts +10 -0
- package/src/marketing/ComparisonTable/examples/options.tsx +48 -0
- package/src/marketing/ComparisonTable/fixtures.ts +83 -0
- package/src/marketing/Faq/Faq.test.tsx +25 -0
- package/src/marketing/Faq/Faq.tsx +68 -0
- package/src/marketing/Faq/block.meta.ts +10 -0
- package/src/marketing/Faq/fixtures.ts +32 -0
- package/src/marketing/FeatureGrid/FeatureGrid.test.tsx +19 -0
- package/src/marketing/FeatureGrid/FeatureGrid.tsx +79 -0
- package/src/marketing/FeatureGrid/block.meta.ts +10 -0
- package/src/marketing/FeatureGrid/fixtures.ts +36 -0
- package/src/marketing/Hero/Hero.test.tsx +33 -0
- package/src/marketing/Hero/Hero.tsx +107 -0
- package/src/marketing/Hero/block.meta.ts +10 -0
- package/src/marketing/PricingTable/PricingTable.test.tsx +92 -0
- package/src/marketing/PricingTable/PricingTable.tsx +323 -0
- package/src/marketing/PricingTable/block.meta.ts +10 -0
- package/src/marketing/PricingTable/fixtures.ts +34 -0
- package/src/marketing/actions.tsx +40 -0
- package/src/marketing/index.ts +15 -0
- package/src/meta.ts +23 -0
- package/src/page-chrome/BulkActionBar/BulkActionBar.test.tsx +34 -0
- package/src/page-chrome/BulkActionBar/BulkActionBar.tsx +76 -0
- package/src/page-chrome/BulkActionBar/block.meta.ts +9 -0
- package/src/page-chrome/PageHeader/PageHeader.test.tsx +68 -0
- package/src/page-chrome/PageHeader/PageHeader.tsx +152 -0
- package/src/page-chrome/PageHeader/block.meta.ts +10 -0
- package/src/page-chrome/PageHeader/fixtures.ts +29 -0
- package/src/page-chrome/PageToolbar/PageToolbar.test.tsx +38 -0
- package/src/page-chrome/PageToolbar/PageToolbar.tsx +138 -0
- package/src/page-chrome/PageToolbar/block.meta.ts +10 -0
- package/src/page-chrome/PageToolbar/fixtures.ts +8 -0
- package/src/page-chrome/SectionHeader/SectionHeader.test.tsx +28 -0
- package/src/page-chrome/SectionHeader/SectionHeader.tsx +73 -0
- package/src/page-chrome/SectionHeader/block.meta.ts +9 -0
- package/src/page-chrome/StickyActionBar/StickyActionBar.test.tsx +29 -0
- package/src/page-chrome/StickyActionBar/StickyActionBar.tsx +66 -0
- package/src/page-chrome/StickyActionBar/block.meta.ts +9 -0
- package/src/page-chrome/index.ts +13 -0
- package/src/page-chrome/types.ts +25 -0
- package/src/scheduling/AgendaList/AgendaList.test.tsx +28 -0
- package/src/scheduling/AgendaList/AgendaList.tsx +165 -0
- package/src/scheduling/AgendaList/block.meta.ts +10 -0
- package/src/scheduling/MonthCalendar/MonthCalendar.test.tsx +32 -0
- package/src/scheduling/MonthCalendar/MonthCalendar.tsx +223 -0
- package/src/scheduling/MonthCalendar/block.meta.ts +10 -0
- package/src/scheduling/TimeSlotPicker/TimeSlotPicker.test.tsx +26 -0
- package/src/scheduling/TimeSlotPicker/TimeSlotPicker.tsx +115 -0
- package/src/scheduling/TimeSlotPicker/block.meta.ts +10 -0
- package/src/scheduling/TimeSlotPicker/fixtures.ts +10 -0
- package/src/scheduling/WeekCalendar/WeekCalendar.test.tsx +27 -0
- package/src/scheduling/WeekCalendar/WeekCalendar.tsx +274 -0
- package/src/scheduling/WeekCalendar/block.meta.ts +10 -0
- package/src/scheduling/index.ts +11 -0
- package/src/scheduling/shared/CalendarHeader.tsx +54 -0
- package/src/scheduling/shared/EventTile.tsx +21 -0
- package/src/scheduling/shared/dates.ts +98 -0
- package/src/scheduling/shared/events.ts +90 -0
- package/src/scheduling/shared/fixtures.ts +54 -0
- package/src/shell/AppSidebar/AppSidebar.test.tsx +38 -0
- package/src/shell/AppSidebar/AppSidebar.tsx +226 -0
- package/src/shell/AppSidebar/block.meta.ts +10 -0
- package/src/shell/AppSidebar/fixtures.ts +58 -0
- package/src/shell/AppTopbar/AppTopbar.test.tsx +41 -0
- package/src/shell/AppTopbar/AppTopbar.tsx +82 -0
- package/src/shell/AppTopbar/block.meta.ts +19 -0
- package/src/shell/AppTopbar/fixtures.ts +22 -0
- package/src/shell/EnvironmentBadge/EnvironmentBadge.test.tsx +22 -0
- package/src/shell/EnvironmentBadge/EnvironmentBadge.tsx +55 -0
- package/src/shell/EnvironmentBadge/block.meta.ts +10 -0
- package/src/shell/GlobalSearch/GlobalSearch.test.tsx +28 -0
- package/src/shell/GlobalSearch/GlobalSearch.tsx +80 -0
- package/src/shell/GlobalSearch/block.meta.ts +10 -0
- package/src/shell/GlobalSearch/fixtures.ts +64 -0
- package/src/shell/index.ts +11 -0
- package/src/styles.css +2 -0
- package/src/tables/DataTable/DataTable.edit-views.test.tsx +249 -0
- package/src/tables/DataTable/DataTable.layout.test.tsx +188 -0
- package/src/tables/DataTable/DataTable.single-select.test.tsx +501 -0
- package/src/tables/DataTable/DataTable.test.tsx +268 -0
- package/src/tables/DataTable/DataTable.tsx +520 -0
- package/src/tables/DataTable/block.meta.ts +27 -0
- package/src/tables/DataTable/column-menu.tsx +109 -0
- package/src/tables/DataTable/column-sizing.ts +166 -0
- package/src/tables/DataTable/data-table-body.tsx +194 -0
- package/src/tables/DataTable/data-table-props.ts +156 -0
- package/src/tables/DataTable/data-table-row.tsx +220 -0
- package/src/tables/DataTable/data-table-utils.ts +195 -0
- package/src/tables/DataTable/density-toggle.tsx +30 -0
- package/src/tables/DataTable/editable-cell.tsx +274 -0
- package/src/tables/DataTable/examples/editable.tsx +71 -0
- package/src/tables/DataTable/examples/grouped.tsx +18 -0
- package/src/tables/DataTable/examples/resizable-pinned.tsx +20 -0
- package/src/tables/DataTable/examples/saved-views.tsx +61 -0
- package/src/tables/DataTable/examples/single-selection.tsx +55 -0
- package/src/tables/DataTable/examples/virtualized.tsx +20 -0
- package/src/tables/DataTable/fixtures.ts +119 -0
- package/src/tables/DataTable/header-cell.tsx +86 -0
- package/src/tables/DataTable/resize-handle.tsx +88 -0
- package/src/tables/DataTable/single-selection.tsx +149 -0
- package/src/tables/DataTable/use-table-views.ts +79 -0
- package/src/tables/DataTable/use-virtual-rows.ts +83 -0
- package/src/tables/DataTable/views.tsx +186 -0
- package/src/tables/FilterBar/FilterBar.test.tsx +43 -0
- package/src/tables/FilterBar/FilterBar.tsx +199 -0
- package/src/tables/FilterBar/block.meta.ts +10 -0
- package/src/tables/FilterBar/fixtures.ts +32 -0
- package/src/tables/InlineEditRow/InlineEditRow.test.tsx +41 -0
- package/src/tables/InlineEditRow/InlineEditRow.tsx +215 -0
- package/src/tables/InlineEditRow/block.meta.ts +10 -0
- package/src/tables/InlineEditRow/fixtures.ts +28 -0
- package/src/tables/PaginationFooter/PaginationFooter.test.tsx +29 -0
- package/src/tables/PaginationFooter/PaginationFooter.tsx +94 -0
- package/src/tables/PaginationFooter/block.meta.ts +10 -0
- package/src/tables/SubordinateList/SubordinateList.test.tsx +351 -0
- package/src/tables/SubordinateList/SubordinateList.tsx +140 -0
- package/src/tables/SubordinateList/block.meta.ts +10 -0
- package/src/tables/SubordinateList/examples/ResourceForm.tsx +129 -0
- package/src/tables/SubordinateList/examples/empty.tsx +5 -0
- package/src/tables/SubordinateList/examples/error.tsx +5 -0
- package/src/tables/SubordinateList/examples/loading.tsx +5 -0
- package/src/tables/TruncatedCollectionFooter/TruncatedCollectionFooter.test.tsx +159 -0
- package/src/tables/TruncatedCollectionFooter/TruncatedCollectionFooter.tsx +60 -0
- package/src/tables/TruncatedCollectionFooter/block.meta.ts +10 -0
- package/src/tables/TruncatedCollectionFooter/examples/exact.tsx +6 -0
- package/src/tables/TruncatedCollectionFooter/examples/has-more.tsx +12 -0
- package/src/tables/TruncatedCollectionFooter/fixtures.ts +2 -0
- package/src/tables/index.ts +45 -0
- package/src/team/MembersTable/MembersTable.test.tsx +29 -0
- package/src/team/MembersTable/MembersTable.tsx +141 -0
- package/src/team/MembersTable/block.meta.ts +9 -0
- package/src/team/MembersTable/fixtures.ts +19 -0
- package/src/team/NotificationMatrix/NotificationMatrix.test.tsx +36 -0
- package/src/team/NotificationMatrix/NotificationMatrix.tsx +144 -0
- package/src/team/NotificationMatrix/block.meta.ts +9 -0
- package/src/team/NotificationMatrix/fixtures.ts +43 -0
- package/src/team/PendingInvitations/PendingInvitations.test.tsx +30 -0
- package/src/team/PendingInvitations/PendingInvitations.tsx +132 -0
- package/src/team/PendingInvitations/block.meta.ts +9 -0
- package/src/team/PendingInvitations/fixtures.ts +39 -0
- package/src/team/ProfileHeader/ProfileHeader.test.tsx +29 -0
- package/src/team/ProfileHeader/ProfileHeader.tsx +106 -0
- package/src/team/ProfileHeader/block.meta.ts +10 -0
- package/src/team/ProfileHeader/fixtures.ts +28 -0
- package/src/team/SessionsDevices/SessionsDevices.test.tsx +30 -0
- package/src/team/SessionsDevices/SessionsDevices.tsx +126 -0
- package/src/team/SessionsDevices/block.meta.ts +10 -0
- package/src/team/SessionsDevices/fixtures.ts +58 -0
- package/src/team/index.ts +15 -0
- package/src/test/a11y.ts +18 -0
- package/src/test/heading-levels.test.tsx +69 -0
- package/src/test/setup.ts +36 -0
- package/README.md +0 -3
package/package.json
CHANGED
|
@@ -1,6 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gntik-ai/blocks",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "gntik-ui blocks: page chrome, data display, tables, forms, feedback, billing, team and builder sections composed from @gntik-ai/ui.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": [
|
|
8
|
+
"*.css"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json",
|
|
22
|
+
"./styles.css": "./src/styles.css"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"registry": "https://registry.npmjs.org/",
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/gntik-ai/gntik-ui.git",
|
|
31
|
+
"directory": "packages/blocks"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": "^19.0.0",
|
|
35
|
+
"react-dom": "^19.0.0"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@gntik-ai/charts": "0.5.0",
|
|
39
|
+
"@gntik-ai/editor": "0.5.0",
|
|
40
|
+
"@gntik-ai/flow": "0.5.0",
|
|
41
|
+
"@gntik-ai/icons": "0.5.0",
|
|
42
|
+
"@gntik-ai/tokens": "0.2.1",
|
|
43
|
+
"@gntik-ai/ui": "0.5.0",
|
|
44
|
+
"lucide-react": "^1.48.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
48
|
+
"@testing-library/react": "^16.3.3",
|
|
49
|
+
"@testing-library/user-event": "^14.6.7",
|
|
50
|
+
"@types/react": "^19.3.0",
|
|
51
|
+
"@types/react-dom": "^19.3.0",
|
|
52
|
+
"axe-core": "^4.13.0",
|
|
53
|
+
"jsdom": "^30.1.1",
|
|
54
|
+
"react": "^19.3.0",
|
|
55
|
+
"react-dom": "^19.3.0",
|
|
56
|
+
"tsdown": "^0.23.0",
|
|
57
|
+
"typescript": "~6.0.3",
|
|
58
|
+
"vitest": "^5.0.2"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsdown",
|
|
62
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
63
|
+
"test": "vitest run"
|
|
64
|
+
}
|
|
6
65
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { expectNoAxeViolations } from '../../test/a11y';
|
|
4
|
+
import { AssistantCardGrid } from './AssistantCardGrid';
|
|
5
|
+
|
|
6
|
+
describe('AssistantCardGrid', () => {
|
|
7
|
+
it('renders one card per assistant with status and runtime', async () => {
|
|
8
|
+
render(<AssistantCardGrid onCreate={() => {}} />);
|
|
9
|
+
expect(screen.getByRole('heading', { name: 'Assistants' })).toBeInTheDocument();
|
|
10
|
+
expect(screen.getAllByRole('listitem')).toHaveLength(6);
|
|
11
|
+
expect(screen.getByText('6 total · 2 active')).toBeInTheDocument();
|
|
12
|
+
expect(screen.getAllByText('Degraded')).toHaveLength(1);
|
|
13
|
+
expect(screen.getByText('reasoning-small · self-hosted')).toBeInTheDocument();
|
|
14
|
+
await expectNoAxeViolations();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('calls onSelect and onCreate', async () => {
|
|
18
|
+
const user = userEvent.setup();
|
|
19
|
+
const onSelect = vi.fn();
|
|
20
|
+
const onCreate = vi.fn();
|
|
21
|
+
render(<AssistantCardGrid onSelect={onSelect} onCreate={onCreate} />);
|
|
22
|
+
await user.click(screen.getByRole('button', { name: 'Invoice checker' }));
|
|
23
|
+
expect(onSelect).toHaveBeenCalledWith(expect.objectContaining({ id: 'a3' }));
|
|
24
|
+
await user.click(screen.getByRole('button', { name: 'New assistant' }));
|
|
25
|
+
expect(onCreate).toHaveBeenCalledTimes(1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('shows an empty state', () => {
|
|
29
|
+
render(<AssistantCardGrid assistants={[]} />);
|
|
30
|
+
expect(screen.getByText('No assistants yet')).toBeInTheDocument();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useId } from 'react';
|
|
2
|
+
import { Bot, Plus } from '@gntik-ai/icons';
|
|
3
|
+
import { Button, ClickableCard, EmptyState, StatusTag, cn, type StatusDefinition } from '@gntik-ai/ui';
|
|
4
|
+
import { assistants as defaultAssistants, type Assistant, type AssistantStatus } from './fixtures';
|
|
5
|
+
|
|
6
|
+
export type { Assistant, AssistantStatus } from './fixtures';
|
|
7
|
+
|
|
8
|
+
export const ASSISTANT_STATUSES: Record<AssistantStatus, StatusDefinition> = {
|
|
9
|
+
active: { label: 'Active', tone: 'success' },
|
|
10
|
+
paused: { label: 'Paused', tone: 'neutral' },
|
|
11
|
+
draft: { label: 'Draft', tone: 'neutral' },
|
|
12
|
+
degraded: { label: 'Degraded', tone: 'warning' },
|
|
13
|
+
failed: { label: 'Failed', tone: 'destructive' },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface AssistantCardGridProps {
|
|
17
|
+
assistants?: Assistant[];
|
|
18
|
+
title?: string;
|
|
19
|
+
/** Called when a card is activated (cards with `href` also navigate). */
|
|
20
|
+
onSelect?: (assistant: Assistant) => void;
|
|
21
|
+
/** Shows a "New assistant" button. */
|
|
22
|
+
onCreate?: () => void;
|
|
23
|
+
/** Heading level of the section title (default h2); card names use the next level. */
|
|
24
|
+
titleAs?: 'h2' | 'h3';
|
|
25
|
+
className?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Grid of assistants as clickable cards (1 → 2 → 3 columns). */
|
|
29
|
+
export function AssistantCardGrid({
|
|
30
|
+
assistants = defaultAssistants,
|
|
31
|
+
title = 'Assistants',
|
|
32
|
+
onSelect,
|
|
33
|
+
onCreate,
|
|
34
|
+
titleAs: TitleTag = 'h2',
|
|
35
|
+
className,
|
|
36
|
+
}: AssistantCardGridProps) {
|
|
37
|
+
const titleId = useId();
|
|
38
|
+
const active = assistants.filter((a) => a.status === 'active').length;
|
|
39
|
+
return (
|
|
40
|
+
<section aria-labelledby={titleId} className={cn('flex flex-col gap-4', className)}>
|
|
41
|
+
<div className="flex flex-wrap items-end justify-between gap-3">
|
|
42
|
+
<div>
|
|
43
|
+
<TitleTag id={titleId} className="text-[15px] font-semibold tracking-tight text-foreground">
|
|
44
|
+
{title}
|
|
45
|
+
</TitleTag>
|
|
46
|
+
<p className="text-[12.5px] text-muted-foreground">
|
|
47
|
+
{assistants.length} total · {active} active
|
|
48
|
+
</p>
|
|
49
|
+
</div>
|
|
50
|
+
{onCreate && (
|
|
51
|
+
<Button size="sm" icon={Plus} onClick={onCreate}>
|
|
52
|
+
New assistant
|
|
53
|
+
</Button>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
{assistants.length === 0 ? (
|
|
57
|
+
<EmptyState
|
|
58
|
+
icon={Bot}
|
|
59
|
+
title="No assistants yet"
|
|
60
|
+
titleAs={TitleTag === 'h2' ? 'h3' : 'h4'}
|
|
61
|
+
description="Create an assistant to automate a task with a model."
|
|
62
|
+
primaryAction={
|
|
63
|
+
onCreate ? (
|
|
64
|
+
<Button size="sm" icon={Plus} onClick={onCreate}>
|
|
65
|
+
New assistant
|
|
66
|
+
</Button>
|
|
67
|
+
) : undefined
|
|
68
|
+
}
|
|
69
|
+
/>
|
|
70
|
+
) : (
|
|
71
|
+
<ul className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
|
72
|
+
{assistants.map((a) => (
|
|
73
|
+
<li key={a.id} className="flex">
|
|
74
|
+
<ClickableCard
|
|
75
|
+
className="w-full"
|
|
76
|
+
titleAs={TitleTag === 'h2' ? 'h3' : 'h4'}
|
|
77
|
+
href={a.href}
|
|
78
|
+
onClick={() => onSelect?.(a)}
|
|
79
|
+
icon={<Bot aria-hidden />}
|
|
80
|
+
title={a.name}
|
|
81
|
+
description={a.description}
|
|
82
|
+
meta={
|
|
83
|
+
<span className="flex flex-wrap items-center gap-x-2 gap-y-1.5">
|
|
84
|
+
<StatusTag status={a.status} statuses={ASSISTANT_STATUSES} />
|
|
85
|
+
<span className="font-mono text-[11px] text-muted-foreground">
|
|
86
|
+
{a.model} · {a.runtime}
|
|
87
|
+
</span>
|
|
88
|
+
{a.updated && <span className="text-[11px] text-muted-foreground">Updated {a.updated}</span>}
|
|
89
|
+
</span>
|
|
90
|
+
}
|
|
91
|
+
/>
|
|
92
|
+
</li>
|
|
93
|
+
))}
|
|
94
|
+
</ul>
|
|
95
|
+
)}
|
|
96
|
+
</section>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BlockMeta } from '../../meta';
|
|
2
|
+
|
|
3
|
+
export const meta: BlockMeta = {
|
|
4
|
+
name: 'Assistant card grid',
|
|
5
|
+
family: 'ai',
|
|
6
|
+
status: 'beta',
|
|
7
|
+
description:
|
|
8
|
+
'Responsive grid of clickable assistant cards: name, description, model and runtime, status and last update, with a create action.',
|
|
9
|
+
uses: ['ClickableCard', 'StatusTag', 'Button', 'EmptyState'],
|
|
10
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export type AssistantStatus = 'active' | 'paused' | 'draft' | 'degraded' | 'failed';
|
|
2
|
+
|
|
3
|
+
export interface Assistant {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
/** Model identifier, e.g. "general-large". */
|
|
8
|
+
model: string;
|
|
9
|
+
/** Where it runs, e.g. "openai-compatible" or "self-hosted". */
|
|
10
|
+
runtime: string;
|
|
11
|
+
status: AssistantStatus;
|
|
12
|
+
/** Relative update time, e.g. "2h ago". */
|
|
13
|
+
updated?: string;
|
|
14
|
+
/** Optional link to the assistant's page. */
|
|
15
|
+
href?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const assistants: Assistant[] = [
|
|
19
|
+
{
|
|
20
|
+
id: 'a1',
|
|
21
|
+
name: 'Support triage',
|
|
22
|
+
description: 'Classifies incoming tickets and drafts first replies.',
|
|
23
|
+
model: 'general-large',
|
|
24
|
+
runtime: 'openai-compatible',
|
|
25
|
+
status: 'active',
|
|
26
|
+
updated: '2h ago',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'a2',
|
|
30
|
+
name: 'Release notes writer',
|
|
31
|
+
description: 'Summarises merged changes into customer-facing notes.',
|
|
32
|
+
model: 'general-medium',
|
|
33
|
+
runtime: 'Provider A',
|
|
34
|
+
status: 'active',
|
|
35
|
+
updated: 'yesterday',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'a3',
|
|
39
|
+
name: 'Invoice checker',
|
|
40
|
+
description: 'Flags invoices whose totals do not match the usage report.',
|
|
41
|
+
model: 'reasoning-small',
|
|
42
|
+
runtime: 'self-hosted',
|
|
43
|
+
status: 'degraded',
|
|
44
|
+
updated: '3d ago',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'a4',
|
|
48
|
+
name: 'Onboarding guide',
|
|
49
|
+
description: 'Answers setup questions from the product documentation.',
|
|
50
|
+
model: 'general-small',
|
|
51
|
+
runtime: 'self-hosted',
|
|
52
|
+
status: 'paused',
|
|
53
|
+
updated: '1w ago',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'a5',
|
|
57
|
+
name: 'Data cleanup',
|
|
58
|
+
description: 'Normalises imported records before they reach the warehouse.',
|
|
59
|
+
model: 'general-medium',
|
|
60
|
+
runtime: 'openai-compatible',
|
|
61
|
+
status: 'draft',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'a6',
|
|
65
|
+
name: 'Incident summariser',
|
|
66
|
+
description: 'Builds a timeline from alerts and chat during an incident.',
|
|
67
|
+
model: 'general-large',
|
|
68
|
+
runtime: 'Provider A',
|
|
69
|
+
status: 'failed',
|
|
70
|
+
updated: '5h ago',
|
|
71
|
+
},
|
|
72
|
+
];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { render, screen, within } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { expectNoAxeViolations } from '../../test/a11y';
|
|
4
|
+
import { EvaluationScorecard } from './EvaluationScorecard';
|
|
5
|
+
|
|
6
|
+
const bodyRows = () => within(screen.getByRole('table')).getAllByRole('row').slice(1);
|
|
7
|
+
|
|
8
|
+
describe('EvaluationScorecard', () => {
|
|
9
|
+
it('renders datasets × metrics with pass rates, deltas and flagged regressions', async () => {
|
|
10
|
+
render(<EvaluationScorecard />);
|
|
11
|
+
expect(bodyRows()).toHaveLength(5);
|
|
12
|
+
expect(screen.getByRole('columnheader', { name: /p95 latency/ })).toBeInTheDocument();
|
|
13
|
+
const billing = bodyRows().find((r) => r.textContent?.includes('billing-questions'))!;
|
|
14
|
+
expect(within(billing).getByText('2 regressions')).toBeInTheDocument();
|
|
15
|
+
expect(within(billing).getByText('80.8%')).toBeInTheDocument();
|
|
16
|
+
expect(within(billing).getAllByText(/\(regression\)/)).toHaveLength(2);
|
|
17
|
+
expect(within(billing).getByText(/\(below threshold\)/)).toBeInTheDocument();
|
|
18
|
+
const docs = bodyRows().find((r) => r.textContent?.includes('product-docs'))!;
|
|
19
|
+
expect(within(docs).getByText('1 regression')).toBeInTheDocument();
|
|
20
|
+
await expectNoAxeViolations();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('filters to regressed datasets only', async () => {
|
|
24
|
+
const user = userEvent.setup();
|
|
25
|
+
render(<EvaluationScorecard />);
|
|
26
|
+
await user.click(screen.getByRole('button', { name: /Regressions/ }));
|
|
27
|
+
expect(bodyRows().map((r) => within(r).getAllByRole('cell')[0]?.textContent)).toEqual([
|
|
28
|
+
expect.stringContaining('billing-questions'),
|
|
29
|
+
expect.stringContaining('product-docs'),
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('shows an empty row when nothing regressed', async () => {
|
|
34
|
+
const user = userEvent.setup();
|
|
35
|
+
render(<EvaluationScorecard rows={[]} />);
|
|
36
|
+
await user.click(screen.getByRole('button', { name: /Regressions/ }));
|
|
37
|
+
expect(screen.getByText('No regressions against baseline.')).toBeInTheDocument();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { useId, useState } from 'react';
|
|
2
|
+
import { ArrowDown, ArrowUp, Minus } from '@gntik-ai/icons';
|
|
3
|
+
import {
|
|
4
|
+
Badge,
|
|
5
|
+
Table,
|
|
6
|
+
TableBody,
|
|
7
|
+
TableCell,
|
|
8
|
+
TableHead,
|
|
9
|
+
TableHeader,
|
|
10
|
+
TableRow,
|
|
11
|
+
Toggle,
|
|
12
|
+
ToggleGroup,
|
|
13
|
+
cn,
|
|
14
|
+
} from '@gntik-ai/ui';
|
|
15
|
+
import { evalMetrics, evalRows, type EvalMetric, type EvalRow } from './fixtures';
|
|
16
|
+
|
|
17
|
+
export type { EvalMetric, EvalRow } from './fixtures';
|
|
18
|
+
|
|
19
|
+
export interface EvaluationScorecardProps {
|
|
20
|
+
metrics?: EvalMetric[];
|
|
21
|
+
rows?: EvalRow[];
|
|
22
|
+
title?: string;
|
|
23
|
+
/** Label of the run compared against, e.g. "baseline · v12". */
|
|
24
|
+
baselineLabel?: string;
|
|
25
|
+
/** Pass-rate drop (ratio) that counts as a regression. */
|
|
26
|
+
passRateTolerance?: number;
|
|
27
|
+
/** Heading level of the title, to fit the page outline (default h3). */
|
|
28
|
+
titleAs?: 'h2' | 'h3' | 'h4';
|
|
29
|
+
className?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type Trend = 'better' | 'worse' | 'flat';
|
|
33
|
+
|
|
34
|
+
function formatValue(value: number, format: EvalMetric['format'] = 'percent') {
|
|
35
|
+
if (format === 'percent') return `${(value * 100).toFixed(1)}%`;
|
|
36
|
+
if (format === 'ms') return `${Math.round(value)}ms`;
|
|
37
|
+
return value.toFixed(2);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function formatDelta(delta: number, format: EvalMetric['format'] = 'percent') {
|
|
41
|
+
const sign = delta > 0 ? '+' : delta < 0 ? '−' : '±';
|
|
42
|
+
const abs = Math.abs(delta);
|
|
43
|
+
if (format === 'percent') return `${sign}${(abs * 100).toFixed(1)} pt`;
|
|
44
|
+
if (format === 'ms') return `${sign}${Math.round(abs)}ms`;
|
|
45
|
+
return `${sign}${abs.toFixed(2)}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function trendOf(delta: number, higherIsBetter: boolean, tolerance: number): Trend {
|
|
49
|
+
const signed = higherIsBetter ? delta : -delta;
|
|
50
|
+
if (signed < -tolerance) return 'worse';
|
|
51
|
+
if (signed > tolerance) return 'better';
|
|
52
|
+
return 'flat';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const TREND: Record<Trend, { className: string; sr: string }> = {
|
|
56
|
+
better: { className: 'text-success-text', sr: 'improved' },
|
|
57
|
+
worse: { className: 'text-destructive-text', sr: 'regression' },
|
|
58
|
+
flat: { className: 'text-muted-foreground', sr: 'unchanged' },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function Delta({
|
|
62
|
+
delta,
|
|
63
|
+
trend,
|
|
64
|
+
format,
|
|
65
|
+
increase,
|
|
66
|
+
}: {
|
|
67
|
+
delta: number;
|
|
68
|
+
trend: Trend;
|
|
69
|
+
format: EvalMetric['format'];
|
|
70
|
+
increase: boolean;
|
|
71
|
+
}) {
|
|
72
|
+
const t = TREND[trend];
|
|
73
|
+
const TrendIcon = trend === 'flat' ? Minus : increase ? ArrowUp : ArrowDown;
|
|
74
|
+
return (
|
|
75
|
+
<span className={cn('inline-flex items-center gap-0.5 font-mono text-[10.5px]', t.className)}>
|
|
76
|
+
<TrendIcon size={11} aria-hidden />
|
|
77
|
+
{formatDelta(delta, format)}
|
|
78
|
+
<span className="sr-only"> ({t.sr})</span>
|
|
79
|
+
</span>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function analyse(row: EvalRow, metrics: EvalMetric[], passRateTolerance: number) {
|
|
84
|
+
const passRate = row.samples > 0 ? row.passed / row.samples : 0;
|
|
85
|
+
const basePassRate = row.samples > 0 ? row.baselinePassed / row.samples : 0;
|
|
86
|
+
const passTrend = trendOf(passRate - basePassRate, true, passRateTolerance);
|
|
87
|
+
const cells = metrics.map((m) => {
|
|
88
|
+
const value = row.scores[m.id];
|
|
89
|
+
const base = row.baseline[m.id];
|
|
90
|
+
const higher = m.higherIsBetter ?? true;
|
|
91
|
+
const delta = value !== undefined && base !== undefined ? value - base : 0;
|
|
92
|
+
const trend = value !== undefined && base !== undefined ? trendOf(delta, higher, m.tolerance ?? 0) : 'flat';
|
|
93
|
+
const passes = value === undefined ? undefined : higher ? value >= m.threshold : value <= m.threshold;
|
|
94
|
+
return { metric: m, value, delta, trend, passes };
|
|
95
|
+
});
|
|
96
|
+
const regressions = cells.filter((c) => c.trend === 'worse').length + (passTrend === 'worse' ? 1 : 0);
|
|
97
|
+
return { passRate, basePassRate, passTrend, cells, regressions };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Evaluation results by dataset and metric, compared with a baseline run. */
|
|
101
|
+
export function EvaluationScorecard({
|
|
102
|
+
metrics = evalMetrics,
|
|
103
|
+
rows = evalRows,
|
|
104
|
+
title = 'Evaluation results',
|
|
105
|
+
baselineLabel = 'baseline',
|
|
106
|
+
passRateTolerance = 0.02,
|
|
107
|
+
titleAs: TitleTag = 'h3',
|
|
108
|
+
className,
|
|
109
|
+
}: EvaluationScorecardProps) {
|
|
110
|
+
const titleId = useId();
|
|
111
|
+
const [filter, setFilter] = useState<'all' | 'regressions'>('all');
|
|
112
|
+
const analysed = rows.map((row) => ({ row, ...analyse(row, metrics, passRateTolerance) }));
|
|
113
|
+
const regressed = analysed.filter((a) => a.regressions > 0);
|
|
114
|
+
const shown = filter === 'regressions' ? regressed : analysed;
|
|
115
|
+
const samples = rows.reduce((s, r) => s + r.samples, 0);
|
|
116
|
+
const overall = samples > 0 ? rows.reduce((s, r) => s + r.passed, 0) / samples : 0;
|
|
117
|
+
const overallBase = samples > 0 ? rows.reduce((s, r) => s + r.baselinePassed, 0) / samples : 0;
|
|
118
|
+
const overallTrend = trendOf(overall - overallBase, true, passRateTolerance / 2);
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<section
|
|
122
|
+
aria-labelledby={titleId}
|
|
123
|
+
className={cn('overflow-hidden rounded-lg border border-border bg-card shadow-sm', className)}
|
|
124
|
+
>
|
|
125
|
+
<header className="flex flex-col gap-3 border-b border-border px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
|
|
126
|
+
<div>
|
|
127
|
+
<TitleTag id={titleId} className="text-[13.5px] font-semibold text-foreground">
|
|
128
|
+
{title}
|
|
129
|
+
</TitleTag>
|
|
130
|
+
<p className="mt-0.5 flex flex-wrap items-center gap-x-2 text-[12px] text-muted-foreground">
|
|
131
|
+
<span>
|
|
132
|
+
Pass rate <span className="font-mono font-semibold text-foreground">{formatValue(overall)}</span>
|
|
133
|
+
</span>
|
|
134
|
+
<Delta
|
|
135
|
+
delta={overall - overallBase}
|
|
136
|
+
trend={overallTrend}
|
|
137
|
+
format="percent"
|
|
138
|
+
increase={overall >= overallBase}
|
|
139
|
+
/>
|
|
140
|
+
<span>
|
|
141
|
+
vs {baselineLabel} · {samples.toLocaleString('en-US')} samples
|
|
142
|
+
</span>
|
|
143
|
+
</p>
|
|
144
|
+
</div>
|
|
145
|
+
<ToggleGroup
|
|
146
|
+
aria-label="Show datasets"
|
|
147
|
+
size="sm"
|
|
148
|
+
value={[filter]}
|
|
149
|
+
onValueChange={(v) => v[0] && setFilter(v[0] as 'all' | 'regressions')}
|
|
150
|
+
>
|
|
151
|
+
<Toggle value="all">
|
|
152
|
+
All <span className="font-mono text-[10.5px] opacity-70">{rows.length}</span>
|
|
153
|
+
</Toggle>
|
|
154
|
+
<Toggle value="regressions">
|
|
155
|
+
Regressions <span className="font-mono text-[10.5px] opacity-70">{regressed.length}</span>
|
|
156
|
+
</Toggle>
|
|
157
|
+
</ToggleGroup>
|
|
158
|
+
</header>
|
|
159
|
+
<Table density="compact" containerLabel={title}>
|
|
160
|
+
<TableHeader>
|
|
161
|
+
<TableRow>
|
|
162
|
+
<TableHead>Dataset</TableHead>
|
|
163
|
+
<TableHead align="right">Pass rate</TableHead>
|
|
164
|
+
{metrics.map((m) => (
|
|
165
|
+
<TableHead key={m.id} align="right">
|
|
166
|
+
{m.label}
|
|
167
|
+
<span className="block font-mono text-[10px] font-normal text-muted-foreground">
|
|
168
|
+
{(m.higherIsBetter ?? true) ? '≥' : '≤'} {formatValue(m.threshold, m.format)}
|
|
169
|
+
</span>
|
|
170
|
+
</TableHead>
|
|
171
|
+
))}
|
|
172
|
+
</TableRow>
|
|
173
|
+
</TableHeader>
|
|
174
|
+
<TableBody>
|
|
175
|
+
{shown.length === 0 ? (
|
|
176
|
+
<TableRow>
|
|
177
|
+
<TableCell colSpan={metrics.length + 2} className="py-6 text-center text-muted-foreground">
|
|
178
|
+
No regressions against {baselineLabel}.
|
|
179
|
+
</TableCell>
|
|
180
|
+
</TableRow>
|
|
181
|
+
) : (
|
|
182
|
+
shown.map(({ row, passRate, basePassRate, passTrend, cells, regressions }) => (
|
|
183
|
+
<TableRow key={row.id} data-regressed={regressions > 0 || undefined}>
|
|
184
|
+
<TableCell>
|
|
185
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
186
|
+
<span className="font-mono text-[12.5px] text-foreground">{row.dataset}</span>
|
|
187
|
+
{regressions > 0 && (
|
|
188
|
+
<Badge tone="destructive" size="sm">
|
|
189
|
+
{regressions} {regressions === 1 ? 'regression' : 'regressions'}
|
|
190
|
+
</Badge>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
<span className="text-[11px] text-muted-foreground">
|
|
194
|
+
{row.passed}/{row.samples} passed
|
|
195
|
+
</span>
|
|
196
|
+
</TableCell>
|
|
197
|
+
<TableCell align="right">
|
|
198
|
+
<span className="block font-mono text-[12.5px] text-foreground">{formatValue(passRate)}</span>
|
|
199
|
+
<Delta
|
|
200
|
+
delta={passRate - basePassRate}
|
|
201
|
+
trend={passTrend}
|
|
202
|
+
format="percent"
|
|
203
|
+
increase={passRate >= basePassRate}
|
|
204
|
+
/>
|
|
205
|
+
</TableCell>
|
|
206
|
+
{cells.map(({ metric, value, delta, trend, passes }) => (
|
|
207
|
+
<TableCell key={metric.id} align="right">
|
|
208
|
+
{value === undefined ? (
|
|
209
|
+
<span className="text-muted-foreground">—</span>
|
|
210
|
+
) : (
|
|
211
|
+
<>
|
|
212
|
+
<span
|
|
213
|
+
className={cn(
|
|
214
|
+
'block font-mono text-[12.5px]',
|
|
215
|
+
passes ? 'text-foreground' : 'text-warning-text',
|
|
216
|
+
)}
|
|
217
|
+
>
|
|
218
|
+
{formatValue(value, metric.format)}
|
|
219
|
+
{!passes && <span className="sr-only"> (below threshold)</span>}
|
|
220
|
+
</span>
|
|
221
|
+
<Delta delta={delta} trend={trend} format={metric.format} increase={delta >= 0} />
|
|
222
|
+
</>
|
|
223
|
+
)}
|
|
224
|
+
</TableCell>
|
|
225
|
+
))}
|
|
226
|
+
</TableRow>
|
|
227
|
+
))
|
|
228
|
+
)}
|
|
229
|
+
</TableBody>
|
|
230
|
+
</Table>
|
|
231
|
+
</section>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BlockMeta } from '../../meta';
|
|
2
|
+
|
|
3
|
+
export const meta: BlockMeta = {
|
|
4
|
+
name: 'Evaluation scorecard',
|
|
5
|
+
family: 'ai',
|
|
6
|
+
status: 'beta',
|
|
7
|
+
description:
|
|
8
|
+
'Evaluation run as a datasets × metrics table: pass rate, scores against thresholds, deltas vs the baseline and flagged regressions, with a regressions-only filter.',
|
|
9
|
+
uses: ['Table', 'Badge', 'ToggleGroup', 'Toggle'],
|
|
10
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export interface EvalMetric {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
/** Minimum (or maximum, when lower is better) value that passes. */
|
|
5
|
+
threshold: number;
|
|
6
|
+
/** Defaults to true; false for costs and latencies. */
|
|
7
|
+
higherIsBetter?: boolean;
|
|
8
|
+
/** How values are shown: ratio as a percentage, or milliseconds. */
|
|
9
|
+
format?: 'percent' | 'ms' | 'number';
|
|
10
|
+
/** Change vs baseline that counts as a regression (same unit as the values). */
|
|
11
|
+
tolerance?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface EvalRow {
|
|
15
|
+
id: string;
|
|
16
|
+
dataset: string;
|
|
17
|
+
samples: number;
|
|
18
|
+
/** Samples that passed every check in this run. */
|
|
19
|
+
passed: number;
|
|
20
|
+
/** Samples that passed in the baseline run. */
|
|
21
|
+
baselinePassed: number;
|
|
22
|
+
scores: Record<string, number>;
|
|
23
|
+
baseline: Record<string, number>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const evalMetrics: EvalMetric[] = [
|
|
27
|
+
{ id: 'accuracy', label: 'Accuracy', threshold: 0.85, format: 'percent', tolerance: 0.02 },
|
|
28
|
+
{ id: 'faithfulness', label: 'Faithfulness', threshold: 0.8, format: 'percent', tolerance: 0.02 },
|
|
29
|
+
{ id: 'relevance', label: 'Relevance', threshold: 0.8, format: 'percent', tolerance: 0.02 },
|
|
30
|
+
{ id: 'latency', label: 'p95 latency', threshold: 2000, higherIsBetter: false, format: 'ms', tolerance: 150 },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export const evalRows: EvalRow[] = [
|
|
34
|
+
{
|
|
35
|
+
id: 'r1',
|
|
36
|
+
dataset: 'support-faq',
|
|
37
|
+
samples: 240,
|
|
38
|
+
passed: 221,
|
|
39
|
+
baselinePassed: 214,
|
|
40
|
+
scores: { accuracy: 0.93, faithfulness: 0.91, relevance: 0.89, latency: 1420 },
|
|
41
|
+
baseline: { accuracy: 0.9, faithfulness: 0.9, relevance: 0.88, latency: 1510 },
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'r2',
|
|
45
|
+
dataset: 'billing-questions',
|
|
46
|
+
samples: 120,
|
|
47
|
+
passed: 97,
|
|
48
|
+
baselinePassed: 106,
|
|
49
|
+
scores: { accuracy: 0.84, faithfulness: 0.86, relevance: 0.82, latency: 1650 },
|
|
50
|
+
baseline: { accuracy: 0.89, faithfulness: 0.87, relevance: 0.83, latency: 1600 },
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'r3',
|
|
54
|
+
dataset: 'product-docs',
|
|
55
|
+
samples: 310,
|
|
56
|
+
passed: 284,
|
|
57
|
+
baselinePassed: 281,
|
|
58
|
+
scores: { accuracy: 0.92, faithfulness: 0.88, relevance: 0.9, latency: 1880 },
|
|
59
|
+
baseline: { accuracy: 0.91, faithfulness: 0.88, relevance: 0.9, latency: 1700 },
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'r4',
|
|
63
|
+
dataset: 'edge-cases',
|
|
64
|
+
samples: 80,
|
|
65
|
+
passed: 58,
|
|
66
|
+
baselinePassed: 55,
|
|
67
|
+
scores: { accuracy: 0.76, faithfulness: 0.79, relevance: 0.81, latency: 2210 },
|
|
68
|
+
baseline: { accuracy: 0.73, faithfulness: 0.78, relevance: 0.8, latency: 2250 },
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'r5',
|
|
72
|
+
dataset: 'multilingual',
|
|
73
|
+
samples: 150,
|
|
74
|
+
passed: 132,
|
|
75
|
+
baselinePassed: 131,
|
|
76
|
+
scores: { accuracy: 0.88, faithfulness: 0.85, relevance: 0.86, latency: 1540 },
|
|
77
|
+
baseline: { accuracy: 0.88, faithfulness: 0.86, relevance: 0.85, latency: 1530 },
|
|
78
|
+
},
|
|
79
|
+
];
|