AI Blog 改用 DeepSeek 官方並通用化提示詞

buildSystemPrompt 不再綁死太陽能行業,角色與公司資料改由
後台 ai_context_prompt / ai_business_context 提供;預設供應商
改為 https://api.deepseek.com 與 deepseek-flash。callChat 只在
baseUrl 含 deepseek.com 時加 thinking: { type: "disabled" },
並將 max_tokens 提升至 4000,避免思考模式令 temperature 失效
同正文被截斷。

同步更新 schemas、seed、/admin/ai 預設值同 placeholder,
以及 AGENTS.md、spec、plan 文件。
This commit is contained in:
2026-09-12 00:31:26 +08:00
parent fdee1aabd7
commit 81fc515d6e
11 changed files with 321 additions and 19 deletions
+3 -1
View File
@@ -19,8 +19,10 @@
- 密碼同 session 比對用 `safeEqual`constant-time),改動要保留防 timing attack 嘅做法。
- **`form.ts`**`parseForm(formData, schema)``FormData` 抽成物件交畀 Zod 驗證;回 `{ ok: true, data }``{ ok: false, errors }`field → 訊息)。admin 表單一律經呢度,schema 喺 `src/schemas/`
- **`ai.ts`**AI 生成 Blog 流程。
- `getAiSettings(db)``site_settings` 砌出供應商設定(enabled / baseUrl / model / context / web search)。
- `getAiSettings(db)``site_settings` 砌出供應商設定(enabled / baseUrl / model / context / web search)。**預設供應商係 DeepSeek 官方**`https://api.deepseek.com`、模型 `deepseek-flash`)。
- `buildSystemPrompt` **通用、唔綁行業**:角色/語氣來自 `ai_context_prompt`,公司事實來自 `ai_business_context`;硬編碼只剩通用 SEO/GEO 規則同 `TITLE` / `CONTENT` / `META_DESC` 輸出格式。新增行業內容一律經後台設定,唔好再寫死公司名。
- Tavily 上網研究(失敗回空字串,唔中斷)+ OpenAI 相容 `chat/completions`(有 timeout)。
- `callChat` 固定 `max_tokens: 4000`**只在 baseUrl 含 `deepseek.com` 時**加 `thinking: { type: "disabled" }`(DeepSeek 思考模式預設開啟,會令 temperature 失效同佔用 token),其他供應商唔會收到非標準參數。
- `parseGeneratedPost(text)` 解析 `TITLE` / `CONTENT` / `META_DESC` 並用 `aiGeneratedPost` schema 驗證。
- `generateBlogPost(db, env, input)` 同步生成:`{ mode: "topic" }``{ mode: "next" }`(攞佇列下一個 pending);一律存 **draft** 並將關鍵字標記 `generated`。缺 `AI_API_KEY` / 未啟用時回 `{ ok: false, error }`,唔會 throw。
- **`markdown.ts`**`renderMarkdown``marked`,內容受信任所以**唔 sanitize**)、`slugify`(保留中文)、`autoExcerpt`
+6 -5
View File
@@ -20,8 +20,8 @@ export async function getAiSettings(db: Db): Promise<AiSettings> {
const s = await getSettings(db);
return {
enabled: s.ai_enabled !== "0",
baseUrl: s.ai_base_url || "https://api.deepinfra.com/v1/openai",
model: s.ai_chat_model || "deepseek-ai/DeepSeek-V3-0324",
baseUrl: s.ai_base_url || "https://api.deepseek.com",
model: s.ai_chat_model || "deepseek-flash",
contextPrompt: s.ai_context_prompt ?? "",
businessContext: s.ai_business_context ?? "",
webSearchEnabled: s.ai_web_search_enabled === "1",
@@ -61,7 +61,7 @@ function buildSystemPrompt(opts: {
if (opts.contextPrompt) parts.push(opts.contextPrompt);
if (opts.businessContext) parts.push(`## 公司背景資料\n${opts.businessContext}`);
if (opts.research) parts.push(`## 網上研究資料(只作參考,唔好照抄)\n${opts.research}`);
parts.push(`你係香港村屋太陽能公司「盈豐太陽能」嘅 SEO 內容寫手。請就以下主題寫一篇 800–1200 字嘅繁體中文(香港廣東話書面語)博客文章。
parts.push(`你係一位專業嘅 SEO 內容寫手。請根據上面嘅公司背景同寫作風格,就以下主題寫一篇 800–1200 字嘅繁體中文(香港廣東話書面語)博客文章。
主題:${opts.topic}
@@ -69,7 +69,7 @@ function buildSystemPrompt(opts: {
- 標題要放焦點關鍵字,首 100 字內再出現一次,關鍵字密度約 1–2%。
- 用 ## / ### 分段,段落清晰易讀。
- 開頭先畀一段總結(summary-first),再展開。
- 內容要對香港村屋太陽能實用、準確,唔好作出未經證實嘅承諾或數字。
- 內容要實用、準確,符合公司業務;唔好作出未經證實嘅承諾或數字。
- 適合 SEO 同 AI 搜尋(GEO):用問答式小標題、必要時用列表。
輸出格式(必須嚴格跟隨,唔要加額外說明):
@@ -87,8 +87,9 @@ async function callChat(settings: AiSettings, apiKey: string, systemPrompt: stri
body: JSON.stringify({
model: settings.model,
messages: [{ role: "user", content: systemPrompt }],
max_tokens: 2200,
max_tokens: 4000,
temperature: 0.7,
...(settings.baseUrl.includes("deepseek.com") ? { thinking: { type: "disabled" } } : {}),
}),
signal: AbortSignal.timeout(90_000),
});