# 第三堂:前後端整合:募資文案智能寫作 ## Whisper 語音轉文字 ### NPM 介紹 * multer:處理上傳檔案 * FormData:包裝成 FormData 請求送出給 OpenAI * axios:發出網路請求 ### 儲存在記憶體跟實體空間的差異 | 項目 | 記憶體儲存 (MemoryStorage) | 實體硬碟儲存 (DiskStorage) | |------------------|--------------------------------------------------------------------|--------------------------------------------------------------------------| | **儲存位置** | 將檔案暫存在伺服器記憶體 (RAM) 中 | 將檔案寫入伺服器硬碟或指定的實體路徑 | | **使用情境** | 適合即時處理、小型檔案處理、多數情況不需長期保存 | 適合需要暫留或後續再處理的檔案、大量或較大檔案上傳 | | **效能** | I/O 速度快,無須讀寫硬碟,對小檔案較有效率 | 需要硬碟 I/O,速度可能稍慢,但對大檔案或長期保存更實用 | | **記憶體壓力** | 檔案全存於記憶體,若並發或檔案過大,可能導致 Out Of Memory | 不會耗用過多 RAM,但在硬碟空間不足時也可能引發其他問題 | | **保留時間** | 只要程式結束或記憶體被釋放,檔案即消失 | 儲存在硬碟上,可持續保存,除非自行刪除或重新部署 | | **部署/權限** | 部署較方便,不需考慮硬碟路徑、讀寫權限 | 需設定讀寫路徑與權限,若在 Serverless 或限制多的環境需特別注意 | | **後續操作** | 資料只在記憶體內,若需要多階段處理必須先轉存 | 可直接存檔後續再加工、分析,可多人或多服務存取同一檔案 | | **除錯與追蹤** | 處理完後即釋放,檔案不留存,事後較難復原檢查 | 上傳後會有實際檔案,可以做後續的除錯或檢視 | | **清理管理** | 不需額外清理檔案,使用後自動釋放 | 需要手動或透過機制定期刪除不再使用的檔案 | ![final-revised-audio-flowchart](https://hackmd.io/_uploads/Sk4eS7qCkx.svg) ### 範例程式碼 ```=javascript 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 流程圖 ![Untitled diagram-2025-04-14-045813](https://hackmd.io/_uploads/H1ObLM5CJe.png) ![截圖 2025-04-14 下午1.01.22](https://hackmd.io/_uploads/rkY3UGq01x.png) ### 沒問題的範例 ```=javascript { "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 ```=JavaScript { "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": "專業權威,強調獨特性和絕對優勢" } ``` ```=javascript /** * 生成募資文案 * @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 || "處理請求時發生錯誤" }); } }); ```