-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCodeProvider.gs
More file actions
210 lines (205 loc) · 8.8 KB
/
Copy pathCodeProvider.gs
File metadata and controls
210 lines (205 loc) · 8.8 KB
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
//CodeProvide.gs** 提供函式原始碼(字串)給側邊欄複製用,全中文註解 */
function getFunctionSnippet_(key) {
switch (key) {
case 'approve': return APPROVE_SNIPPET_();
case 'deps': return DEPS_SNIPPET_();
case 'executor':return EXECUTOR_SNIPPET_();
case 'resuggest': return RESUGGEST_SNIPPET_();
default: return '// 不支援的鍵:' + key;
}
}
function getFunctionBundle_() {
return DEPS_SNIPPET_() + '\n\n' + EXECUTOR_SNIPPET_();
}
/**
* —— 補建議功能:只補「待批准」且分類資訊為空的列 ——
* 使用 OpenAI 分類,並依照設定的 TARGET_FOLDERS_JSON 對應分類 ID。
*/
function RESUGGEST_SNIPPET_() {
return String.raw`function reSuggestMissing_(){
const cfg = getConfig_();
const targets = JSON.parse(cfg.TARGET_FOLDERS_JSON || '{}');
const categories = Object.keys(targets);
if (!categories.length) throw new Error('TARGET_FOLDERS_JSON 空白,請先設定分類→資料夾ID');
const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('📓檔案索引');
if (!sh) throw new Error("找不到 '📓檔案索引' 工作表");
const last = sh.getLastRow();
if (last < 2) {
SpreadsheetApp.getUi().alert('沒有資料列可處理');
return;
}
const data = sh.getRange(2,1,last-1,9).getValues();
let filled = 0;
for (let i=0;i<data.length;i++){
const row = data[i];
const fileId = row[0];
const origName = row[1];
const aiCat = row[4];
const aiCatId = row[5];
const status = row[7];
if (status === '待批准' && (!aiCat || !aiCatId)) {
try {
const file = DriveApp.getFileById(fileId);
const text = (function(){
let content='';
const mt=file.getMimeType();
try{
if(mt===MimeType.GOOGLE_DOCS){content=DocumentApp.openById(fileId).getBody().getText();}
else if(String(mt).startsWith('text/')){content=file.getBlob().getDataAsString('UTF-8');}
else {content=file.getName();}
}catch(e){content=file.getName();}
if(content.length>2000) content=content.slice(0,2000);
return content;
})();
const ai = callAIToClassifyAI_(text, origName || file.getName(), categories, cfg);
if (ai && ai.error) throw new Error(ai.error);
const sug = mapAIToSuggestion_(ai, targets);
const newName = normalizeFileName_(sug.name || origName || '');
sh.getRange(i+2,4,1,3).setValues([[newName, sug.category || '', sug.categoryId || '']]);
sh.getRange(i+2,9).setValue('');
filled++;
} catch (e) {
sh.getRange(i+2,9).setValue('補建議失敗:' + e.message);
}
}
}
SpreadsheetApp.getUi().alert('補齊完成:' + filled + ' 筆');
}`;
}
/** —— 批准清單:把指定 ID 的列(📓檔案索引)G 欄設為 OK —— */
function APPROVE_SNIPPET_() {
return String.raw`function approveSuggested_(ids) {
// ids 可為陣列,若未傳入則自行編輯函式內部的清單
ids = ids && ids.length ? ids : [
'示例ID_A','示例ID_B'
];
const sh = SpreadsheetApp.getActive().getSheetByName('📓檔案索引');
if (!sh) throw new Error("找不到『📓檔案索引』");
const last = sh.getLastRow();
if (last < 2) return 0;
const data = sh.getRange(2,1,last-1,9).getValues(); // A~I
let n = 0;
for (let i=0;i<data.length;i++) {
const row = data[i];
const fileId = String(row[0] || '');
if (ids.indexOf(fileId) >= 0) {
sh.getRange(i+2, 7).setValue('OK'); // G 欄
n++;
}
}
SpreadsheetApp.getUi().alert('已將 ' + n + ' 筆設為 OK');
return n;
}`;
}
/** —— 依賴工具:判斷資料夾 / 搬移 / 重新命名 / 日誌 —— */
function DEPS_SNIPPET_() {
return String.raw`function isFolder_(id) {
const meta = Drive.Files.get(id, { supportsAllDrives: true });
return meta && meta.mimeType === 'application/vnd.google-apps.folder';
}
function moveItem_(id, destFolderId) {
const useAdvanced = true; // 共用雲端硬碟建議 true
try {
if (isFolder_(id)) {
if (!useAdvanced) throw new Error('資料夾搬移需進階 API');
const meta = Drive.Files.get(id, { supportsAllDrives: true });
const removeParents = (meta.parents || []).map(p => p.id).join(',');
const opt = { supportsAllDrives: true, addParents: destFolderId };
if (removeParents) opt.removeParents = removeParents;
Drive.Files.update({}, id, null, opt);
} else {
const file = DriveApp.getFileById(id);
try {
file.moveTo(DriveApp.getFolderById(destFolderId));
} catch (e) {
if (!useAdvanced) throw e;
const meta = Drive.Files.get(id, { supportsAllDrives: true });
const removeParents = (meta.parents || []).map(p => p.id).join(',');
const opt = { supportsAllDrives: true, addParents: destFolderId };
if (removeParents) opt.removeParents = removeParents;
Drive.Files.update({}, id, null, opt);
}
}
} catch (e) { throw new Error('MOVE_FAIL: ' + e.message); }
}
function renameItem_(id, newName) {
try {
if (isFolder_(id)) DriveApp.getFolderById(id).setName(newName);
else DriveApp.getFileById(id).setName(newName);
} catch (e) { throw new Error('RENAME_FAIL: ' + e.message); }
}
function ensureLogSheet_() {
const name = '🧾執行日誌';
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName(name) || ss.insertSheet(name);
if (sh.getLastRow() === 0) {
sh.appendRow(['時間','TXID','類型','檔案/資料夾ID','原父夾ID','原父夾名','原名','新名','分類','策略','層級','動作','問題代碼','訊息','Token數']);
}
return sh;
}
function getParentInfo_(id) {
try {
const meta = Drive.Files.get(id, { supportsAllDrives: true });
const p = (meta.parents && meta.parents[0]) ? meta.parents[0] : null;
let parentName = '';
if (p && p.id) { try { parentName = DriveApp.getFolderById(p.id).getName(); } catch(_){} }
return { parentId: p ? p.id : '', parentName: parentName, title: meta && meta.title ? meta.title : '' };
} catch(_) { return { parentId: '', parentName: '', title: '' }; }
}
function appendLog_(row) {
const sh = ensureLogSheet_();
sh.appendRow([
new Date(), row.txid || Utilities.getUuid(), row.type || '',
row.id || '', row.parentId || '', row.parentName || '',
row.oldName || '', row.newName || '', row.category || '',
row.strategy || '', row.level || '', row.action || '',
row.code || '', row.message || '', row.tokens || ''
]);
}`;
}
/** —— 新版執行器:支援資料夾+寫日誌 —— */
function EXECUTOR_SNIPPET_() {
return String.raw`function executeApproved_() {
const sh = SpreadsheetApp.getActive().getSheetByName('📓檔案索引');
if (!sh) return SpreadsheetApp.getUi().alert("找不到『📓檔案索引』");
// 若有 getConfig_ 則沿用,否則 DRY_RUN 預設 true
const cfg = (typeof getConfig_ === 'function') ? getConfig_() : { DRY_RUN: 'true' };
const DRY_RUN = (String(cfg.DRY_RUN || 'true') === 'true');
const last = sh.getLastRow();
if (last < 2) return SpreadsheetApp.getUi().alert('沒有任何待處理項目。');
const data = sh.getRange(2,1,last-1,9).getValues(); // A~I
let ok = 0;
for (let i=0;i<data.length;i++) {
const r = i + 2;
const [id, oldName, , newName, category, dstFolderId, approval, status] = data[i];
if (approval === 'OK' && status === '待批准') {
const meta = getParentInfo_(id);
const level = isFolder_(id) ? 'folder' : 'file';
const txid = Utilities.getUuid();
try {
sh.getRange(r, 8).setValue('處理中'); SpreadsheetApp.flush();
if (!DRY_RUN) {
if (newName) renameItem_(id, newName);
if (dstFolderId) moveItem_(id, dstFolderId);
}
appendLog_({ txid, type: level, id, parentId: meta.parentId, parentName: meta.parentName,
oldName, newName, category, strategy: DRY_RUN ? 'DRY_RUN' : 'APPLY',
level, action: (newName ? 'rename' : 'noop'), code: '', message: '' });
if (dstFolderId) appendLog_({ txid, type: level, id, parentId: meta.parentId, parentName: meta.parentName,
oldName, newName, category, strategy: DRY_RUN ? 'DRY_RUN' : 'APPLY',
level, action: 'move', code: '', message: '' });
sh.getRange(r, 8).setValue(DRY_RUN ? '預演成功' : '已完成');
sh.getRange(r, 9).setValue('');
ok++;
} catch (e) {
sh.getRange(r, 8).setValue('失敗');
sh.getRange(r, 9).setValue(String(e.message));
appendLog_({ txid, type: level, id, parentId: meta.parentId, parentName: meta.parentName,
oldName, newName, category, strategy: DRY_RUN ? 'DRY_RUN' : 'APPLY',
level, action: 'error', code: (String(e.message).split(':')[0] || ''), message: String(e.message) });
}
}
}
SpreadsheetApp.getUi().alert('執行完成:' + ok + ' 筆。');
}`;
}