marketing-pipeline-share-automation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Marketing Pipeline Share Automation

营销流水线共享自动化

Skill by ara.so — Marketing Skills collection.
This skill enables AI coding agents to work with marketing-pipeline-share, a complete AI-powered content automation system that handles research (web crawling), script generation (Claude/OpenAI), Facebook auto-posting, and video rendering (Remotion). It's designed for marketers and content creators who want to automate their entire content workflow from keyword input to published video.
ara.so提供的Skill —— 营销技能合集。
本Skill支持AI编码Agent使用marketing-pipeline-share这一完整的AI驱动内容自动化系统,该系统可处理调研(网页爬取)、脚本生成(Claude/OpenAI)、Facebook自动发布以及视频渲染(Remotion)。它专为希望实现从关键词输入到视频发布全内容工作流自动化的营销人员和内容创作者设计。

What This Project Does

本项目功能

Marketing Pipeline Share is a TypeScript/Next.js application that automates the complete content creation pipeline:
  1. Auto-Research: Crawls news sources (TechCrunch, a16z, Twitter, LinkedIn) for recent data
  2. AI Content Generation: Uses Claude 3 or OpenAI to generate articles in multiple formats (toplist, POV, case study, how-to)
  3. Multi-language Support: Generates content in both English and Vietnamese
  4. Auto-Posting: Publishes directly to Facebook pages
  5. Video Rendering: Converts content to videos/infographics using Remotion for Reels/TikTok/Shorts
Marketing Pipeline Share是一款TypeScript/Next.js应用,可自动化完整的内容创作流程:
  1. 自动调研:爬取新闻来源(TechCrunch、a16z、Twitter、LinkedIn)获取最新数据
  2. AI内容生成:使用Claude 3或OpenAI生成多种格式的文章(榜单、观点文、案例研究、教程)
  3. 多语言支持:生成英文和越南语内容
  4. 自动发布:直接发布至Facebook主页
  5. 视频渲染:通过Remotion将内容转换为短视频/信息图,适用于Reels/TikTok/Shorts

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

Copy environment template

Copy environment template

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

Configuration

配置

Set up your environment variables in
.env.local
:
bash
undefined
.env.local
中设置环境变量:
bash
undefined

AI Provider APIs

AI Provider APIs

ANTHROPIC_API_KEY=your_claude_api_key OPENAI_API_KEY=your_openai_api_key
ANTHROPIC_API_KEY=your_claude_api_key OPENAI_API_KEY=your_openai_api_key

Research/Crawling APIs

Research/Crawling APIs

RAPIDAPI_KEY=your_rapidapi_key RAPIDAPI_HOST=news-api-host
RAPIDAPI_KEY=your_rapidapi_key RAPIDAPI_HOST=news-api-host

Facebook Integration

Facebook Integration

FACEBOOK_PAGE_ACCESS_TOKEN=your_fb_page_token FACEBOOK_PAGE_ID=your_page_id
FACEBOOK_PAGE_ACCESS_TOKEN=your_fb_page_token FACEBOOK_PAGE_ID=your_page_id

Remotion Configuration

Remotion Configuration

REMOTION_CLOUDRUN_URL=your_cloudrun_url REMOTION_API_KEY=your_remotion_key
REMOTION_CLOUDRUN_URL=your_cloudrun_url REMOTION_API_KEY=your_remotion_key

Application

Application

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

Key Architecture

核心架构

typescript
// Typical project structure
src/
├── app/                    # Next.js app directory
│   ├── api/               # API routes
│   │   ├── research/      # News crawling endpoints
│   │   ├── generate/      # AI content generation
│   │   ├── post/          # Facebook posting
│   │   └── render/        # Video rendering
│   └── dashboard/         # UI components
├── lib/
│   ├── ai/                # AI provider integrations
│   ├── crawlers/          # Web scraping utilities
│   ├── facebook/          # FB API integration
│   └── remotion/          # Video rendering logic
└── types/                 # TypeScript definitions
typescript
// Typical project structure
src/
├── app/                    # Next.js app directory
│   ├── api/               # API routes
│   │   ├── research/      # News crawling endpoints
│   │   ├── generate/      # AI content generation
│   │   ├── post/          # Facebook posting
│   │   └── render/        # Video rendering
│   └── dashboard/         # UI components
├── lib/
│   ├── ai/                # AI provider integrations
│   ├── crawlers/          # Web scraping utilities
│   ├── facebook/          # FB API integration
│   └── remotion/          # Video rendering logic
└── types/                 # TypeScript definitions

Core Usage Patterns

核心使用模式

1. Research & Content Generation Flow

1. 调研与内容生成流程

typescript
// lib/ai/content-generator.ts
import Anthropic from '@anthropic-ai/sdk';
import OpenAI from 'openai';

export interface ContentRequest {
  keyword: string;
  format: 'toplist' | 'pov' | 'case-study' | 'how-to';
  language: 'en' | 'vi';
  tone: 'expert' | 'friendly' | 'humorous';
  researchData?: any[];
}

export class ContentGenerator {
  private claude: Anthropic;
  private openai: OpenAI;

  constructor() {
    this.claude = new Anthropic({
      apiKey: process.env.ANTHROPIC_API_KEY,
    });
    this.openai = new OpenAI({
      apiKey: process.env.OPENAI_API_KEY,
    });
  }

  async generateWithClaude(request: ContentRequest): Promise<string> {
    const prompt = this.buildPrompt(request);
    
    const message = await this.claude.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 
      : '';
  }

  async generateWithOpenAI(request: ContentRequest): Promise<string> {
    const prompt = this.buildPrompt(request);
    
    const completion = await this.openai.chat.completions.create({
      model: 'gpt-4-turbo',
      messages: [{
        role: 'user',
        content: prompt
      }],
      temperature: 0.7,
    });

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

  private buildPrompt(request: ContentRequest): string {
    const { keyword, format, language, tone, researchData } = request;
    
    let prompt = `Create a ${format} article about "${keyword}" in ${language}.`;
    prompt += `\nTone: ${tone}`;
    
    if (researchData && researchData.length > 0) {
      prompt += `\n\nRecent research data:\n`;
      researchData.forEach((item, idx) => {
        prompt += `${idx + 1}. ${item.title}\n${item.summary}\n`;
      });
    }
    
    return prompt;
  }
}
typescript
// lib/ai/content-generator.ts
import Anthropic from '@anthropic-ai/sdk';
import OpenAI from 'openai';

export interface ContentRequest {
  keyword: string;
  format: 'toplist' | 'pov' | 'case-study' | 'how-to';
  language: 'en' | 'vi';
  tone: 'expert' | 'friendly' | 'humorous';
  researchData?: any[];
}

export class ContentGenerator {
  private claude: Anthropic;
  private openai: OpenAI;

  constructor() {
    this.claude = new Anthropic({
      apiKey: process.env.ANTHROPIC_API_KEY,
    });
    this.openai = new OpenAI({
      apiKey: process.env.OPENAI_API_KEY,
    });
  }

  async generateWithClaude(request: ContentRequest): Promise<string> {
    const prompt = this.buildPrompt(request);
    
    const message = await this.claude.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 
      : '';
  }

  async generateWithOpenAI(request: ContentRequest): Promise<string> {
    const prompt = this.buildPrompt(request);
    
    const completion = await this.openai.chat.completions.create({
      model: 'gpt-4-turbo',
      messages: [{
        role: 'user',
        content: prompt
      }],
      temperature: 0.7,
    });

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

  private buildPrompt(request: ContentRequest): string {
    const { keyword, format, language, tone, researchData } = request;
    
    let prompt = `Create a ${format} article about "${keyword}" in ${language}.`;
    prompt += `\nTone: ${tone}`;
    
    if (researchData && researchData.length > 0) {
      prompt += `\n\nRecent research data:\n`;
      researchData.forEach((item, idx) => {
        prompt += `${idx + 1}. ${item.title}\n${item.summary}\n`;
      });
    }
    
    return prompt;
  }
}

2. News Crawling/Research System

2. 新闻爬取/调研系统

typescript
// lib/crawlers/news-crawler.ts
import axios from 'axios';

export interface NewsArticle {
  title: string;
  url: string;
  source: string;
  publishedAt: string;
  summary: string;
}

export class NewsCrawler {
  private rapidApiKey: string;
  private rapidApiHost: string;

  constructor() {
    this.rapidApiKey = process.env.RAPIDAPI_KEY!;
    this.rapidApiHost = process.env.RAPIDAPI_HOST!;
  }

  async crawlRecentNews(keyword: string, hours: number = 24): Promise<NewsArticle[]> {
    try {
      const response = await axios.get('https://api.rapidapi.com/v1/news/search', {
        params: {
          q: keyword,
          from: this.getTimeRange(hours),
          sortBy: 'publishedAt',
          language: 'en'
        },
        headers: {
          'X-RapidAPI-Key': this.rapidApiKey,
          'X-RapidAPI-Host': this.rapidApiHost
        }
      });

      return response.data.articles.map((article: any) => ({
        title: article.title,
        url: article.url,
        source: article.source.name,
        publishedAt: article.publishedAt,
        summary: article.description || ''
      }));
    } catch (error) {
      console.error('News crawling error:', error);
      return [];
    }
  }

  private getTimeRange(hours: number): string {
    const date = new Date();
    date.setHours(date.getHours() - hours);
    return date.toISOString();
  }

  async crawlMultipleSources(keyword: string): Promise<NewsArticle[]> {
    // Crawl from TechCrunch, a16z, etc.
    const sources = ['techcrunch', 'a16z-blog'];
    const allArticles: NewsArticle[] = [];

    for (const source of sources) {
      const articles = await this.crawlRecentNews(`${keyword} site:${source}`, 24);
      allArticles.push(...articles);
    }

    return allArticles;
  }
}
typescript
// lib/crawlers/news-crawler.ts
import axios from 'axios';

export interface NewsArticle {
  title: string;
  url: string;
  source: string;
  publishedAt: string;
  summary: string;
}

export class NewsCrawler {
  private rapidApiKey: string;
  private rapidApiHost: string;

  constructor() {
    this.rapidApiKey = process.env.RAPIDAPI_KEY!;
    this.rapidApiHost = process.env.RAPIDAPI_HOST!;
  }

  async crawlRecentNews(keyword: string, hours: number = 24): Promise<NewsArticle[]> {
    try {
      const response = await axios.get('https://api.rapidapi.com/v1/news/search', {
        params: {
          q: keyword,
          from: this.getTimeRange(hours),
          sortBy: 'publishedAt',
          language: 'en'
        },
        headers: {
          'X-RapidAPI-Key': this.rapidApiKey,
          'X-RapidAPI-Host': this.rapidApiHost
        }
      });

      return response.data.articles.map((article: any) => ({
        title: article.title,
        url: article.url,
        source: article.source.name,
        publishedAt: article.publishedAt,
        summary: article.description || ''
      }));
    } catch (error) {
      console.error('News crawling error:', error);
      return [];
    }
  }

  private getTimeRange(hours: number): string {
    const date = new Date();
    date.setHours(date.getHours() - hours);
    return date.toISOString();
  }

  async crawlMultipleSources(keyword: string): Promise<NewsArticle[]> {
    // Crawl from TechCrunch, a16z, etc.
    const sources = ['techcrunch', 'a16z-blog'];
    const allArticles: NewsArticle[] = [];

    for (const source of sources) {
      const articles = await this.crawlRecentNews(`${keyword} site:${source}`, 24);
      allArticles.push(...articles);
    }

    return allArticles;
  }
}

3. API Route Example - Generate Content

3. API路由示例 - 生成内容

typescript
// app/api/generate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { ContentGenerator } from '@/lib/ai/content-generator';
import { NewsCrawler } from '@/lib/crawlers/news-crawler';

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

    let researchData = [];
    
    if (useResearch) {
      const crawler = new NewsCrawler();
      researchData = await crawler.crawlMultipleSources(keyword);
    }

    const generator = new ContentGenerator();
    let content: string;

    if (aiProvider === 'claude') {
      content = await generator.generateWithClaude({
        keyword,
        format,
        language,
        tone,
        researchData
      });
    } else {
      content = await generator.generateWithOpenAI({
        keyword,
        format,
        language,
        tone,
        researchData
      });
    }

    return NextResponse.json({
      success: true,
      content,
      researchCount: researchData.length
    });
  } catch (error: any) {
    return NextResponse.json(
      { success: false, error: error.message },
      { status: 500 }
    );
  }
}
typescript
// app/api/generate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { ContentGenerator } from '@/lib/ai/content-generator';
import { NewsCrawler } from '@/lib/crawlers/news-crawler';

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

    let researchData = [];
    
    if (useResearch) {
      const crawler = new NewsCrawler();
      researchData = await crawler.crawlMultipleSources(keyword);
    }

    const generator = new ContentGenerator();
    let content: string;

    if (aiProvider === 'claude') {
      content = await generator.generateWithClaude({
        keyword,
        format,
        language,
        tone,
        researchData
      });
    } else {
      content = await generator.generateWithOpenAI({
        keyword,
        format,
        language,
        tone,
        researchData
      });
    }

    return NextResponse.json({
      success: true,
      content,
      researchCount: researchData.length
    });
  } catch (error: any) {
    return NextResponse.json(
      { success: false, error: error.message },
      { status: 500 }
    );
  }
}

4. Facebook Auto-Posting

4. Facebook自动发布

typescript
// lib/facebook/post-manager.ts
import axios from 'axios';

export interface FacebookPost {
  message: string;
  link?: string;
  imageUrl?: string;
}

export class FacebookPostManager {
  private pageAccessToken: string;
  private pageId: string;

  constructor() {
    this.pageAccessToken = process.env.FACEBOOK_PAGE_ACCESS_TOKEN!;
    this.pageId = process.env.FACEBOOK_PAGE_ID!;
  }

  async publishPost(post: FacebookPost): Promise<string> {
    try {
      const endpoint = `https://graph.facebook.com/v18.0/${this.pageId}/feed`;
      
      const response = await axios.post(endpoint, {
        message: post.message,
        link: post.link,
        access_token: this.pageAccessToken
      });

      return response.data.id;
    } catch (error: any) {
      throw new Error(`Facebook posting failed: ${error.response?.data?.error?.message || error.message}`);
    }
  }

  async publishPhoto(post: FacebookPost): Promise<string> {
    if (!post.imageUrl) {
      throw new Error('Image URL required for photo post');
    }

    const endpoint = `https://graph.facebook.com/v18.0/${this.pageId}/photos`;
    
    const response = await axios.post(endpoint, {
      url: post.imageUrl,
      caption: post.message,
      access_token: this.pageAccessToken
    });

    return response.data.id;
  }

  async schedulePost(post: FacebookPost, publishTime: Date): Promise<string> {
    const endpoint = `https://graph.facebook.com/v18.0/${this.pageId}/feed`;
    
    const response = await axios.post(endpoint, {
      message: post.message,
      link: post.link,
      published: false,
      scheduled_publish_time: Math.floor(publishTime.getTime() / 1000),
      access_token: this.pageAccessToken
    });

    return response.data.id;
  }
}
typescript
// lib/facebook/post-manager.ts
import axios from 'axios';

export interface FacebookPost {
  message: string;
  link?: string;
  imageUrl?: string;
}

export class FacebookPostManager {
  private pageAccessToken: string;
  private pageId: string;

  constructor() {
    this.pageAccessToken = process.env.FACEBOOK_PAGE_ACCESS_TOKEN!;
    this.pageId = process.env.FACEBOOK_PAGE_ID!;
  }

  async publishPost(post: FacebookPost): Promise<string> {
    try {
      const endpoint = `https://graph.facebook.com/v18.0/${this.pageId}/feed`;
      
      const response = await axios.post(endpoint, {
        message: post.message,
        link: post.link,
        access_token: this.pageAccessToken
      });

      return response.data.id;
    } catch (error: any) {
      throw new Error(`Facebook posting failed: ${error.response?.data?.error?.message || error.message}`);
    }
  }

  async publishPhoto(post: FacebookPost): Promise<string> {
    if (!post.imageUrl) {
      throw new Error('Image URL required for photo post');
    }

    const endpoint = `https://graph.facebook.com/v18.0/${this.pageId}/photos`;
    
    const response = await axios.post(endpoint, {
      url: post.imageUrl,
      caption: post.message,
      access_token: this.pageAccessToken
    });

    return response.data.id;
  }

  async schedulePost(post: FacebookPost, publishTime: Date): Promise<string> {
    const endpoint = `https://graph.facebook.com/v18.0/${this.pageId}/feed`;
    
    const response = await axios.post(endpoint, {
      message: post.message,
      link: post.link,
      published: false,
      scheduled_publish_time: Math.floor(publishTime.getTime() / 1000),
      access_token: this.pageAccessToken
    });

    return response.data.id;
  }
}

5. Video Rendering with Remotion

5. 基于Remotion的视频渲染

typescript
// lib/remotion/video-renderer.ts
import { bundle } from '@remotion/bundler';
import { renderMedia, selectComposition } from '@remotion/renderer';
import { webpackOverride } from './webpack-override';

export interface VideoConfig {
  title: string;
  content: string[];
  duration: number;
  format: 'reels' | 'tiktok' | 'shorts';
}

export class VideoRenderer {
  async renderContentVideo(config: VideoConfig): Promise<string> {
    const bundled = await bundle({
      entryPoint: './src/remotion/index.ts',
      webpackOverride: webpackOverride,
    });

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

    const outputLocation = `./output/video-${Date.now()}.mp4`;

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

    return outputLocation;
  }

  getAspectRatio(format: string): { width: number; height: number } {
    switch (format) {
      case 'reels':
      case 'tiktok':
      case 'shorts':
        return { width: 1080, height: 1920 }; // 9:16
      default:
        return { width: 1920, height: 1080 }; // 16:9
    }
  }
}
typescript
// lib/remotion/video-renderer.ts
import { bundle } from '@remotion/bundler';
import { renderMedia, selectComposition } from '@remotion/renderer';
import { webpackOverride } from './webpack-override';

export interface VideoConfig {
  title: string;
  content: string[];
  duration: number;
  format: 'reels' | 'tiktok' | 'shorts';
}

export class VideoRenderer {
  async renderContentVideo(config: VideoConfig): Promise<string> {
    const bundled = await bundle({
      entryPoint: './src/remotion/index.ts',
      webpackOverride: webpackOverride,
    });

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

    const outputLocation = `./output/video-${Date.now()}.mp4`;

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

    return outputLocation;
  }

  getAspectRatio(format: string): { width: number; height: number } {
    switch (format) {
      case 'reels':
      case 'tiktok':
      case 'shorts':
        return { width: 1080, height: 1920 }; // 9:16
      default:
        return { width: 1920, height: 1080 }; // 16:9
    }
  }
}

6. Complete Pipeline Example

6. 完整流水线示例

typescript
// lib/pipeline/content-pipeline.ts
import { ContentGenerator } from '@/lib/ai/content-generator';
import { NewsCrawler } from '@/lib/crawlers/news-crawler';
import { FacebookPostManager } from '@/lib/facebook/post-manager';
import { VideoRenderer } from '@/lib/remotion/video-renderer';

export class ContentPipeline {
  private generator: ContentGenerator;
  private crawler: NewsCrawler;
  private fbManager: FacebookPostManager;
  private videoRenderer: VideoRenderer;

  constructor() {
    this.generator = new ContentGenerator();
    this.crawler = new NewsCrawler();
    this.fbManager = new FacebookPostManager();
    this.videoRenderer = new VideoRenderer();
  }

  async executeFullPipeline(keyword: string) {
    console.log('Step 1: Researching...');
    const researchData = await this.crawler.crawlMultipleSources(keyword);

    console.log('Step 2: Generating content...');
    const content = await this.generator.generateWithClaude({
      keyword,
      format: 'toplist',
      language: 'en',
      tone: 'expert',
      researchData
    });

    console.log('Step 3: Rendering video...');
    const videoPath = await this.videoRenderer.renderContentVideo({
      title: keyword,
      content: content.split('\n').filter(line => line.trim()),
      duration: 30,
      format: 'reels'
    });

    console.log('Step 4: Posting to Facebook...');
    const postId = await this.fbManager.publishPost({
      message: `New content about ${keyword}!`,
      link: `https://example.com/videos/${videoPath}`
    });

    return {
      success: true,
      researchCount: researchData.length,
      content,
      videoPath,
      postId
    };
  }
}
typescript
// lib/pipeline/content-pipeline.ts
import { ContentGenerator } from '@/lib/ai/content-generator';
import { NewsCrawler } from '@/lib/crawlers/news-crawler';
import { FacebookPostManager } from '@/lib/facebook/post-manager';
import { VideoRenderer } from '@/lib/remotion/video-renderer';

export class ContentPipeline {
  private generator: ContentGenerator;
  private crawler: NewsCrawler;
  private fbManager: FacebookPostManager;
  private videoRenderer: VideoRenderer;

  constructor() {
    this.generator = new ContentGenerator();
    this.crawler = new NewsCrawler();
    this.fbManager = new FacebookPostManager();
    this.videoRenderer = new VideoRenderer();
  }

  async executeFullPipeline(keyword: string) {
    console.log('Step 1: Researching...');
    const researchData = await this.crawler.crawlMultipleSources(keyword);

    console.log('Step 2: Generating content...');
    const content = await this.generator.generateWithClaude({
      keyword,
      format: 'toplist',
      language: 'en',
      tone: 'expert',
      researchData
    });

    console.log('Step 3: Rendering video...');
    const videoPath = await this.videoRenderer.renderContentVideo({
      title: keyword,
      content: content.split('\n').filter(line => line.trim()),
      duration: 30,
      format: 'reels'
    });

    console.log('Step 4: Posting to Facebook...');
    const postId = await this.fbManager.publishPost({
      message: `New content about ${keyword}!`,
      link: `https://example.com/videos/${videoPath}`
    });

    return {
      success: true,
      researchCount: researchData.length,
      content,
      videoPath,
      postId
    };
  }
}

7. API Route - Full Pipeline

7. API路由 - 完整流水线

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

export async function POST(request: NextRequest) {
  try {
    const { keyword } = await request.json();

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

    const pipeline = new ContentPipeline();
    const result = await pipeline.executeFullPipeline(keyword);

    return NextResponse.json(result);
  } catch (error: any) {
    return NextResponse.json(
      { success: false, error: error.message },
      { status: 500 }
    );
  }
}
typescript
// app/api/pipeline/execute/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { ContentPipeline } from '@/lib/pipeline/content-pipeline';

export async function POST(request: NextRequest) {
  try {
    const { keyword } = await request.json();

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

    const pipeline = new ContentPipeline();
    const result = await pipeline.executeFullPipeline(keyword);

    return NextResponse.json(result);
  } catch (error: any) {
    return NextResponse.json(
      { success: false, error: error.message },
      { status: 500 }
    );
  }
}

Running the Application

运行应用

bash
undefined
bash
undefined

Development

Development

npm run dev
npm run dev

Production build

Production build

npm run build npm start
npm run build npm start

Run specific pipeline steps

Run specific pipeline steps

npm run research -- --keyword "AI trends" npm run generate -- --keyword "AI trends" --format toplist npm run render -- --input content.json
undefined
npm run research -- --keyword "AI trends" npm run generate -- --keyword "AI trends" --format toplist npm run render -- --input content.json
undefined

Common Workflows

常见工作流

Workflow 1: Research-Only Mode

工作流1:仅调研模式

typescript
// Quick research without content generation
import { NewsCrawler } from '@/lib/crawlers/news-crawler';

const crawler = new NewsCrawler();
const articles = await crawler.crawlRecentNews('AI marketing', 48);
console.log(`Found ${articles.length} articles`);
typescript
// Quick research without content generation
import { NewsCrawler } from '@/lib/crawlers/news-crawler';

const crawler = new NewsCrawler();
const articles = await crawler.crawlRecentNews('AI marketing', 48);
console.log(`Found ${articles.length} articles`);

Workflow 2: Multi-Language Content

工作流2:多语言内容

typescript
// Generate content in both languages
const generator = new ContentGenerator();

const englishContent = await generator.generateWithClaude({
  keyword: 'Marketing automation',
  format: 'how-to',
  language: 'en',
  tone: 'expert'
});

const vietnameseContent = await generator.generateWithClaude({
  keyword: 'Marketing automation',
  format: 'how-to',
  language: 'vi',
  tone: 'friendly'
});
typescript
// Generate content in both languages
const generator = new ContentGenerator();

const englishContent = await generator.generateWithClaude({
  keyword: 'Marketing automation',
  format: 'how-to',
  language: 'en',
  tone: 'expert'
});

const vietnameseContent = await generator.generateWithClaude({
  keyword: 'Marketing automation',
  format: 'how-to',
  language: 'vi',
  tone: 'friendly'
});

Workflow 3: Scheduled Posting

工作流3:定时发布

typescript
// Schedule content for future posting
const fbManager = new FacebookPostManager();
const publishTime = new Date();
publishTime.setHours(publishTime.getHours() + 24);

const postId = await fbManager.schedulePost({
  message: generatedContent,
  link: 'https://example.com/article'
}, publishTime);
typescript
// Schedule content for future posting
const fbManager = new FacebookPostManager();
const publishTime = new Date();
publishTime.setHours(publishTime.getHours() + 24);

const postId = await fbManager.schedulePost({
  message: generatedContent,
  link: 'https://example.com/article'
}, publishTime);

Troubleshooting

故障排查

API Key Issues

API密钥问题

typescript
// Verify API keys are loaded
if (!process.env.ANTHROPIC_API_KEY) {
  throw new Error('ANTHROPIC_API_KEY not found in environment');
}

// Test API connection
const testConnection = async () => {
  try {
    const claude = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
    await claude.messages.create({
      model: 'claude-3-5-sonnet-20241022',
      max_tokens: 10,
      messages: [{ role: 'user', content: 'test' }]
    });
    console.log('✓ Claude API connected');
  } catch (error) {
    console.error('✗ Claude API failed:', error);
  }
};
typescript
// Verify API keys are loaded
if (!process.env.ANTHROPIC_API_KEY) {
  throw new Error('ANTHROPIC_API_KEY not found in environment');
}

// Test API connection
const testConnection = async () => {
  try {
    const claude = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
    await claude.messages.create({
      model: 'claude-3-5-sonnet-20241022',
      max_tokens: 10,
      messages: [{ role: 'user', content: 'test' }]
    });
    console.log('✓ Claude API connected');
  } catch (error) {
    console.error('✗ Claude API failed:', error);
  }
};

Rate Limiting

速率限制

typescript
// Implement retry logic with exponential backoff
async function withRetry<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error: any) {
      if (error.status === 429 && i < maxRetries - 1) {
        await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
        continue;
      }
      throw error;
    }
  }
  throw new Error('Max retries exceeded');
}
typescript
// Implement retry logic with exponential backoff
async function withRetry<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error: any) {
      if (error.status === 429 && i < maxRetries - 1) {
        await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
        continue;
      }
      throw error;
    }
  }
  throw new Error('Max retries exceeded');
}

Facebook Posting Errors

Facebook发布错误

typescript
// Debug Facebook API errors
try {
  await fbManager.publishPost(post);
} catch (error: any) {
  if (error.message.includes('access token')) {
    console.error('Facebook token expired or invalid');
    // Refresh token logic here
  } else if (error.message.includes('rate limit')) {
    console.error('Facebook rate limit reached');
    // Queue for later
  } else {
    console.error('Unknown Facebook error:', error);
  }
}
typescript
// Debug Facebook API errors
try {
  await fbManager.publishPost(post);
} catch (error: any) {
  if (error.message.includes('access token')) {
    console.error('Facebook token expired or invalid');
    // Refresh token logic here
  } else if (error.message.includes('rate limit')) {
    console.error('Facebook rate limit reached');
    // Queue for later
  } else {
    console.error('Unknown Facebook error:', error);
  }
}

Video Rendering Issues

视频渲染问题

typescript
// Check Remotion dependencies
import { getCompositions } from '@remotion/renderer';

const checkRemotionSetup = async () => {
  try {
    const compositions = await getCompositions('./src/remotion/index.ts');
    console.log('Available compositions:', compositions.map(c => c.id));
  } catch (error) {
    console.error('Remotion setup error:', error);
  }
};
This skill enables complete automation of content marketing workflows from research through video publication using modern AI tools and TypeScript.
typescript
// Check Remotion dependencies
import { getCompositions } from '@remotion/renderer';

const checkRemotionSetup = async () => {
  try {
    const compositions = await getCompositions('./src/remotion/index.ts');
    console.log('Available compositions:', compositions.map(c => c.id));
  } catch (error) {
    console.error('Remotion setup error:', error);
  }
};
本Skill借助现代AI工具和TypeScript,实现了从调研到视频发布的完整营销内容工作流自动化。