專案變更總覽(你要做什麼)
✅ 新增檔案
src/aiClient.js:集中管理 OpenAI Client(之後各天都共用)。
src/promptPresets.js:常用提示詞模板(語氣、長度、格式控制)。
src/day2_text_generation.js:Day 2 主程式,參數化文字生成。
index.js:指令列入口,可選要跑哪個 day(今天預設跑 Day 2)。
♻️ 修改檔案
package.json:加入方便的 npm run day2 指令。
🗑️ 需要刪除 / 改名(擇一)
你昨天若只留有 index.js(Day 1 測試檔):
建議改名成 src/day1_hello.js 保留紀錄;
或者你不想留:就刪除舊的 index.js(因為今天會放新的 index.js)。
若選擇保留 Day1:把昨天 index.js 檔名改為 src/day1_hello.js 即可,內容不用改。
集中管理 OpenAI Client,之後每天都能重用。
// src/aiClient.js
import OpenAI from "openai";
import dotenv from "dotenv";
dotenv.config();
if (!process.env.OPENAI_API_KEY) {
throw new Error("OPENAI_API_KEY 未設定,請在 .env 加上你的金鑰。");
}
export const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
整理可重用的 Prompt 模板與控制規則。
// src/promptPresets.js
// 長度控制說明
const LENGTH_HINT = {
short: "請控制在 80~120 字之內。",
medium: "請控制在 200~300 字之內。",
long: "請控制在 500~800 字之內,分段清楚。",
};
// 語氣控制說明
const TONE_HINT = {
friendly: "語氣親切、鼓勵式、貼近讀者。",
professional: "語氣專業、精煉,避免口語化。",
persuasive: "語氣具說服力,強調價值與行動。",
};
// 輸出格式控制
const FORMAT_HINT = {
plain: "以一般段落輸出,不需要條列。",
bullets: "請用條列式(- 開頭)輸出重點,最多 5 點。",
markdown: "請使用 Markdown 標題與小節清楚呈現。",
};
// 可選的主題預設
export const PRESETS = {
blog: ({ topic, tone, length, format }) => `
你是一位資深技術部落客,請針對「${topic}」寫一段短文。
${TONE_HINT[tone] ?? ""}
${LENGTH_HINT[length] ?? ""}
${FORMAT_HINT[format] ?? ""}
請避免虛構數據,聚焦實務價值。`,
diary: ({ topic, tone, length, format }) => `
你是一位工程師,正在撰寫今日學習日誌,主題:「${topic}」。
${TONE_HINT[tone] ?? ""}
${LENGTH_HINT[length] ?? ""}
${FORMAT_HINT[format] ?? ""}
請包含:今天做了什麼、遇到的問題、下一步計畫。`,
product: ({ topic, tone, length, format }) => `
你是產品行銷,請為「${topic}」撰寫產品簡介。
${TONE_HINT[tone] ?? ""}
${LENGTH_HINT[length] ?? ""}
${FORMAT_HINT[format] ?? ""}
請包含:目標用戶、核心賣點、使用情境、CTA。`,
};
export const defaultParams = {
tone: "friendly", // friendly | professional | persuasive
length: "medium", // short | medium | long
format: "markdown", // plain | bullets | markdown
preset: "blog", // blog | diary | product
};
Day 2 主程式:把參數轉成可控輸出。
// src/day2_text_generation.js
import { openai } from "./aiClient.js";
import { PRESETS, defaultParams } from "./promptPresets.js";
/**
* 產生文字
* @param {Object} opts
* @param {string} opts.topic - 文章主題
* @param {string} [opts.preset] - 使用哪個模板(blog/diary/product)
* @param {string} [opts.tone] - 語氣(friendly/professional/persuasive)
* @param {string} [opts.length] - 長度(short/medium/long)
* @param {string} [opts.format] - 輸出格式(plain/bullets/markdown)
* @param {number} [opts.temperature] - 0~2,越高越有創意
* @param {number} [opts.max_tokens] - 生成的最大 token 數
*/
export async function generateText(opts = {}) {
const {
topic = "今天的學習",
preset = defaultParams.preset,
tone = defaultParams.tone,
length = defaultParams.length,
format = defaultParams.format,
temperature = 0.7,
max_tokens = 600,
} = opts;
const builder = PRESETS[preset];
if (!builder) throw new Error(`未知的 preset:${preset}`);
const userPrompt = builder({ topic, tone, length, format });
const response = await openai.chat.completions.create({
model: "gpt-4o-mini",
temperature,
max_tokens,
messages: [
{
role: "system",
content:
"你是嚴謹可靠的中文寫作助手,輸出需具可讀性與實用性,避免編造事實。",
},
{ role: "user", content: userPrompt },
],
});
return response.choices?.[0]?.message?.content?.trim() ?? "";
}
提供 CLI 參數(之後也能當作 Node 腳本被 Next.js API 呼叫)。
// index.js
import { generateText } from "./src/day2_text_generation.js";
// 解析簡易 CLI 參數
// 範例:node index.js --topic "RAG 入門" --preset blog --tone professional --length short --format markdown --temp 0.6
const args = Object.fromEntries(
process.argv.slice(2).reduce((acc, cur, i, arr) => {
if (cur.startsWith("--")) {
const key = cur.replace(/^--/, "");
const val = arr[i + 1] && !arr[i + 1].startsWith("--") ? arr[i + 1] : true;
acc.push([key, val]);
}
return acc;
}, [])
);
async function main() {
const content = await generateText({
topic: args.topic || "生成式 AI 實戰挑戰 Day 2",
preset: args.preset || "blog",
tone: args.tone || "friendly",
length: args.length || "medium",
format: args.format || "markdown",
temperature: args.temp ? Number(args.temp) : 0.7,
max_tokens: args.max_tokens ? Number(args.max_tokens) : 600,
});
console.log("\n=== 生成內容 ===\n");
console.log(content);
}
main().catch((e) => {
console.error("發生錯誤:", e.message);
process.exit(1);
});
加入快捷指令(保留你原本的內容,只需新增 scripts)。
{
"scripts": {
"day2": "node index.js --topic \"RAG 入門\" --preset blog --tone professional --length short --format markdown --temp 0.6"
}
}
你也可以隨時換參數,例如:
npm run day2 -- --topic "用 LLM 做學習日誌" --preset diary --tone friendly --length medium --format bullets --temp 0.7
如何執行(測試)
# 推薦先把 Day1 改名保存(可選)
# mv index.js src/day1_hello.js
node index.js --topic "用 LLM 生產部落格文章的注意事項" --preset blog --tone professional --length medium --format markdown --temp 0.5
你會得到一段 可控長度、語氣、格式 的 Markdown 文章。
(temp 越低越穩定,越高越有創意;max_tokens 可避免生成過多。)