create-nextblock 0.12.9 → 0.12.12
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/docker-template/.dockerignore +23 -23
- package/docker-template/.env.docker.example +69 -69
- package/docker-template/docker/db/init/99-jwt.sql +6 -6
- package/docker-template/docker/db/init/99-roles.sql +25 -25
- package/docker-template/docker/kong/kong.yml +112 -112
- package/docker-template/scripts/docker-setup.mjs +310 -310
- package/libs/db/tsconfig.lib.json +3 -3
- package/libs/editor/tsconfig.lib.json +3 -3
- package/libs/ui/tsconfig.lib.json +3 -3
- package/libs/utils/tsconfig.json +3 -3
- package/package.json +1 -1
- package/project.json +19 -19
- package/templates/nextblock-template/.browserslistrc +11 -11
- package/templates/nextblock-template/.dockerignore +23 -23
- package/templates/nextblock-template/README.md +34 -34
- package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -9
- package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -27
- package/templates/nextblock-template/app/(auth-pages)/sign-up/SignUpForm.tsx +141 -141
- package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +46 -46
- package/templates/nextblock-template/app/(auth-pages)/two-factor/actions.ts +91 -91
- package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx +123 -123
- package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -51
- package/templates/nextblock-template/app/ToasterProvider.tsx +17 -17
- package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -7
- package/templates/nextblock-template/app/actions/consent.ts +57 -57
- package/templates/nextblock-template/app/actions/interactions.test.ts +301 -301
- package/templates/nextblock-template/app/actions/interactions.ts +373 -373
- package/templates/nextblock-template/app/actions/postActions.ts +146 -146
- package/templates/nextblock-template/app/actions/twoFactorEmail.ts +22 -22
- package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -153
- package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -96
- package/templates/nextblock-template/app/api/ai/global-agent/route.ts +964 -964
- package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -48
- package/templates/nextblock-template/app/api/cms/check-updates/route.ts +44 -44
- package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -33
- package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -63
- package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3490 -3490
- package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +5597 -5597
- package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +39 -39
- package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -92
- package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -43
- package/templates/nextblock-template/app/api/media/library/route.ts +69 -69
- package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -53
- package/templates/nextblock-template/app/api/media/record/route.ts +160 -160
- package/templates/nextblock-template/app/api/search/route.ts +43 -43
- package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +441 -441
- package/templates/nextblock-template/app/auth/callback/route.ts +31 -31
- package/templates/nextblock-template/app/cart/page.tsx +7 -7
- package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -160
- package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
- package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
- package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +29 -29
- package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +90 -90
- package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -122
- package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
- package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +244 -244
- package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
- package/templates/nextblock-template/app/cms/components/SystemAlertsBanner.tsx +112 -112
- package/templates/nextblock-template/app/cms/components/TwoFactorReminderBanner.tsx +45 -45
- package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -102
- package/templates/nextblock-template/app/cms/components/system-alerts-actions.ts +31 -31
- package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -66
- package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -519
- package/templates/nextblock-template/app/cms/custom-blocks/components/BlockComposer.tsx +1522 -1522
- package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -256
- package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -384
- package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -221
- package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -438
- package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -228
- package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +130 -130
- package/templates/nextblock-template/app/cms/import-export/ContentTransferControls.tsx +431 -391
- package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -226
- package/templates/nextblock-template/app/cms/interactions/InteractionsModerationClient.tsx +408 -408
- package/templates/nextblock-template/app/cms/interactions/page.tsx +51 -51
- package/templates/nextblock-template/app/cms/layout.tsx +73 -73
- package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -273
- package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -122
- package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
- package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -69
- package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +149 -158
- package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +101 -101
- package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +3 -3
- package/templates/nextblock-template/app/cms/pages/page.tsx +18 -17
- package/templates/nextblock-template/app/cms/posts/page.tsx +19 -18
- package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +12 -0
- package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -13
- package/templates/nextblock-template/app/cms/products/new/page.tsx +135 -135
- package/templates/nextblock-template/app/cms/products/page.tsx +39 -27
- package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -133
- package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -5
- package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -456
- package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -115
- package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -31
- package/templates/nextblock-template/app/cms/revisions/service.ts +19 -19
- package/templates/nextblock-template/app/cms/revisions/utils.ts +132 -132
- package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -1004
- package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -29
- package/templates/nextblock-template/app/cms/settings/bot-protection/actions.ts +93 -93
- package/templates/nextblock-template/app/cms/settings/bot-protection/components/BotProtectionForm.tsx +129 -129
- package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -24
- package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +497 -497
- package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +410 -410
- package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +246 -246
- package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +80 -80
- package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -331
- package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -494
- package/templates/nextblock-template/app/cms/settings/email/actions.ts +64 -64
- package/templates/nextblock-template/app/cms/settings/email/components/EmailForm.tsx +181 -181
- package/templates/nextblock-template/app/cms/settings/email/page.tsx +28 -28
- package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -767
- package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -93
- package/templates/nextblock-template/app/cms/settings/google-analytics/actions.ts +60 -60
- package/templates/nextblock-template/app/cms/settings/google-analytics/components/GoogleAnalyticsForm.tsx +129 -129
- package/templates/nextblock-template/app/cms/settings/google-analytics/page.tsx +26 -26
- package/templates/nextblock-template/app/cms/settings/languages/actions.ts +16 -16
- package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
- package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -339
- package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -21
- package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -20
- package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -42
- package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -133
- package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
- package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -122
- package/templates/nextblock-template/app/cms/settings/privacy/actions.ts +52 -52
- package/templates/nextblock-template/app/cms/settings/privacy/components/PrivacyForm.tsx +148 -148
- package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/registration/actions.ts +44 -44
- package/templates/nextblock-template/app/cms/settings/registration/components/RegistrationForm.tsx +65 -65
- package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/security/actions.ts +284 -284
- package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +522 -522
- package/templates/nextblock-template/app/cms/settings/security/page.tsx +33 -33
- package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -21
- package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -20
- package/templates/nextblock-template/app/cms/users/actions.ts +222 -222
- package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +12 -12
- package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +44 -44
- package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
- package/templates/nextblock-template/app/lib/sitemap-utils.ts +253 -253
- package/templates/nextblock-template/app/profile/ProfileAccountSidebar.tsx +73 -73
- package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -16
- package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -9
- package/templates/nextblock-template/app/profile/account-data.ts +37 -37
- package/templates/nextblock-template/app/profile/account-links.ts +22 -22
- package/templates/nextblock-template/app/profile/account-types.ts +11 -11
- package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
- package/templates/nextblock-template/app/profile/orders/[id]/CustomerOrderDetailPageClient.tsx +79 -79
- package/templates/nextblock-template/app/profile/orders/[id]/page.tsx +32 -32
- package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
- package/templates/nextblock-template/app/profile/page.tsx +42 -42
- package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -128
- package/templates/nextblock-template/app/profile/password/actions.ts +59 -59
- package/templates/nextblock-template/app/profile/password/page.tsx +27 -27
- package/templates/nextblock-template/app/setup/SetupWizard.tsx +678 -678
- package/templates/nextblock-template/app/setup/layout.tsx +13 -13
- package/templates/nextblock-template/app/setup/page.tsx +111 -111
- package/templates/nextblock-template/app/sitemap.ts +130 -130
- package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -7
- package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -23
- package/templates/nextblock-template/components/DeferredGoogleAnalytics.tsx +70 -70
- package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -70
- package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -69
- package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -47
- package/templates/nextblock-template/components/FooterNavigation.tsx +32 -32
- package/templates/nextblock-template/components/GitHubLoginButton.tsx +15 -15
- package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -47
- package/templates/nextblock-template/components/LanguageSwitcher.tsx +2 -2
- package/templates/nextblock-template/components/PostCommentsSection.tsx +369 -369
- package/templates/nextblock-template/components/ProductReviewsSection.tsx +419 -419
- package/templates/nextblock-template/components/PublicEnvBootstrap.tsx +30 -30
- package/templates/nextblock-template/components/ResponsiveNav.tsx +14 -14
- package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -182
- package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -12
- package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
- package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
- package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +249 -249
- package/templates/nextblock-template/components/blocks/types.ts +7 -7
- package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
- package/templates/nextblock-template/components/form-message.tsx +32 -32
- package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +170 -170
- package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +70 -70
- package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -28
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -166
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +464 -464
- package/templates/nextblock-template/components/submit-button.tsx +23 -23
- package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
- package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -21
- package/templates/nextblock-template/context/AuthContext.tsx +23 -23
- package/templates/nextblock-template/context/LanguageContext.tsx +16 -16
- package/templates/nextblock-template/context/language-rest-client.ts +32 -32
- package/templates/nextblock-template/docker/db/init/99-jwt.sql +6 -6
- package/templates/nextblock-template/docker/db/init/99-roles.sql +25 -25
- package/templates/nextblock-template/docker/kong/kong.yml +112 -112
- package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -94
- package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -364
- package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -202
- package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +246 -246
- package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +176 -176
- package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -146
- package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +1319 -1319
- package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -222
- package/templates/nextblock-template/docs/11-SELF-HOSTED-DOCKER.md +173 -173
- package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +170 -170
- package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +151 -151
- package/templates/nextblock-template/docs/README.md +39 -39
- package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12505 -12505
- package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -21
- package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -101
- package/templates/nextblock-template/index.d.ts +7 -7
- package/templates/nextblock-template/lib/app-secrets.ts +39 -39
- package/templates/nextblock-template/lib/auth/cookies.ts +47 -47
- package/templates/nextblock-template/lib/auth/crypto.ts +45 -45
- package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -92
- package/templates/nextblock-template/lib/auth/twoFactor.ts +208 -208
- package/templates/nextblock-template/lib/auth-redirects.ts +46 -46
- package/templates/nextblock-template/lib/blocks/README.md +13 -13
- package/templates/nextblock-template/lib/blocks/blockRegistry.ts +6 -6
- package/templates/nextblock-template/lib/blocks/blockTypes.ts +18 -18
- package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
- package/templates/nextblock-template/lib/cms-transfer/csv.test.ts +100 -77
- package/templates/nextblock-template/lib/cms-transfer/csv.ts +403 -399
- package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -2243
- package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -145
- package/templates/nextblock-template/lib/config/email-settings.ts +217 -217
- package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -87
- package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -178
- package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -140
- package/templates/nextblock-template/lib/custom-block-r2-upload.ts +88 -88
- package/templates/nextblock-template/lib/custom-block-relation-registry.ts +256 -256
- package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -227
- package/templates/nextblock-template/lib/custom-block-relations.ts +279 -279
- package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -14
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -172
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -213
- package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -22
- package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -193
- package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -133
- package/templates/nextblock-template/lib/email/branding-format.ts +123 -123
- package/templates/nextblock-template/lib/email/branding.ts +76 -76
- package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -121
- package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -206
- package/templates/nextblock-template/lib/full-backup/server.ts +743 -743
- package/templates/nextblock-template/lib/logos/active-logo.ts +53 -53
- package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +54 -54
- package/templates/nextblock-template/lib/onboarding/actions.ts +31 -31
- package/templates/nextblock-template/lib/onboarding/status.ts +222 -222
- package/templates/nextblock-template/lib/posts/readTime.ts +60 -60
- package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -57
- package/templates/nextblock-template/lib/privacy/contact-emails.ts +64 -64
- package/templates/nextblock-template/lib/privacy/settings.ts +115 -115
- package/templates/nextblock-template/lib/privacy/types.ts +69 -69
- package/templates/nextblock-template/lib/promotions/server.test.ts +74 -74
- package/templates/nextblock-template/lib/promotions/server.ts +741 -741
- package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -142
- package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -255
- package/templates/nextblock-template/lib/search/server.ts +585 -585
- package/templates/nextblock-template/lib/search/types.ts +27 -27
- package/templates/nextblock-template/lib/setup/actions.ts +460 -460
- package/templates/nextblock-template/lib/setup/env-status.ts +125 -125
- package/templates/nextblock-template/lib/setup/env-write.ts +111 -111
- package/templates/nextblock-template/lib/setup/migrations-bundle.ts +37 -37
- package/templates/nextblock-template/lib/setup/provisioning.ts +59 -59
- package/templates/nextblock-template/lib/setup/schema-apply.ts +408 -408
- package/templates/nextblock-template/lib/setup/system-config.ts +105 -105
- package/templates/nextblock-template/lib/setup/types.ts +18 -18
- package/templates/nextblock-template/lib/site-url.ts +48 -48
- package/templates/nextblock-template/lib/storage/provider.ts +66 -66
- package/templates/nextblock-template/lib/storage/supabase-storage.ts +103 -103
- package/templates/nextblock-template/lib/updates/check-upstream.ts +441 -441
- package/templates/nextblock-template/lib/updates/github-device.ts +206 -206
- package/templates/nextblock-template/lib/updates/repo-identity.ts +56 -56
- package/templates/nextblock-template/package.json +1 -1
- package/templates/nextblock-template/postcss.config.js +6 -6
- package/templates/nextblock-template/scripts/backup.js +115 -115
- package/templates/nextblock-template/scripts/docker-setup.mjs +310 -310
- package/templates/nextblock-template/scripts/restore.js +385 -385
- package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +98 -98
- package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -62
- package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -537
- package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -58
- package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -188
- package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -123
- package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -133
- package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -65
- package/templates/nextblock-template/tailwind.config.js +25 -25
- package/templates/nextblock-template/tools/build-migrate.mjs +209 -209
- package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
- package/templates/nextblock-template/types/jsdom.d.ts +6 -6
- package/tsconfig.base.json +3 -3
- package/templates/nextblock-template/tsconfig.tsbuildinfo +0 -1
package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx
CHANGED
|
@@ -1,410 +1,410 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { useState } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
Alert,
|
|
6
|
-
AlertDescription,
|
|
7
|
-
AlertTitle,
|
|
8
|
-
Badge,
|
|
9
|
-
Button,
|
|
10
|
-
Card,
|
|
11
|
-
CardContent,
|
|
12
|
-
CardDescription,
|
|
13
|
-
CardHeader,
|
|
14
|
-
CardTitle,
|
|
15
|
-
Input,
|
|
16
|
-
Label,
|
|
17
|
-
SearchableSelect,
|
|
18
|
-
} from '@nextblock-cms/ui';
|
|
19
|
-
import {
|
|
20
|
-
AlertTriangle,
|
|
21
|
-
Brain,
|
|
22
|
-
CheckCircle2,
|
|
23
|
-
Cpu,
|
|
24
|
-
KeyRound,
|
|
25
|
-
ServerCog,
|
|
26
|
-
ShieldCheck,
|
|
27
|
-
Trash2,
|
|
28
|
-
} from 'lucide-react';
|
|
29
|
-
|
|
30
|
-
import type { CortexAiStoredModelSelection } from '@nextblock-cms/cortex/client';
|
|
31
|
-
import {
|
|
32
|
-
clearCortexAiModelSelectionAction,
|
|
33
|
-
clearOpenRouterApiKeyAction,
|
|
34
|
-
saveCortexAiModelSelectionAction,
|
|
35
|
-
saveOpenRouterApiKeyAction,
|
|
36
|
-
} from './actions';
|
|
37
|
-
|
|
38
|
-
const CORTEX_AI_SETTINGS_CHANGED_EVENT = 'nextblock:cortex-ai-settings-changed';
|
|
39
|
-
|
|
40
|
-
type StoredCortexAiSettingsClientProps = {
|
|
41
|
-
compatibleModels: Array<{
|
|
42
|
-
id: string;
|
|
43
|
-
name: string;
|
|
44
|
-
pricing: Record<string, string>;
|
|
45
|
-
context_length: number | null;
|
|
46
|
-
}>;
|
|
47
|
-
isPackageActive: boolean;
|
|
48
|
-
hasEnvOpenRouterKey: boolean;
|
|
49
|
-
maskedEnvOpenRouterKey: string | null;
|
|
50
|
-
hasStoredOpenRouterKey: boolean;
|
|
51
|
-
maskedStoredOpenRouterKey: string | null;
|
|
52
|
-
storedKeyUpdatedAt: string | null;
|
|
53
|
-
selectedModel: CortexAiStoredModelSelection | null;
|
|
54
|
-
selectedModelUpdatedAt: string | null;
|
|
55
|
-
hasEncryptionKey: boolean;
|
|
56
|
-
modelCatalogError: string | null;
|
|
57
|
-
successMessage?: string;
|
|
58
|
-
errorMessage?: string;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
function formatTokenPrice(value: string | undefined) {
|
|
62
|
-
const amount = Number(value);
|
|
63
|
-
if (!Number.isFinite(amount)) return null;
|
|
64
|
-
if (amount === 0) return '$0';
|
|
65
|
-
const perMillion = amount * 1_000_000;
|
|
66
|
-
return `$${perMillion < 0.01 ? perMillion.toFixed(4) : perMillion.toFixed(2)}`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function formatModelPricing(pricing: Record<string, string>) {
|
|
70
|
-
const promptPrice = formatTokenPrice(pricing.prompt);
|
|
71
|
-
const completionPrice = formatTokenPrice(pricing.completion);
|
|
72
|
-
if (promptPrice === '$0' && completionPrice === '$0') return 'Free';
|
|
73
|
-
if (promptPrice && completionPrice) return `${promptPrice}/1M input - ${completionPrice}/1M output`;
|
|
74
|
-
return 'Pricing varies';
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function notifyCortexAiSettingsChanged() {
|
|
78
|
-
window.dispatchEvent(new Event(CORTEX_AI_SETTINGS_CHANGED_EVENT));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function StoredCortexAiSettingsClient({
|
|
82
|
-
compatibleModels,
|
|
83
|
-
isPackageActive,
|
|
84
|
-
hasEnvOpenRouterKey,
|
|
85
|
-
maskedEnvOpenRouterKey,
|
|
86
|
-
hasStoredOpenRouterKey,
|
|
87
|
-
maskedStoredOpenRouterKey,
|
|
88
|
-
storedKeyUpdatedAt,
|
|
89
|
-
selectedModel,
|
|
90
|
-
selectedModelUpdatedAt,
|
|
91
|
-
hasEncryptionKey,
|
|
92
|
-
modelCatalogError,
|
|
93
|
-
successMessage,
|
|
94
|
-
errorMessage,
|
|
95
|
-
}: StoredCortexAiSettingsClientProps) {
|
|
96
|
-
const [apiKeyInput, setApiKeyInput] = useState('');
|
|
97
|
-
const [modelInput, setModelInput] = useState<string>(selectedModel?.modelId || '');
|
|
98
|
-
|
|
99
|
-
const isKeyDirty = apiKeyInput.trim().length > 0;
|
|
100
|
-
const isModelDirty = modelInput !== (selectedModel?.modelId || '');
|
|
101
|
-
|
|
102
|
-
const selectedModelIsInCatalog = compatibleModels.some(
|
|
103
|
-
(model) => model.id === selectedModel?.modelId
|
|
104
|
-
);
|
|
105
|
-
const modelOptions =
|
|
106
|
-
selectedModel && !selectedModelIsInCatalog
|
|
107
|
-
? [
|
|
108
|
-
{
|
|
109
|
-
context_length: selectedModel.contextLength,
|
|
110
|
-
id: selectedModel.modelId,
|
|
111
|
-
name: `${selectedModel.name} (saved)`,
|
|
112
|
-
pricing: selectedModel.pricing,
|
|
113
|
-
},
|
|
114
|
-
...compatibleModels,
|
|
115
|
-
]
|
|
116
|
-
: compatibleModels;
|
|
117
|
-
|
|
118
|
-
const canSelectModel = hasStoredOpenRouterKey && compatibleModels.length > 0;
|
|
119
|
-
|
|
120
|
-
const searchableOptions = modelOptions.map((model) => ({
|
|
121
|
-
value: model.id,
|
|
122
|
-
label: model.name,
|
|
123
|
-
description: `${model.id} - ${formatModelPricing(model.pricing as any)}`,
|
|
124
|
-
}));
|
|
125
|
-
|
|
126
|
-
return (
|
|
127
|
-
<div className="mx-auto w-full max-w-6xl space-y-6 px-6 py-8">
|
|
128
|
-
<div>
|
|
129
|
-
<div className="flex items-center gap-3">
|
|
130
|
-
<Brain className="h-7 w-7 text-primary" />
|
|
131
|
-
<h1 className="text-2xl font-semibold">NextBlock Cortex AI</h1>
|
|
132
|
-
</div>
|
|
133
|
-
<p className="mt-2 text-sm text-muted-foreground">
|
|
134
|
-
Manage premium activation and the OpenRouter key used by Cortex AI.
|
|
135
|
-
</p>
|
|
136
|
-
</div>
|
|
137
|
-
|
|
138
|
-
{successMessage && (
|
|
139
|
-
<Alert variant="success">
|
|
140
|
-
<CheckCircle2 className="h-4 w-4" />
|
|
141
|
-
<AlertTitle>Saved</AlertTitle>
|
|
142
|
-
<AlertDescription>{successMessage}</AlertDescription>
|
|
143
|
-
</Alert>
|
|
144
|
-
)}
|
|
145
|
-
|
|
146
|
-
{errorMessage && (
|
|
147
|
-
<Alert variant="destructive">
|
|
148
|
-
<AlertTriangle className="h-4 w-4" />
|
|
149
|
-
<AlertTitle>Unable to save</AlertTitle>
|
|
150
|
-
<AlertDescription>{errorMessage}</AlertDescription>
|
|
151
|
-
</Alert>
|
|
152
|
-
)}
|
|
153
|
-
|
|
154
|
-
<div className="grid gap-4 md:grid-cols-4">
|
|
155
|
-
<Card>
|
|
156
|
-
<CardHeader>
|
|
157
|
-
<CardTitle className="flex items-center gap-2 text-base">
|
|
158
|
-
<ShieldCheck className="h-4 w-4" />
|
|
159
|
-
Package
|
|
160
|
-
</CardTitle>
|
|
161
|
-
<CardDescription>Premium access</CardDescription>
|
|
162
|
-
</CardHeader>
|
|
163
|
-
<CardContent>
|
|
164
|
-
<Badge variant={isPackageActive ? 'default' : 'outline'}>
|
|
165
|
-
{isPackageActive ? 'Active' : 'Inactive'}
|
|
166
|
-
</Badge>
|
|
167
|
-
</CardContent>
|
|
168
|
-
</Card>
|
|
169
|
-
|
|
170
|
-
<Card>
|
|
171
|
-
<CardHeader>
|
|
172
|
-
<CardTitle className="flex items-center gap-2 text-base">
|
|
173
|
-
<ServerCog className="h-4 w-4" />
|
|
174
|
-
Environment
|
|
175
|
-
</CardTitle>
|
|
176
|
-
<CardDescription>OPENROUTER_API_KEY</CardDescription>
|
|
177
|
-
</CardHeader>
|
|
178
|
-
<CardContent className="space-y-2">
|
|
179
|
-
<Badge variant={hasEnvOpenRouterKey ? 'default' : 'outline'}>
|
|
180
|
-
{hasEnvOpenRouterKey ? 'Configured' : 'Not set'}
|
|
181
|
-
</Badge>
|
|
182
|
-
{maskedEnvOpenRouterKey && (
|
|
183
|
-
<p className="font-mono text-xs text-muted-foreground">
|
|
184
|
-
{maskedEnvOpenRouterKey}
|
|
185
|
-
</p>
|
|
186
|
-
)}
|
|
187
|
-
</CardContent>
|
|
188
|
-
</Card>
|
|
189
|
-
|
|
190
|
-
<Card>
|
|
191
|
-
<CardHeader>
|
|
192
|
-
<CardTitle className="flex items-center gap-2 text-base">
|
|
193
|
-
<KeyRound className="h-4 w-4" />
|
|
194
|
-
Stored BYOK
|
|
195
|
-
</CardTitle>
|
|
196
|
-
<CardDescription>Encrypted database key</CardDescription>
|
|
197
|
-
</CardHeader>
|
|
198
|
-
<CardContent className="space-y-2">
|
|
199
|
-
<Badge variant={hasStoredOpenRouterKey ? 'default' : 'outline'}>
|
|
200
|
-
{hasStoredOpenRouterKey ? 'Stored' : 'Empty'}
|
|
201
|
-
</Badge>
|
|
202
|
-
{maskedStoredOpenRouterKey && (
|
|
203
|
-
<p className="font-mono text-xs text-muted-foreground">
|
|
204
|
-
{maskedStoredOpenRouterKey}
|
|
205
|
-
</p>
|
|
206
|
-
)}
|
|
207
|
-
{storedKeyUpdatedAt && (
|
|
208
|
-
<p className="text-xs text-muted-foreground">{storedKeyUpdatedAt}</p>
|
|
209
|
-
)}
|
|
210
|
-
</CardContent>
|
|
211
|
-
</Card>
|
|
212
|
-
|
|
213
|
-
<Card>
|
|
214
|
-
<CardHeader>
|
|
215
|
-
<CardTitle className="flex items-center gap-2 text-base">
|
|
216
|
-
<Cpu className="h-4 w-4" />
|
|
217
|
-
Model
|
|
218
|
-
</CardTitle>
|
|
219
|
-
<CardDescription>Stored BYOK routing</CardDescription>
|
|
220
|
-
</CardHeader>
|
|
221
|
-
<CardContent className="space-y-2">
|
|
222
|
-
<Badge variant={selectedModel ? 'default' : 'outline'}>
|
|
223
|
-
{selectedModel ? 'Selected' : 'Free registry'}
|
|
224
|
-
</Badge>
|
|
225
|
-
{selectedModel && (
|
|
226
|
-
<>
|
|
227
|
-
<p className="text-xs font-medium">{selectedModel.name}</p>
|
|
228
|
-
<p className="break-all font-mono text-xs text-muted-foreground">
|
|
229
|
-
{selectedModel.modelId}
|
|
230
|
-
</p>
|
|
231
|
-
{selectedModelUpdatedAt && (
|
|
232
|
-
<p className="text-xs text-muted-foreground">{selectedModelUpdatedAt}</p>
|
|
233
|
-
)}
|
|
234
|
-
</>
|
|
235
|
-
)}
|
|
236
|
-
</CardContent>
|
|
237
|
-
</Card>
|
|
238
|
-
</div>
|
|
239
|
-
|
|
240
|
-
{hasEnvOpenRouterKey && !hasStoredOpenRouterKey && (
|
|
241
|
-
<Alert>
|
|
242
|
-
<ServerCog className="h-4 w-4" />
|
|
243
|
-
<AlertTitle>Sandbox free-model lock active</AlertTitle>
|
|
244
|
-
<AlertDescription>
|
|
245
|
-
Cortex AI will only use the three configured free OpenRouter models until a
|
|
246
|
-
stored BYOK is saved.
|
|
247
|
-
</AlertDescription>
|
|
248
|
-
</Alert>
|
|
249
|
-
)}
|
|
250
|
-
|
|
251
|
-
{hasEnvOpenRouterKey && hasStoredOpenRouterKey && (
|
|
252
|
-
<Alert>
|
|
253
|
-
<KeyRound className="h-4 w-4" />
|
|
254
|
-
<AlertTitle>Stored BYOK active</AlertTitle>
|
|
255
|
-
<AlertDescription>
|
|
256
|
-
Cortex AI will use the stored BYOK before the server environment key, so the
|
|
257
|
-
selected compatible OpenRouter model can run across the website.
|
|
258
|
-
</AlertDescription>
|
|
259
|
-
</Alert>
|
|
260
|
-
)}
|
|
261
|
-
|
|
262
|
-
{!hasEncryptionKey && (
|
|
263
|
-
<Alert variant="warning">
|
|
264
|
-
<AlertTriangle className="h-4 w-4" />
|
|
265
|
-
<AlertTitle>Encryption key missing</AlertTitle>
|
|
266
|
-
<AlertDescription>
|
|
267
|
-
Set CORTEX_AI_ENCRYPTION_KEY before saving an OpenRouter key here.
|
|
268
|
-
</AlertDescription>
|
|
269
|
-
</Alert>
|
|
270
|
-
)}
|
|
271
|
-
|
|
272
|
-
<Card>
|
|
273
|
-
<CardHeader className="flex flex-row items-start justify-between">
|
|
274
|
-
<div>
|
|
275
|
-
<CardTitle className="text-lg">OpenRouter BYOK</CardTitle>
|
|
276
|
-
<CardDescription>
|
|
277
|
-
The saved key is encrypted and masked after submission.
|
|
278
|
-
</CardDescription>
|
|
279
|
-
</div>
|
|
280
|
-
{hasStoredOpenRouterKey && (
|
|
281
|
-
<form action={clearOpenRouterApiKeyAction} onSubmit={notifyCortexAiSettingsChanged}>
|
|
282
|
-
<Button
|
|
283
|
-
type="submit"
|
|
284
|
-
variant="outline"
|
|
285
|
-
size="sm"
|
|
286
|
-
className="text-destructive hover:text-destructive"
|
|
287
|
-
>
|
|
288
|
-
<Trash2 className="mr-2 h-4 w-4" />
|
|
289
|
-
Clear
|
|
290
|
-
</Button>
|
|
291
|
-
</form>
|
|
292
|
-
)}
|
|
293
|
-
</CardHeader>
|
|
294
|
-
<CardContent>
|
|
295
|
-
<form action={saveOpenRouterApiKeyAction} onSubmit={notifyCortexAiSettingsChanged}>
|
|
296
|
-
<div className="flex items-end gap-3">
|
|
297
|
-
<div className="flex-1 space-y-2">
|
|
298
|
-
<Label htmlFor="openrouter_api_key">OpenRouter API key</Label>
|
|
299
|
-
<Input
|
|
300
|
-
id="openrouter_api_key"
|
|
301
|
-
name="openrouter_api_key"
|
|
302
|
-
type="password"
|
|
303
|
-
autoComplete="off"
|
|
304
|
-
minLength={12}
|
|
305
|
-
placeholder={hasStoredOpenRouterKey ? 'Enter new key to overwrite...' : 'sk-or-v1-...'}
|
|
306
|
-
value={apiKeyInput}
|
|
307
|
-
onChange={(e) => setApiKeyInput(e.target.value)}
|
|
308
|
-
required
|
|
309
|
-
/>
|
|
310
|
-
</div>
|
|
311
|
-
<Button type="submit" disabled={!isKeyDirty}>
|
|
312
|
-
{isKeyDirty ? (
|
|
313
|
-
<>
|
|
314
|
-
<KeyRound className="mr-2 h-4 w-4" />
|
|
315
|
-
Save Key
|
|
316
|
-
</>
|
|
317
|
-
) : (
|
|
318
|
-
<>
|
|
319
|
-
<CheckCircle2 className="mr-2 h-4 w-4" />
|
|
320
|
-
Saved
|
|
321
|
-
</>
|
|
322
|
-
)}
|
|
323
|
-
</Button>
|
|
324
|
-
</div>
|
|
325
|
-
</form>
|
|
326
|
-
</CardContent>
|
|
327
|
-
</Card>
|
|
328
|
-
|
|
329
|
-
<Card>
|
|
330
|
-
<CardHeader className="flex flex-row items-start justify-between">
|
|
331
|
-
<div>
|
|
332
|
-
<CardTitle className="text-lg">OpenRouter Model</CardTitle>
|
|
333
|
-
<CardDescription>
|
|
334
|
-
Stored BYOK can use compatible text models that support structured outputs and
|
|
335
|
-
tool calling.
|
|
336
|
-
</CardDescription>
|
|
337
|
-
</div>
|
|
338
|
-
{selectedModel && (
|
|
339
|
-
<form action={clearCortexAiModelSelectionAction} onSubmit={notifyCortexAiSettingsChanged}>
|
|
340
|
-
<Button
|
|
341
|
-
type="submit"
|
|
342
|
-
variant="outline"
|
|
343
|
-
size="sm"
|
|
344
|
-
className="text-destructive hover:text-destructive"
|
|
345
|
-
>
|
|
346
|
-
<Trash2 className="mr-2 h-4 w-4" />
|
|
347
|
-
Clear
|
|
348
|
-
</Button>
|
|
349
|
-
</form>
|
|
350
|
-
)}
|
|
351
|
-
</CardHeader>
|
|
352
|
-
<CardContent className="space-y-4">
|
|
353
|
-
{!hasStoredOpenRouterKey && (
|
|
354
|
-
<Alert>
|
|
355
|
-
<KeyRound className="h-4 w-4" />
|
|
356
|
-
<AlertTitle>Stored BYOK required</AlertTitle>
|
|
357
|
-
<AlertDescription>
|
|
358
|
-
Save an encrypted OpenRouter key before choosing a paid model.
|
|
359
|
-
</AlertDescription>
|
|
360
|
-
</Alert>
|
|
361
|
-
)}
|
|
362
|
-
|
|
363
|
-
{modelCatalogError && (
|
|
364
|
-
<Alert variant="warning">
|
|
365
|
-
<AlertTriangle className="h-4 w-4" />
|
|
366
|
-
<AlertTitle>Model catalog unavailable</AlertTitle>
|
|
367
|
-
<AlertDescription>{modelCatalogError}</AlertDescription>
|
|
368
|
-
</Alert>
|
|
369
|
-
)}
|
|
370
|
-
|
|
371
|
-
<form action={saveCortexAiModelSelectionAction} onSubmit={notifyCortexAiSettingsChanged}>
|
|
372
|
-
{/* We need a hidden input because SearchableSelect isn't a native form element that automatically passes 'name' inside form submission natively (unless we do) */}
|
|
373
|
-
<input type="hidden" name="openrouter_model_id" value={modelInput} />
|
|
374
|
-
|
|
375
|
-
<div className="flex items-end gap-3">
|
|
376
|
-
<div className="flex-1 space-y-2">
|
|
377
|
-
<Label htmlFor="openrouter_model_id_select">Model</Label>
|
|
378
|
-
<SearchableSelect
|
|
379
|
-
options={searchableOptions}
|
|
380
|
-
value={modelInput}
|
|
381
|
-
onChange={(val) => setModelInput(val)}
|
|
382
|
-
disabled={!canSelectModel}
|
|
383
|
-
placeholder="Select a compatible model..."
|
|
384
|
-
/>
|
|
385
|
-
<p className="text-xs text-muted-foreground mt-1">
|
|
386
|
-
{canSelectModel
|
|
387
|
-
? `${compatibleModels.length} compatible models available`
|
|
388
|
-
: 'Cortex AI will use the free registry until model selection is available.'}
|
|
389
|
-
</p>
|
|
390
|
-
</div>
|
|
391
|
-
<Button type="submit" disabled={!canSelectModel || !isModelDirty}>
|
|
392
|
-
{isModelDirty ? (
|
|
393
|
-
<>
|
|
394
|
-
<Cpu className="mr-2 h-4 w-4" />
|
|
395
|
-
Save Model
|
|
396
|
-
</>
|
|
397
|
-
) : (
|
|
398
|
-
<>
|
|
399
|
-
<CheckCircle2 className="mr-2 h-4 w-4" />
|
|
400
|
-
Saved
|
|
401
|
-
</>
|
|
402
|
-
)}
|
|
403
|
-
</Button>
|
|
404
|
-
</div>
|
|
405
|
-
</form>
|
|
406
|
-
</CardContent>
|
|
407
|
-
</Card>
|
|
408
|
-
</div>
|
|
409
|
-
);
|
|
410
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
Alert,
|
|
6
|
+
AlertDescription,
|
|
7
|
+
AlertTitle,
|
|
8
|
+
Badge,
|
|
9
|
+
Button,
|
|
10
|
+
Card,
|
|
11
|
+
CardContent,
|
|
12
|
+
CardDescription,
|
|
13
|
+
CardHeader,
|
|
14
|
+
CardTitle,
|
|
15
|
+
Input,
|
|
16
|
+
Label,
|
|
17
|
+
SearchableSelect,
|
|
18
|
+
} from '@nextblock-cms/ui';
|
|
19
|
+
import {
|
|
20
|
+
AlertTriangle,
|
|
21
|
+
Brain,
|
|
22
|
+
CheckCircle2,
|
|
23
|
+
Cpu,
|
|
24
|
+
KeyRound,
|
|
25
|
+
ServerCog,
|
|
26
|
+
ShieldCheck,
|
|
27
|
+
Trash2,
|
|
28
|
+
} from 'lucide-react';
|
|
29
|
+
|
|
30
|
+
import type { CortexAiStoredModelSelection } from '@nextblock-cms/cortex/client';
|
|
31
|
+
import {
|
|
32
|
+
clearCortexAiModelSelectionAction,
|
|
33
|
+
clearOpenRouterApiKeyAction,
|
|
34
|
+
saveCortexAiModelSelectionAction,
|
|
35
|
+
saveOpenRouterApiKeyAction,
|
|
36
|
+
} from './actions';
|
|
37
|
+
|
|
38
|
+
const CORTEX_AI_SETTINGS_CHANGED_EVENT = 'nextblock:cortex-ai-settings-changed';
|
|
39
|
+
|
|
40
|
+
type StoredCortexAiSettingsClientProps = {
|
|
41
|
+
compatibleModels: Array<{
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
pricing: Record<string, string>;
|
|
45
|
+
context_length: number | null;
|
|
46
|
+
}>;
|
|
47
|
+
isPackageActive: boolean;
|
|
48
|
+
hasEnvOpenRouterKey: boolean;
|
|
49
|
+
maskedEnvOpenRouterKey: string | null;
|
|
50
|
+
hasStoredOpenRouterKey: boolean;
|
|
51
|
+
maskedStoredOpenRouterKey: string | null;
|
|
52
|
+
storedKeyUpdatedAt: string | null;
|
|
53
|
+
selectedModel: CortexAiStoredModelSelection | null;
|
|
54
|
+
selectedModelUpdatedAt: string | null;
|
|
55
|
+
hasEncryptionKey: boolean;
|
|
56
|
+
modelCatalogError: string | null;
|
|
57
|
+
successMessage?: string;
|
|
58
|
+
errorMessage?: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function formatTokenPrice(value: string | undefined) {
|
|
62
|
+
const amount = Number(value);
|
|
63
|
+
if (!Number.isFinite(amount)) return null;
|
|
64
|
+
if (amount === 0) return '$0';
|
|
65
|
+
const perMillion = amount * 1_000_000;
|
|
66
|
+
return `$${perMillion < 0.01 ? perMillion.toFixed(4) : perMillion.toFixed(2)}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function formatModelPricing(pricing: Record<string, string>) {
|
|
70
|
+
const promptPrice = formatTokenPrice(pricing.prompt);
|
|
71
|
+
const completionPrice = formatTokenPrice(pricing.completion);
|
|
72
|
+
if (promptPrice === '$0' && completionPrice === '$0') return 'Free';
|
|
73
|
+
if (promptPrice && completionPrice) return `${promptPrice}/1M input - ${completionPrice}/1M output`;
|
|
74
|
+
return 'Pricing varies';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function notifyCortexAiSettingsChanged() {
|
|
78
|
+
window.dispatchEvent(new Event(CORTEX_AI_SETTINGS_CHANGED_EVENT));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function StoredCortexAiSettingsClient({
|
|
82
|
+
compatibleModels,
|
|
83
|
+
isPackageActive,
|
|
84
|
+
hasEnvOpenRouterKey,
|
|
85
|
+
maskedEnvOpenRouterKey,
|
|
86
|
+
hasStoredOpenRouterKey,
|
|
87
|
+
maskedStoredOpenRouterKey,
|
|
88
|
+
storedKeyUpdatedAt,
|
|
89
|
+
selectedModel,
|
|
90
|
+
selectedModelUpdatedAt,
|
|
91
|
+
hasEncryptionKey,
|
|
92
|
+
modelCatalogError,
|
|
93
|
+
successMessage,
|
|
94
|
+
errorMessage,
|
|
95
|
+
}: StoredCortexAiSettingsClientProps) {
|
|
96
|
+
const [apiKeyInput, setApiKeyInput] = useState('');
|
|
97
|
+
const [modelInput, setModelInput] = useState<string>(selectedModel?.modelId || '');
|
|
98
|
+
|
|
99
|
+
const isKeyDirty = apiKeyInput.trim().length > 0;
|
|
100
|
+
const isModelDirty = modelInput !== (selectedModel?.modelId || '');
|
|
101
|
+
|
|
102
|
+
const selectedModelIsInCatalog = compatibleModels.some(
|
|
103
|
+
(model) => model.id === selectedModel?.modelId
|
|
104
|
+
);
|
|
105
|
+
const modelOptions =
|
|
106
|
+
selectedModel && !selectedModelIsInCatalog
|
|
107
|
+
? [
|
|
108
|
+
{
|
|
109
|
+
context_length: selectedModel.contextLength,
|
|
110
|
+
id: selectedModel.modelId,
|
|
111
|
+
name: `${selectedModel.name} (saved)`,
|
|
112
|
+
pricing: selectedModel.pricing,
|
|
113
|
+
},
|
|
114
|
+
...compatibleModels,
|
|
115
|
+
]
|
|
116
|
+
: compatibleModels;
|
|
117
|
+
|
|
118
|
+
const canSelectModel = hasStoredOpenRouterKey && compatibleModels.length > 0;
|
|
119
|
+
|
|
120
|
+
const searchableOptions = modelOptions.map((model) => ({
|
|
121
|
+
value: model.id,
|
|
122
|
+
label: model.name,
|
|
123
|
+
description: `${model.id} - ${formatModelPricing(model.pricing as any)}`,
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<div className="mx-auto w-full max-w-6xl space-y-6 px-6 py-8">
|
|
128
|
+
<div>
|
|
129
|
+
<div className="flex items-center gap-3">
|
|
130
|
+
<Brain className="h-7 w-7 text-primary" />
|
|
131
|
+
<h1 className="text-2xl font-semibold">NextBlock Cortex AI</h1>
|
|
132
|
+
</div>
|
|
133
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
134
|
+
Manage premium activation and the OpenRouter key used by Cortex AI.
|
|
135
|
+
</p>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
{successMessage && (
|
|
139
|
+
<Alert variant="success">
|
|
140
|
+
<CheckCircle2 className="h-4 w-4" />
|
|
141
|
+
<AlertTitle>Saved</AlertTitle>
|
|
142
|
+
<AlertDescription>{successMessage}</AlertDescription>
|
|
143
|
+
</Alert>
|
|
144
|
+
)}
|
|
145
|
+
|
|
146
|
+
{errorMessage && (
|
|
147
|
+
<Alert variant="destructive">
|
|
148
|
+
<AlertTriangle className="h-4 w-4" />
|
|
149
|
+
<AlertTitle>Unable to save</AlertTitle>
|
|
150
|
+
<AlertDescription>{errorMessage}</AlertDescription>
|
|
151
|
+
</Alert>
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
<div className="grid gap-4 md:grid-cols-4">
|
|
155
|
+
<Card>
|
|
156
|
+
<CardHeader>
|
|
157
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
158
|
+
<ShieldCheck className="h-4 w-4" />
|
|
159
|
+
Package
|
|
160
|
+
</CardTitle>
|
|
161
|
+
<CardDescription>Premium access</CardDescription>
|
|
162
|
+
</CardHeader>
|
|
163
|
+
<CardContent>
|
|
164
|
+
<Badge variant={isPackageActive ? 'default' : 'outline'}>
|
|
165
|
+
{isPackageActive ? 'Active' : 'Inactive'}
|
|
166
|
+
</Badge>
|
|
167
|
+
</CardContent>
|
|
168
|
+
</Card>
|
|
169
|
+
|
|
170
|
+
<Card>
|
|
171
|
+
<CardHeader>
|
|
172
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
173
|
+
<ServerCog className="h-4 w-4" />
|
|
174
|
+
Environment
|
|
175
|
+
</CardTitle>
|
|
176
|
+
<CardDescription>OPENROUTER_API_KEY</CardDescription>
|
|
177
|
+
</CardHeader>
|
|
178
|
+
<CardContent className="space-y-2">
|
|
179
|
+
<Badge variant={hasEnvOpenRouterKey ? 'default' : 'outline'}>
|
|
180
|
+
{hasEnvOpenRouterKey ? 'Configured' : 'Not set'}
|
|
181
|
+
</Badge>
|
|
182
|
+
{maskedEnvOpenRouterKey && (
|
|
183
|
+
<p className="font-mono text-xs text-muted-foreground">
|
|
184
|
+
{maskedEnvOpenRouterKey}
|
|
185
|
+
</p>
|
|
186
|
+
)}
|
|
187
|
+
</CardContent>
|
|
188
|
+
</Card>
|
|
189
|
+
|
|
190
|
+
<Card>
|
|
191
|
+
<CardHeader>
|
|
192
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
193
|
+
<KeyRound className="h-4 w-4" />
|
|
194
|
+
Stored BYOK
|
|
195
|
+
</CardTitle>
|
|
196
|
+
<CardDescription>Encrypted database key</CardDescription>
|
|
197
|
+
</CardHeader>
|
|
198
|
+
<CardContent className="space-y-2">
|
|
199
|
+
<Badge variant={hasStoredOpenRouterKey ? 'default' : 'outline'}>
|
|
200
|
+
{hasStoredOpenRouterKey ? 'Stored' : 'Empty'}
|
|
201
|
+
</Badge>
|
|
202
|
+
{maskedStoredOpenRouterKey && (
|
|
203
|
+
<p className="font-mono text-xs text-muted-foreground">
|
|
204
|
+
{maskedStoredOpenRouterKey}
|
|
205
|
+
</p>
|
|
206
|
+
)}
|
|
207
|
+
{storedKeyUpdatedAt && (
|
|
208
|
+
<p className="text-xs text-muted-foreground">{storedKeyUpdatedAt}</p>
|
|
209
|
+
)}
|
|
210
|
+
</CardContent>
|
|
211
|
+
</Card>
|
|
212
|
+
|
|
213
|
+
<Card>
|
|
214
|
+
<CardHeader>
|
|
215
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
216
|
+
<Cpu className="h-4 w-4" />
|
|
217
|
+
Model
|
|
218
|
+
</CardTitle>
|
|
219
|
+
<CardDescription>Stored BYOK routing</CardDescription>
|
|
220
|
+
</CardHeader>
|
|
221
|
+
<CardContent className="space-y-2">
|
|
222
|
+
<Badge variant={selectedModel ? 'default' : 'outline'}>
|
|
223
|
+
{selectedModel ? 'Selected' : 'Free registry'}
|
|
224
|
+
</Badge>
|
|
225
|
+
{selectedModel && (
|
|
226
|
+
<>
|
|
227
|
+
<p className="text-xs font-medium">{selectedModel.name}</p>
|
|
228
|
+
<p className="break-all font-mono text-xs text-muted-foreground">
|
|
229
|
+
{selectedModel.modelId}
|
|
230
|
+
</p>
|
|
231
|
+
{selectedModelUpdatedAt && (
|
|
232
|
+
<p className="text-xs text-muted-foreground">{selectedModelUpdatedAt}</p>
|
|
233
|
+
)}
|
|
234
|
+
</>
|
|
235
|
+
)}
|
|
236
|
+
</CardContent>
|
|
237
|
+
</Card>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
{hasEnvOpenRouterKey && !hasStoredOpenRouterKey && (
|
|
241
|
+
<Alert>
|
|
242
|
+
<ServerCog className="h-4 w-4" />
|
|
243
|
+
<AlertTitle>Sandbox free-model lock active</AlertTitle>
|
|
244
|
+
<AlertDescription>
|
|
245
|
+
Cortex AI will only use the three configured free OpenRouter models until a
|
|
246
|
+
stored BYOK is saved.
|
|
247
|
+
</AlertDescription>
|
|
248
|
+
</Alert>
|
|
249
|
+
)}
|
|
250
|
+
|
|
251
|
+
{hasEnvOpenRouterKey && hasStoredOpenRouterKey && (
|
|
252
|
+
<Alert>
|
|
253
|
+
<KeyRound className="h-4 w-4" />
|
|
254
|
+
<AlertTitle>Stored BYOK active</AlertTitle>
|
|
255
|
+
<AlertDescription>
|
|
256
|
+
Cortex AI will use the stored BYOK before the server environment key, so the
|
|
257
|
+
selected compatible OpenRouter model can run across the website.
|
|
258
|
+
</AlertDescription>
|
|
259
|
+
</Alert>
|
|
260
|
+
)}
|
|
261
|
+
|
|
262
|
+
{!hasEncryptionKey && (
|
|
263
|
+
<Alert variant="warning">
|
|
264
|
+
<AlertTriangle className="h-4 w-4" />
|
|
265
|
+
<AlertTitle>Encryption key missing</AlertTitle>
|
|
266
|
+
<AlertDescription>
|
|
267
|
+
Set CORTEX_AI_ENCRYPTION_KEY before saving an OpenRouter key here.
|
|
268
|
+
</AlertDescription>
|
|
269
|
+
</Alert>
|
|
270
|
+
)}
|
|
271
|
+
|
|
272
|
+
<Card>
|
|
273
|
+
<CardHeader className="flex flex-row items-start justify-between">
|
|
274
|
+
<div>
|
|
275
|
+
<CardTitle className="text-lg">OpenRouter BYOK</CardTitle>
|
|
276
|
+
<CardDescription>
|
|
277
|
+
The saved key is encrypted and masked after submission.
|
|
278
|
+
</CardDescription>
|
|
279
|
+
</div>
|
|
280
|
+
{hasStoredOpenRouterKey && (
|
|
281
|
+
<form action={clearOpenRouterApiKeyAction} onSubmit={notifyCortexAiSettingsChanged}>
|
|
282
|
+
<Button
|
|
283
|
+
type="submit"
|
|
284
|
+
variant="outline"
|
|
285
|
+
size="sm"
|
|
286
|
+
className="text-destructive hover:text-destructive"
|
|
287
|
+
>
|
|
288
|
+
<Trash2 className="mr-2 h-4 w-4" />
|
|
289
|
+
Clear
|
|
290
|
+
</Button>
|
|
291
|
+
</form>
|
|
292
|
+
)}
|
|
293
|
+
</CardHeader>
|
|
294
|
+
<CardContent>
|
|
295
|
+
<form action={saveOpenRouterApiKeyAction} onSubmit={notifyCortexAiSettingsChanged}>
|
|
296
|
+
<div className="flex items-end gap-3">
|
|
297
|
+
<div className="flex-1 space-y-2">
|
|
298
|
+
<Label htmlFor="openrouter_api_key">OpenRouter API key</Label>
|
|
299
|
+
<Input
|
|
300
|
+
id="openrouter_api_key"
|
|
301
|
+
name="openrouter_api_key"
|
|
302
|
+
type="password"
|
|
303
|
+
autoComplete="off"
|
|
304
|
+
minLength={12}
|
|
305
|
+
placeholder={hasStoredOpenRouterKey ? 'Enter new key to overwrite...' : 'sk-or-v1-...'}
|
|
306
|
+
value={apiKeyInput}
|
|
307
|
+
onChange={(e) => setApiKeyInput(e.target.value)}
|
|
308
|
+
required
|
|
309
|
+
/>
|
|
310
|
+
</div>
|
|
311
|
+
<Button type="submit" disabled={!isKeyDirty}>
|
|
312
|
+
{isKeyDirty ? (
|
|
313
|
+
<>
|
|
314
|
+
<KeyRound className="mr-2 h-4 w-4" />
|
|
315
|
+
Save Key
|
|
316
|
+
</>
|
|
317
|
+
) : (
|
|
318
|
+
<>
|
|
319
|
+
<CheckCircle2 className="mr-2 h-4 w-4" />
|
|
320
|
+
Saved
|
|
321
|
+
</>
|
|
322
|
+
)}
|
|
323
|
+
</Button>
|
|
324
|
+
</div>
|
|
325
|
+
</form>
|
|
326
|
+
</CardContent>
|
|
327
|
+
</Card>
|
|
328
|
+
|
|
329
|
+
<Card>
|
|
330
|
+
<CardHeader className="flex flex-row items-start justify-between">
|
|
331
|
+
<div>
|
|
332
|
+
<CardTitle className="text-lg">OpenRouter Model</CardTitle>
|
|
333
|
+
<CardDescription>
|
|
334
|
+
Stored BYOK can use compatible text models that support structured outputs and
|
|
335
|
+
tool calling.
|
|
336
|
+
</CardDescription>
|
|
337
|
+
</div>
|
|
338
|
+
{selectedModel && (
|
|
339
|
+
<form action={clearCortexAiModelSelectionAction} onSubmit={notifyCortexAiSettingsChanged}>
|
|
340
|
+
<Button
|
|
341
|
+
type="submit"
|
|
342
|
+
variant="outline"
|
|
343
|
+
size="sm"
|
|
344
|
+
className="text-destructive hover:text-destructive"
|
|
345
|
+
>
|
|
346
|
+
<Trash2 className="mr-2 h-4 w-4" />
|
|
347
|
+
Clear
|
|
348
|
+
</Button>
|
|
349
|
+
</form>
|
|
350
|
+
)}
|
|
351
|
+
</CardHeader>
|
|
352
|
+
<CardContent className="space-y-4">
|
|
353
|
+
{!hasStoredOpenRouterKey && (
|
|
354
|
+
<Alert>
|
|
355
|
+
<KeyRound className="h-4 w-4" />
|
|
356
|
+
<AlertTitle>Stored BYOK required</AlertTitle>
|
|
357
|
+
<AlertDescription>
|
|
358
|
+
Save an encrypted OpenRouter key before choosing a paid model.
|
|
359
|
+
</AlertDescription>
|
|
360
|
+
</Alert>
|
|
361
|
+
)}
|
|
362
|
+
|
|
363
|
+
{modelCatalogError && (
|
|
364
|
+
<Alert variant="warning">
|
|
365
|
+
<AlertTriangle className="h-4 w-4" />
|
|
366
|
+
<AlertTitle>Model catalog unavailable</AlertTitle>
|
|
367
|
+
<AlertDescription>{modelCatalogError}</AlertDescription>
|
|
368
|
+
</Alert>
|
|
369
|
+
)}
|
|
370
|
+
|
|
371
|
+
<form action={saveCortexAiModelSelectionAction} onSubmit={notifyCortexAiSettingsChanged}>
|
|
372
|
+
{/* We need a hidden input because SearchableSelect isn't a native form element that automatically passes 'name' inside form submission natively (unless we do) */}
|
|
373
|
+
<input type="hidden" name="openrouter_model_id" value={modelInput} />
|
|
374
|
+
|
|
375
|
+
<div className="flex items-end gap-3">
|
|
376
|
+
<div className="flex-1 space-y-2">
|
|
377
|
+
<Label htmlFor="openrouter_model_id_select">Model</Label>
|
|
378
|
+
<SearchableSelect
|
|
379
|
+
options={searchableOptions}
|
|
380
|
+
value={modelInput}
|
|
381
|
+
onChange={(val) => setModelInput(val)}
|
|
382
|
+
disabled={!canSelectModel}
|
|
383
|
+
placeholder="Select a compatible model..."
|
|
384
|
+
/>
|
|
385
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
386
|
+
{canSelectModel
|
|
387
|
+
? `${compatibleModels.length} compatible models available`
|
|
388
|
+
: 'Cortex AI will use the free registry until model selection is available.'}
|
|
389
|
+
</p>
|
|
390
|
+
</div>
|
|
391
|
+
<Button type="submit" disabled={!canSelectModel || !isModelDirty}>
|
|
392
|
+
{isModelDirty ? (
|
|
393
|
+
<>
|
|
394
|
+
<Cpu className="mr-2 h-4 w-4" />
|
|
395
|
+
Save Model
|
|
396
|
+
</>
|
|
397
|
+
) : (
|
|
398
|
+
<>
|
|
399
|
+
<CheckCircle2 className="mr-2 h-4 w-4" />
|
|
400
|
+
Saved
|
|
401
|
+
</>
|
|
402
|
+
)}
|
|
403
|
+
</Button>
|
|
404
|
+
</div>
|
|
405
|
+
</form>
|
|
406
|
+
</CardContent>
|
|
407
|
+
</Card>
|
|
408
|
+
</div>
|
|
409
|
+
);
|
|
410
|
+
}
|