mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
improvement(cdn): add cdn for large video assets with fallback to static assets (#809)
* added CDN for large assets with fallback to static assets * remove video assets from docs --------- Co-authored-by: waleedlatif <waleedlatif@waleedlatifs-MacBook-Pro.local>
This commit is contained in:
co-authored by
waleedlatif
parent
abed816afd
commit
510ce4b7da
@@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<img src="apps/sim/public/static/sim.png" alt="Sim Studio Logo" width="500"/>
|
||||
<img src="https://nwkhgj772h6t23m2.public.blob.vercel-storage.com/static/sim.png" alt="Sim Studio Logo" width="500"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -15,7 +15,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="apps/sim/public/static/demo.gif" alt="Sim Studio Demo" width="800"/>
|
||||
<img src="https://nwkhgj772h6t23m2.public.blob.vercel-storage.com/static/demo.gif" alt="Sim Studio Demo" width="800"/>
|
||||
</p>
|
||||
|
||||
## Getting Started
|
||||
|
||||
+17
-7
@@ -9,13 +9,23 @@ export function cn(...inputs: ClassValue[]) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full URL for a video asset stored in Vercel Blob
|
||||
* Get the full URL for an asset stored in Vercel Blob or local fallback
|
||||
* - If CDN is configured (NEXT_PUBLIC_BLOB_BASE_URL), uses CDN URL
|
||||
* - Otherwise falls back to local static assets served from root path
|
||||
*/
|
||||
export function getAssetUrl(filename: string) {
|
||||
const cdnBaseUrl = process.env.NEXT_PUBLIC_BLOB_BASE_URL
|
||||
if (cdnBaseUrl) {
|
||||
return `${cdnBaseUrl}/${filename}`
|
||||
}
|
||||
return `/${filename}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full URL for a video asset stored in Vercel Blob or local fallback
|
||||
* - If CDN is configured (NEXT_PUBLIC_BLOB_BASE_URL), uses CDN URL
|
||||
* - Otherwise falls back to local static assets served from root path
|
||||
*/
|
||||
export function getVideoUrl(filename: string) {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_BLOB_BASE_URL
|
||||
if (!baseUrl) {
|
||||
console.warn('NEXT_PUBLIC_BLOB_BASE_URL not configured, falling back to local path')
|
||||
return `/${filename}`
|
||||
}
|
||||
return `${baseUrl}/${filename}`
|
||||
return getAssetUrl(filename)
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import { getAssetUrl } from '@/lib/utils'
|
||||
import { BlogCard } from '@/app/(landing)/components/blog-card'
|
||||
|
||||
function Blogs() {
|
||||
@@ -50,7 +51,7 @@ function Blogs() {
|
||||
date={new Date('25 April 2025')}
|
||||
author='Emir Ayaz'
|
||||
authorRole='Designer'
|
||||
avatar='/static/sim.png'
|
||||
avatar={getAssetUrl('static/sim.png')}
|
||||
type='Agents'
|
||||
readTime='6'
|
||||
/>
|
||||
@@ -61,7 +62,7 @@ function Blogs() {
|
||||
date={new Date('25 April 2025')}
|
||||
author='Emir Ayaz'
|
||||
authorRole='Designer'
|
||||
avatar='/static/sim.png'
|
||||
avatar={getAssetUrl('static/sim.png')}
|
||||
type='Agents'
|
||||
readTime='6'
|
||||
/>
|
||||
@@ -80,10 +81,10 @@ function Blogs() {
|
||||
date={new Date('25 April 2025')}
|
||||
author='Emir Ayaz'
|
||||
authorRole='Designer'
|
||||
avatar='/static/sim.png'
|
||||
avatar={getAssetUrl('static/sim.png')}
|
||||
type='Agents'
|
||||
readTime='6'
|
||||
image='/static/hero.png'
|
||||
image={getAssetUrl('static/hero.png')}
|
||||
/>
|
||||
<BlogCard
|
||||
href='/blog/test'
|
||||
@@ -91,7 +92,7 @@ function Blogs() {
|
||||
description="Learn how to create a fully functional AI agent using SimStudio.ai's unified API and workflows."
|
||||
author='Emir Ayaz'
|
||||
authorRole='Designer'
|
||||
avatar='/static/sim.png'
|
||||
avatar={getAssetUrl('static/sim.png')}
|
||||
type='Agents'
|
||||
readTime='6'
|
||||
/>
|
||||
@@ -110,7 +111,7 @@ function Blogs() {
|
||||
date={new Date('25 April 2025')}
|
||||
author='Emir Ayaz'
|
||||
authorRole='Designer'
|
||||
avatar='/static/sim.png'
|
||||
avatar={getAssetUrl('static/sim.png')}
|
||||
type='Agents'
|
||||
readTime='6'
|
||||
/>
|
||||
@@ -121,7 +122,7 @@ function Blogs() {
|
||||
date={new Date('25 April 2025')}
|
||||
author='Emir Ayaz'
|
||||
authorRole='Designer'
|
||||
avatar='/static/sim.png'
|
||||
avatar={getAssetUrl('static/sim.png')}
|
||||
type='Functions'
|
||||
readTime='6'
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import { getAssetUrl } from '@/lib/utils'
|
||||
import useIsMobile from '@/app/(landing)/components/hooks/use-is-mobile'
|
||||
import { Marquee } from '@/app/(landing)/components/magicui/marquee'
|
||||
|
||||
@@ -10,63 +11,63 @@ const X_TESTIMONIALS = [
|
||||
username: '@GithubProjects',
|
||||
viewCount: '90.4k',
|
||||
tweetUrl: 'https://x.com/GithubProjects/status/1906383555707490499',
|
||||
profileImage: '/twitter/github-projects.jpg',
|
||||
profileImage: getAssetUrl('twitter/github-projects.jpg'),
|
||||
},
|
||||
{
|
||||
text: 'A very good looking agent workflow builder 🔥 and open source!',
|
||||
username: '@xyflowdev',
|
||||
viewCount: '3,246',
|
||||
tweetUrl: 'https://x.com/xyflowdev/status/1909501499719438670',
|
||||
profileImage: '/twitter/xyflow.jpg',
|
||||
profileImage: getAssetUrl('twitter/xyflow.jpg'),
|
||||
},
|
||||
{
|
||||
text: "🚨 BREAKING: This startup just dropped the fastest way to build AI agents.\n\nThis Figma-like canvas to build agents will blow your mind.\n\nHere's why this is the best tool for building AI agents:",
|
||||
username: '@hasantoxr',
|
||||
viewCount: '515k',
|
||||
tweetUrl: 'https://x.com/hasantoxr/status/1912909502036525271',
|
||||
profileImage: '/twitter/hasan.jpg',
|
||||
profileImage: getAssetUrl('twitter/hasan.jpg'),
|
||||
},
|
||||
{
|
||||
text: 'omfggggg this is the zapier of agent building\n\ni always believed that building agents and using ai should not be limited to technical people. i think this solves just that\n\nthe fact that this is also open source makes me so optimistic about the future of building with ai :)))\n\ncongrats @karabegemir & @typingwala !!!',
|
||||
username: '@nizzyabi',
|
||||
viewCount: '6,269',
|
||||
tweetUrl: 'https://x.com/nizzyabi/status/1907864421227180368',
|
||||
profileImage: '/twitter/nizzy.jpg',
|
||||
profileImage: getAssetUrl('twitter/nizzy.jpg'),
|
||||
},
|
||||
{
|
||||
text: "One of the best products I've seen in the space, and the hustle and grind I've seen from @karabegemir and @typingwala is insane. Sim Studio is positioned to build something game-changing, and there's no better team for the job.\n\nCongrats on the launch 🚀 🎊 great things ahead!",
|
||||
username: '@firestorm776',
|
||||
viewCount: '956',
|
||||
tweetUrl: 'https://x.com/firestorm776/status/1907896097735061598',
|
||||
profileImage: '/twitter/samarth.jpg',
|
||||
profileImage: getAssetUrl('twitter/samarth.jpg'),
|
||||
},
|
||||
{
|
||||
text: 'lfgg got access to @simstudioai via @zerodotemail 😎',
|
||||
username: '@nizzyabi',
|
||||
viewCount: '1,585',
|
||||
tweetUrl: 'https://x.com/nizzyabi/status/1910482357821595944',
|
||||
profileImage: '/twitter/nizzy.jpg',
|
||||
profileImage: getAssetUrl('twitter/nizzy.jpg'),
|
||||
},
|
||||
{
|
||||
text: 'Feels like we\'re finally getting a "Photoshop moment" for AI devs—visual, intuitive, and fast enough to keep up with ideas mid-flow.',
|
||||
username: '@syamrajk',
|
||||
viewCount: '2,643',
|
||||
tweetUrl: 'https://x.com/syamrajk/status/1912911980110946491',
|
||||
profileImage: '/twitter/syamrajk.jpg',
|
||||
profileImage: getAssetUrl('twitter/syamrajk.jpg'),
|
||||
},
|
||||
{
|
||||
text: "🚨 BREAKING: This startup just dropped the fastest way to build AI agents.\n\nThis Figma-like canvas to build agents will blow your mind.\n\nHere's why this is the best tool for building AI agents:",
|
||||
username: '@lazukars',
|
||||
viewCount: '47.4k',
|
||||
tweetUrl: 'https://x.com/lazukars/status/1913136390503600575',
|
||||
profileImage: '/twitter/lazukars.png',
|
||||
profileImage: getAssetUrl('twitter/lazukars.png'),
|
||||
},
|
||||
{
|
||||
text: 'The use cases are endless. Great work @simstudioai',
|
||||
username: '@daniel_zkim',
|
||||
viewCount: '103',
|
||||
tweetUrl: 'https://x.com/daniel_zkim/status/1907891273664782708',
|
||||
profileImage: '/twitter/daniel.jpg',
|
||||
profileImage: getAssetUrl('twitter/daniel.jpg'),
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { Metadata, Viewport } from 'next'
|
||||
import { PublicEnvScript } from 'next-runtime-env'
|
||||
import { env, isTruthy } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { getAssetUrl } from '@/lib/utils'
|
||||
import { TelemetryConsentDialog } from '@/app/telemetry-consent-dialog'
|
||||
import '@/app/globals.css'
|
||||
|
||||
@@ -118,7 +119,7 @@ export const metadata: Metadata = {
|
||||
siteName: 'Sim Studio',
|
||||
images: [
|
||||
{
|
||||
url: 'https://simstudio.ai/social/facebook.png',
|
||||
url: getAssetUrl('social/facebook.png'),
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'Sim Studio',
|
||||
@@ -130,7 +131,7 @@ export const metadata: Metadata = {
|
||||
title: 'Sim Studio',
|
||||
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: ['https://simstudio.ai/social/twitter.png'],
|
||||
images: [getAssetUrl('social/twitter.png')],
|
||||
creator: '@simstudioai',
|
||||
site: '@simstudioai',
|
||||
},
|
||||
@@ -231,7 +232,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
<meta name='twitter:domain' content='simstudio.ai' />
|
||||
|
||||
{/* Additional image sources */}
|
||||
<link rel='image_src' href='https://simstudio.ai/social/facebook.png' />
|
||||
<link rel='image_src' href={getAssetUrl('social/facebook.png')} />
|
||||
|
||||
<PublicEnvScript />
|
||||
</head>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Container, Img, Link, Section, Text } from '@react-email/components'
|
||||
import { env } from '@/lib/env'
|
||||
import { getAssetUrl } from '@/lib/utils'
|
||||
|
||||
interface UnsubscribeOptions {
|
||||
unsubscribeToken?: string
|
||||
@@ -25,13 +26,13 @@ export const EmailFooter = ({
|
||||
<tr>
|
||||
<td align='center' style={{ padding: '0 8px' }}>
|
||||
<Link href='https://x.com/simstudioai' rel='noopener noreferrer'>
|
||||
<Img src={`${baseUrl}/static/x-icon.png`} width='24' height='24' alt='X' />
|
||||
<Img src={getAssetUrl('static/x-icon.png')} width='24' height='24' alt='X' />
|
||||
</Link>
|
||||
</td>
|
||||
<td align='center' style={{ padding: '0 8px' }}>
|
||||
<Link href='https://discord.gg/Hr4UWYEcTT' rel='noopener noreferrer'>
|
||||
<Img
|
||||
src={`${baseUrl}/static/discord-icon.png`}
|
||||
src={getAssetUrl('static/discord-icon.png')}
|
||||
width='24'
|
||||
height='24'
|
||||
alt='Discord'
|
||||
@@ -41,7 +42,7 @@ export const EmailFooter = ({
|
||||
<td align='center' style={{ padding: '0 8px' }}>
|
||||
<Link href='https://github.com/simstudioai/sim' rel='noopener noreferrer'>
|
||||
<Img
|
||||
src={`${baseUrl}/static/github-icon.png`}
|
||||
src={getAssetUrl('static/github-icon.png')}
|
||||
width='24'
|
||||
height='24'
|
||||
alt='GitHub'
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '@react-email/components'
|
||||
import { format } from 'date-fns'
|
||||
import { env } from '@/lib/env'
|
||||
import { getAssetUrl } from '@/lib/utils'
|
||||
import { baseStyles } from './base-styles'
|
||||
import EmailFooter from './footer'
|
||||
|
||||
@@ -59,7 +60,7 @@ export const InvitationEmail = ({
|
||||
<Row>
|
||||
<Column style={{ textAlign: 'center' }}>
|
||||
<Img
|
||||
src={`${baseUrl}/static/sim.png`}
|
||||
src={getAssetUrl('static/sim.png')}
|
||||
width='114'
|
||||
alt='Sim Studio'
|
||||
style={{
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Text,
|
||||
} from '@react-email/components'
|
||||
import { env } from '@/lib/env'
|
||||
import { getAssetUrl } from '@/lib/utils'
|
||||
import { baseStyles } from './base-styles'
|
||||
import EmailFooter from './footer'
|
||||
|
||||
@@ -68,7 +69,7 @@ export const OTPVerificationEmail = ({
|
||||
<Row>
|
||||
<Column style={{ textAlign: 'center' }}>
|
||||
<Img
|
||||
src={`${baseUrl}/static/sim.png`}
|
||||
src={getAssetUrl('static/sim.png')}
|
||||
width='114'
|
||||
alt='Sim Studio'
|
||||
style={{
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '@react-email/components'
|
||||
import { format } from 'date-fns'
|
||||
import { env } from '@/lib/env'
|
||||
import { getAssetUrl } from '@/lib/utils'
|
||||
import { baseStyles } from './base-styles'
|
||||
import EmailFooter from './footer'
|
||||
|
||||
@@ -39,7 +40,7 @@ export const ResetPasswordEmail = ({
|
||||
<Row>
|
||||
<Column style={{ textAlign: 'center' }}>
|
||||
<Img
|
||||
src={`${baseUrl}/static/sim.png`}
|
||||
src={getAssetUrl('static/sim.png')}
|
||||
width='114'
|
||||
alt='Sim Studio'
|
||||
style={{
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
Text,
|
||||
} from '@react-email/components'
|
||||
import { env } from '@/lib/env'
|
||||
import { getAssetUrl } from '@/lib/utils'
|
||||
import { baseStyles } from './base-styles'
|
||||
import EmailFooter from './footer'
|
||||
|
||||
@@ -56,7 +57,7 @@ export const WorkspaceInvitationEmail = ({
|
||||
<Row>
|
||||
<Column style={{ textAlign: 'center' }}>
|
||||
<Img
|
||||
src={`${baseUrl}/static/sim.png`}
|
||||
src={getAssetUrl('static/sim.png')}
|
||||
width='114'
|
||||
alt='Sim Studio'
|
||||
style={{
|
||||
|
||||
@@ -152,6 +152,9 @@ export const env = createEnv({
|
||||
NEXT_PUBLIC_SENTRY_DSN: z.string().url().optional(), // Sentry DSN for client-side error tracking
|
||||
NEXT_PUBLIC_SOCKET_URL: z.string().url().optional(), // WebSocket server URL for real-time features
|
||||
|
||||
// Asset Storage
|
||||
NEXT_PUBLIC_BLOB_BASE_URL: z.string().url().optional(), // Base URL for Vercel Blob storage (CDN assets)
|
||||
|
||||
// Google Services - For client-side Google integrations
|
||||
NEXT_PUBLIC_GOOGLE_CLIENT_ID: z.string().optional(), // Google OAuth client ID for browser auth
|
||||
NEXT_PUBLIC_GOOGLE_API_KEY: z.string().optional(), // Google API key for client-side API calls
|
||||
@@ -168,6 +171,7 @@ export const env = createEnv({
|
||||
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
|
||||
NEXT_PUBLIC_VERCEL_URL: process.env.NEXT_PUBLIC_VERCEL_URL,
|
||||
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
NEXT_PUBLIC_BLOB_BASE_URL: process.env.NEXT_PUBLIC_BLOB_BASE_URL,
|
||||
NEXT_PUBLIC_GOOGLE_CLIENT_ID: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
|
||||
NEXT_PUBLIC_GOOGLE_API_KEY: process.env.NEXT_PUBLIC_GOOGLE_API_KEY,
|
||||
NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER: process.env.NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER,
|
||||
|
||||
@@ -49,6 +49,7 @@ export const cspDirectives: CSPDirectives = {
|
||||
'https://*.atlassian.com',
|
||||
'https://cdn.discordapp.com',
|
||||
'https://*.githubusercontent.com',
|
||||
'https://*.public.blob.vercel-storage.com',
|
||||
],
|
||||
|
||||
'media-src': ["'self'", 'blob:'],
|
||||
|
||||
@@ -379,6 +379,19 @@ export function getInvalidCharacters(name: string): string[] {
|
||||
return invalidChars ? [...new Set(invalidChars)] : []
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full URL for an asset stored in Vercel Blob or local fallback
|
||||
* - If CDN is configured (NEXT_PUBLIC_BLOB_BASE_URL), uses CDN URL
|
||||
* - Otherwise falls back to local static assets served from root path
|
||||
*/
|
||||
export function getAssetUrl(filename: string) {
|
||||
const cdnBaseUrl = env.NEXT_PUBLIC_BLOB_BASE_URL
|
||||
if (cdnBaseUrl) {
|
||||
return `${cdnBaseUrl}/${filename}`
|
||||
}
|
||||
return `/${filename}`
|
||||
}
|
||||
|
||||
/**
|
||||
* No-operation function for use as default callback
|
||||
*/
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 35 MiB |
Reference in New Issue
Block a user