marketing-pipeline-auto-content

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Marketing Pipeline Auto Content

营销流水线自动化内容生成

Skill by ara.so — Marketing Skills collection.
This skill enables AI coding agents to work with the Ultimate AI Content Pipeline - an automated content creation system that handles research, scriptwriting, and video generation using Claude 3, OpenAI, and Remotion.
ara.so提供的技能 — 营销技能合集。
该技能使AI编码Agent能够使用终极AI内容流水线——一个基于Claude 3、OpenAI和Remotion的自动化内容创作系统,可处理调研、脚本撰写和视频生成任务。

What This Project Does

项目功能

The Marketing Pipeline is an end-to-end automated content creation system that:
  • Auto-scans research sources: Crawls TechCrunch, a16z, Twitter/X, LinkedIn for fresh content within 24 hours
  • Generates diverse content formats: Creates toplist, POV, case study, and how-to articles in multiple languages
  • Renders videos automatically: Converts text content into infographics and short-form videos using Remotion
  • Multi-platform optimization: Outputs video in formats optimized for Reels, TikTok, and YouTube Shorts
Built with Next.js and TypeScript, it integrates Claude (Anthropic), OpenAI, RapidAPI, and Remotion for a complete content automation workflow.
营销流水线是一个端到端的自动化内容创作系统,具备以下功能:
  • 自动扫描调研来源:抓取TechCrunch、a16z、Twitter/X、LinkedIn平台24小时内的最新内容
  • 生成多样化内容格式:创建榜单类、观点类、案例研究类和教程类文章,支持多语言
  • 自动渲染视频:使用Remotion将文本内容转换为信息图和短视频
  • 多平台优化:输出针对Reels、TikTok和YouTube Shorts优化的视频格式
系统基于Next.js和TypeScript构建,集成了Claude(Anthropic)、OpenAI、RapidAPI和Remotion,实现完整的内容自动化工作流。

Installation

安装

bash
undefined
bash
undefined

Clone the repository

Clone the repository

git clone https://github.com/pennydinh/marketing-pineline-share.git cd marketing-pineline-share
git clone https://github.com/pennydinh/marketing-pineline-share.git cd marketing-pineline-share

Install dependencies

Install dependencies

npm install
npm install

or

or

yarn install
yarn install

or

or

pnpm install
pnpm install

Set up environment variables

Set up environment variables

cp .env.example .env.local
undefined
cp .env.example .env.local
undefined

Environment Configuration

环境配置

Create a
.env.local
file with the following variables:
bash
undefined
创建
.env.local
文件并配置以下变量:
bash
undefined

AI Provider Keys

AI Provider Keys

ANTHROPIC_API_KEY=your_claude_key_here OPENAI_API_KEY=your_openai_key_here
ANTHROPIC_API_KEY=your_claude_key_here OPENAI_API_KEY=your_openai_key_here

Research APIs

Research APIs

RAPIDAPI_KEY=your_rapidapi_key_here
RAPIDAPI_KEY=your_rapidapi_key_here

Database (if applicable)

Database (if applicable)

DATABASE_URL=your_database_connection_string
DATABASE_URL=your_database_connection_string

Remotion Configuration

Remotion Configuration

REMOTION_LICENSE_KEY=your_remotion_license_key
REMOTION_LICENSE_KEY=your_remotion_license_key

Application Settings

Application Settings

NEXT_PUBLIC_APP_URL=http://localhost:3000
undefined
NEXT_PUBLIC_APP_URL=http://localhost:3000
undefined

Key Components & Architecture

核心组件与架构

1. Research Module (Auto-Scan)

1. 调研模块(自动扫描)

The research module crawls news sources and extracts insights:
typescript
// lib/research/crawler.ts
import { RapidAPIClient } from '@/lib/api/rapidapi';

interface NewsArticle {
  title: string;
  url: string;
  publishedAt: string;
  content: string;
  source: string;
}

export async function scanLatestNews(
  keyword: string,
  sources: string[] = ['techcrunch', 'a16z', 'twitter']
): Promise<NewsArticle[]> {
  const rapidApi = new RapidAPIClient(process.env.RAPIDAPI_KEY!);
  
  const articles: NewsArticle[] = [];
  
  for (const source of sources) {
    const results = await rapidApi.searchNews({
      query: keyword,
      source: source,
      timeRange: '24h'
    });
    
    articles.push(...results);
  }
  
  return articles;
}

export async function extractInsights(articles: NewsArticle[]): Promise<string[]> {
  const insights = articles.map(article => ({
    headline: article.title,
    key_points: extractKeyPoints(article.content),
    data_points: extractDataPoints(article.content)
  }));
  
  return insights;
}
调研模块爬取新闻来源并提取洞察:
typescript
// lib/research/crawler.ts
import { RapidAPIClient } from '@/lib/api/rapidapi';

interface NewsArticle {
  title: string;
  url: string;
  publishedAt: string;
  content: string;
  source: string;
}

export async function scanLatestNews(
  keyword: string,
  sources: string[] = ['techcrunch', 'a16z', 'twitter']
): Promise<NewsArticle[]> {
  const rapidApi = new RapidAPIClient(process.env.RAPIDAPI_KEY!);
  
  const articles: NewsArticle[] = [];
  
  for (const source of sources) {
    const results = await rapidApi.searchNews({
      query: keyword,
      source: source,
      timeRange: '24h'
    });
    
    articles.push(...results);
  }
  
  return articles;
}

export async function extractInsights(articles: NewsArticle[]): Promise<string[]> {
  const insights = articles.map(article => ({
    headline: article.title,
    key_points: extractKeyPoints(article.content),
    data_points: extractDataPoints(article.content)
  }));
  
  return insights;
}

2. Content Generation with AI

2. AI内容生成

Using Claude or OpenAI to generate content in various formats:
typescript
// lib/ai/content-generator.ts
import Anthropic from '@anthropic-ai/sdk';
import OpenAI from 'openai';

type ContentFormat = 'toplist' | 'pov' | 'case-study' | 'how-to';
type Language = 'en' | 'vi';
type Tone = 'expert' | 'friendly' | 'humorous';

interface ContentGenerationOptions {
  keyword: string;
  format: ContentFormat;
  language: Language;
  tone: Tone;
  researchData: string[];
}

export async function generateContentWithClaude(
  options: ContentGenerationOptions
): Promise<string> {
  const anthropic = new Anthropic({
    apiKey: process.env.ANTHROPIC_API_KEY,
  });

  const prompt = buildPrompt(options);

  const message = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 4096,
    messages: [
      {
        role: 'user',
        content: prompt,
      },
    ],
  });

  return message.content[0].type === 'text' 
    ? message.content[0].text 
    : '';
}

export async function generateContentWithOpenAI(
  options: ContentGenerationOptions
): Promise<string> {
  const openai = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY,
  });

  const prompt = buildPrompt(options);

  const completion = await openai.chat.completions.create({
    model: 'gpt-4-turbo-preview',
    messages: [
      {
        role: 'system',
        content: 'You are an expert content writer specializing in marketing content.',
      },
      {
        role: 'user',
        content: prompt,
      },
    ],
    temperature: 0.7,
  });

  return completion.choices[0]?.message?.content || '';
}

function buildPrompt(options: ContentGenerationOptions): string {
  const formatInstructions = {
    'toplist': 'Create a numbered list article with at least 7 items',
    'pov': 'Write from a unique perspective or angle',
    'case-study': 'Analyze a real-world example with data and insights',
    'how-to': 'Create a step-by-step tutorial guide',
  };

  const toneInstructions = {
    'expert': 'Use professional, authoritative language',
    'friendly': 'Use conversational, approachable language',
    'humorous': 'Include wit and light humor while staying informative',
  };

  return `
Write a ${options.format} article about "${options.keyword}" in ${options.language}.
${formatInstructions[options.format]}
${toneInstructions[options.tone]}

Use the following research data as context:
${options.researchData.join('\n\n')}

Requirements:
- Include specific data points and statistics
- Make it SEO-optimized
- Add clear headings and subheadings
- Include a compelling introduction and conclusion
`;
}
使用Claude或OpenAI生成多种格式的内容:
typescript
// lib/ai/content-generator.ts
import Anthropic from '@anthropic-ai/sdk';
import OpenAI from 'openai';

type ContentFormat = 'toplist' | 'pov' | 'case-study' | 'how-to';
type Language = 'en' | 'vi';
type Tone = 'expert' | 'friendly' | 'humorous';

interface ContentGenerationOptions {
  keyword: string;
  format: ContentFormat;
  language: Language;
  tone: Tone;
  researchData: string[];
}

export async function generateContentWithClaude(
  options: ContentGenerationOptions
): Promise<string> {
  const anthropic = new Anthropic({
    apiKey: process.env.ANTHROPIC_API_KEY,
  });

  const prompt = buildPrompt(options);

  const message = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 4096,
    messages: [
      {
        role: 'user',
        content: prompt,
      },
    ],
  });

  return message.content[0].type === 'text' 
    ? message.content[0].text 
    : '';
}

export async function generateContentWithOpenAI(
  options: ContentGenerationOptions
): Promise<string> {
  const openai = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY,
  });

  const prompt = buildPrompt(options);

  const completion = await openai.chat.completions.create({
    model: 'gpt-4-turbo-preview',
    messages: [
      {
        role: 'system',
        content: 'You are an expert content writer specializing in marketing content.',
      },
      {
        role: 'user',
        content: prompt,
      },
    ],
    temperature: 0.7,
  });

  return completion.choices[0]?.message?.content || '';
}

function buildPrompt(options: ContentGenerationOptions): string {
  const formatInstructions = {
    'toplist': 'Create a numbered list article with at least 7 items',
    'pov': 'Write from a unique perspective or angle',
    'case-study': 'Analyze a real-world example with data and insights',
    'how-to': 'Create a step-by-step tutorial guide',
  };

  const toneInstructions = {
    'expert': 'Use professional, authoritative language',
    'friendly': 'Use conversational, approachable language',
    'humorous': 'Include wit and light humor while staying informative',
  };

  return `
Write a ${options.format} article about "${options.keyword}" in ${options.language}.
${formatInstructions[options.format]}
${toneInstructions[options.tone]}

Use the following research data as context:
${options.researchData.join('\n\n')}

Requirements:
- Include specific data points and statistics
- Make it SEO-optimized
- Add clear headings and subheadings
- Include a compelling introduction and conclusion
`;
}

3. Video Generation with Remotion

3. Remotion视频生成

Convert text content into videos:
typescript
// lib/video/remotion-renderer.ts
import { bundle } from '@remotion/bundler';
import { renderMedia, selectComposition } from '@remotion/renderer';
import path from 'path';

interface VideoConfig {
  content: string;
  title: string;
  platform: 'reels' | 'tiktok' | 'shorts';
}

const platformSpecs = {
  reels: { width: 1080, height: 1920, fps: 30 },
  tiktok: { width: 1080, height: 1920, fps: 30 },
  shorts: { width: 1080, height: 1920, fps: 30 },
};

export async function generateVideo(config: VideoConfig): Promise<string> {
  const specs = platformSpecs[config.platform];
  
  // Bundle the Remotion project
  const bundleLocation = await bundle({
    entryPoint: path.join(process.cwd(), 'remotion/index.ts'),
    webpackOverride: (config) => config,
  });

  // Get composition
  const composition = await selectComposition({
    serveUrl: bundleLocation,
    id: 'ContentVideo',
    inputProps: {
      title: config.title,
      content: config.content,
    },
  });

  // Render video
  const outputLocation = path.join(
    process.cwd(),
    'public/videos',
    `${Date.now()}-${config.platform}.mp4`
  );

  await renderMedia({
    composition,
    serveUrl: bundleLocation,
    codec: 'h264',
    outputLocation,
    inputProps: {
      title: config.title,
      content: config.content,
    },
  });

  return outputLocation;
}
Remotion composition example:
typescript
// remotion/ContentVideo.tsx
import { AbsoluteFill, Sequence, useCurrentFrame, useVideoConfig } from 'remotion';
import React from 'react';

interface ContentVideoProps {
  title: string;
  content: string;
}

export const ContentVideo: React.FC<ContentVideoProps> = ({ title, content }) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  const opacity = Math.min(1, frame / 30);
  const contentPoints = content.split('\n').filter(Boolean);

  return (
    <AbsoluteFill style={{ backgroundColor: '#1a1a2e' }}>
      <Sequence from={0} durationInFrames={60}>
        <AbsoluteFill
          style={{
            justifyContent: 'center',
            alignItems: 'center',
            opacity,
          }}
        >
          <h1
            style={{
              fontSize: 80,
              color: 'white',
              textAlign: 'center',
              padding: 40,
            }}
          >
            {title}
          </h1>
        </AbsoluteFill>
      </Sequence>

      {contentPoints.map((point, index) => (
        <Sequence
          key={index}
          from={60 + index * 90}
          durationInFrames={90}
        >
          <AbsoluteFill
            style={{
              justifyContent: 'center',
              alignItems: 'center',
              padding: 60,
            }}
          >
            <div
              style={{
                fontSize: 48,
                color: 'white',
                textAlign: 'center',
                backgroundColor: 'rgba(255, 255, 255, 0.1)',
                padding: 40,
                borderRadius: 20,
              }}
            >
              {point}
            </div>
          </AbsoluteFill>
        </Sequence>
      ))}
    </AbsoluteFill>
  );
};
将文本内容转换为视频:
typescript
// lib/video/remotion-renderer.ts
import { bundle } from '@remotion/bundler';
import { renderMedia, selectComposition } from '@remotion/renderer';
import path from 'path';

interface VideoConfig {
  content: string;
  title: string;
  platform: 'reels' | 'tiktok' | 'shorts';
}

const platformSpecs = {
  reels: { width: 1080, height: 1920, fps: 30 },
  tiktok: { width: 1080, height: 1920, fps: 30 },
  shorts: { width: 1080, height: 1920, fps: 30 },
};

export async function generateVideo(config: VideoConfig): Promise<string> {
  const specs = platformSpecs[config.platform];
  
  // Bundle the Remotion project
  const bundleLocation = await bundle({
    entryPoint: path.join(process.cwd(), 'remotion/index.ts'),
    webpackOverride: (config) => config,
  });

  // Get composition
  const composition = await selectComposition({
    serveUrl: bundleLocation,
    id: 'ContentVideo',
    inputProps: {
      title: config.title,
      content: config.content,
    },
  });

  // Render video
  const outputLocation = path.join(
    process.cwd(),
    'public/videos',
    `${Date.now()}-${config.platform}.mp4`
  );

  await renderMedia({
    composition,
    serveUrl: bundleLocation,
    codec: 'h264',
    outputLocation,
    inputProps: {
      title: config.title,
      content: config.content,
    },
  });

  return outputLocation;
}
Remotion合成示例:
typescript
// remotion/ContentVideo.tsx
import { AbsoluteFill, Sequence, useCurrentFrame, useVideoConfig } from 'remotion';
import React from 'react';

interface ContentVideoProps {
  title: string;
  content: string;
}

export const ContentVideo: React.FC<ContentVideoProps> = ({ title, content }) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  const opacity = Math.min(1, frame / 30);
  const contentPoints = content.split('\n').filter(Boolean);

  return (
    <AbsoluteFill style={{ backgroundColor: '#1a1a2e' }}>
      <Sequence from={0} durationInFrames={60}>
        <AbsoluteFill
          style={{
            justifyContent: 'center',
            alignItems: 'center',
            opacity,
          }}
        >
          <h1
            style={{
              fontSize: 80,
              color: 'white',
              textAlign: 'center',
              padding: 40,
            }}
          >
            {title}
          </h1>
        </AbsoluteFill>
      </Sequence>

      {contentPoints.map((point, index) => (
        <Sequence
          key={index}
          from={60 + index * 90}
          durationInFrames={90}
        >
          <AbsoluteFill
            style={{
              justifyContent: 'center',
              alignItems: 'center',
              padding: 60,
            }}
          >
            <div
              style={{
                fontSize: 48,
                color: 'white',
                textAlign: 'center',
                backgroundColor: 'rgba(255, 255, 255, 0.1)',
                padding: 40,
                borderRadius: 20,
              }}
            >
              {point}
            </div>
          </AbsoluteFill>
        </Sequence>
      ))}
    </AbsoluteFill>
  );
};

4. Complete Pipeline Workflow

4. 完整流水线工作流

typescript
// lib/pipeline/content-pipeline.ts
import { scanLatestNews, extractInsights } from '@/lib/research/crawler';
import { generateContentWithClaude } from '@/lib/ai/content-generator';
import { generateVideo } from '@/lib/video/remotion-renderer';

interface PipelineConfig {
  keyword: string;
  format: ContentFormat;
  language: Language;
  tone: Tone;
  platforms: ('reels' | 'tiktok' | 'shorts')[];
}

export async function runContentPipeline(
  config: PipelineConfig
): Promise<{
  article: string;
  videos: string[];
}> {
  // Step 1: Research
  console.log('🔍 Scanning latest news...');
  const articles = await scanLatestNews(config.keyword);
  const insights = await extractInsights(articles);

  // Step 2: Generate Content
  console.log('✍️ Generating content...');
  const article = await generateContentWithClaude({
    keyword: config.keyword,
    format: config.format,
    language: config.language,
    tone: config.tone,
    researchData: insights,
  });

  // Step 3: Generate Videos
  console.log('🎬 Rendering videos...');
  const videos: string[] = [];
  
  for (const platform of config.platforms) {
    const videoPath = await generateVideo({
      content: article,
      title: config.keyword,
      platform,
    });
    videos.push(videoPath);
  }

  return { article, videos };
}
typescript
// lib/pipeline/content-pipeline.ts
import { scanLatestNews, extractInsights } from '@/lib/research/crawler';
import { generateContentWithClaude } from '@/lib/ai/content-generator';
import { generateVideo } from '@/lib/video/remotion-renderer';

interface PipelineConfig {
  keyword: string;
  format: ContentFormat;
  language: Language;
  tone: Tone;
  platforms: ('reels' | 'tiktok' | 'shorts')[];
}

export async function runContentPipeline(
  config: PipelineConfig
): Promise<{
  article: string;
  videos: string[];
}> {
  // Step 1: Research
  console.log('🔍 Scanning latest news...');
  const articles = await scanLatestNews(config.keyword);
  const insights = await extractInsights(articles);

  // Step 2: Generate Content
  console.log('✍️ Generating content...');
  const article = await generateContentWithClaude({
    keyword: config.keyword,
    format: config.format,
    language: config.language,
    tone: config.tone,
    researchData: insights,
  });

  // Step 3: Generate Videos
  console.log('🎬 Rendering videos...');
  const videos: string[] = [];
  
  for (const platform of config.platforms) {
    const videoPath = await generateVideo({
      content: article,
      title: config.keyword,
      platform,
    });
    videos.push(videoPath);
  }

  return { article, videos };
}

API Routes (Next.js)

API路由(Next.js)

typescript
// app/api/generate-content/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { runContentPipeline } from '@/lib/pipeline/content-pipeline';

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    
    const { keyword, format, language, tone, platforms } = body;

    if (!keyword) {
      return NextResponse.json(
        { error: 'Keyword is required' },
        { status: 400 }
      );
    }

    const result = await runContentPipeline({
      keyword,
      format: format || 'toplist',
      language: language || 'en',
      tone: tone || 'friendly',
      platforms: platforms || ['reels'],
    });

    return NextResponse.json({
      success: true,
      data: result,
    });
  } catch (error) {
    console.error('Pipeline error:', error);
    return NextResponse.json(
      { error: 'Failed to generate content' },
      { status: 500 }
    );
  }
}
typescript
// app/api/generate-content/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { runContentPipeline } from '@/lib/pipeline/content-pipeline';

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    
    const { keyword, format, language, tone, platforms } = body;

    if (!keyword) {
      return NextResponse.json(
        { error: 'Keyword is required' },
        { status: 400 }
      );
    }

    const result = await runContentPipeline({
      keyword,
      format: format || 'toplist',
      language: language || 'en',
      tone: tone || 'friendly',
      platforms: platforms || ['reels'],
    });

    return NextResponse.json({
      success: true,
      data: result,
    });
  } catch (error) {
    console.error('Pipeline error:', error);
    return NextResponse.json(
      { error: 'Failed to generate content' },
      { status: 500 }
    );
  }
}

Frontend Usage

前端使用

typescript
// app/page.tsx
'use client';

import { useState } from 'react';

export default function Home() {
  const [keyword, setKeyword] = useState('');
  const [loading, setLoading] = useState(false);
  const [result, setResult] = useState<any>(null);

  const handleGenerate = async () => {
    setLoading(true);
    try {
      const response = await fetch('/api/generate-content', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          keyword,
          format: 'toplist',
          language: 'en',
          tone: 'friendly',
          platforms: ['reels', 'tiktok'],
        }),
      });

      const data = await response.json();
      setResult(data);
    } catch (error) {
      console.error('Error:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <main className="container mx-auto p-8">
      <h1 className="text-4xl font-bold mb-8">AI Content Pipeline</h1>
      
      <div className="space-y-4">
        <input
          type="text"
          value={keyword}
          onChange={(e) => setKeyword(e.target.value)}
          placeholder="Enter keyword..."
          className="w-full p-4 border rounded"
        />
        
        <button
          onClick={handleGenerate}
          disabled={loading || !keyword}
          className="px-6 py-3 bg-blue-600 text-white rounded disabled:opacity-50"
        >
          {loading ? 'Generating...' : 'Generate Content'}
        </button>

        {result && (
          <div className="mt-8 space-y-4">
            <div className="p-4 bg-gray-100 rounded">
              <h2 className="font-bold mb-2">Article</h2>
              <pre className="whitespace-pre-wrap">{result.data.article}</pre>
            </div>
            
            <div>
              <h2 className="font-bold mb-2">Videos</h2>
              {result.data.videos.map((video: string, i: number) => (
                <div key={i}>{video}</div>
              ))}
            </div>
          </div>
        )}
      </div>
    </main>
  );
}
typescript
// app/page.tsx
'use client';

import { useState } from 'react';

export default function Home() {
  const [keyword, setKeyword] = useState('');
  const [loading, setLoading] = useState(false);
  const [result, setResult] = useState<any>(null);

  const handleGenerate = async () => {
    setLoading(true);
    try {
      const response = await fetch('/api/generate-content', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          keyword,
          format: 'toplist',
          language: 'en',
          tone: 'friendly',
          platforms: ['reels', 'tiktok'],
        }),
      });

      const data = await response.json();
      setResult(data);
    } catch (error) {
      console.error('Error:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <main className="container mx-auto p-8">
      <h1 className="text-4xl font-bold mb-8">AI Content Pipeline</h1>
      
      <div className="space-y-4">
        <input
          type="text"
          value={keyword}
          onChange={(e) => setKeyword(e.target.value)}
          placeholder="Enter keyword..."
          className="w-full p-4 border rounded"
        />
        
        <button
          onClick={handleGenerate}
          disabled={loading || !keyword}
          className="px-6 py-3 bg-blue-600 text-white rounded disabled:opacity-50"
        >
          {loading ? 'Generating...' : 'Generate Content'}
        </button>

        {result && (
          <div className="mt-8 space-y-4">
            <div className="p-4 bg-gray-100 rounded">
              <h2 className="font-bold mb-2">Article</h2>
              <pre className="whitespace-pre-wrap">{result.data.article}</pre>
            </div>
            
            <div>
              <h2 className="font-bold mb-2">Videos</h2>
              {result.data.videos.map((video: string, i: number) => (
                <div key={i}>{video}</div>
              ))}
            </div>
          </div>
        )}
      </div>
    </main>
  );
}

Common Patterns

常见模式

Batch Content Generation

批量内容生成

typescript
// lib/batch/batch-processor.ts
export async function batchGenerateContent(
  keywords: string[],
  config: Partial<PipelineConfig>
): Promise<Map<string, any>> {
  const results = new Map();

  for (const keyword of keywords) {
    try {
      const result = await runContentPipeline({
        keyword,
        format: config.format || 'toplist',
        language: config.language || 'en',
        tone: config.tone || 'friendly',
        platforms: config.platforms || ['reels'],
      });
      
      results.set(keyword, result);
    } catch (error) {
      console.error(`Failed for keyword: ${keyword}`, error);
      results.set(keyword, { error: error.message });
    }
  }

  return results;
}
typescript
// lib/batch/batch-processor.ts
export async function batchGenerateContent(
  keywords: string[],
  config: Partial<PipelineConfig>
): Promise<Map<string, any>> {
  const results = new Map();

  for (const keyword of keywords) {
    try {
      const result = await runContentPipeline({
        keyword,
        format: config.format || 'toplist',
        language: config.language || 'en',
        tone: config.tone || 'friendly',
        platforms: config.platforms || ['reels'],
      });
      
      results.set(keyword, result);
    } catch (error) {
      console.error(`Failed for keyword: ${keyword}`, error);
      results.set(keyword, { error: error.message });
    }
  }

  return results;
}

Scheduling Content Generation

内容生成调度

typescript
// lib/scheduler/content-scheduler.ts
import cron from 'node-cron';

export function scheduleContentGeneration(
  schedule: string,
  config: PipelineConfig
) {
  cron.schedule(schedule, async () => {
    console.log('Running scheduled content generation...');
    
    try {
      const result = await runContentPipeline(config);
      // Save to database or publish directly
      console.log('Content generated successfully:', result);
    } catch (error) {
      console.error('Scheduled generation failed:', error);
    }
  });
}

// Usage: Run daily at 9 AM
scheduleContentGeneration('0 9 * * *', {
  keyword: 'AI trends',
  format: 'toplist',
  language: 'en',
  tone: 'expert',
  platforms: ['reels', 'tiktok'],
});
typescript
// lib/scheduler/content-scheduler.ts
import cron from 'node-cron';

export function scheduleContentGeneration(
  schedule: string,
  config: PipelineConfig
) {
  cron.schedule(schedule, async () => {
    console.log('Running scheduled content generation...');
    
    try {
      const result = await runContentPipeline(config);
      // Save to database or publish directly
      console.log('Content generated successfully:', result);
    } catch (error) {
      console.error('Scheduled generation failed:', error);
    }
  });
}

// Usage: Run daily at 9 AM
scheduleContentGeneration('0 9 * * *', {
  keyword: 'AI trends',
  format: 'toplist',
  language: 'en',
  tone: 'expert',
  platforms: ['reels', 'tiktok'],
});

Troubleshooting

故障排除

Issue: API Rate Limits

问题:API速率限制

typescript
// lib/utils/rate-limiter.ts
export class RateLimiter {
  private queue: (() => Promise<any>)[] = [];
  private processing = false;
  private delay: number;

  constructor(requestsPerMinute: number) {
    this.delay = 60000 / requestsPerMinute;
  }

  async add<T>(fn: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      this.queue.push(async () => {
        try {
          const result = await fn();
          resolve(result);
        } catch (error) {
          reject(error);
        }
      });
      
      this.process();
    });
  }

  private async process() {
    if (this.processing || this.queue.length === 0) return;
    
    this.processing = true;
    
    while (this.queue.length > 0) {
      const fn = this.queue.shift()!;
      await fn();
      await new Promise(resolve => setTimeout(resolve, this.delay));
    }
    
    this.processing = false;
  }
}

// Usage
const limiter = new RateLimiter(10); // 10 requests per minute

await limiter.add(() => generateContentWithClaude(options));
typescript
// lib/utils/rate-limiter.ts
export class RateLimiter {
  private queue: (() => Promise<any>)[] = [];
  private processing = false;
  private delay: number;

  constructor(requestsPerMinute: number) {
    this.delay = 60000 / requestsPerMinute;
  }

  async add<T>(fn: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      this.queue.push(async () => {
        try {
          const result = await fn();
          resolve(result);
        } catch (error) {
          reject(error);
        }
      });
      
      this.process();
    });
  }

  private async process() {
    if (this.processing || this.queue.length === 0) return;
    
    this.processing = true;
    
    while (this.queue.length > 0) {
      const fn = this.queue.shift()!;
      await fn();
      await new Promise(resolve => setTimeout(resolve, this.delay));
    }
    
    this.processing = false;
  }
}

// Usage
const limiter = new RateLimiter(10); // 10 requests per minute

await limiter.add(() => generateContentWithClaude(options));

Issue: Video Rendering Timeout

问题:视频渲染超时

Increase timeout for long videos:
typescript
await renderMedia({
  composition,
  serveUrl: bundleLocation,
  codec: 'h264',
  outputLocation,
  timeoutInMilliseconds: 300000, // 5 minutes
  inputProps: {
    title: config.title,
    content: config.content,
  },
});
为长视频增加超时时间:
typescript
await renderMedia({
  composition,
  serveUrl: bundleLocation,
  codec: 'h264',
  outputLocation,
  timeoutInMilliseconds: 300000, // 5 minutes
  inputProps: {
    title: config.title,
    content: config.content,
  },
});

Issue: Missing Research Data

问题:调研数据缺失

Add fallback content when research fails:
typescript
export async function scanLatestNewsWithFallback(
  keyword: string
): Promise<NewsArticle[]> {
  try {
    return await scanLatestNews(keyword);
  } catch (error) {
    console.warn('Research failed, using cached data');
    return getCachedNews(keyword);
  }
}
当调研失败时添加备用内容:
typescript
export async function scanLatestNewsWithFallback(
  keyword: string
): Promise<NewsArticle[]> {
  try {
    return await scanLatestNews(keyword);
  } catch (error) {
    console.warn('Research failed, using cached data');
    return getCachedNews(keyword);
  }
}

Running the Development Server

运行开发服务器

bash
npm run dev
bash
npm run dev

or

or

yarn dev
yarn dev

or

or

pnpm dev

Open [http://localhost:3000](http://localhost:3000) to see the application.
pnpm dev

打开[http://localhost:3000](http://localhost:3000)查看应用。

Building for Production

生产环境构建

bash
npm run build
npm start
This skill provides comprehensive coverage of the marketing pipeline automation system, enabling AI agents to assist with content generation, video rendering, and workflow automation tasks.
bash
npm run build
npm start
该技能全面覆盖了营销流水线自动化系统,使AI Agent能够协助完成内容生成、视频渲染和工作流自动化任务。