Merge remote-tracking branch 'origin/main' into cloudflare

This commit is contained in:
javayhu 2025-08-03 15:20:14 +08:00
commit 62eb4124be
32 changed files with 478 additions and 689 deletions

View File

@ -181,8 +181,9 @@ FAL_API_KEY=""
FIREWORKS_API_KEY=""
OPENAI_API_KEY=""
REPLICATE_API_TOKEN=""
GOOGLE_API_KEY=""
GOOGLE_GENERATIVE_AI_API_KEY=""
DEEPSEEK_API_KEY=""
OPENROUTER_API_KEY=""
# -----------------------------------------------------------------------------
# Web Content Analyzer (Firecrawl)

View File

@ -25,13 +25,12 @@
"knip": "knip"
},
"dependencies": {
"@ai-sdk/deepseek": "^0.2.16",
"@ai-sdk/fal": "^0.1.12",
"@ai-sdk/fireworks": "^0.2.14",
"@ai-sdk/google": "^1.2.22",
"@ai-sdk/google-vertex": "^2.2.24",
"@ai-sdk/openai": "^1.1.13",
"@ai-sdk/replicate": "^0.2.8",
"@ai-sdk/deepseek": "^1.0.0",
"@ai-sdk/fal": "^1.0.0",
"@ai-sdk/fireworks": "^1.0.0",
"@ai-sdk/google": "^2.0.0",
"@ai-sdk/openai": "^2.0.0",
"@ai-sdk/replicate": "^1.0.0",
"@base-ui-components/react": "1.0.0-beta.0",
"@better-fetch/fetch": "^1.1.18",
"@dnd-kit/core": "^6.3.1",
@ -43,6 +42,7 @@
"@mendable/firecrawl-js": "^1.29.1",
"@next/third-parties": "^15.3.0",
"@openpanel/nextjs": "^1.0.7",
"@openrouter/ai-sdk-provider": "^1.0.0-beta.6",
"@orama/orama": "^3.1.4",
"@orama/tokenizers": "^3.1.4",
"@radix-ui/react-accordion": "^1.2.3",
@ -83,7 +83,7 @@
"@vercel/analytics": "^1.5.0",
"@vercel/speed-insights": "^1.2.0",
"@widgetbot/react-embed": "^1.9.0",
"ai": "^4.1.45",
"ai": "^5.0.0",
"better-auth": "^1.1.19",
"canvas-confetti": "^1.9.3",
"class-variance-authority": "^0.7.1",
@ -98,9 +98,9 @@
"drizzle-orm": "^0.39.3",
"embla-carousel-react": "^8.5.2",
"framer-motion": "^12.4.7",
"fumadocs-core": "^15.5.3",
"fumadocs-mdx": "^11.6.8",
"fumadocs-ui": "^15.5.3",
"fumadocs-core": "^15.6.7",
"fumadocs-mdx": "^11.7.3",
"fumadocs-ui": "^15.6.7",
"inngest": "^3.40.1",
"input-otp": "^1.4.2",
"lucide-react": "^0.483.0",
@ -133,7 +133,7 @@
"use-intl": "^3.26.5",
"use-media": "^1.5.0",
"vaul": "^1.1.2",
"zod": "^3.24.2",
"zod": "^4.0.14",
"zustand": "^5.0.3"
},
"devDependencies": {

959
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ const actionClient = createSafeActionClient();
// Newsletter schema for validation
const newsletterSchema = z.object({
email: z.string().email({ message: 'Please enter a valid email address' }),
email: z.email({ error: 'Please enter a valid email address' }),
});
// Create a safe action to check if a user is subscribed to the newsletter

View File

@ -18,10 +18,10 @@ const actionClient = createSafeActionClient();
// Checkout schema for validation
// metadata is optional, and may contain referral information if you need
const checkoutSchema = z.object({
userId: z.string().min(1, { message: 'User ID is required' }),
planId: z.string().min(1, { message: 'Plan ID is required' }),
priceId: z.string().min(1, { message: 'Price ID is required' }),
metadata: z.record(z.string()).optional(),
userId: z.string().min(1, { error: 'User ID is required' }),
planId: z.string().min(1, { error: 'Plan ID is required' }),
priceId: z.string().min(1, { error: 'Price ID is required' }),
metadata: z.record(z.string(), z.string()).optional(),
});
/**

View File

@ -18,10 +18,10 @@ const actionClient = createSafeActionClient();
// Credit checkout schema for validation
// metadata is optional, and may contain referral information if you need
const creditCheckoutSchema = z.object({
userId: z.string().min(1, { message: 'User ID is required' }),
packageId: z.string().min(1, { message: 'Package ID is required' }),
priceId: z.string().min(1, { message: 'Price ID is required' }),
metadata: z.record(z.string()).optional(),
userId: z.string().min(1, { error: 'User ID is required' }),
packageId: z.string().min(1, { error: 'Package ID is required' }),
priceId: z.string().min(1, { error: 'Price ID is required' }),
metadata: z.record(z.string(), z.string()).optional(),
});
/**

View File

@ -16,10 +16,10 @@ const actionClient = createSafeActionClient();
// Portal schema for validation
const portalSchema = z.object({
userId: z.string().min(1, { message: 'User ID is required' }),
userId: z.string().min(1, { error: 'User ID is required' }),
returnUrl: z
.string()
.url({ message: 'Return URL must be a valid URL' })
.url({ error: 'Return URL must be a valid URL' })
.optional(),
});

View File

@ -10,7 +10,7 @@ const actionClient = createSafeActionClient();
// Input schema
const schema = z.object({
userId: z.string().min(1, { message: 'User ID is required' }),
userId: z.string().min(1, { error: 'User ID is required' }),
});
/**
@ -47,6 +47,18 @@ export const getActiveSubscriptionAction = actionClient
};
}
// Check if Stripe environment variables are configured
const stripeSecretKey = process.env.STRIPE_SECRET_KEY;
const stripeWebhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
if (!stripeSecretKey || !stripeWebhookSecret) {
console.log('Stripe environment variables not configured, return');
return {
success: true,
data: null, // No subscription = free plan
};
}
try {
// Find the user's most recent active subscription
const subscriptions = await getSubscriptions({

View File

@ -14,7 +14,7 @@ const actionClient = createSafeActionClient();
// Input schema
const schema = z.object({
userId: z.string().min(1, { message: 'User ID is required' }),
userId: z.string().min(1, { error: 'User ID is required' }),
});
/**

View File

@ -17,13 +17,13 @@ const actionClient = createSafeActionClient();
const contactFormSchema = z.object({
name: z
.string()
.min(3, { message: 'Name must be at least 3 characters' })
.max(30, { message: 'Name must not exceed 30 characters' }),
email: z.string().email({ message: 'Please enter a valid email address' }),
.min(3, { error: 'Name must be at least 3 characters' })
.max(30, { error: 'Name must not exceed 30 characters' }),
email: z.email({ error: 'Please enter a valid email address' }),
message: z
.string()
.min(10, { message: 'Message must be at least 10 characters' })
.max(500, { message: 'Message must not exceed 500 characters' }),
.min(10, { error: 'Message must be at least 10 characters' })
.max(500, { error: 'Message must not exceed 500 characters' }),
});
// Create a safe action for contact form submission

View File

@ -11,7 +11,7 @@ const actionClient = createSafeActionClient();
// Newsletter schema for validation
const newsletterSchema = z.object({
email: z.string().email({ message: 'Please enter a valid email address' }),
email: z.email({ error: 'Please enter a valid email address' }),
});
// Create a safe action for newsletter subscription

View File

@ -10,7 +10,7 @@ const actionClient = createSafeActionClient();
// Newsletter schema for validation
const newsletterSchema = z.object({
email: z.string().email({ message: 'Please enter a valid email address' }),
email: z.email({ error: 'Please enter a valid email address' }),
});
// Create a safe action for newsletter unsubscription

View File

@ -9,7 +9,7 @@ const actionClient = createSafeActionClient();
// Captcha validation schema
const captchaSchema = z.object({
captchaToken: z.string().min(1, { message: 'Captcha token is required' }),
captchaToken: z.string().min(1, { error: 'Captcha token is required' }),
});
// Create a safe action for captcha validation

View File

@ -61,7 +61,6 @@ export function ImagePlayground({
const providerToModel = {
replicate: selectedModels.replicate,
// vertex: selectedModels.vertex,
openai: selectedModels.openai,
fireworks: selectedModels.fireworks,
fal: selectedModels.fal,

View File

@ -15,7 +15,6 @@ import {
FireworksIcon,
OpenAIIcon,
ReplicateIcon,
// VertexIcon,
falAILogo,
} from '../lib/logos';
import type { ProviderKey } from '../lib/provider-config';
@ -40,7 +39,6 @@ interface ModelSelectProps {
const PROVIDER_ICONS = {
openai: OpenAIIcon,
replicate: ReplicateIcon,
// vertex: VertexIcon,
fireworks: FireworksIcon,
fal: falAILogo,
} as const;
@ -48,7 +46,6 @@ const PROVIDER_ICONS = {
const PROVIDER_LINKS = {
openai: 'openai',
replicate: 'replicate',
// vertex: 'google-vertex',
fireworks: 'fireworks',
fal: 'fal',
} as const;

View File

@ -62,55 +62,6 @@ export const ReplicateIcon = ({ size = 16 }) => {
);
};
export const VertexIcon = ({ size = 16 }) => {
return (
<svg
height={size}
width={size}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
style={{ color: 'currentcolor' }}
>
<g transform="scale(0.8) translate(65,65)">
<path
d="M128,249c-8.8,0-16-7.2-16-16v-105c0-8.8,7.2-16,16-16s16,7.2,16,16v105c0,8.8-7.2,16-16,16Z"
fill="white"
/>
<path
d="M256,464c-3,0-6-.8-8.6-2.5l-176-112c-7.5-4.7-9.7-14.6-4.9-22.1,4.8-7.5,14.6-9.6,22.1-4.9l167.4,106.5,167.4-106.5c7.5-4.7,17.3-2.5,22.1,4.9,4.7,7.5,2.5,17.3-4.9,22.1l-176,112c-2.6,1.7-5.6,2.5-8.6,2.5h0Z"
fill="white"
/>
<path
d="M256,394c-8.8,0-16-7.2-16-16v-73.1c0-8.8,7.2-16,16-16s16,7.2,16,16v73.1c0,8.8-7.2,16-16,16Z"
fill="white"
/>
<circle cx="128" cy="64" r="16" fill="white" />
<circle cx="128" cy="297" r="16" fill="white" />
<path
d="M384.2,314c-8.8,0-16-7.1-16-16l-.2-106c0-8.8,7.1-16,16-16h0c8.8,0,16,7.1,16,16l.2,106c0,8.8-7.1,16-16,16h0Z"
fill="white"
/>
<circle cx="384" cy="64" r="16" fill="white" />
<circle cx="384" cy="128" r="16" fill="white" />
<path
d="M320,225c-8.8,0-16-7.2-16-16v-103c0-8.8,7.2-16,16-16s16,7.2,16,16v103c0,8.8-7.2,16-16,16Z"
fill="white"
/>
<circle cx="256" cy="177" r="16" fill="white" />
<circle cx="256" cy="241" r="16" fill="white" />
<circle cx="320" cy="273" r="16" fill="white" />
<circle cx="320" cy="337" r="16" fill="white" />
<path
d="M192,225c-8.8,0-16-7.2-16-16v-103c0-8.8,7.2-16,16-16s16,7.2,16,16v103c0,8.8-7.2,16-16,16Z"
fill="white"
/>
<circle cx="192" cy="273" r="16" fill="white" />
<circle cx="192" cy="337" r="16" fill="white" />
</g>
</svg>
);
};
export const falAILogo = ({ size = 16 }: { size: number }) => {
return (
<svg

View File

@ -1,9 +1,4 @@
export type ProviderKey =
| 'replicate'
// | 'vertex'
| 'openai'
| 'fireworks'
| 'fal';
export type ProviderKey = 'replicate' | 'openai' | 'fireworks' | 'fal';
export type ModelMode = 'performance' | 'quality';
export const PROVIDERS: Record<
@ -37,12 +32,6 @@ export const PROVIDERS: Record<
'stability-ai/stable-diffusion-3.5-large-turbo',
],
},
// vertex: {
// displayName: 'Vertex AI',
// iconPath: '/provider-icons/vertex.svg',
// color: 'from-green-500 to-emerald-500',
// models: ['imagen-3.0-generate-001', 'imagen-3.0-fast-generate-001'],
// },
// https://ai-sdk.dev/providers/ai-sdk-providers/openai#image-models
openai: {
displayName: 'OpenAI',
@ -92,14 +81,12 @@ export const PROVIDERS: Record<
export const MODEL_CONFIGS: Record<ModelMode, Record<ProviderKey, string>> = {
performance: {
replicate: 'black-forest-labs/flux-1.1-pro',
// vertex: 'imagen-3.0-fast-generate-001',
openai: 'dall-e-3',
fireworks: 'accounts/fireworks/models/flux-1-schnell-fp8',
fal: 'fal-ai/flux/dev',
},
quality: {
replicate: 'stability-ai/stable-diffusion-3.5-large',
// vertex: 'imagen-3.0-generate-001',
openai: 'dall-e-3',
fireworks: 'accounts/fireworks/models/flux-1-dev-fp8',
fal: 'fal-ai/flux-pro/v1.1-ultra',
@ -108,7 +95,6 @@ export const MODEL_CONFIGS: Record<ModelMode, Record<ProviderKey, string>> = {
export const PROVIDER_ORDER: ProviderKey[] = [
'replicate',
// 'vertex',
'openai',
'fireworks',
'fal',

View File

@ -40,7 +40,7 @@ import { useDebounce } from '../utils/performance';
// Form schema for URL input
const urlFormSchema = z.object({
url: z.string().url().optional(), // Allow empty string for initial state
url: z.url().optional(), // Allow empty string for initial state
});
type UrlFormData = z.infer<typeof urlFormSchema>;
@ -164,6 +164,7 @@ export function UrlInputForm({
<SelectItem value="openai">OpenAI GPT-4o</SelectItem>
<SelectItem value="gemini">Google Gemini</SelectItem>
<SelectItem value="deepseek">DeepSeek</SelectItem>
<SelectItem value="openrouter">OpenRouter</SelectItem>
</SelectContent>
</Select>
</div>

View File

@ -117,6 +117,13 @@ export const webContentAnalyzerConfig = {
temperature: 0.1,
maxTokens: 2000,
},
openrouter: {
model: 'openrouter/horizon-beta',
// model: 'x-ai/grok-3-beta',
// model: 'openai/gpt-4o-mini',
temperature: 0.1,
maxTokens: 2000,
},
} as const;
/**

View File

@ -97,9 +97,8 @@ export interface LoadingStatesProps {
// URL Validation Schema
export const urlSchema = z
.string()
.url()
.min(1, 'URL is required')
.url('Please enter a valid URL')
.refine(
(url) => url.startsWith('http://') || url.startsWith('https://'),
'URL must start with http:// or https://'
@ -114,13 +113,13 @@ export const analysisResultsSchema = z.object({
pricing: z.string().default('Not specified'),
useCases: z.array(z.string()).default([]),
url: urlSchema,
analyzedAt: z.string().datetime(),
analyzedAt: z.iso.datetime(),
});
// API Request Schema
export const analyzeContentRequestSchema = z.object({
url: urlSchema,
modelProvider: z.enum(['openai', 'gemini', 'deepseek']),
modelProvider: z.enum(['openai', 'gemini', 'deepseek', 'openrouter']),
});
// API Response Schema

View File

@ -1,12 +1,17 @@
import { UpdateAvatarCard } from '@/components/settings/profile/update-avatar-card';
import { UpdateNameCard } from '@/components/settings/profile/update-name-card';
import { websiteConfig } from '@/config/website';
export default function ProfilePage() {
const enableUpdateAvatar = websiteConfig.features.enableUpdateAvatar;
return (
<div className="flex flex-col gap-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<UpdateAvatarCard />
</div>
{enableUpdateAvatar && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<UpdateAvatarCard />
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<UpdateNameCard />
</div>

View File

@ -1,10 +1,10 @@
'use client';
import { ActiveThemeProvider } from '@/components/layout/active-theme-provider';
import { CreditsProvider } from '@/components/layout/credits-provider';
import { PaymentProvider } from '@/components/layout/payment-provider';
import { TooltipProvider } from '@/components/ui/tooltip';
import { websiteConfig } from '@/config/website';
import { CreditsProvider } from '@/providers/credits-provider';
import type { Translations } from 'fumadocs-ui/i18n';
import { RootProvider } from 'fumadocs-ui/provider';
import { useTranslations } from 'next-intl';

View File

@ -23,6 +23,7 @@ import { createDeepSeek } from '@ai-sdk/deepseek';
import { createGoogleGenerativeAI } from '@ai-sdk/google';
import { createOpenAI } from '@ai-sdk/openai';
import FirecrawlApp from '@mendable/firecrawl-js';
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
import { generateObject } from 'ai';
import { type NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
@ -218,7 +219,7 @@ async function analyzeContent(
break;
case 'gemini':
model = createGoogleGenerativeAI({
apiKey: process.env.GOOGLE_API_KEY,
apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
}).chat(webContentAnalyzerConfig.gemini.model);
temperature = webContentAnalyzerConfig.gemini.temperature;
maxTokens = webContentAnalyzerConfig.gemini.maxTokens;
@ -230,6 +231,13 @@ async function analyzeContent(
temperature = webContentAnalyzerConfig.deepseek.temperature;
maxTokens = webContentAnalyzerConfig.deepseek.maxTokens;
break;
case 'openrouter':
model = createOpenRouter({
apiKey: process.env.OPENROUTER_API_KEY,
}).chat(webContentAnalyzerConfig.openrouter.model);
temperature = webContentAnalyzerConfig.openrouter.temperature;
maxTokens = webContentAnalyzerConfig.openrouter.maxTokens;
break;
default:
throw new WebContentAnalyzerError(
ErrorType.VALIDATION,
@ -254,7 +262,7 @@ async function analyzeContent(
- Ensure the title and description are meaningful and based on the actual content
`,
temperature,
maxTokens,
maxOutputTokens: maxTokens,
});
return {
@ -336,7 +344,7 @@ export async function POST(req: NextRequest) {
if (!urlValidation.success) {
const urlError = new WebContentAnalyzerError(
ErrorType.VALIDATION,
urlValidation.error.errors[0]?.message || 'Invalid URL',
urlValidation.error.issues[0]?.message || 'Invalid URL',
'Please enter a valid URL starting with http:// or https://',
ErrorSeverity.MEDIUM,
false

View File

@ -32,7 +32,7 @@ export const ForgotPasswordForm = ({ className }: { className?: string }) => {
const searchParams = useSearchParams();
const ForgotPasswordSchema = z.object({
email: z.string().email({
email: z.email({
message: t('emailRequired'),
}),
});

View File

@ -69,7 +69,7 @@ export const LoginForm = ({
: z.string().optional();
const LoginSchema = z.object({
email: z.string().email({
email: z.email({
message: t('emailRequired'),
}),
password: z.string().min(1, {

View File

@ -64,7 +64,7 @@ export const RegisterForm = ({
: z.string().optional();
const RegisterSchema = z.object({
email: z.string().email({
email: z.email({
message: t('emailRequired'),
}),
password: z.string().min(1, {

View File

@ -40,7 +40,7 @@ export function ContactFormCard() {
// Create a schema for contact form validation
const formSchema = z.object({
name: z.string().min(3, t('nameMinLength')).max(30, t('nameMaxLength')),
email: z.string().email(t('emailValidation')),
email: z.email(t('emailValidation')),
message: z
.string()
.min(10, t('messageMinLength'))

View File

@ -29,7 +29,7 @@ export function NewsletterForm() {
// newsletter schema
const NewsletterFormSchema = z.object({
email: z.string().email({
email: z.email({
message: t('emailValidation'),
}),
});

View File

@ -38,7 +38,7 @@ export function WaitlistFormCard() {
// Create a schema for waitlist form validation
const formSchema = z.object({
email: z.string().email({ message: t('emailValidation') }),
email: z.email({ message: t('emailValidation') }),
});
// Initialize the form

View File

@ -36,6 +36,7 @@ export const websiteConfig: WebsiteConfig = {
enableDiscordWidget: false,
enableCrispChat: process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true',
enableUpgradeCard: true,
enableUpdateAvatar: true,
enableAffonsoAffiliate: false,
enablePromotekitAffiliate: false,
enableDatafastRevenueTrack: false,

View File

@ -71,6 +71,7 @@ export interface FeaturesConfig {
enableDiscordWidget?: boolean; // Whether to enable the discord widget, deprecated
enableCrispChat?: boolean; // Whether to enable the crisp chat
enableUpgradeCard?: boolean; // Whether to enable the upgrade card in the sidebar
enableUpdateAvatar?: boolean; // Whether to enable the update avatar in settings
enableAffonsoAffiliate?: boolean; // Whether to enable affonso affiliate
enablePromotekitAffiliate?: boolean; // Whether to enable promotekit affiliate
enableDatafastRevenueTrack?: boolean; // Whether to enable datafast revenue tracking