mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-08 22:48:14 -05:00
feat(whitelabel): add in the ability to whitelabel via envvars (#887)
* feat(whitelabel): add in the ability to whitelabel via envvars * restore site.webmanifest * fix(dynamic): remove force-dynamic from routes that don't need it (#888) * Reinstall dependencies * Update docs
This commit is contained in:
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -537,7 +537,7 @@ This visibility system ensures clean user interfaces while maintaining full flex
|
|||||||
|
|
||||||
### Guidelines & Best Practices
|
### Guidelines & Best Practices
|
||||||
|
|
||||||
- **Code Style:** Follow the project's ESLint and Prettier configurations. Use meaningful variable names and small, focused functions.
|
- **Code Style:** Follow the project's Biome configurations. Use meaningful variable names and small, focused functions.
|
||||||
- **Documentation:** Clearly document the purpose, inputs, outputs, and any special behavior for your block/tool.
|
- **Documentation:** Clearly document the purpose, inputs, outputs, and any special behavior for your block/tool.
|
||||||
- **Error Handling:** Implement robust error handling and provide user-friendly error messages.
|
- **Error Handling:** Implement robust error handling and provide user-friendly error messages.
|
||||||
- **Parameter Visibility:** Always specify the appropriate visibility level for each parameter to ensure proper UI behavior and LLM integration.
|
- **Parameter Visibility:** Always specify the appropriate visibility level for each parameter to ensure proper UI behavior and LLM integration.
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ The File Parser tool is particularly useful for scenarios where your agents need
|
|||||||
|
|
||||||
## Usage Instructions
|
## Usage Instructions
|
||||||
|
|
||||||
Upload and extract contents from structured file formats including PDFs, CSV spreadsheets, and Word documents (DOCX). Upload files directly. Specialized parsers extract text and metadata from each format. You can upload multiple files at once and access them individually or as a combined document.
|
Upload and extract contents from structured file formats including PDFs, CSV spreadsheets, and Word documents (DOCX). You can either provide a URL to a file or upload files directly. Specialized parsers extract text and metadata from each format. You can upload multiple files at once and access them individually or as a combined document.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ With Hunter.io, you can:
|
|||||||
In Sim, the Hunter.io integration enables your agents to programmatically search for and verify email addresses, discover companies, and enrich contact data using Hunter.io’s API. This allows you to automate lead generation, contact enrichment, and email verification directly within your workflows. Your agents can leverage Hunter.io’s tools to streamline outreach, keep your CRM up-to-date, and power intelligent automation scenarios for sales, recruiting, and more.
|
In Sim, the Hunter.io integration enables your agents to programmatically search for and verify email addresses, discover companies, and enrich contact data using Hunter.io’s API. This allows you to automate lead generation, contact enrichment, and email verification directly within your workflows. Your agents can leverage Hunter.io’s tools to streamline outreach, keep your CRM up-to-date, and power intelligent automation scenarios for sales, recruiting, and more.
|
||||||
{/* MANUAL-CONTENT-END */}
|
{/* MANUAL-CONTENT-END */}
|
||||||
|
|
||||||
|
|
||||||
## Usage Instructions
|
## Usage Instructions
|
||||||
|
|
||||||
Search for email addresses, verify their deliverability, discover companies, and enrich contact data using Hunter.io's powerful email finding capabilities.
|
Search for email addresses, verify their deliverability, discover companies, and enrich contact data using Hunter.io's powerful email finding capabilities.
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ Search for similar content in a knowledge base using vector similarity
|
|||||||
| Parameter | Type | Required | Description |
|
| Parameter | Type | Required | Description |
|
||||||
| --------- | ---- | -------- | ----------- |
|
| --------- | ---- | -------- | ----------- |
|
||||||
| `knowledgeBaseId` | string | Yes | ID of the knowledge base to search in |
|
| `knowledgeBaseId` | string | Yes | ID of the knowledge base to search in |
|
||||||
| `query` | string | Yes | Search query text |
|
| `query` | string | No | Search query text \(optional when using tag filters\) |
|
||||||
| `topK` | number | No | Number of most similar results to return \(1-100\) |
|
| `topK` | number | No | Number of most similar results to return \(1-100\) |
|
||||||
| `tagFilters` | any | No | Array of tag filters with tagName and tagValue properties |
|
| `tagFilters` | any | No | Array of tag filters with tagName and tagValue properties |
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ The Mistral Parse tool is particularly useful for scenarios where your agents ne
|
|||||||
|
|
||||||
## Usage Instructions
|
## Usage Instructions
|
||||||
|
|
||||||
Extract text and structure from PDF documents using Mistral's OCR API. Configure processing options and get the content in your preferred format. For URLs, they must be publicly accessible and point to a valid PDF file. Note: Google Drive, Dropbox, and other cloud storage links are not supported; use a direct download URL from a web server instead.
|
Extract text and structure from PDF documents using Mistral's OCR API. Either enter a URL to a PDF document or upload a PDF file directly. Configure processing options and get the content in your preferred format. For URLs, they must be publicly accessible and point to a valid PDF file. Note: Google Drive, Dropbox, and other cloud storage links are not supported; use a direct download URL from a web server instead.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -158,6 +158,10 @@ Send emails using Outlook
|
|||||||
| `to` | string | Yes | Recipient email address |
|
| `to` | string | Yes | Recipient email address |
|
||||||
| `subject` | string | Yes | Email subject |
|
| `subject` | string | Yes | Email subject |
|
||||||
| `body` | string | Yes | Email body content |
|
| `body` | string | Yes | Email body content |
|
||||||
|
| `replyToMessageId` | string | No | Message ID to reply to \(for threading\) |
|
||||||
|
| `conversationId` | string | No | Conversation ID for threading |
|
||||||
|
| `cc` | string | No | CC recipients \(comma-separated\) |
|
||||||
|
| `bcc` | string | No | BCC recipients \(comma-separated\) |
|
||||||
|
|
||||||
#### Output
|
#### Output
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
|
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
import { useBrandConfig } from '@/lib/branding/branding'
|
||||||
import { GridPattern } from '@/app/(landing)/components/grid-pattern'
|
import { GridPattern } from '@/app/(landing)/components/grid-pattern'
|
||||||
|
|
||||||
export default function AuthLayout({ children }: { children: React.ReactNode }) {
|
export default function AuthLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
const brand = useBrandConfig()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className='relative flex min-h-screen flex-col bg-[#0C0C0C] font-geist-sans text-white'>
|
<main className='relative flex min-h-screen flex-col bg-[#0C0C0C] font-geist-sans text-white'>
|
||||||
{/* Background pattern */}
|
{/* Background pattern */}
|
||||||
@@ -21,7 +24,17 @@ export default function AuthLayout({ children }: { children: React.ReactNode })
|
|||||||
<div className='relative z-10 px-6 pt-9'>
|
<div className='relative z-10 px-6 pt-9'>
|
||||||
<div className='mx-auto max-w-7xl'>
|
<div className='mx-auto max-w-7xl'>
|
||||||
<Link href='/' className='inline-flex'>
|
<Link href='/' className='inline-flex'>
|
||||||
<Image src='/sim.svg' alt='Sim Logo' width={42} height={42} />
|
{brand.logoUrl ? (
|
||||||
|
<img
|
||||||
|
src={brand.logoUrl}
|
||||||
|
alt={`${brand.name} Logo`}
|
||||||
|
width={42}
|
||||||
|
height={42}
|
||||||
|
className='h-[42px] w-[42px] object-contain'
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Image src='/sim.svg' alt={`${brand.name} Logo`} width={42} height={42} />
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
SheetTitle,
|
SheetTitle,
|
||||||
SheetTrigger,
|
SheetTrigger,
|
||||||
} from '@/components/ui/sheet'
|
} from '@/components/ui/sheet'
|
||||||
|
import { useBrandConfig } from '@/lib/branding/branding'
|
||||||
import { usePrefetchOnHover } from '@/app/(landing)/utils/prefetch'
|
import { usePrefetchOnHover } from '@/app/(landing)/utils/prefetch'
|
||||||
|
|
||||||
// --- Framer Motion Variants ---
|
// --- Framer Motion Variants ---
|
||||||
@@ -165,6 +166,7 @@ export default function NavClient({
|
|||||||
const [isMobile, setIsMobile] = useState(initialIsMobile ?? false)
|
const [isMobile, setIsMobile] = useState(initialIsMobile ?? false)
|
||||||
const [isSheetOpen, setIsSheetOpen] = useState(false)
|
const [isSheetOpen, setIsSheetOpen] = useState(false)
|
||||||
const _router = useRouter()
|
const _router = useRouter()
|
||||||
|
const brand = useBrandConfig()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true)
|
setMounted(true)
|
||||||
@@ -199,7 +201,17 @@ export default function NavClient({
|
|||||||
<div className='flex flex-1 items-center'>
|
<div className='flex flex-1 items-center'>
|
||||||
<div className='inline-block'>
|
<div className='inline-block'>
|
||||||
<Link href='/' className='inline-flex'>
|
<Link href='/' className='inline-flex'>
|
||||||
<Image src='/sim.svg' alt='Sim Logo' width={42} height={42} />
|
{brand.logoUrl ? (
|
||||||
|
<img
|
||||||
|
src={brand.logoUrl}
|
||||||
|
alt={`${brand.name} Logo`}
|
||||||
|
width={42}
|
||||||
|
height={42}
|
||||||
|
className='h-[42px] w-[42px] object-contain'
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Image src='/sim.svg' alt={`${brand.name} Logo`} width={42} height={42} />
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Analytics } from '@vercel/analytics/next'
|
import { Analytics } from '@vercel/analytics/next'
|
||||||
import { SpeedInsights } from '@vercel/speed-insights/next'
|
import { SpeedInsights } from '@vercel/speed-insights/next'
|
||||||
import { GeistSans } from 'geist/font/sans'
|
|
||||||
import type { Metadata, Viewport } from 'next'
|
import type { Metadata, Viewport } from 'next'
|
||||||
import { PublicEnvScript } from 'next-runtime-env'
|
import { PublicEnvScript } from 'next-runtime-env'
|
||||||
|
import { BrandedLayout } from '@/components/branded-layout'
|
||||||
|
import { generateBrandedMetadata, generateStructuredData } from '@/lib/branding/metadata'
|
||||||
|
import { env } from '@/lib/env'
|
||||||
import { isHosted } from '@/lib/environment'
|
import { isHosted } from '@/lib/environment'
|
||||||
import { createLogger } from '@/lib/logs/console/logger'
|
import { createLogger } from '@/lib/logs/console/logger'
|
||||||
import { getAssetUrl } from '@/lib/utils'
|
import { getAssetUrl } from '@/lib/utils'
|
||||||
@@ -51,149 +53,20 @@ export const viewport: Viewport = {
|
|||||||
userScalable: false,
|
userScalable: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
// Generate dynamic metadata based on brand configuration
|
||||||
title: {
|
export const metadata: Metadata = generateBrandedMetadata()
|
||||||
template: '',
|
|
||||||
default: 'Sim',
|
|
||||||
},
|
|
||||||
description:
|
|
||||||
'Build and deploy AI agents using our Figma-like canvas. Build, write evals, and deploy AI agent workflows that automate workflows and streamline your business processes.',
|
|
||||||
applicationName: 'Sim',
|
|
||||||
authors: [{ name: 'Sim' }],
|
|
||||||
generator: 'Next.js',
|
|
||||||
keywords: [
|
|
||||||
'AI agent',
|
|
||||||
'AI agent builder',
|
|
||||||
'AI agent workflow',
|
|
||||||
'AI workflow automation',
|
|
||||||
'visual workflow editor',
|
|
||||||
'AI agents',
|
|
||||||
'workflow canvas',
|
|
||||||
'intelligent automation',
|
|
||||||
'AI tools',
|
|
||||||
'workflow designer',
|
|
||||||
'artificial intelligence',
|
|
||||||
'business automation',
|
|
||||||
'AI agent workflows',
|
|
||||||
'visual programming',
|
|
||||||
],
|
|
||||||
referrer: 'origin-when-cross-origin',
|
|
||||||
creator: 'Sim',
|
|
||||||
publisher: 'Sim',
|
|
||||||
metadataBase: new URL('https://sim.ai'),
|
|
||||||
alternates: {
|
|
||||||
canonical: '/',
|
|
||||||
languages: {
|
|
||||||
'en-US': '/en-US',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
robots: {
|
|
||||||
index: true,
|
|
||||||
follow: true,
|
|
||||||
googleBot: {
|
|
||||||
index: true,
|
|
||||||
follow: true,
|
|
||||||
'max-image-preview': 'large',
|
|
||||||
'max-video-preview': -1,
|
|
||||||
'max-snippet': -1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
openGraph: {
|
|
||||||
type: 'website',
|
|
||||||
locale: 'en_US',
|
|
||||||
url: 'https://sim.ai',
|
|
||||||
title: 'Sim',
|
|
||||||
description:
|
|
||||||
'Build and deploy AI agents using our Figma-like canvas. Build, write evals, and deploy AI agent workflows that automate workflows and streamline your business processes.',
|
|
||||||
siteName: 'Sim',
|
|
||||||
images: [
|
|
||||||
{
|
|
||||||
url: getAssetUrl('social/facebook.png'),
|
|
||||||
width: 1200,
|
|
||||||
height: 630,
|
|
||||||
alt: 'Sim',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
twitter: {
|
|
||||||
card: 'summary_large_image',
|
|
||||||
title: 'Sim',
|
|
||||||
description:
|
|
||||||
'Build and deploy AI agents using our Figma-like canvas. Build, write evals, and deploy AI agent workflows that automate workflows and streamline your business processes.',
|
|
||||||
images: [getAssetUrl('social/twitter.png')],
|
|
||||||
creator: '@simstudioai',
|
|
||||||
site: '@simstudioai',
|
|
||||||
},
|
|
||||||
manifest: '/favicon/site.webmanifest',
|
|
||||||
icons: {
|
|
||||||
icon: [
|
|
||||||
{ url: '/favicon/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
|
|
||||||
{ url: '/favicon/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
|
|
||||||
{
|
|
||||||
url: '/favicon/favicon-192x192.png',
|
|
||||||
sizes: '192x192',
|
|
||||||
type: 'image/png',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/favicon/favicon-512x512.png',
|
|
||||||
sizes: '512x512',
|
|
||||||
type: 'image/png',
|
|
||||||
},
|
|
||||||
{ url: '/sim.png', sizes: 'any', type: 'image/png' },
|
|
||||||
],
|
|
||||||
apple: '/favicon/apple-touch-icon.png',
|
|
||||||
shortcut: '/favicon/favicon.ico',
|
|
||||||
},
|
|
||||||
appleWebApp: {
|
|
||||||
capable: true,
|
|
||||||
statusBarStyle: 'default',
|
|
||||||
title: 'Sim',
|
|
||||||
},
|
|
||||||
formatDetection: {
|
|
||||||
telephone: false,
|
|
||||||
},
|
|
||||||
category: 'technology',
|
|
||||||
other: {
|
|
||||||
'apple-mobile-web-app-capable': 'yes',
|
|
||||||
'mobile-web-app-capable': 'yes',
|
|
||||||
'msapplication-TileColor': '#ffffff',
|
|
||||||
'msapplication-config': '/favicon/browserconfig.xml',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
const structuredData = generateStructuredData()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang='en' suppressHydrationWarning className={GeistSans.className}>
|
<html lang='en' suppressHydrationWarning>
|
||||||
<head>
|
<head>
|
||||||
{/* Structured Data for SEO */}
|
{/* Structured Data for SEO */}
|
||||||
<script
|
<script
|
||||||
type='application/ld+json'
|
type='application/ld+json'
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: JSON.stringify({
|
__html: JSON.stringify(structuredData),
|
||||||
'@context': 'https://schema.org',
|
|
||||||
'@type': 'SoftwareApplication',
|
|
||||||
name: 'Sim',
|
|
||||||
description:
|
|
||||||
'Build and deploy AI agents using our Figma-like canvas. Build, write evals, and deploy AI agent workflows that automate workflows and streamline your business processes.',
|
|
||||||
url: 'https://sim.ai',
|
|
||||||
applicationCategory: 'BusinessApplication',
|
|
||||||
operatingSystem: 'Web Browser',
|
|
||||||
offers: {
|
|
||||||
'@type': 'Offer',
|
|
||||||
category: 'SaaS',
|
|
||||||
},
|
|
||||||
creator: {
|
|
||||||
'@type': 'Organization',
|
|
||||||
name: 'Sim',
|
|
||||||
url: 'https://sim.ai',
|
|
||||||
},
|
|
||||||
featureList: [
|
|
||||||
'Visual AI Agent Builder',
|
|
||||||
'Workflow Canvas Interface',
|
|
||||||
'AI Agent Automation',
|
|
||||||
'Custom AI Workflows',
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -226,24 +99,26 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
<PublicEnvScript />
|
<PublicEnvScript />
|
||||||
|
|
||||||
{/* RB2B Script - Only load on hosted version */}
|
{/* RB2B Script - Only load on hosted version */}
|
||||||
{/* {isHosted && env.NEXT_PUBLIC_RB2B_KEY && (
|
{isHosted && env.NEXT_PUBLIC_RB2B_KEY && (
|
||||||
<script
|
<script
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: `!function () {var reb2b = window.reb2b = window.reb2b || [];if (reb2b.invoked) return;reb2b.invoked = true;reb2b.methods = ["identify", "collect"];reb2b.factory = function (method) {return function () {var args = Array.prototype.slice.call(arguments);args.unshift(method);reb2b.push(args);return reb2b;};};for (var i = 0; i < reb2b.methods.length; i++) {var key = reb2b.methods[i];reb2b[key] = reb2b.factory(key);}reb2b.load = function (key) {var script = document.createElement("script");script.type = "text/javascript";script.async = true;script.src = "https://b2bjsstore.s3.us-west-2.amazonaws.com/b/" + key + "/${env.NEXT_PUBLIC_RB2B_KEY}.js.gz";var first = document.getElementsByTagName("script")[0];first.parentNode.insertBefore(script, first);};reb2b.SNIPPET_VERSION = "1.0.1";reb2b.load("${env.NEXT_PUBLIC_RB2B_KEY}");}();`,
|
__html: `!function () {var reb2b = window.reb2b = window.reb2b || [];if (reb2b.invoked) return;reb2b.invoked = true;reb2b.methods = ["identify", "collect"];reb2b.factory = function (method) {return function () {var args = Array.prototype.slice.call(arguments);args.unshift(method);reb2b.push(args);return reb2b;};};for (var i = 0; i < reb2b.methods.length; i++) {var key = reb2b.methods[i];reb2b[key] = reb2b.factory(key);}reb2b.load = function (key) {var script = document.createElement("script");script.type = "text/javascript";script.async = true;script.src = "https://b2bjsstore.s3.us-west-2.amazonaws.com/b/" + key + "/${env.NEXT_PUBLIC_RB2B_KEY}.js.gz";var first = document.getElementsByTagName("script")[0];first.parentNode.insertBefore(script, first);};reb2b.SNIPPET_VERSION = "1.0.1";reb2b.load("${env.NEXT_PUBLIC_RB2B_KEY}");}();`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)} */}
|
)}
|
||||||
</head>
|
</head>
|
||||||
<body suppressHydrationWarning>
|
<body suppressHydrationWarning>
|
||||||
<ZoomPrevention />
|
<BrandedLayout>
|
||||||
<TelemetryConsentDialog />
|
<ZoomPrevention />
|
||||||
{children}
|
<TelemetryConsentDialog />
|
||||||
{isHosted && (
|
{children}
|
||||||
<>
|
{isHosted && (
|
||||||
<SpeedInsights />
|
<>
|
||||||
<Analytics />
|
<SpeedInsights />
|
||||||
</>
|
<Analytics />
|
||||||
)}
|
</>
|
||||||
|
)}
|
||||||
|
</BrandedLayout>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
|
|||||||
29
apps/sim/app/manifest.ts
Normal file
29
apps/sim/app/manifest.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import type { MetadataRoute } from 'next'
|
||||||
|
import { getBrandConfig } from '@/lib/branding/branding'
|
||||||
|
|
||||||
|
export default function manifest(): MetadataRoute.Manifest {
|
||||||
|
const brand = getBrandConfig()
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: brand.name,
|
||||||
|
short_name: brand.name,
|
||||||
|
description:
|
||||||
|
'Build and deploy AI agents using our Figma-like canvas. Build, write evals, and deploy AI agent workflows that automate workflows and streamline your business processes.',
|
||||||
|
start_url: '/',
|
||||||
|
display: 'standalone',
|
||||||
|
background_color: brand.primaryColor || '#ffffff',
|
||||||
|
theme_color: brand.primaryColor || '#ffffff',
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: '/favicon/android-chrome-192x192.png',
|
||||||
|
sizes: '192x192',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/favicon/android-chrome-512x512.png',
|
||||||
|
sizes: '512x512',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { Suspense, useEffect, useState } from 'react'
|
|||||||
import { CheckCircle, Heart, Info, Loader2, XCircle } from 'lucide-react'
|
import { CheckCircle, Heart, Info, Loader2, XCircle } from 'lucide-react'
|
||||||
import { useSearchParams } from 'next/navigation'
|
import { useSearchParams } from 'next/navigation'
|
||||||
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui'
|
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui'
|
||||||
|
import { useBrandConfig } from '@/lib/branding/branding'
|
||||||
|
|
||||||
interface UnsubscribeData {
|
interface UnsubscribeData {
|
||||||
success: boolean
|
success: boolean
|
||||||
@@ -26,6 +27,7 @@ function UnsubscribeContent() {
|
|||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [processing, setProcessing] = useState(false)
|
const [processing, setProcessing] = useState(false)
|
||||||
const [unsubscribed, setUnsubscribed] = useState(false)
|
const [unsubscribed, setUnsubscribed] = useState(false)
|
||||||
|
const brand = useBrandConfig()
|
||||||
|
|
||||||
const email = searchParams.get('email')
|
const email = searchParams.get('email')
|
||||||
const token = searchParams.get('token')
|
const token = searchParams.get('token')
|
||||||
@@ -160,7 +162,7 @@ function UnsubscribeContent() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
window.open(
|
window.open(
|
||||||
'mailto:help@sim.ai?subject=Unsubscribe%20Help&body=Hi%2C%20I%20need%20help%20unsubscribing%20from%20emails.%20My%20unsubscribe%20link%20is%20not%20working.',
|
`mailto:${brand.supportEmail}?subject=Unsubscribe%20Help&body=Hi%2C%20I%20need%20help%20unsubscribing%20from%20emails.%20My%20unsubscribe%20link%20is%20not%20working.`,
|
||||||
'_blank'
|
'_blank'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -176,8 +178,8 @@ function UnsubscribeContent() {
|
|||||||
<div className='mt-4 text-center'>
|
<div className='mt-4 text-center'>
|
||||||
<p className='text-muted-foreground text-xs'>
|
<p className='text-muted-foreground text-xs'>
|
||||||
Need immediate help? Email us at{' '}
|
Need immediate help? Email us at{' '}
|
||||||
<a href='mailto:help@sim.ai' className='text-primary hover:underline'>
|
<a href={`mailto:${brand.supportEmail}`} className='text-primary hover:underline'>
|
||||||
help@sim.ai
|
{brand.supportEmail}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -222,7 +224,7 @@ function UnsubscribeContent() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
window.open(
|
window.open(
|
||||||
'mailto:help@sim.ai?subject=Account%20Help&body=Hi%2C%20I%20need%20help%20with%20my%20account%20emails.',
|
`mailto:${brand.supportEmail}?subject=Account%20Help&body=Hi%2C%20I%20need%20help%20with%20my%20account%20emails.`,
|
||||||
'_blank'
|
'_blank'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -256,8 +258,8 @@ function UnsubscribeContent() {
|
|||||||
<p className='text-muted-foreground text-sm'>
|
<p className='text-muted-foreground text-sm'>
|
||||||
If you change your mind, you can always update your email preferences in your account
|
If you change your mind, you can always update your email preferences in your account
|
||||||
settings or contact us at{' '}
|
settings or contact us at{' '}
|
||||||
<a href='mailto:help@sim.ai' className='text-primary hover:underline'>
|
<a href={`mailto:${brand.supportEmail}`} className='text-primary hover:underline'>
|
||||||
help@sim.ai
|
{brand.supportEmail}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -369,8 +371,8 @@ function UnsubscribeContent() {
|
|||||||
|
|
||||||
<p className='text-center text-muted-foreground text-xs'>
|
<p className='text-center text-muted-foreground text-xs'>
|
||||||
Questions? Contact us at{' '}
|
Questions? Contact us at{' '}
|
||||||
<a href='mailto:help@sim.ai' className='text-primary hover:underline'>
|
<a href={`mailto:${brand.supportEmail}`} className='text-primary hover:underline'>
|
||||||
help@sim.ai
|
{brand.supportEmail}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,10 +7,14 @@ import { BookOpen, Building2, LibraryBig, ScrollText, Search, Shapes, Workflow }
|
|||||||
import { useParams, useRouter } from 'next/navigation'
|
import { useParams, useRouter } from 'next/navigation'
|
||||||
import { Dialog, DialogOverlay, DialogPortal, DialogTitle } from '@/components/ui/dialog'
|
import { Dialog, DialogOverlay, DialogPortal, DialogTitle } from '@/components/ui/dialog'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { useBrandConfig } from '@/lib/branding/branding'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import {
|
||||||
|
TemplateCard,
|
||||||
|
TemplateCardSkeleton,
|
||||||
|
} from '@/app/workspace/[workspaceId]/templates/components/template-card'
|
||||||
|
import { getKeyboardShortcutText } from '@/app/workspace/[workspaceId]/w/hooks/use-keyboard-shortcuts'
|
||||||
import { getAllBlocks } from '@/blocks'
|
import { getAllBlocks } from '@/blocks'
|
||||||
import { TemplateCard, TemplateCardSkeleton } from '../../../templates/components/template-card'
|
|
||||||
import { getKeyboardShortcutText } from '../../hooks/use-keyboard-shortcuts'
|
|
||||||
import { type NavigationSection, useSearchNavigation } from './hooks/use-search-navigation'
|
import { type NavigationSection, useSearchNavigation } from './hooks/use-search-navigation'
|
||||||
|
|
||||||
interface SearchModalProps {
|
interface SearchModalProps {
|
||||||
@@ -100,6 +104,7 @@ export function SearchModal({
|
|||||||
const params = useParams()
|
const params = useParams()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const workspaceId = params.workspaceId as string
|
const workspaceId = params.workspaceId as string
|
||||||
|
const brand = useBrandConfig()
|
||||||
|
|
||||||
// Local state for templates to handle star changes
|
// Local state for templates to handle star changes
|
||||||
const [localTemplates, setLocalTemplates] = useState<TemplateData[]>(templates)
|
const [localTemplates, setLocalTemplates] = useState<TemplateData[]>(templates)
|
||||||
@@ -182,7 +187,7 @@ export function SearchModal({
|
|||||||
id: 'docs',
|
id: 'docs',
|
||||||
name: 'Docs',
|
name: 'Docs',
|
||||||
icon: BookOpen,
|
icon: BookOpen,
|
||||||
href: 'https://docs.simstudio.ai/',
|
href: brand.documentationUrl || 'https://docs.sim.ai/',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[workspaceId]
|
[workspaceId]
|
||||||
|
|||||||
55
apps/sim/components/branded-layout.tsx
Normal file
55
apps/sim/components/branded-layout.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { generateBrandCSS, getBrandConfig } from '@/lib/branding/branding'
|
||||||
|
|
||||||
|
interface BrandedLayoutProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BrandedLayout({ children }: BrandedLayoutProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
const config = getBrandConfig()
|
||||||
|
|
||||||
|
// Update document title
|
||||||
|
if (config.name !== 'Sim') {
|
||||||
|
document.title = config.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update favicon
|
||||||
|
if (config.faviconUrl) {
|
||||||
|
const faviconLink = document.querySelector("link[rel*='icon']") as HTMLLinkElement
|
||||||
|
if (faviconLink) {
|
||||||
|
faviconLink.href = config.faviconUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject brand CSS
|
||||||
|
const brandStyleId = 'brand-styles'
|
||||||
|
let brandStyleElement = document.getElementById(brandStyleId) as HTMLStyleElement
|
||||||
|
|
||||||
|
if (!brandStyleElement) {
|
||||||
|
brandStyleElement = document.createElement('style')
|
||||||
|
brandStyleElement.id = brandStyleId
|
||||||
|
document.head.appendChild(brandStyleElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
brandStyleElement.textContent = generateBrandCSS(config)
|
||||||
|
|
||||||
|
// Load custom CSS if provided
|
||||||
|
if (config.customCssUrl) {
|
||||||
|
const customCssId = 'custom-brand-css'
|
||||||
|
let customCssLink = document.getElementById(customCssId) as HTMLLinkElement
|
||||||
|
|
||||||
|
if (!customCssLink) {
|
||||||
|
customCssLink = document.createElement('link')
|
||||||
|
customCssLink.id = customCssId
|
||||||
|
customCssLink.rel = 'stylesheet'
|
||||||
|
customCssLink.href = config.customCssUrl
|
||||||
|
document.head.appendChild(customCssLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <>{children}</>
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
Section,
|
Section,
|
||||||
Text,
|
Text,
|
||||||
} from '@react-email/components'
|
} from '@react-email/components'
|
||||||
|
import { getBrandConfig } from '@/lib/branding/branding'
|
||||||
|
|
||||||
interface WorkspaceInvitation {
|
interface WorkspaceInvitation {
|
||||||
workspaceId: string
|
workspaceId: string
|
||||||
@@ -57,6 +58,7 @@ export const BatchInvitationEmail = ({
|
|||||||
workspaceInvitations = [],
|
workspaceInvitations = [],
|
||||||
acceptUrl,
|
acceptUrl,
|
||||||
}: BatchInvitationEmailProps) => {
|
}: BatchInvitationEmailProps) => {
|
||||||
|
const brand = getBrandConfig()
|
||||||
const hasWorkspaces = workspaceInvitations.length > 0
|
const hasWorkspaces = workspaceInvitations.length > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -69,7 +71,13 @@ export const BatchInvitationEmail = ({
|
|||||||
<Body style={main}>
|
<Body style={main}>
|
||||||
<Container style={container}>
|
<Container style={container}>
|
||||||
<Section style={logoContainer}>
|
<Section style={logoContainer}>
|
||||||
<Img src='https://sim.ai/logo.png' width='120' height='36' alt='Sim' style={logo} />
|
<Img
|
||||||
|
src={brand.logoUrl || 'https://sim.ai/logo.png'}
|
||||||
|
width='120'
|
||||||
|
height='36'
|
||||||
|
alt={brand.name}
|
||||||
|
style={logo}
|
||||||
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Heading style={h1}>You're invited to join {organizationName}!</Heading>
|
<Heading style={h1}>You're invited to join {organizationName}!</Heading>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Container, Img, Link, Section, Text } from '@react-email/components'
|
import { Container, Img, Link, Section, Text } from '@react-email/components'
|
||||||
|
import { getBrandConfig } from '@/lib/branding/branding'
|
||||||
import { env } from '@/lib/env'
|
import { env } from '@/lib/env'
|
||||||
import { getAssetUrl } from '@/lib/utils'
|
import { getAssetUrl } from '@/lib/utils'
|
||||||
|
|
||||||
@@ -16,6 +17,8 @@ export const EmailFooter = ({
|
|||||||
baseUrl = env.NEXT_PUBLIC_APP_URL || 'https://sim.ai',
|
baseUrl = env.NEXT_PUBLIC_APP_URL || 'https://sim.ai',
|
||||||
unsubscribe,
|
unsubscribe,
|
||||||
}: EmailFooterProps) => {
|
}: EmailFooterProps) => {
|
||||||
|
const brand = getBrandConfig()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Section style={{ maxWidth: '580px', margin: '0 auto', padding: '20px 0' }}>
|
<Section style={{ maxWidth: '580px', margin: '0 auto', padding: '20px 0' }}>
|
||||||
@@ -62,11 +65,11 @@ export const EmailFooter = ({
|
|||||||
margin: '8px 0 0 0',
|
margin: '8px 0 0 0',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
© {new Date().getFullYear()} Sim, All Rights Reserved
|
© {new Date().getFullYear()} {brand.name}, All Rights Reserved
|
||||||
<br />
|
<br />
|
||||||
If you have any questions, please contact us at{' '}
|
If you have any questions, please contact us at{' '}
|
||||||
<a
|
<a
|
||||||
href='mailto:help@sim.ai'
|
href={`mailto:${brand.supportEmail}`}
|
||||||
style={{
|
style={{
|
||||||
color: '#706a7b !important',
|
color: '#706a7b !important',
|
||||||
textDecoration: 'underline',
|
textDecoration: 'underline',
|
||||||
@@ -74,7 +77,7 @@ export const EmailFooter = ({
|
|||||||
fontFamily: 'HelveticaNeue, Helvetica, Arial, sans-serif',
|
fontFamily: 'HelveticaNeue, Helvetica, Arial, sans-serif',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
help@sim.ai
|
{brand.supportEmail}
|
||||||
</a>
|
</a>
|
||||||
</Text>
|
</Text>
|
||||||
<table cellPadding={0} cellSpacing={0} style={{ width: '100%', marginTop: '4px' }}>
|
<table cellPadding={0} cellSpacing={0} style={{ width: '100%', marginTop: '4px' }}>
|
||||||
@@ -118,7 +121,7 @@ export const EmailFooter = ({
|
|||||||
href={
|
href={
|
||||||
unsubscribe?.unsubscribeToken && unsubscribe?.email
|
unsubscribe?.unsubscribeToken && unsubscribe?.email
|
||||||
? `${baseUrl}/unsubscribe?token=${unsubscribe.unsubscribeToken}&email=${encodeURIComponent(unsubscribe.email)}`
|
? `${baseUrl}/unsubscribe?token=${unsubscribe.unsubscribeToken}&email=${encodeURIComponent(unsubscribe.email)}`
|
||||||
: `mailto:help@sim.ai?subject=Unsubscribe%20Request&body=Please%20unsubscribe%20me%20from%20all%20emails.`
|
: `mailto:${brand.supportEmail}?subject=Unsubscribe%20Request&body=Please%20unsubscribe%20me%20from%20all%20emails.`
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
color: '#706a7b !important',
|
color: '#706a7b !important',
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
} from '@react-email/components'
|
} from '@react-email/components'
|
||||||
import { format } from 'date-fns'
|
import { format } from 'date-fns'
|
||||||
|
import { getBrandConfig } from '@/lib/branding/branding'
|
||||||
import { env } from '@/lib/env'
|
import { env } from '@/lib/env'
|
||||||
import { getAssetUrl } from '@/lib/utils'
|
import { getAssetUrl } from '@/lib/utils'
|
||||||
import { baseStyles } from './base-styles'
|
import { baseStyles } from './base-styles'
|
||||||
@@ -34,6 +35,8 @@ export const InvitationEmail = ({
|
|||||||
invitedEmail = '',
|
invitedEmail = '',
|
||||||
updatedDate = new Date(),
|
updatedDate = new Date(),
|
||||||
}: InvitationEmailProps) => {
|
}: InvitationEmailProps) => {
|
||||||
|
const brand = getBrandConfig()
|
||||||
|
|
||||||
// Extract invitation ID or token from inviteLink if present
|
// Extract invitation ID or token from inviteLink if present
|
||||||
let enhancedLink = inviteLink
|
let enhancedLink = inviteLink
|
||||||
|
|
||||||
@@ -60,9 +63,9 @@ export const InvitationEmail = ({
|
|||||||
<Row>
|
<Row>
|
||||||
<Column style={{ textAlign: 'center' }}>
|
<Column style={{ textAlign: 'center' }}>
|
||||||
<Img
|
<Img
|
||||||
src={getAssetUrl('static/sim.png')}
|
src={brand.logoUrl || getAssetUrl('static/sim.png')}
|
||||||
width='114'
|
width='114'
|
||||||
alt='Sim'
|
alt={brand.name}
|
||||||
style={{
|
style={{
|
||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
Section,
|
Section,
|
||||||
Text,
|
Text,
|
||||||
} from '@react-email/components'
|
} from '@react-email/components'
|
||||||
|
import { getBrandConfig } from '@/lib/branding/branding'
|
||||||
import { env } from '@/lib/env'
|
import { env } from '@/lib/env'
|
||||||
import { getAssetUrl } from '@/lib/utils'
|
import { getAssetUrl } from '@/lib/utils'
|
||||||
import { baseStyles } from './base-styles'
|
import { baseStyles } from './base-styles'
|
||||||
@@ -24,18 +25,18 @@ interface OTPVerificationEmailProps {
|
|||||||
|
|
||||||
const baseUrl = env.NEXT_PUBLIC_APP_URL || 'https://sim.ai'
|
const baseUrl = env.NEXT_PUBLIC_APP_URL || 'https://sim.ai'
|
||||||
|
|
||||||
const getSubjectByType = (type: string, chatTitle?: string) => {
|
const getSubjectByType = (type: string, brandName: string, chatTitle?: string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'sign-in':
|
case 'sign-in':
|
||||||
return 'Sign in to Sim'
|
return `Sign in to ${brandName}`
|
||||||
case 'email-verification':
|
case 'email-verification':
|
||||||
return 'Verify your email for Sim'
|
return `Verify your email for ${brandName}`
|
||||||
case 'forget-password':
|
case 'forget-password':
|
||||||
return 'Reset your Sim password'
|
return `Reset your ${brandName} password`
|
||||||
case 'chat-access':
|
case 'chat-access':
|
||||||
return `Verification code for ${chatTitle || 'Chat'}`
|
return `Verification code for ${chatTitle || 'Chat'}`
|
||||||
default:
|
default:
|
||||||
return 'Verification code for Sim'
|
return `Verification code for ${brandName}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,17 +46,19 @@ export const OTPVerificationEmail = ({
|
|||||||
type = 'email-verification',
|
type = 'email-verification',
|
||||||
chatTitle,
|
chatTitle,
|
||||||
}: OTPVerificationEmailProps) => {
|
}: OTPVerificationEmailProps) => {
|
||||||
|
const brand = getBrandConfig()
|
||||||
|
|
||||||
// Get a message based on the type
|
// Get a message based on the type
|
||||||
const getMessage = () => {
|
const getMessage = () => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'sign-in':
|
case 'sign-in':
|
||||||
return 'Sign in to Sim'
|
return `Sign in to ${brand.name}`
|
||||||
case 'forget-password':
|
case 'forget-password':
|
||||||
return 'Reset your password for Sim'
|
return `Reset your password for ${brand.name}`
|
||||||
case 'chat-access':
|
case 'chat-access':
|
||||||
return `Access ${chatTitle || 'the chat'}`
|
return `Access ${chatTitle || 'the chat'}`
|
||||||
default:
|
default:
|
||||||
return 'Welcome to Sim'
|
return `Welcome to ${brand.name}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,15 +66,15 @@ export const OTPVerificationEmail = ({
|
|||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
<Body style={baseStyles.main}>
|
<Body style={baseStyles.main}>
|
||||||
<Preview>{getSubjectByType(type, chatTitle)}</Preview>
|
<Preview>{getSubjectByType(type, brand.name, chatTitle)}</Preview>
|
||||||
<Container style={baseStyles.container}>
|
<Container style={baseStyles.container}>
|
||||||
<Section style={{ padding: '30px 0', textAlign: 'center' }}>
|
<Section style={{ padding: '30px 0', textAlign: 'center' }}>
|
||||||
<Row>
|
<Row>
|
||||||
<Column style={{ textAlign: 'center' }}>
|
<Column style={{ textAlign: 'center' }}>
|
||||||
<Img
|
<Img
|
||||||
src={getAssetUrl('static/sim.png')}
|
src={brand.logoUrl || getAssetUrl('static/sim.png')}
|
||||||
width='114'
|
width='114'
|
||||||
alt='Sim'
|
alt={brand.name}
|
||||||
style={{
|
style={{
|
||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
} from '@react-email/components'
|
} from '@react-email/components'
|
||||||
import { format } from 'date-fns'
|
import { format } from 'date-fns'
|
||||||
|
import { getBrandConfig } from '@/lib/branding/branding'
|
||||||
import { env } from '@/lib/env'
|
import { env } from '@/lib/env'
|
||||||
import { getAssetUrl } from '@/lib/utils'
|
import { getAssetUrl } from '@/lib/utils'
|
||||||
import { baseStyles } from './base-styles'
|
import { baseStyles } from './base-styles'
|
||||||
@@ -30,19 +31,21 @@ export const ResetPasswordEmail = ({
|
|||||||
resetLink = '',
|
resetLink = '',
|
||||||
updatedDate = new Date(),
|
updatedDate = new Date(),
|
||||||
}: ResetPasswordEmailProps) => {
|
}: ResetPasswordEmailProps) => {
|
||||||
|
const brand = getBrandConfig()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
<Body style={baseStyles.main}>
|
<Body style={baseStyles.main}>
|
||||||
<Preview>Reset your Sim password</Preview>
|
<Preview>Reset your {brand.name} password</Preview>
|
||||||
<Container style={baseStyles.container}>
|
<Container style={baseStyles.container}>
|
||||||
<Section style={{ padding: '30px 0', textAlign: 'center' }}>
|
<Section style={{ padding: '30px 0', textAlign: 'center' }}>
|
||||||
<Row>
|
<Row>
|
||||||
<Column style={{ textAlign: 'center' }}>
|
<Column style={{ textAlign: 'center' }}>
|
||||||
<Img
|
<Img
|
||||||
src={getAssetUrl('static/sim.png')}
|
src={brand.logoUrl || getAssetUrl('static/sim.png')}
|
||||||
width='114'
|
width='114'
|
||||||
alt='Sim'
|
alt={brand.name}
|
||||||
style={{
|
style={{
|
||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
}}
|
}}
|
||||||
@@ -62,8 +65,8 @@ export const ResetPasswordEmail = ({
|
|||||||
<Section style={baseStyles.content}>
|
<Section style={baseStyles.content}>
|
||||||
<Text style={baseStyles.paragraph}>Hello {username},</Text>
|
<Text style={baseStyles.paragraph}>Hello {username},</Text>
|
||||||
<Text style={baseStyles.paragraph}>
|
<Text style={baseStyles.paragraph}>
|
||||||
You recently requested to reset your password for your Sim account. Use the button
|
You recently requested to reset your password for your {brand.name} account. Use the
|
||||||
below to reset it. This password reset is only valid for the next 24 hours.
|
button below to reset it. This password reset is only valid for the next 24 hours.
|
||||||
</Text>
|
</Text>
|
||||||
<Link href={resetLink} style={{ textDecoration: 'none' }}>
|
<Link href={resetLink} style={{ textDecoration: 'none' }}>
|
||||||
<Text style={baseStyles.button}>Reset Your Password</Text>
|
<Text style={baseStyles.button}>Reset Your Password</Text>
|
||||||
@@ -75,7 +78,7 @@ export const ResetPasswordEmail = ({
|
|||||||
<Text style={baseStyles.paragraph}>
|
<Text style={baseStyles.paragraph}>
|
||||||
Best regards,
|
Best regards,
|
||||||
<br />
|
<br />
|
||||||
The Sim Team
|
The {brand.name} Team
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
Section,
|
Section,
|
||||||
Text,
|
Text,
|
||||||
} from '@react-email/components'
|
} from '@react-email/components'
|
||||||
|
import { getBrandConfig } from '@/lib/branding/branding'
|
||||||
import { env } from '@/lib/env'
|
import { env } from '@/lib/env'
|
||||||
import { getAssetUrl } from '@/lib/utils'
|
import { getAssetUrl } from '@/lib/utils'
|
||||||
import { baseStyles } from './base-styles'
|
import { baseStyles } from './base-styles'
|
||||||
@@ -29,6 +30,8 @@ export const WorkspaceInvitationEmail = ({
|
|||||||
inviterName = 'Someone',
|
inviterName = 'Someone',
|
||||||
invitationLink = '',
|
invitationLink = '',
|
||||||
}: WorkspaceInvitationEmailProps) => {
|
}: WorkspaceInvitationEmailProps) => {
|
||||||
|
const brand = getBrandConfig()
|
||||||
|
|
||||||
// Extract token from the link to ensure we're using the correct format
|
// Extract token from the link to ensure we're using the correct format
|
||||||
let enhancedLink = invitationLink
|
let enhancedLink = invitationLink
|
||||||
|
|
||||||
@@ -55,9 +58,9 @@ export const WorkspaceInvitationEmail = ({
|
|||||||
<Row>
|
<Row>
|
||||||
<Column style={{ textAlign: 'center' }}>
|
<Column style={{ textAlign: 'center' }}>
|
||||||
<Img
|
<Img
|
||||||
src={getAssetUrl('static/sim.png')}
|
src={brand.logoUrl || getAssetUrl('static/sim.png')}
|
||||||
width='114'
|
width='114'
|
||||||
alt='Sim'
|
alt={brand.name}
|
||||||
style={{
|
style={{
|
||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
}}
|
}}
|
||||||
|
|||||||
81
apps/sim/lib/branding/branding.ts
Normal file
81
apps/sim/lib/branding/branding.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { getEnv } from '@/lib/env'
|
||||||
|
|
||||||
|
export interface BrandConfig {
|
||||||
|
name: string
|
||||||
|
logoUrl?: string
|
||||||
|
faviconUrl?: string
|
||||||
|
primaryColor?: string
|
||||||
|
secondaryColor?: string
|
||||||
|
accentColor?: string
|
||||||
|
customCssUrl?: string
|
||||||
|
hideBranding?: boolean
|
||||||
|
footerText?: string
|
||||||
|
supportEmail?: string
|
||||||
|
supportUrl?: string
|
||||||
|
documentationUrl?: string
|
||||||
|
termsUrl?: string
|
||||||
|
privacyUrl?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default brand configuration values
|
||||||
|
*/
|
||||||
|
const defaultConfig: BrandConfig = {
|
||||||
|
name: 'Sim',
|
||||||
|
logoUrl: undefined,
|
||||||
|
faviconUrl: '/favicon/favicon.ico',
|
||||||
|
primaryColor: '#000000',
|
||||||
|
secondaryColor: '#6366f1',
|
||||||
|
accentColor: '#f59e0b',
|
||||||
|
customCssUrl: undefined,
|
||||||
|
hideBranding: false,
|
||||||
|
footerText: undefined,
|
||||||
|
supportEmail: 'help@sim.ai',
|
||||||
|
supportUrl: undefined,
|
||||||
|
documentationUrl: undefined,
|
||||||
|
termsUrl: undefined,
|
||||||
|
privacyUrl: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get branding configuration from environment variables
|
||||||
|
* Supports runtime configuration via Docker/Kubernetes
|
||||||
|
*/
|
||||||
|
export const getBrandConfig = (): BrandConfig => {
|
||||||
|
return {
|
||||||
|
name: getEnv('NEXT_PUBLIC_BRAND_NAME') || defaultConfig.name,
|
||||||
|
logoUrl: getEnv('NEXT_PUBLIC_BRAND_LOGO_URL') || defaultConfig.logoUrl,
|
||||||
|
faviconUrl: getEnv('NEXT_PUBLIC_BRAND_FAVICON_URL') || defaultConfig.faviconUrl,
|
||||||
|
primaryColor: getEnv('NEXT_PUBLIC_BRAND_PRIMARY_COLOR') || defaultConfig.primaryColor,
|
||||||
|
secondaryColor: getEnv('NEXT_PUBLIC_BRAND_SECONDARY_COLOR') || defaultConfig.secondaryColor,
|
||||||
|
accentColor: getEnv('NEXT_PUBLIC_BRAND_ACCENT_COLOR') || defaultConfig.accentColor,
|
||||||
|
customCssUrl: getEnv('NEXT_PUBLIC_CUSTOM_CSS_URL') || defaultConfig.customCssUrl,
|
||||||
|
hideBranding: getEnv('NEXT_PUBLIC_HIDE_BRANDING') === 'true',
|
||||||
|
footerText: getEnv('NEXT_PUBLIC_CUSTOM_FOOTER_TEXT') || defaultConfig.footerText,
|
||||||
|
supportEmail: getEnv('NEXT_PUBLIC_SUPPORT_EMAIL') || defaultConfig.supportEmail,
|
||||||
|
supportUrl: getEnv('NEXT_PUBLIC_SUPPORT_URL') || defaultConfig.supportUrl,
|
||||||
|
documentationUrl: getEnv('NEXT_PUBLIC_DOCUMENTATION_URL') || defaultConfig.documentationUrl,
|
||||||
|
termsUrl: getEnv('NEXT_PUBLIC_TERMS_URL') || defaultConfig.termsUrl,
|
||||||
|
privacyUrl: getEnv('NEXT_PUBLIC_PRIVACY_URL') || defaultConfig.privacyUrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate CSS custom properties for brand colors
|
||||||
|
*/
|
||||||
|
export const generateBrandCSS = (config: BrandConfig): string => {
|
||||||
|
return `
|
||||||
|
:root {
|
||||||
|
--brand-primary: ${config.primaryColor};
|
||||||
|
--brand-secondary: ${config.secondaryColor};
|
||||||
|
--brand-accent: ${config.accentColor};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to use brand configuration in React components
|
||||||
|
*/
|
||||||
|
export const useBrandConfig = () => {
|
||||||
|
return getBrandConfig()
|
||||||
|
}
|
||||||
153
apps/sim/lib/branding/metadata.ts
Normal file
153
apps/sim/lib/branding/metadata.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import type { Metadata } from 'next'
|
||||||
|
import { getBrandConfig } from '@/lib/branding/branding'
|
||||||
|
import { env } from '@/lib/env'
|
||||||
|
import { getAssetUrl } from '@/lib/utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate dynamic metadata based on brand configuration
|
||||||
|
*/
|
||||||
|
export function generateBrandedMetadata(override: Partial<Metadata> = {}): Metadata {
|
||||||
|
const brand = getBrandConfig()
|
||||||
|
|
||||||
|
const defaultTitle = brand.name
|
||||||
|
const defaultDescription = `Build and deploy AI agents using our Figma-like canvas. Build, write evals, and deploy AI agent workflows that automate workflows and streamline your business processes.`
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: {
|
||||||
|
template: `%s | ${brand.name}`,
|
||||||
|
default: defaultTitle,
|
||||||
|
},
|
||||||
|
description: defaultDescription,
|
||||||
|
applicationName: brand.name,
|
||||||
|
authors: [{ name: brand.name }],
|
||||||
|
generator: 'Next.js',
|
||||||
|
keywords: [
|
||||||
|
'AI agent',
|
||||||
|
'AI agent builder',
|
||||||
|
'AI agent workflow',
|
||||||
|
'AI workflow automation',
|
||||||
|
'visual workflow editor',
|
||||||
|
'AI agents',
|
||||||
|
'workflow canvas',
|
||||||
|
'intelligent automation',
|
||||||
|
'AI tools',
|
||||||
|
'workflow designer',
|
||||||
|
'artificial intelligence',
|
||||||
|
'business automation',
|
||||||
|
'AI agent workflows',
|
||||||
|
'visual programming',
|
||||||
|
],
|
||||||
|
referrer: 'origin-when-cross-origin',
|
||||||
|
creator: brand.name,
|
||||||
|
publisher: brand.name,
|
||||||
|
metadataBase: new URL(env.NEXT_PUBLIC_APP_URL),
|
||||||
|
alternates: {
|
||||||
|
canonical: '/',
|
||||||
|
languages: {
|
||||||
|
'en-US': '/en-US',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
robots: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
googleBot: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
'max-image-preview': 'large',
|
||||||
|
'max-video-preview': -1,
|
||||||
|
'max-snippet': -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
type: 'website',
|
||||||
|
locale: 'en_US',
|
||||||
|
url: env.NEXT_PUBLIC_APP_URL,
|
||||||
|
title: defaultTitle,
|
||||||
|
description: defaultDescription,
|
||||||
|
siteName: brand.name,
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: brand.logoUrl || getAssetUrl('social/facebook.png'),
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
alt: brand.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: 'summary_large_image',
|
||||||
|
title: defaultTitle,
|
||||||
|
description: defaultDescription,
|
||||||
|
images: [brand.logoUrl || getAssetUrl('social/twitter.png')],
|
||||||
|
creator: '@simstudioai',
|
||||||
|
site: '@simstudioai',
|
||||||
|
},
|
||||||
|
manifest: '/manifest.webmanifest',
|
||||||
|
icons: {
|
||||||
|
icon: [
|
||||||
|
{ url: '/favicon/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
|
||||||
|
{ url: '/favicon/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
|
||||||
|
{
|
||||||
|
url: '/favicon/favicon-192x192.png',
|
||||||
|
sizes: '192x192',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/favicon/favicon-512x512.png',
|
||||||
|
sizes: '512x512',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
{ url: brand.faviconUrl || '/sim.png', sizes: 'any', type: 'image/png' },
|
||||||
|
],
|
||||||
|
apple: '/favicon/apple-touch-icon.png',
|
||||||
|
shortcut: brand.faviconUrl || '/favicon/favicon.ico',
|
||||||
|
},
|
||||||
|
appleWebApp: {
|
||||||
|
capable: true,
|
||||||
|
statusBarStyle: 'default',
|
||||||
|
title: brand.name,
|
||||||
|
},
|
||||||
|
formatDetection: {
|
||||||
|
telephone: false,
|
||||||
|
},
|
||||||
|
category: 'technology',
|
||||||
|
other: {
|
||||||
|
'apple-mobile-web-app-capable': 'yes',
|
||||||
|
'mobile-web-app-capable': 'yes',
|
||||||
|
'msapplication-TileColor': brand.primaryColor || '#ffffff',
|
||||||
|
'msapplication-config': '/favicon/browserconfig.xml',
|
||||||
|
},
|
||||||
|
...override,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate static structured data for SEO
|
||||||
|
*/
|
||||||
|
export function generateStructuredData() {
|
||||||
|
return {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'SoftwareApplication',
|
||||||
|
name: 'Sim',
|
||||||
|
description:
|
||||||
|
'Build and deploy AI agents using our Figma-like canvas. Build, write evals, and deploy AI agent workflows that automate workflows and streamline your business processes.',
|
||||||
|
url: 'https://sim.ai',
|
||||||
|
applicationCategory: 'BusinessApplication',
|
||||||
|
operatingSystem: 'Web Browser',
|
||||||
|
offers: {
|
||||||
|
'@type': 'Offer',
|
||||||
|
category: 'SaaS',
|
||||||
|
},
|
||||||
|
creator: {
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: 'Sim',
|
||||||
|
url: 'https://sim.ai',
|
||||||
|
},
|
||||||
|
featureList: [
|
||||||
|
'Visual AI Agent Builder',
|
||||||
|
'Workflow Canvas Interface',
|
||||||
|
'AI Agent Automation',
|
||||||
|
'Custom AI Workflows',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -170,6 +170,22 @@ export const env = createEnv({
|
|||||||
NEXT_PUBLIC_RB2B_KEY: z.string().optional(), // RB2B tracking key for B2B analytics
|
NEXT_PUBLIC_RB2B_KEY: z.string().optional(), // RB2B tracking key for B2B analytics
|
||||||
NEXT_PUBLIC_GOOGLE_API_KEY: z.string().optional(), // Google API key for client-side API calls
|
NEXT_PUBLIC_GOOGLE_API_KEY: z.string().optional(), // Google API key for client-side API calls
|
||||||
NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER: z.string().optional(), // Google project number for Drive picker
|
NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER: z.string().optional(), // Google project number for Drive picker
|
||||||
|
|
||||||
|
// UI Branding & Whitelabeling
|
||||||
|
NEXT_PUBLIC_BRAND_NAME: z.string().optional(), // Custom brand name (defaults to "Sim")
|
||||||
|
NEXT_PUBLIC_BRAND_LOGO_URL: z.string().url().optional(), // Custom logo URL
|
||||||
|
NEXT_PUBLIC_BRAND_FAVICON_URL: z.string().url().optional(), // Custom favicon URL
|
||||||
|
NEXT_PUBLIC_BRAND_PRIMARY_COLOR: z.string().optional(), // Primary brand color (hex)
|
||||||
|
NEXT_PUBLIC_BRAND_SECONDARY_COLOR: z.string().optional(), // Secondary brand color (hex)
|
||||||
|
NEXT_PUBLIC_BRAND_ACCENT_COLOR: z.string().optional(), // Accent brand color (hex)
|
||||||
|
NEXT_PUBLIC_CUSTOM_CSS_URL: z.string().url().optional(), // Custom CSS stylesheet URL
|
||||||
|
NEXT_PUBLIC_HIDE_BRANDING: z.string().optional(), // Hide "Powered by" branding
|
||||||
|
NEXT_PUBLIC_CUSTOM_FOOTER_TEXT: z.string().optional(), // Custom footer text
|
||||||
|
NEXT_PUBLIC_SUPPORT_EMAIL: z.string().email().optional(), // Custom support email
|
||||||
|
NEXT_PUBLIC_SUPPORT_URL: z.string().url().optional(), // Custom support URL
|
||||||
|
NEXT_PUBLIC_DOCUMENTATION_URL: z.string().url().optional(), // Custom documentation URL
|
||||||
|
NEXT_PUBLIC_TERMS_URL: z.string().url().optional(), // Custom terms of service URL
|
||||||
|
NEXT_PUBLIC_PRIVACY_URL: z.string().url().optional(), // Custom privacy policy URL
|
||||||
},
|
},
|
||||||
|
|
||||||
// Variables available on both server and client
|
// Variables available on both server and client
|
||||||
@@ -188,6 +204,20 @@ export const env = createEnv({
|
|||||||
NEXT_PUBLIC_GOOGLE_API_KEY: process.env.NEXT_PUBLIC_GOOGLE_API_KEY,
|
NEXT_PUBLIC_GOOGLE_API_KEY: process.env.NEXT_PUBLIC_GOOGLE_API_KEY,
|
||||||
NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER: process.env.NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER,
|
NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER: process.env.NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER,
|
||||||
NEXT_PUBLIC_SOCKET_URL: process.env.NEXT_PUBLIC_SOCKET_URL,
|
NEXT_PUBLIC_SOCKET_URL: process.env.NEXT_PUBLIC_SOCKET_URL,
|
||||||
|
NEXT_PUBLIC_BRAND_NAME: process.env.NEXT_PUBLIC_BRAND_NAME,
|
||||||
|
NEXT_PUBLIC_BRAND_LOGO_URL: process.env.NEXT_PUBLIC_BRAND_LOGO_URL,
|
||||||
|
NEXT_PUBLIC_BRAND_FAVICON_URL: process.env.NEXT_PUBLIC_BRAND_FAVICON_URL,
|
||||||
|
NEXT_PUBLIC_BRAND_PRIMARY_COLOR: process.env.NEXT_PUBLIC_BRAND_PRIMARY_COLOR,
|
||||||
|
NEXT_PUBLIC_BRAND_SECONDARY_COLOR: process.env.NEXT_PUBLIC_BRAND_SECONDARY_COLOR,
|
||||||
|
NEXT_PUBLIC_BRAND_ACCENT_COLOR: process.env.NEXT_PUBLIC_BRAND_ACCENT_COLOR,
|
||||||
|
NEXT_PUBLIC_CUSTOM_CSS_URL: process.env.NEXT_PUBLIC_CUSTOM_CSS_URL,
|
||||||
|
NEXT_PUBLIC_HIDE_BRANDING: process.env.NEXT_PUBLIC_HIDE_BRANDING,
|
||||||
|
NEXT_PUBLIC_CUSTOM_FOOTER_TEXT: process.env.NEXT_PUBLIC_CUSTOM_FOOTER_TEXT,
|
||||||
|
NEXT_PUBLIC_SUPPORT_EMAIL: process.env.NEXT_PUBLIC_SUPPORT_EMAIL,
|
||||||
|
NEXT_PUBLIC_SUPPORT_URL: process.env.NEXT_PUBLIC_SUPPORT_URL,
|
||||||
|
NEXT_PUBLIC_DOCUMENTATION_URL: process.env.NEXT_PUBLIC_DOCUMENTATION_URL,
|
||||||
|
NEXT_PUBLIC_TERMS_URL: process.env.NEXT_PUBLIC_TERMS_URL,
|
||||||
|
NEXT_PUBLIC_PRIVACY_URL: process.env.NEXT_PUBLIC_PRIVACY_URL,
|
||||||
NODE_ENV: process.env.NODE_ENV,
|
NODE_ENV: process.env.NODE_ENV,
|
||||||
NEXT_TELEMETRY_DISABLED: process.env.NEXT_TELEMETRY_DISABLED,
|
NEXT_TELEMETRY_DISABLED: process.env.NEXT_TELEMETRY_DISABLED,
|
||||||
},
|
},
|
||||||
|
|||||||
15
bun.lock
15
bun.lock
@@ -15,12 +15,9 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.0.0-beta.5",
|
"@biomejs/biome": "2.0.0-beta.5",
|
||||||
"@next/env": "^15.3.2",
|
"@next/env": "^15.3.2",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
|
||||||
"dotenv-cli": "^8.0.0",
|
"dotenv-cli": "^8.0.0",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
"lint-staged": "16.0.0",
|
"lint-staged": "16.0.0",
|
||||||
"prettier": "^3.5.3",
|
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
|
||||||
"turbo": "2.5.5",
|
"turbo": "2.5.5",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1260,8 +1257,6 @@
|
|||||||
|
|
||||||
"@trigger.dev/sdk": ["@trigger.dev/sdk@3.3.17", "", { "dependencies": { "@opentelemetry/api": "1.9.0", "@opentelemetry/api-logs": "0.52.1", "@opentelemetry/semantic-conventions": "1.25.1", "@trigger.dev/core": "3.3.17", "chalk": "^5.2.0", "cronstrue": "^2.21.0", "debug": "^4.3.4", "evt": "^2.4.13", "slug": "^6.0.0", "terminal-link": "^3.0.0", "ulid": "^2.3.0", "uncrypto": "^0.1.3", "uuid": "^9.0.0", "ws": "^8.11.0" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-wjIjlQWKybYWw/J7LxFIOO1pXzxXoj9lxbFMvjb51JtfebxnQnh6aExN47nOGhVhV38wHYstfBI/8ClWwBnFYw=="],
|
"@trigger.dev/sdk": ["@trigger.dev/sdk@3.3.17", "", { "dependencies": { "@opentelemetry/api": "1.9.0", "@opentelemetry/api-logs": "0.52.1", "@opentelemetry/semantic-conventions": "1.25.1", "@trigger.dev/core": "3.3.17", "chalk": "^5.2.0", "cronstrue": "^2.21.0", "debug": "^4.3.4", "evt": "^2.4.13", "slug": "^6.0.0", "terminal-link": "^3.0.0", "ulid": "^2.3.0", "uncrypto": "^0.1.3", "uuid": "^9.0.0", "ws": "^8.11.0" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-wjIjlQWKybYWw/J7LxFIOO1pXzxXoj9lxbFMvjb51JtfebxnQnh6aExN47nOGhVhV38wHYstfBI/8ClWwBnFYw=="],
|
||||||
|
|
||||||
"@trivago/prettier-plugin-sort-imports": ["@trivago/prettier-plugin-sort-imports@5.2.2", "", { "dependencies": { "@babel/generator": "^7.26.5", "@babel/parser": "^7.26.7", "@babel/traverse": "^7.26.7", "@babel/types": "^7.26.7", "javascript-natural-sort": "^0.7.1", "lodash": "^4.17.21" }, "peerDependencies": { "@vue/compiler-sfc": "3.x", "prettier": "2.x - 3.x", "prettier-plugin-svelte": "3.x", "svelte": "4.x || 5.x" }, "optionalPeers": ["@vue/compiler-sfc", "prettier-plugin-svelte", "svelte"] }, "sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA=="],
|
|
||||||
|
|
||||||
"@tweenjs/tween.js": ["@tweenjs/tween.js@23.1.3", "", {}, "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA=="],
|
"@tweenjs/tween.js": ["@tweenjs/tween.js@23.1.3", "", {}, "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA=="],
|
||||||
|
|
||||||
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
|
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
|
||||||
@@ -2210,8 +2205,6 @@
|
|||||||
|
|
||||||
"jaeger-client": ["jaeger-client@3.19.0", "", { "dependencies": { "node-int64": "^0.4.0", "opentracing": "^0.14.4", "thriftrw": "^3.5.0", "uuid": "^8.3.2", "xorshift": "^1.1.1" } }, "sha512-M0c7cKHmdyEUtjemnJyx/y9uX16XHocL46yQvyqDlPdvAcwPDbHrIbKjQdBqtiE4apQ/9dmr+ZLJYYPGnurgpw=="],
|
"jaeger-client": ["jaeger-client@3.19.0", "", { "dependencies": { "node-int64": "^0.4.0", "opentracing": "^0.14.4", "thriftrw": "^3.5.0", "uuid": "^8.3.2", "xorshift": "^1.1.1" } }, "sha512-M0c7cKHmdyEUtjemnJyx/y9uX16XHocL46yQvyqDlPdvAcwPDbHrIbKjQdBqtiE4apQ/9dmr+ZLJYYPGnurgpw=="],
|
||||||
|
|
||||||
"javascript-natural-sort": ["javascript-natural-sort@0.7.1", "", {}, "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw=="],
|
|
||||||
|
|
||||||
"jest-diff": ["jest-diff@26.6.2", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^26.6.2", "jest-get-type": "^26.3.0", "pretty-format": "^26.6.2" } }, "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA=="],
|
"jest-diff": ["jest-diff@26.6.2", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^26.6.2", "jest-get-type": "^26.3.0", "pretty-format": "^26.6.2" } }, "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA=="],
|
||||||
|
|
||||||
"jest-get-type": ["jest-get-type@26.3.0", "", {}, "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig=="],
|
"jest-get-type": ["jest-get-type@26.3.0", "", {}, "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig=="],
|
||||||
@@ -2670,9 +2663,7 @@
|
|||||||
|
|
||||||
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
|
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
|
||||||
|
|
||||||
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
|
"prettier": ["prettier@3.4.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ=="],
|
||||||
|
|
||||||
"prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.6.14", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-import-sort": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-style-order": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-import-sort", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-style-order", "prettier-plugin-svelte"] }, "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg=="],
|
|
||||||
|
|
||||||
"pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
|
"pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
|
||||||
|
|
||||||
@@ -3474,8 +3465,6 @@
|
|||||||
|
|
||||||
"@react-email/code-block/prismjs": ["prismjs@1.29.0", "", {}, "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q=="],
|
"@react-email/code-block/prismjs": ["prismjs@1.29.0", "", {}, "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q=="],
|
||||||
|
|
||||||
"@react-email/render/prettier": ["prettier@3.4.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ=="],
|
|
||||||
|
|
||||||
"@rollup/plugin-commonjs/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
"@rollup/plugin-commonjs/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
|
|
||||||
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
@@ -4088,6 +4077,8 @@
|
|||||||
|
|
||||||
"ora/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
"ora/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||||
|
|
||||||
|
"resend/@react-email/render/prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
|
||||||
|
|
||||||
"restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
"restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
||||||
|
|
||||||
"sim/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
"sim/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||||
|
|||||||
91
helm/sim/examples/values-whitelabeled.yaml
Normal file
91
helm/sim/examples/values-whitelabeled.yaml
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# Whitelabeled deployment example for Sim
|
||||||
|
# This configuration shows how to customize branding for Acme Corp
|
||||||
|
|
||||||
|
# Global configuration
|
||||||
|
global:
|
||||||
|
imageRegistry: "ghcr.io"
|
||||||
|
storageClass: "managed-csi-premium"
|
||||||
|
|
||||||
|
# Main application with custom branding
|
||||||
|
app:
|
||||||
|
enabled: true
|
||||||
|
replicaCount: 1
|
||||||
|
|
||||||
|
# Custom branding configuration
|
||||||
|
env:
|
||||||
|
# Application URLs (update with your domain)
|
||||||
|
NEXT_PUBLIC_APP_URL: "https://sim.acme.ai"
|
||||||
|
BETTER_AUTH_URL: "https://sim.acme.ai"
|
||||||
|
SOCKET_SERVER_URL: "https://sim-ws.acme.ai"
|
||||||
|
NEXT_PUBLIC_SOCKET_URL: "https://sim-ws.acme.ai"
|
||||||
|
|
||||||
|
# Security settings (REQUIRED)
|
||||||
|
BETTER_AUTH_SECRET: "your-production-auth-secret-here"
|
||||||
|
ENCRYPTION_KEY: "your-production-encryption-key-here"
|
||||||
|
|
||||||
|
# UI Branding & Whitelabeling Configuration
|
||||||
|
NEXT_PUBLIC_BRAND_NAME: "Acme AI Studio"
|
||||||
|
NEXT_PUBLIC_BRAND_LOGO_URL: "https://acme.com/assets/logo.png"
|
||||||
|
NEXT_PUBLIC_BRAND_FAVICON_URL: "https://acme.com/assets/favicon.ico"
|
||||||
|
NEXT_PUBLIC_BRAND_PRIMARY_COLOR: "#1a365d" # Acme blue
|
||||||
|
NEXT_PUBLIC_BRAND_SECONDARY_COLOR: "#2d3748" # Dark gray
|
||||||
|
NEXT_PUBLIC_BRAND_ACCENT_COLOR: "#38b2ac" # Teal accent
|
||||||
|
NEXT_PUBLIC_CUSTOM_CSS_URL: "https://acme.com/assets/theme.css"
|
||||||
|
NEXT_PUBLIC_HIDE_BRANDING: "true" # Hide "Powered by Sim"
|
||||||
|
NEXT_PUBLIC_CUSTOM_FOOTER_TEXT: "© 2024 Acme Corp. All rights reserved."
|
||||||
|
NEXT_PUBLIC_SUPPORT_EMAIL: "ai-support@acme.com"
|
||||||
|
NEXT_PUBLIC_SUPPORT_URL: "https://help.acme.com/ai-studio"
|
||||||
|
NEXT_PUBLIC_DOCUMENTATION_URL: "https://docs.acme.com/ai-studio"
|
||||||
|
NEXT_PUBLIC_TERMS_URL: "https://acme.com/terms"
|
||||||
|
NEXT_PUBLIC_PRIVACY_URL: "https://acme.com/privacy"
|
||||||
|
|
||||||
|
# Realtime service
|
||||||
|
realtime:
|
||||||
|
enabled: true
|
||||||
|
replicaCount: 1
|
||||||
|
env:
|
||||||
|
NEXT_PUBLIC_APP_URL: "https://sim.acme.ai"
|
||||||
|
BETTER_AUTH_URL: "https://sim.acme.ai"
|
||||||
|
NEXT_PUBLIC_SOCKET_URL: "https://sim-ws.acme.ai"
|
||||||
|
BETTER_AUTH_SECRET: "your-production-auth-secret-here"
|
||||||
|
ALLOWED_ORIGINS: "https://sim.acme.ai"
|
||||||
|
|
||||||
|
# PostgreSQL database
|
||||||
|
postgresql:
|
||||||
|
enabled: true
|
||||||
|
auth:
|
||||||
|
password: "your-secure-db-password-here"
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
size: 20Gi
|
||||||
|
|
||||||
|
# Ingress configuration
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: "nginx"
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||||
|
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||||
|
|
||||||
|
app:
|
||||||
|
host: "sim.acme.ai"
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
|
||||||
|
realtime:
|
||||||
|
host: "sim-ws.acme.ai"
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
|
||||||
|
tls:
|
||||||
|
enabled: true
|
||||||
|
secretName: "sim-acme-tls"
|
||||||
|
|
||||||
|
# Auto-scaling
|
||||||
|
autoscaling:
|
||||||
|
enabled: true
|
||||||
|
minReplicas: 2
|
||||||
|
maxReplicas: 10
|
||||||
|
targetCPUUtilizationPercentage: 70
|
||||||
@@ -71,6 +71,22 @@ app:
|
|||||||
GITHUB_CLIENT_ID: ""
|
GITHUB_CLIENT_ID: ""
|
||||||
GITHUB_CLIENT_SECRET: ""
|
GITHUB_CLIENT_SECRET: ""
|
||||||
RESEND_API_KEY: ""
|
RESEND_API_KEY: ""
|
||||||
|
|
||||||
|
# UI Branding & Whitelabeling Configuration
|
||||||
|
NEXT_PUBLIC_BRAND_NAME: "Sim" # Custom brand name
|
||||||
|
NEXT_PUBLIC_BRAND_LOGO_URL: "" # Custom logo URL (leave empty for default)
|
||||||
|
NEXT_PUBLIC_BRAND_FAVICON_URL: "" # Custom favicon URL (leave empty for default)
|
||||||
|
NEXT_PUBLIC_BRAND_PRIMARY_COLOR: "#000000" # Primary brand color (hex)
|
||||||
|
NEXT_PUBLIC_BRAND_SECONDARY_COLOR: "#6366f1" # Secondary brand color (hex)
|
||||||
|
NEXT_PUBLIC_BRAND_ACCENT_COLOR: "#f59e0b" # Accent brand color (hex)
|
||||||
|
NEXT_PUBLIC_CUSTOM_CSS_URL: "" # Custom stylesheet URL (leave empty for none)
|
||||||
|
NEXT_PUBLIC_HIDE_BRANDING: "false" # Hide "Powered by" branding (true/false)
|
||||||
|
NEXT_PUBLIC_CUSTOM_FOOTER_TEXT: "" # Custom footer text (leave empty for default)
|
||||||
|
NEXT_PUBLIC_SUPPORT_EMAIL: "help@sim.ai" # Support email address
|
||||||
|
NEXT_PUBLIC_SUPPORT_URL: "" # Support page URL (leave empty for none)
|
||||||
|
NEXT_PUBLIC_DOCUMENTATION_URL: "" # Documentation URL (leave empty for none)
|
||||||
|
NEXT_PUBLIC_TERMS_URL: "" # Terms of service URL (leave empty for none)
|
||||||
|
NEXT_PUBLIC_PRIVACY_URL: "" # Privacy policy URL (leave empty for none)
|
||||||
|
|
||||||
# Service configuration
|
# Service configuration
|
||||||
service:
|
service:
|
||||||
|
|||||||
@@ -41,12 +41,9 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.0.0-beta.5",
|
"@biomejs/biome": "2.0.0-beta.5",
|
||||||
"@next/env": "^15.3.2",
|
"@next/env": "^15.3.2",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
|
||||||
"dotenv-cli": "^8.0.0",
|
"dotenv-cli": "^8.0.0",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
"lint-staged": "16.0.0",
|
"lint-staged": "16.0.0",
|
||||||
"prettier": "^3.5.3",
|
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
|
||||||
"turbo": "2.5.5"
|
"turbo": "2.5.5"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
|
|||||||
Reference in New Issue
Block a user