Try   HackMD

第三堂:前後端整合:募資文案智能寫作

Whisper 語音轉文字

NPM 介紹

  • multer:處理上傳檔案
  • FormData:包裝成 FormData 請求送出給 OpenAI
  • axios:發出網路請求

儲存在記憶體跟實體空間的差異

項目 記憶體儲存 (MemoryStorage) 實體硬碟儲存 (DiskStorage)
儲存位置 將檔案暫存在伺服器記憶體 (RAM) 中 將檔案寫入伺服器硬碟或指定的實體路徑
使用情境 適合即時處理、小型檔案處理、多數情況不需長期保存 適合需要暫留或後續再處理的檔案、大量或較大檔案上傳
效能 I/O 速度快,無須讀寫硬碟,對小檔案較有效率 需要硬碟 I/O,速度可能稍慢,但對大檔案或長期保存更實用
記憶體壓力 檔案全存於記憶體,若並發或檔案過大,可能導致 Out Of Memory 不會耗用過多 RAM,但在硬碟空間不足時也可能引發其他問題
保留時間 只要程式結束或記憶體被釋放,檔案即消失 儲存在硬碟上,可持續保存,除非自行刪除或重新部署
部署/權限 部署較方便,不需考慮硬碟路徑、讀寫權限 需設定讀寫路徑與權限,若在 Serverless 或限制多的環境需特別注意
後續操作 資料只在記憶體內,若需要多階段處理必須先轉存 可直接存檔後續再加工、分析,可多人或多服務存取同一檔案
除錯與追蹤 處理完後即釋放,檔案不留存,事後較難復原檢查 上傳後會有實際檔案,可以做後續的除錯或檢視
清理管理 不需額外清理檔案,使用後自動釋放 需要手動或透過機制定期刪除不再使用的檔案

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

範例程式碼

const express = require("express");
const multer = require("multer");
const axios = require("axios");
const FormData = require("form-data");
require("dotenv").config();

// 初始化 Express 應用程式
const app = express();
const PORT = 3000;

// 中間件設定
app.use(express.json());
app.use(express.static("public"));

// 設定檔案上傳 - 使用記憶體儲存
const upload = multer({
  storage: multer.memoryStorage(), // 使用記憶體儲存
  limits: { fileSize: 25 * 1024 * 1024 }, // 25MB 限制
  fileFilter: (req, file, cb) => {
    // 只允許音訊檔案
    if (file.mimetype.startsWith("audio/")) {
      cb(null, true);
    } else {
      cb(new Error("請上傳音訊檔案"));
    }
  },
});

// 音訊轉文字函式 - 使用記憶體緩衝區
async function transcribeAudio(audioBuffer, mimeType, originalFilename) {
  try {
    // 取得副檔名
    const extension = originalFilename.split(".").pop() || "mp3";

    // 創建 FormData
    const form = new FormData();
    form.append("model", "whisper-1");
    form.append("language", "zh");

    // 直接將緩衝區添加到 FormData
    form.append("file", audioBuffer, {
      filename: `audio.${extension}`,
      contentType: mimeType,
    });

    // 使用 axios 發送請求到 OpenAI API
    const response = await axios.post(
      "https://api.openai.com/v1/audio/transcriptions",
      form,
      {
        headers: {
          ...form.getHeaders(),
          Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
        },
      }
    );

    return {
      success: true,
      text: response.data.text,
    };
  } catch (error) {
    return {
      success: false,
      error: error.response?.data?.error?.message || error.message,
    };
  }
}

// API 端點 - 音訊轉文字
app.post("/api/transcribe", upload.single("audio"), async (req, res) => {
  if (!req.file) {
    return res.status(400).json({ error: "請上傳音訊檔案" });
  }

  console.log(
    `收到音訊檔案: ${req.file.originalname}, 大小: ${req.file.size} bytes`
  );

  // 使用緩衝區而非檔案路徑
  const result = await transcribeAudio(
    req.file.buffer,
    req.file.mimetype,
    req.file.originalname
  );

  if (result.success) {
    res.json(result);
  } else {
    res.status(500).json(result);
  }
});

// 啟動伺服器
app.listen(PORT, () => {
  console.log(`伺服器運行在 http://localhost:${PORT}`);
});

募資文案優化重點

  1. 提升文案效率
  2. 法規跟敏感詞彙檢查

募資文案 API 流程圖

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

沒問題的範例

{
    "project_name": "智能環保水壺",
    "target_fund": "NT$500,000",
    "start_date": "2023-12-01",
    "end_date": "2024-01-31",
    "product_type": "智能硬體",
    "brand_background": "由一群環保工程師和設計師組成的團隊,致力於減少一次性塑料使用",
    "core_features": "溫度監控、水質檢測、飲水提醒、可替換濾芯、環保材質",
    "target_audience": "注重健康和環保的年輕專業人士和戶外愛好者",
    "tone_style": "專業但友善,強調環保和科技創新"
  }

會出事的範例 XD

{
  "project_name": "黃金投資致富計畫",
  "target_fund": "NT$2,000,000",
  "start_date": "2024-01-01",
  "end_date": "2024-02-28",
  "product_type": "貴金屬投資",
  "brand_background": "由一群具有10年以上金融市場經驗的專業投資顧問組成,我們的團隊成員曾在國際知名金融機構工作,擁有豐富的貴金屬交易經驗。",
  "core_features": "保證獲利的黃金投資方案、零風險投資組合、100%回報保證、獨家市場預測系統、政府立案的投資顧問團隊、絕對保密的VIP客戶服務",
  "target_audience": "想要快速致富的投資者、退休規劃人士、尋求穩定高回報的保守型投資者",
  "tone_style": "專業權威,強調獨特性和絕對優勢"
}
/**
 * 生成募資文案
 * @param {Object} projectData - 專案資料
 * @returns {Promise<string>} - 生成的文案
 */
async function generateFundraisingCopy(projectData) {
  try {
    console.log(`生成募資文案: ${projectData.project_name}`);

    // 構建結構化的提示
    const prompt = `
你是一位專業的文案撰寫專家,以下是專案資訊:
專案名稱:${projectData.project_name}
募資目標金額:${projectData.target_fund}
開始/結束日期:${projectData.start_date} ~ ${projectData.end_date}
產品類型:${projectData.product_type}
團隊背景:${projectData.brand_background}
核心特色:${projectData.core_features}
目標客群:${projectData.target_audience}
希望文案風格:${projectData.tone_style}

請根據以上資訊,創建一個完整的募資頁面文案,包含以下部分:
1. 引人注目的標題
2. 簡短有力的專案摘要
3. 專案背景與故事
4. 產品/服務特色與優勢[可以有多個區塊,不需要只有一個]
5. 團隊介紹
6. 資金用途說明
7. 回饋方案建議
8. 時程規劃
9. 結尾呼籲行動

使用繁體中文,並根據指定的文案風格撰寫。
`;

    const response = await openai.responses.create({
      model: "gpt-4o",
      instructions:
        "你是一位專業的募資文案撰寫專家,擅長撰寫吸引人的眾籌專案文案。",
      input: [{ role: "user", content: prompt }],
      temperature: 0.7,
    });

    return response.output_text;
  } catch (error) {
    console.error("生成募資文案時出錯:", error);
    throw error;
  }
}

/**
 * 檢測敏感詞
 * @param {string} content - 要檢測的文案內容
 * @returns {Promise<Object>} - 檢測結果
 */
async function detectSensitiveContent(content) {
  try {
    console.log("進行敏感詞檢測");

    const sensitiveWordsList = `毒品,海洛因,大麻,槍械,武器,彈藥,炸彈,爆炸物,賭博,賭場,偽造,人口販賣,
保證致富,永遠有效,無副作用,100%有效,立刻見效,史上最強,絕無僅有,獨一無二,前所未有,終極解決方案,
台獨,港獨,藏獨,納粹,三K黨,伊斯蘭國,ISIS,基地組織,恐怖分子,極端主義,聖戰,
黑鬼,支那,小日本,台巴子,基佬,人妖,娘炮,婊子,智障,殘廢,
色情,A片,淫穢,性交,口交,強姦,兒童色情,血腥,屠殺,虐殺,謀殺,
治癒癌症,包治百病,奇蹟療效,立即見效,無副作用,藥到病除,根治,永不復發,醫生推薦,權威認證,
快速致富,一夜致富,穩賺不賠,高收益,零風險,保證回報,利潤翻倍,一本萬利,傳銷,老鼠會`;

    // 定義函式
    const tools = [
      {
        type: "function",
        name: "sensitive_content_analysis",
        description: "分析文案中的敏感內容並提供結果",
        parameters: {
          type: "object",
          properties: {
            hasSensitiveContent: {
              type: "boolean",
              description: "是否發現敏感內容",
            },
            sensitiveWords: {
              type: "array",
              items: {
                type: "string",
              },
              description: "發現的敏感詞列表",
            },
            issues: {
              type: "array",
              items: {
                type: "object",
                properties: {
                  category: {
                    type: "string",
                    description: "問題類別,如政治敏感、歧視性語言等",
                  },
                  description: {
                    type: "string",
                    description: "問題說明",
                  },
                  suggestion: {
                    type: "string",
                    description: "修改建議",
                  },
                },
              },
              description: "敏感內容問題列表",
            },
            summary: {
              type: "string",
              description: "敏感內容分析總結",
            },
          },
          required: [
            "hasSensitiveContent",
            "sensitiveWords",
            "issues",
            "summary",
          ],
        },
      },
    ];

    const response = await openai.responses.create({
      model: "gpt-4o",
      instructions: `你是一位內容審核專家,負責檢測文案中可能存在的敏感詞或不適當內容。
請特別檢查以下敏感詞列表中的詞語是否出現在文案中:
${sensitiveWordsList}

此外,也請檢查以下類別的敏感內容:
1. 政治敏感詞
2. 歧視性語言
3. 違法或灰色地帶內容
4. 誇大不實的宣傳
5. 侵犯智慧財產權的內容
6. 違反平台規範的內容

請使用 sensitive_content_analysis 函式回傳分析結果,提供詳細的敏感詞檢測報告。`,
      input: [{ role: "user", content: content }],
      tools: tools,
      tool_choice: "auto",
      temperature: 0.3,
    });

    // 解析函式呼叫結果
    if (response.output && response.output.length > 0) {
      const functionCall = response.output.find(
        (item) =>
          item.type === "function_call" &&
          item.name === "sensitive_content_analysis"
      );

      if (functionCall) {
        const result = JSON.parse(functionCall.arguments);

        // 添加原始分析文本
        const analysisText = `敏感詞檢測結果:
是否發現敏感內容:${result.hasSensitiveContent ? "是" : "否"}
${
  result.hasSensitiveContent
    ? `發現的敏感詞:${result.sensitiveWords.join("、")}`
    : "未發現敏感詞"
}

問題詳情:
${result.issues
  .map(
    (issue) =>
      `- ${issue.category}:${issue.description}\n  建議:${issue.suggestion}`
  )
  .join("\n\n")}

總結:${result.summary}`;

        return {
          ...result,
          analysis: analysisText,
        };
      }
    }

    // 如果函式呼叫失敗,回傳預設結果
    return {
      hasSensitiveContent: false,
      sensitiveWords: [],
      issues: [],
      summary: "無法進行敏感詞分析",
      analysis: "無法進行敏感詞分析,請稍後再試。",
    };
  } catch (error) {
    console.error("檢測敏感詞時出錯:", error);
    throw error;
  }
}

/**
 * 進行法規檢測
 * @param {string} content - 要檢測的文案內容
 * @param {Object} projectData - 專案資料
 * @returns {Promise<Object>} - 檢測結果
 */
async function regulatoryCompliance(content, projectData) {
  try {
    console.log("進行法規檢測");

    const checkList = `一、交易/集資類(4 大常見違規重點)
1. 保證獲利、零風險、穩賺不賠(如「穩賺不賠」「保證本金」);可能違反《公平交易法》虛偽廣告、金管會廣告辦法、銀行法非法吸金等。
2. 誇大收益、高額回報(如「短期翻倍」「年化收益率XX%」);可能違反《公平交易法》虛偽不實、銀行法第29條之1非法吸收資金。
3. 冒用官方或第三方背書(如「政府立案」「金管會核准」「官方保證」);可能違反《公平交易法》誤導表示、相關刑法偽造文書規範。
4. 未揭露風險或限制(只談好處、不提風險,或警語極小字);可能違反《消費者保護法》重要資訊揭露、《公平交易法》誤導宣傳。

二、功能性產品(智慧裝置、AI 工具等)
1. 使用最高級、絕對化詞彙(如「全球唯一」「第一品牌」「無失誤」);恐違反《公平交易法》第21條誇大不實。
2. 暗示醫療功效或醫療級(如「治療失眠」「舒緩關節疼痛」「醫生背書」);恐違反《藥事法》《醫療器材管理法》等。
3. 虛假專利或官方認證(如「NASA技術」「軍規測試」「FDA核可」);恐違反《公平交易法》或《專利法》。

三、營養保健食品(膠原蛋白、益生菌、魚油等)
1. 宣稱醫療功效(如「治療糖尿病」「預防感冒」「降血壓、抗癌」);違反《食品安全衛生管理法》第28條(不得標榜療效)。
2. 誇大瘦身或速效(如「免運動就能瘦」「3天見效」「一週瘦10公斤」);違反食安法誇大宣傳,亦涉《公平交易法》虛偽不實。
3. 偽專業背書或不實認證(如「衛福部小綠人認證」「FDA核可」「名醫推薦」);違反食安法及《公平交易法》虛假廣告。

四、美容保健產品(瘦身、美白、抗老化等)
1. 不實速效、永久功效(如「瞬間美白」「一週逆齡」「永不復發」);違反《化粧品衛生安全管理法》第10條、亦涉《公平交易法》。
2. 醫藥療效宣稱(如「治療粉刺」「醫療級除皺」「根治痘痘」);超出化粧品範疇,違反化粧品法及《藥事法》。
3. 誇大成分作用(如「幹細胞再生技術」「醫美級玻尿酸」);無科學依據或專利說明則違反《化粧品衛生安全管理法》《公平交易法》。`;

    // 定義函式
    const tools = [
      {
        type: "function",
        name: "regulatory_compliance_analysis",
        description: "分析文案是否符合法規並提供結果",
        parameters: {
          type: "object",
          properties: {
            hasRegulatoryIssues: {
              type: "boolean",
              description: "是否發現法規問題",
            },
            violations: {
              type: "array",
              items: {
                type: "object",
                properties: {
                  category: {
                    type: "string",
                    description: "違規類別",
                  },
                  content: {
                    type: "string",
                    description: "違規內容",
                  },
                  law: {
                    type: "string",
                    description: "相關法規",
                  },
                  suggestion: {
                    type: "string",
                    description: "修正建議",
                  },
                },
              },
              description: "法規違規列表",
            },
            missingElements: {
              type: "array",
              items: {
                type: "string",
              },
              description: "缺少的必要元素(如風險揭露、退款政策等)",
            },
            summary: {
              type: "string",
              description: "法規檢測總結",
            },
          },
          required: [
            "hasRegulatoryIssues",
            "violations",
            "missingElements",
            "summary",
          ],
        },
      },
    ];

    const response = await openai.responses.create({
      model: "gpt-4o",
      instructions: `你是一位募資平台法規專家,負責檢查募資文案是否符合相關法規。
請特別檢查以下法規清單中提到的違規情況是否出現在文案中:
${checkList}

此外,也請檢查以下方面:
1. 消費者保護法相關規範
2. 廣告法規遵循
3. 募資平台特定規範
4. 產品安全與責任聲明
5. 智慧財產權聲明
6. 退款與交付政策

請使用 regulatory_compliance_analysis 函式回傳分析結果,提供詳細的法規檢測報告。`,
      input: [
        {
          role: "user",
          content: `專案資料: ${JSON.stringify(
            projectData
          )}\n\n文案內容: ${content}`,
        },
      ],
      tools: tools,
      tool_choice: "auto",
      temperature: 0.3,
    });

    // 解析函式呼叫結果
    if (response.output && response.output.length > 0) {
      const functionCall = response.output.find(
        (item) =>
          item.type === "function_call" &&
          item.name === "regulatory_compliance_analysis"
      );

      if (functionCall) {
        const result = JSON.parse(functionCall.arguments);

        // 添加原始分析文本
        const analysisText = `法規檢測結果:
是否發現法規問題:${result.hasRegulatoryIssues ? "是" : "否"}

${result.hasRegulatoryIssues ? "違規項目:" : ""}
${result.violations
  .map(
    (v) =>
      `- ${v.category}:${v.content}\n  相關法規:${v.law}\n  建議:${v.suggestion}`
  )
  .join("\n\n")}

${result.missingElements.length > 0 ? "缺少的必要元素:" : ""}
${result.missingElements.map((e) => `- ${e}`).join("\n")}

總結:${result.summary}`;

        return {
          ...result,
          analysis: analysisText,
        };
      }
    }

    // 如果函式呼叫失敗,回傳預設結果
    return {
      hasRegulatoryIssues: false,
      violations: [],
      missingElements: [],
      summary: "無法進行法規檢測",
      analysis: "無法進行法規檢測,請稍後再試。",
    };
  } catch (error) {
    console.error("進行法規檢測時出錯:", error);
    throw error;
  }
}

/**
 * 生成總結回饋
 * @param {Object} results - 各階段結果
 * @returns {Promise<string>} - 總結回饋
 */
async function generateFeedback(results) {
  try {
    console.log("生成總結回饋");

    const response = await openai.responses.create({
      model: "gpt-4o",
      instructions: `你是一位募資顧問專家,負責提供專業的募資文案回饋。
根據文案生成、敏感詞檢測和法規檢測的結果,提供全面的總結回饋。
包含以下方面:
1. 文案整體評價
2. 敏感內容處理建議
3. 法規合規性建議
4. 改進方向
5. 募資成功要點提示

使用繁體中文,提供專業、實用且具體的建議。`,
      input: [{ role: "user", content: JSON.stringify(results) }],
      temperature: 0.5,
    });

    return response.output_text;
  } catch (error) {
    console.error("生成總結回饋時出錯:", error);
    throw error;
  }
}

/**
 * 處理募資文案生成
 * @param {Object} projectData - 專案資料
 * @returns {Promise<Object>} - 生成的文案
 */
async function processFundraisingCopy(projectData) {
  try {
    console.log(`生成募資文案: "${projectData.project_name}"`);
    const copyContent = await generateFundraisingCopy(projectData);

    return {
      projectData,
      copyContent,
    };
  } catch (error) {
    console.error("處理募資文案生成時出錯:", error);
    throw error;
  }
}

/**
 * 處理文案檢測流程
 * @param {Object} data - 包含文案內容和專案資料
 * @returns {Promise<Object>} - 檢測結果
 */
async function processCopyCheck(data) {
  try {
    const { copyContent, projectData } = data;

    // 第一階段:敏感詞檢測
    console.log("第一階段:敏感詞檢測");
    const sensitiveCheck = await detectSensitiveContent(copyContent);

    // 第二階段:法規檢測
    console.log("第二階段:法規檢測");
    const regulatoryCheck = await regulatoryCompliance(
      copyContent,
      projectData
    );

    // 生成總結回饋
    const results = {
      projectData,
      copyContent,
      sensitiveCheck,
      regulatoryCheck,
    };

    const feedback = await generateFeedback(results);

    return {
      sensitiveCheck,
      regulatoryCheck,
      feedback,
    };
  } catch (error) {
    console.error("處理文案檢測時出錯:", error);
    throw error;
  }
}

// API 端點 - 處理募資文案生成
app.post("/api/fundraising/generate", async (req, res) => {
  try {
    const projectData = req.body;

    // 檢查必要欄位
    if (!projectData.project_name) {
      return res.status(400).json({ error: "請提供專案名稱" });
    }

    // 設定預設值
    const processedData = {
      project_name: projectData.project_name,
      target_fund: projectData.target_fund || "未指定",
      start_date: projectData.start_date || "未指定",
      end_date: projectData.end_date || "未指定",
      product_type: projectData.product_type || "未指定",
      brand_background: projectData.brand_background || "未指定",
      core_features: projectData.core_features || "未指定",
      target_audience: projectData.target_audience || "未指定",
      tone_style: projectData.tone_style || "專業友善",
    };

    console.log(`收到募資文案生成請求: "${processedData.project_name}"`);
    const result = await processFundraisingCopy(processedData);

    res.json(result);
  } catch (error) {
    console.error("處理募資文案生成時出錯:", error);
    res.status(500).json({ error: error.message || "處理請求時發生錯誤" });
  }
});

// API 端點 - 處理文案檢測
app.post("/api/fundraising/check", async (req, res) => {
  try {
    const { copyContent, projectData } = req.body;

    // 檢查必要欄位
    if (!copyContent) {
      return res.status(400).json({ error: "請提供文案內容" });
    }

    if (!projectData || !projectData.project_name) {
      return res.status(400).json({ error: "請提供完整的專案資料" });
    }

    console.log(`收到文案檢測請求: "${projectData.project_name}"`);
    const result = await processCopyCheck({ copyContent, projectData });

    res.json(result);
  } catch (error) {
    console.error("處理文案檢測時出錯:", error);
    res.status(500).json({ error: error.message || "處理請求時發生錯誤" });
  }
});