標籤彙整: deep research

Gemini的Deep Research產出研究報告中的LaTex語法轉換

如果Gemini的Deep Research產出研究報告裡面有數學公式或上下標,研究報告匯出成Google文件時,就會有很多$….$的符號,無法轉換成正確格式。
找Gemini解決這個問題非常不成功,就換成ChatGPT,大致上解決了。
解決的方法是土法煉鋼,將文章中LaTex的語法一一對照取代,用窮舉式的寫法,一個代碼一個代碼設定在程式碼裡面。未來若遇到程式不認得的代碼,就再加進去。
當初有個瓶頸是替換LaTex代碼時,文章格式會跑掉,但AI最後還是想辦法解決了。
程式碼寫在Google文件的Apps Script裡面,跟著一份Google文件走。所以複製這份Google文件就可以有這個功能,不用輸入程式碼。
這是可以複製去用的文件範本,點選連結後,網頁畫面就會直接要求你建立一個副本,文件中有使用說明。

結尾有我和AI的對話,其中有如何將APP Script加入Google文件的流程。未來如果有LaTex代碼沒解析到的,可以把程式碼貼給AI叫他加,或是自己手動加在程式碼裡面。
程式碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
function onOpen() {
  DocumentApp.getUi()
    .createMenu('LaTeX 轉換')
    .addItem('建立轉換後的新文件(保留格式)', 'createLatexConvertedCopy')
    .addToUi();
}
 
// 1. 複製整份文件 → 2. 在副本中逐段處理 $...$ 與 frac{...}{...}
function createLatexConvertedCopy() {
  var srcDoc = DocumentApp.getActiveDocument();
  var srcId  = srcDoc.getId();
 
  // 1. 用 Drive 複製整份文件(格式、圖片、表格都保留)
  var file   = DriveApp.getFileById(srcId);
  var copy   = file.makeCopy(srcDoc.getName() + '(LaTeX 轉換版)');
  var newDoc = DocumentApp.openById(copy.getId());
  var body   = newDoc.getBody();
 
  // 2. 在副本裡處理所有段落 / 清單 / 表格中的文字
  processElement_(body);
 
  newDoc.saveAndClose();
 
  DocumentApp.getUi().alert(
    '已建立 LaTeX 轉換版文件(格式及圖片保留):\n' +
    newDoc.getUrl()
  );
}
 
/**
 * 遞迴走訪整個文件樹:
 *   - Paragraph / ListItem → 以「整行文字」為單位處理
 *   - Table / Cell → 繼續往下走
 */
function processElement_(elem) {
  var ElementType = DocumentApp.ElementType;
  var type = elem.getType && elem.getType();
 
  if (type === ElementType.PARAGRAPH || type === ElementType.LIST_ITEM) {
    var text = elem.editAsText();
    if (text) {
      convertParagraphText_(text);
    }
  }
 
  if (elem.getNumChildren) {
    var n = elem.getNumChildren();
    for (var i = 0; i < n; i++) {
      processElement_(elem.getChild(i));
    }
  }
}
 
/**
 * 對單一段落 (editAsText):
 *   1. 先處理整段裡所有 $...$
 *   2. 再處理「裸的」 frac{a}{b}
 */
function convertParagraphText_(text) {
  var full = text.getText();
  if (full.indexOf('$') === -1 && full.indexOf('frac{') === -1) return;
 
  // --- 找出所有 $...$ 的位置(用原始 full 索引)---
  var dollarMatches = [];
  var re = /\$([^$]+)\$/g;
  var m;
  while ((m = re.exec(full)) !== null) {
    dollarMatches.push({
      start: m.index,
      end: re.lastIndex - 1,
      inner: m[1]
    });
  }
 
  // 從後往前改,避免索引被前面的修改影響
  for (var i = dollarMatches.length - 1; i >= 0; i--) {
    var dm = dollarMatches[i];
    var converted = latexToUnicode_(dm.inner);
    text.deleteText(dm.start, dm.end);
    if (converted.length > 0) {
      text.insertText(dm.start, converted);
    }
  }
 
  // --- 再處理沒有 $ 包起來的 frac{a}{b} ---
  full = text.getText();
  var fracRe = /frac\{([^}]+)\}\{([^}]+)\}/g;
  var fracMatches = [];
  while ((m = fracRe.exec(full)) !== null) {
    fracMatches.push({
      start: m.index,
      end: fracRe.lastIndex - 1,
      num: m[1],
      den: m[2]
    });
  }
 
  for (var j = fracMatches.length - 1; j >= 0; j--) {
    var fm = fracMatches[j];
    var rep = '(' + fm.num + '/' + fm.den + ')';
    text.deleteText(fm.start, fm.end);
    text.insertText(fm.start, rep);
  }
}
 
/**
 * 處理一小段 LaTeX(不含外層 $)
 * 例如:"V_E", "R_{\\text{ref}}", "\\mu = 3.986 \\times 10^{14}"
 */
function latexToUnicode_(latex) {
  var t = latex;
 
  // 1. \text{ km } → km
  t = t.replace(/\\text\{([^}]*)\}/g, '$1');
 
  // 2. \frac{a}{b} → (a/b)
  t = t.replace(/\\frac\{([^}]+)\}\{([^}]+)\}/g, function(match, num, den) {
    return '(' + num + '/' + den + ')';
  });
 
  // 3. LaTeX 指令 → 對應符號(μ, ρ, ×, ≈, ∞...)
  var map = getSymbolMap_();
  t = t.replace(/\\[a-zA-Z]+/g, function(cmd) {
    return map[cmd] || cmd.slice(1); // 未定義 → 去掉反斜線
  });
 
  // 4. _{...} / _x / ^{...} / ^x → Unicode 上下標
  return applySubSupUnicode_(t);
}
 
/**
 * 把 _、^ 語法變成 Unicode 上/下標字元
 */
function applySubSupUnicode_(str) {
  var result = '';
  var i = 0;
 
  while (i < str.length) {
    var ch = str.charAt(i);
 
    if ((ch === '_' || ch === '^') && i + 1 < str.length) {
      var isSub = (ch === '_');
      var next = str.charAt(i + 1);
      var content = '';
      var j;
 
      if (next === '{') {
        j = i + 2;
        while (j < str.length && str.charAt(j) !== '}') {
          content += str.charAt(j);
          j++;
        }
        if (j < str.length && str.charAt(j) === '}') j++;
      } else {
        content = next;
        j = i + 2;
      }
 
      result += mapToSubSup_(content, isSub);
      i = j;
    } else {
      result += ch;
      i++;
    }
  }
 
  return result;
}
 
/**
 * 把 content 裡每個字元映射成上標/下標
 * 支援 a–z / A–Z / 0–9
 */
function mapToSubSup_(content, isSub) {
  // 下標對照表
  var subMap = {
    '0':'₀','1':'₁','2':'₂','3':'₃','4':'₄','5':'₅','6':'₆','7':'₇','8':'₈','9':'₉',
    'a':'ₐ','b':'ᵦ','c':'꜀','d':'꜁','e':'ₑ','f':'ᵮ','g':'ᵧ','h':'ₕ',
    'i':'ᵢ','j':'ⱼ','k':'ₖ','l':'ₗ','m':'ₘ','n':'ₙ','o':'ₒ','p':'ₚ',
    'q':'ᑫ','r':'ᵣ','s':'ₛ','t':'ₜ','u':'ᵤ','v':'ᵥ','w':'𝓌','x':'ₓ',
    'y':'ᵧ','z':'𝓏',
    // 大寫下標(無正式 Unicode,下列用近似字元)
    'A':'ₐ','B':'ᵦ','C':'꜀','D':'ᵭ','E':'ₑ','F':'𝆑','G':'ᵍ',
    'H':'ₕ','I':'ᵢ','J':'ⱼ','K':'ₖ','L':'ₗ','M':'ₘ','N':'ₙ','O':'ₒ',
    'P':'ₚ','Q':'ᑫ','R':'ᵣ','S':'ₛ','T':'ₜ','U':'ᵤ','V':'ᵥ',
    'W':'𝓌','X':'ₓ','Y':'ᵧ','Z':'𝓏'
  };
 
  // 上標對照表
  var supMap = {
    '0':'⁰','1':'¹','2':'²','3':'³','4':'⁴','5':'⁵','6':'⁶','7':'⁷','8':'⁸','9':'⁹',
    'a':'ᵃ','b':'ᵇ','c':'ᶜ','d':'ᵈ','e':'ᵉ','f':'ᶠ','g':'ᵍ','h':'ʰ',
    'i':'ⁱ','j':'ʲ','k':'ᵏ','l':'ˡ','m':'ᵐ','n':'ⁿ','o':'ᵒ','p':'ᵖ',
    'q':'ᑫ','r':'ʳ','s':'ˢ','t':'ᵗ','u':'ᵘ','v':'ᵛ','w':'ʷ','x':'ˣ',
    'y':'ʸ','z':'ᶻ',
    // 大寫上標
    'A':'ᴬ','B':'ᴮ','C':'ᶜ','D':'ᴰ','E':'ᴱ','F':'ᶠ','G':'ᴳ',
    'H':'ᴴ','I':'ᴵ','J':'ᴶ','K':'ᴷ','L':'ᴸ','M':'ᴹ','N':'ᴺ',
    'O':'ᴼ','P':'ᴾ','Q':'ᑫ','R':'ᴿ','S':'ˢ','T':'ᵀ','U':'ᵁ',
    'V':'ⱽ','W':'ᵂ','X':'ˣ','Y':'ʸ','Z':'ᶻ'
  };
 
  var table = isSub ? subMap : supMap;
  var out = '';
 
  for (var i = 0; i < content.length; i++) {
    var c = content.charAt(i);
    out += (table[c] || c);
  }
  return out;
}
 
/**
 * LaTeX → Unicode 符號表(可再擴充)
 */
function getSymbolMap_() {
  return {
    // 希臘字母
    '\\mu': 'μ',
    '\\rho': 'ρ',
    '\\alpha': 'α',
    '\\beta': 'β',
    '\\gamma': 'γ',
    '\\delta': 'δ',
    '\\epsilon': 'ε',
    '\\varepsilon': 'ε',
    '\\theta': 'θ',
    '\\lambda': 'λ',
    '\\sigma': 'σ',
    '\\omega': 'ω',
    '\\Omega': 'Ω',
 
    // 常用數學符號
    '\\infty': '∞',
    '\\times': '×',
    '\\cdot': '·',
    '\\approx': '≈',
    '\\propto': '∝',
    '\\leq': '≤',
    '\\geq': '≥',
    '\\neq': '≠',
    '\\gg': '≫',
    '\\ll': '≪',
    '\\pm': '±',
    '\\sqrt': '√'
  };
}

程式碼寫在Google文件的Apps Script裡面,跟著一份Google文件走。所以複製這份Google文件就可以有這個功能,不用輸入程式碼。
這是可以複製去用的文件範本,點選連結後,網頁畫面就會直接要求你建立一個副本,文件中有使用說明。

這是和AI的對話過程