This repository was archived by the owner on Jun 27, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgoogle app script.gs
More file actions
354 lines (315 loc) · 8.76 KB
/
Copy pathgoogle app script.gs
File metadata and controls
354 lines (315 loc) · 8.76 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
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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
const apiKey = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY');// See https://developers.google.com/apps-script/guides/properties
const WORKING_FOLDER_NAME='AI工作流';
const PROGRESS_KEY = 'user_task_progress';// 存储进度的键名,可以使用用户session id或任务id来确保唯一性
function doGet() {
var htmlOutput = HtmlService.createHtmlOutputFromFile('index');
htmlOutput.setTitle('工作流');
return htmlOutput;
}
function executeWorkflow(data,targetnodes) {
setProgress(0);
const workflowData = JSON.parse(data);
if (!workflowData || typeof workflowData.nodes !== 'object') { throw new Error('无效 JSON 格式'); }
//build DAG
const graph = new Map();
for (const nodeId in workflowData.nodes) {
graph.set(nodeId, workflowData.nodes[nodeId].outputs.slice());
}
const order = topologicalSort(graph);
targetnodes = new Set(targetnodes);
let finished_nodes_cnt = 0;
const total_nodes_cnt = (targetnodes.size > 0) ? targetnodes.size : workflowData.nodeIdCounter;
//process
order.forEach(function (nodeid) {
++finished_nodes_cnt;
const cur = workflowData.nodes[nodeid];
if (cur.type == '常数')
return;
if (targetnodes.size > 0 && !targetnodes.has(nodeid))
return;
var contents = [];
//set input parameter
for (const inputid of cur.inputs) {
contents.push(
{
role: 'user',
parts: [
{ text: 'reference:' + workflowData.nodes[inputid].name + '\ncontent:\n' + workflowData.nodes[inputid].content },
],
});
}
//set history
if (cur.usehistory == true)
contents.push(
{
role: 'user',
parts: [
{ text: 'history:' + cur.content },
],
});
else
cur.content = 'null';
//set current task
contents.push({
role: 'user',
parts: [
{ text: cur.description },
],
});
//run AI
var response;
if (cur.type == '快速')
response = Call_gemini_2_0_flash(contents);
else if (cur.type == '标准')
response = Call_gemini_2_5_flash(contents);
else if (cur.type == '难题')
response = Call_gemini_2_5_pro(contents);
var result = JSON.parse(response);
if (result && result.candidates && result.candidates.length > 0) {
const str = result.candidates[0].content.parts[0].text;
if (str) {
if (cur.usehistory == true)
cur.content = cur.content + '\n' + str;
else
cur.content = str;
}
}else
throw new Error(response);
setProgress(Math.round(finished_nodes_cnt*100 / total_nodes_cnt));
});
setProgress(100);
return workflowData;
}
const testworkflowstr = `{
"nodes": {
"node-1": {
"id": "node-1",
"x": 732.9453125,
"y": 173.828125,
"name": "A",
"description": "返回任意数字",
"content": "",
"type": "快速",
"inputs": [],
"outputs": [
"node-2"
]
},
"node-2": {
"id": "node-2",
"x": 869.171875,
"y": 296.6796875,
"name": "B",
"description": "返回两倍输入数字",
"content": "",
"type": "难题",
"inputs": [
"node-1"
],
"outputs": []
}
},
"nodeIdCounter": 3,
"defaultNameCounter": 3
}`;
const testairesult=`{
"candidates": [
{
"content": {
"parts": [
{
"text": "123"
}
],
"role": "model"
},
"finishReason": "STOP",
"avgLogprobs": -0.52464914321899414
}
],
"usageMetadata": {
"promptTokenCount": 6,
"candidatesTokenCount": 2,
"totalTokenCount": 8,
"promptTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 6
}
],
"candidatesTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 2
}
]
},
"modelVersion": "gemini-2.0-flash"
}`;
function findFilesInFolder(fileName) {
// 1. 获取文件夹对象
var folders = DriveApp.getFoldersByName(WORKING_FOLDER_NAME);
var folder = null;
if (folders.hasNext()) {
folder = folders.next();
} else
return null;
//console.log(folder.getName());
// 2. 使用文件夹对象的 searchFiles() 方法
var searchResult = folder.searchFiles('title = "' + fileName + '"');
var file = null;
if (searchResult.hasNext()) {
file = searchResult.next();
}
return file;
}
function test() {
const r=executeWorkflow(testworkflowstr);
return;
var file=findFilesInFolder('丽塔-失落迷迭-三视图.png');
console.log(file.getUrl())
}
function Call_gemini_2_0_flash(contents) {
const generationConfig = {
temperature: 1,
topP: 0.95,
topK: 40,
maxOutputTokens: 8192,
responseMimeType: 'text/plain',
};
const data = {
generationConfig,
contents,
};
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
const options = {
method: 'POST',
contentType: 'application/json',
payload: JSON.stringify(data)
};
const response = UrlFetchApp.fetch(url, options);
return response;
}
function Call_gemini_2_5_flash(contents) {
const generationConfig = {
temperature: 1,
topP: 0.95,
topK: 64,
maxOutputTokens: 65536,
responseMimeType: 'text/plain',
};
const data = {
generationConfig,
contents,
};
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent?key=${apiKey}`;
const options = {
method: 'POST',
contentType: 'application/json',
payload: JSON.stringify(data)
};
const response = UrlFetchApp.fetch(url, options);
return response;
}
function Call_gemini_2_5_pro(contents) {
const generationConfig = {
temperature: 1,
topP: 0.95,
topK: 64,
maxOutputTokens: 65536,
responseMimeType: 'text/plain',
};
const data = {
generationConfig,
contents,
};
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro-preview-05-06:generateContent?key=${apiKey}`;
const options = {
method: 'POST',
contentType: 'application/json',
payload: JSON.stringify(data)
};
const response = UrlFetchApp.fetch(url, options);
return response;
}
/**
* 设置任务进度
*/
function setProgress(val) {
const cache = CacheService.getUserCache();
cache.put(PROGRESS_KEY, val, 3600);
}
/**
* 获取当前任务进度
* @returns {number} 当前进度 (0-100)
*/
function getProgress() {
const cache = CacheService.getUserCache();
const progress = cache.get(PROGRESS_KEY);
return progress ? progress + '%' : '空闲';
}
/**
* 删除进度缓存 (可选,在任务完成后或开始新任务前调用)
*/
function clearProgress() {
const cache = CacheService.getUserCache();
cache.remove(PROGRESS_KEY);
}
/**
* 执行拓扑排序。
*
* @param {Map<any, any[]>} graph - 表示图的邻接列表。
* 键是节点,值是该节点指向的相邻节点数组。
* 例如:new Map([['A', ['B', 'C']], ['B', ['D']], ['C', ['D']], ['D', []]])
* @returns {any[] | null} - 如果存在拓扑排序,则返回节点的排序数组;
* 如果图包含循环,则返回 null。
*/
function topologicalSort(graph) {
// 1. 计算每个节点的入度 (In-degree)
// 入度是指向该节点的边的数量。
const inDegree = new Map();
graph.forEach((neighbors, node) => {
// 初始化所有节点的入度为 0
if (!inDegree.has(node)) {
inDegree.set(node, 0);
}
// 增加相邻节点的入度
neighbors.forEach(neighbor => {
if (!inDegree.has(neighbor)) {
inDegree.set(neighbor, 0);
}
inDegree.set(neighbor, inDegree.get(neighbor) + 1);
});
});
// 2. 初始化一个队列,加入所有入度为 0 的节点
const queue = [];
inDegree.forEach((degree, node) => {
if (degree === 0) {
queue.push(node);
}
});
// 3. 执行拓扑排序
const result = [];
while (queue.length > 0) {
const currentNode = queue.shift(); // 取出队列中的第一个节点
result.push(currentNode); // 将当前节点加入结果列表
// 遍历当前节点的所有相邻节点
const neighbors = graph.get(currentNode) || [];
neighbors.forEach(neighbor => {
// 减少相邻节点的入度
inDegree.set(neighbor, inDegree.get(neighbor) - 1);
// 如果相邻节点的入度变为 0,将其加入队列
if (inDegree.get(neighbor) === 0) {
queue.push(neighbor);
}
});
}
// 4. 检查是否存在循环
// 如果结果列表中的节点数量不等于图中的节点总数,说明存在循环。
if (result.length !== graph.size) {
// 图包含循环,无法进行拓扑排序
return null;
}
// 返回拓扑排序结果
return result;
}