Skip to content
Merged

Dev #103

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 25 additions & 141 deletions docs/AI_GENERATION_GUIDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AI Generation & Conversion Guide (AI 輔助生成與轉換指南)

本文件旨在提供給大型語言模型 (LLM) 閱讀,以便精確地將現有的內容轉換為 **MD2DOC-Evolution** 專屬格式。
本文件旨在提供給大型語言模型 (LLM) 閱讀,以便精確地將現有的內容轉換為 **MD2DOC-Evolution** 專屬格式,並符合專業出版社的寫作規範

---

Expand All @@ -23,33 +23,43 @@
- **禁止使用 H4, H5, H6**:本專案僅支援 `#`, `##`, `###`。若原始稿件有更深層級,請將其轉換為 `**粗體項目**`。
- **目錄標籤**:在 Frontmatter 結束後的下一行,必須插入 `[TOC]`。

### 3. 程式碼區塊 (Code Blocks)
### 3. 出版級文字規範 (Publishing Standards)
- **中英文空格**:中文與英文、數字之間 **不需要空格**。 (例:`使用 VS Code` 而非 `使用 VS Code`)
- **標點符號**:中文句子中夾雜英文時,必須使用 **中文標點符號**。 (例:`開設 FB、IG 帳號。` 而非 `開設 FB, IG 帳號。`)
- **UI 強調**:介紹軟體介面操作時,使用 `「」` 符號加以強調。 (例:完成後按 「Test Connection」)

### 4. 圖片與圖號 (Images & Figures)
- **自動編號**:系統會自動根據出現順序編號為「圖 X」。
- **圖名語法**:使用 `![圖名](url)`。
- **全頁圖片**:若圖片需放整頁 (13x18cm),請在 Alt 文字中加入 `full-page` 標記。 (例:`![這是全頁圖 full-page](url)`)
- **截圖規範**:截圖請務必使用 **淺色亮底** 主題,並在關鍵步驟加上醒目框線。
- **寬度限制**:系統會自動將圖片限制在 **13 cm** 寬度內。

### 5. 程式碼區塊 (Code Blocks)
- **語法**:```語言[:ln|:no-ln]
- **細節**:
- 預設會顯示行號。
- 若為短小的設定檔,請強制標註 `:no-ln`。
- **範例**:```json:no-ln

### 4. 提示區塊 (Callouts)
### 6. 提示區塊 (Callouts)
- **格式**:必須使用 `> [!標記]`。
- **類型限制**:僅支援 `TIP`, `NOTE`, `WARNING`。
- **轉換邏輯**:
- 「注意」、「補充」 -> `> [!NOTE]`
- 「技巧」、「建議」 -> `> [!TIP]`
- 「警告」、「重要」 -> `> [!WARNING]`

### 5. 角色對話 (Chat Dialogues) - **極重要**
這是 AI 最容易出錯的地方,請嚴格執行:
- **左側 (AI/講者)**:`角色名稱 ":: 對話內容` (注意引號位置)
- **右側 (User/讀者)**:`對話內容 ::" 角色`
- **範例**:
- `GPT ":: 您好!有什麼我能幫您的?`
- `請幫我寫一段程式碼 ::" 使用者`
### 7. 角色對話 (Chat Dialogues)
- **左側 (AI/他人)**:`角色名稱 "::` (引號在冒號前)
- **右側 (User/作者)**:`角色名稱 ::"` (引號在冒號後)
- **置中 (System/旁白)**:`角色名稱 :":` (引號在中間)

### 6. 行內樣式轉換表
### 8. 行內樣式轉換表
| 原始內容 | 轉換後格式 | 說明 |
| :--- | :--- | :--- |
| 「點擊設定」 | `【設定】` | 所有 UI 按鈕、選單項目 |
| 「點擊設定」 | `「設定」` | UI 元素強調 (建議優先使用) |
| 【點擊設定】 | `【設定】` | UI 按鈕、選單項目 (帶底色) |
| Ctrl+C | `[Ctrl]`+`[C]` | 所有實體按鍵 |
| 《深入淺出》 | `『深入淺出』` | 所有書名、軟體專案名 |
| [連結](url) | `[連結](url)` | 保持原樣,系統會自動轉 QR Code |
Expand All @@ -59,132 +69,6 @@
## 負面約束 (Negative Constraints)
- **不要** 使用 HTML 標籤(如 `<u>`, `<br>`)。
- **不要** 在 Callout 內嵌套另一個 Callout。
- **不要** 自行發明 Callout 標籤(如 `[!DANGER]` 是不支援的)。
- **不要** 改變 Mermaid 的標準語法。

---
# AI Generation Guide for MD2DOC-Evolution

本指南定義了將 Markdown 轉換為 MD2DOC-Evolution 格式的標準規範。AI 模型應嚴格遵守以下語法規則。

## 1. Frontmatter (YAML)
文件必須以標準 YAML 格式開頭。

### ✅ 正確範例
```markdown
---
title: 第2章:工具箱——打造你的數位軍火庫
author: ChiYu
---

[TOC]

# 第2章:工具箱——打造你的數位軍火庫

```

### ❌ 錯誤範例 (禁止)

* ❌ 缺少結尾的 `---`
* ❌ 在 YAML 中使用 `##` 標題
* ❌ 將 `[TOC]` 放進 YAML 區塊中
* 如果有需要目錄才添加[TOC]

---

## 2. 列表 (Lists)

子列表必須使用 **2個空白 (Spaces)** 進行縮排,以確保層級正確。

### ✅ 正確範例

```markdown
* **『Python (Microsoft)』**
* 這是什麼:微軟官方出品的 Python 語言支援包。
* 為什麼必裝:裝上它,你的『VS Code』才會真正「看懂」Python。
* **『Prettier』**
* 這是什麼:你的程式碼專屬造型師。

```

### ❌ 錯誤範例 (扁平化)

```markdown
* **『Python (Microsoft)』**
* 這是什麼:微軟官方出品的 Python 語言支援包。 (❌ 錯誤:沒有縮排,被視為同一層級)

```

---

## 3. 對話 (Chat)

使用特定的前綴語法來表示對話氣泡。

### 語法

`角色名稱 ::"` 換行後接續對話內容。

### ✅ 正確範例

```markdown
讀者 ::"
ChiYu,既然我們已經決定用 Python 來開發 RPG 遊戲的後端了,為什麼現在又要裝一個叫 Node.js 的東西?

ChiYu ::"
這是一個很棒的問題!我們要區分清楚「產品」與「工具」的差別。

```

### ❌ 錯誤範例

* ❌ `ChiYu "::` (錯誤的引號位置)
* ❌ `::" ChiYu` (不需要結尾標籤)

---

## 4. 行內樣式 (Inline Styles)

請根據語意選擇正確的括號樣式。

| 類型 | 語法 | 範例 |
| --- | --- | --- |
| **UI 元素 / 按鈕** | `【】` | 請點擊【確定】按鈕、查看【檔案總管】。 |
| **快捷鍵** | `[]` | 按下 [Ctrl] + [C] 複製。 |
| **書名 / 專案名 / 強調物件** | `『』` | 本書使用『VS Code』進行開發。 |
| **一般強調** | `**` | 這是 **非常重要** 的觀念。 |

---

## 5. 提示區塊 (Callouts)

將筆記或警告轉換為 GitHub 風味的 Blockquotes。

### ✅ 正確範例

```markdown
> [!NOTE]
> 这是一个補充說明。

> [!WARNING]
> 【Windows 使用者請注意】:請務必勾選 Add to PATH。

```

---

## 6. 標題與結構

* 僅使用 H1 (#), H2 (##), H3 (###)。
* H4 以下請轉換為粗體文字或列表項目。
* 確保程式碼區塊包含語言標籤 (例如 ````python`)。

```

### 修改重點說明:

1. **YAML 修正**:在 Prompt 的 `Rule #1` 中,我特別強調了「頭尾都要有 `---`」以及「嚴禁使用 `#` 符號」,這能直接解決 AI 生成 `## title:` 這種錯誤格式的問題。
2. **列表縮排**:在 Prompt 的 `Rule #4` 和 Guide 的 `Section 2` 中,我明確要求了「2個空白縮排」,並給出了「扁平化錯誤」的負面範例,這對 AI 理解結構非常有幫助。
3. **Chat 語法簡化**:根據您的指示,將語法統一為 `角色 ::"`。我在 Prompt 中加入了一條「禁止使用結尾標籤」的規則,以防止 AI 因為過度熱心而自行閉合標籤。

```
- **不要** 自行發明 Callout 標籤。
- **不要** 忽略關鍵步驟,避免用「這個大家應該都知道」為前提。
- **不要** 忘記專有名詞第一次出現時要簡短解釋。
6 changes: 6 additions & 0 deletions services/docx/builders/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ export const parseInlineStyles = async (text: string, config?: DocxConfig): Prom
shading: { fill: COLORS.BG_BUTTON, type: ShadingType.CLEAR, color: "auto" }
}));
break;
case InlineStyleType.UI_EMPHASIS:
runs.push(new TextRun({
...baseConfig,
bold: true
}));
break;
case InlineStyleType.SHORTCUT:
runs.push(new TextRun({
...baseConfig,
Expand Down
67 changes: 45 additions & 22 deletions services/docx/builders/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Licensed under the MIT License.
*/

import { Paragraph, ImageRun, AlignmentType } from "docx";
import { Paragraph, ImageRun, AlignmentType, TextRun } from "docx";
import { DocxConfig } from "../types";
import { WORD_THEME } from "../../../constants/theme";

Expand Down Expand Up @@ -60,13 +60,39 @@ export const createImageBlock = async (src: string, alt: string, config: DocxCon
const dims = await getImageDimensions(realSrc);
if (dims.width === 0) return [];

// Force image to fill the maximum page width
const maxWidthPx = (config.widthCm - 4) * 37.8; // Use slightly larger width (less margin)
// Increment Figure Counter
if (config.counters) {
config.counters.figure++;
}
const figNum = config.counters ? config.counters.figure : 0;

// Publisher Requirement 09 & 10
// Max width is 13cm. Full page is 13x18cm.
const isFullPage = alt.includes('full-page');
const cleanAlt = alt.replace('full-page', '').trim();

// Calculate target height to maintain aspect ratio
const ratio = maxWidthPx / dims.width;
const targetWidth = maxWidthPx;
const targetHeight = dims.height * ratio;
const MAX_WIDTH_CM = 13;
const MAX_HEIGHT_CM = isFullPage ? 18 : 20; // Limit height too to avoid breaking layout

const maxWidthPx = MAX_WIDTH_CM * 37.8; // 1cm approx 37.8px (96dpi)
const maxHeightPx = MAX_HEIGHT_CM * 37.8;

let targetWidth = dims.width;
let targetHeight = dims.height;

// Scale to fit max width
if (targetWidth > maxWidthPx) {
const ratio = maxWidthPx / targetWidth;
targetWidth = maxWidthPx;
targetHeight = targetHeight * ratio;
}

// Scale to fit max height if still too large
if (targetHeight > maxHeightPx) {
const ratio = maxHeightPx / targetHeight;
targetHeight = maxHeightPx;
targetWidth = targetWidth * ratio;
}

// 4. Create Paragraphs
const imagePara = new Paragraph({
Expand All @@ -86,21 +112,18 @@ export const createImageBlock = async (src: string, alt: string, config: DocxCon

const result = [imagePara];

// 4. Add Caption if Alt text exists
if (alt) {
result.push(new Paragraph({
alignment: AlignmentType.CENTER,
children: [
new TextRun({
text: ` ▲ ${alt}`,
italics: true,
size: 18, // 9pt
color: "666666"
})
],
spacing: { before: 0, after: 200 },
}));
}
// 4. Add Caption with Figure Number (Requirement 05)
result.push(new Paragraph({
alignment: AlignmentType.CENTER,
children: [
new TextRun({
text: `圖 ${figNum} ${cleanAlt}`,
bold: true,
size: 20, // 10pt
})
],
spacing: { before: 0, after: 200 },
}));

return result;
} catch (e) {
Expand Down
4 changes: 4 additions & 0 deletions services/docx/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ export interface DocxConfig {
showLineNumbers?: boolean;
meta?: DocumentMeta;
imageRegistry?: Record<string, string>;
counters?: {
figure: number;
qr: number;
};
}
6 changes: 6 additions & 0 deletions services/docxGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ export const generateDocx = async (
config: DocxConfig = { widthCm: 17, heightCm: 23 }
): Promise<Blob> => {

// Initialize counters for automatic numbering (Figures, QRs)
config.counters = {
figure: 0,
qr: 0
};

const docChildren: (Paragraph | Table)[] = [];

for (const block of blocks) {
Expand Down
Loading