Last active
January 23, 2024 09:42
-
-
Save hankei6km/ac0324db986be1a6d16000f4e6f8e93a to your computer and use it in GitHub Desktop.
「Gemini API を使いスマホでスキャンした表やグラフから自動的にスプレッドシートを作ってみる」用の.gs ソース。(https://zenn.dev/hankei6km/articles/scanned-tables-to-spreadsheets-with-gemini-gas)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function writeToSheet_(destFolder, fileName, values) { | |
const fileList = destFolder.getFilesByName(fileName) | |
let ssFile = null | |
//console.log(fileName) | |
if (fileList.hasNext()) { | |
ssFile = SpreadsheetApp.openById(fileList.next().getId()) | |
} else { | |
ssFile = SpreadsheetApp.create(fileName) | |
DriveApp.getFileById(ssFile.getId()).moveTo(destFolder) | |
} | |
ssFile.getActiveSheet().getRange(1, 1, values.length, values[0].length).setValues(values) | |
} | |
function scan_(apiKey, destFolder, file) { | |
const ssFileName = file.getName().split('.').slice(0, -1).join('.') | |
const descripton = file.getDescription() | |
if (descripton === undefined || descripton === null || descripton === '') { | |
file.setDescription('scaning...') | |
const [kind, values, body] = ScanImageWithGemini.scan(apiKey, file.getId()) | |
if (kind === ' ## 表が含まれている画像' || kind === ' ## グラフが含まれている画像') { | |
writeToSheet_(destFolder, ssFileName, values) | |
} | |
file.setDescription(body.substring(0, 400)) | |
} | |
} | |
function scan(changeList) { | |
const props = PropertiesService.getScriptProperties() | |
const apiKey = props.getProperty('GEMINI_API_KEY') | |
const destFolder = DriveApp.getFolderById(props.getProperty('DEST_FOLDER')) | |
const list = Object.assign({}, changeList) | |
for (const item of list.items || []) { | |
if (item.file) { | |
if ((item.file.description === undefined || item.file.description === '') && item.file.parents?.some((p) => p.id && p.id === (props.getProperty('SRC_FOLDER')))) { | |
scan_(apiKey, destFolder, DriveApp.getFileById(item.file.id)) | |
} | |
} | |
} | |
} | |
function start() { | |
const props = PropertiesService.getScriptProperties() | |
const apiKey = props.getProperty('GEMINI_API_KEY') | |
const srcFolder = DriveApp.getFolderById(props.getProperty('SRC_FOLDER')) | |
const destFolder = DriveApp.getFolderById(props.getProperty('DEST_FOLDER')) | |
const files = srcFolder.getFiles() | |
while (files.hasNext()) { | |
scan_(apiKey, destFolder, files.next()) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const PROMPT_ = `画像の内容について。 | |
以下の中から最も適切な方法で説明してください。 | |
## 表が含まれている画像の場会 | |
返答の最初に「## 表が含まれている画像」と記述したあと、表の内容をマークダウン形式で記述してください。 | |
## グラフが含まれている画像の場合 | |
返答の最初に「## グラフが含まれている画像」と記述したあと、グラフの内容をマークダウンの表として記述してください。 | |
## テキスト(文章または簡単な説明文など)が含まれている画像の場合 | |
返答の最初に「## テキストが含まれている画像」と記述したあと、テキストの内容を記述してください。 | |
## その他の画像の場合 | |
返答の最初に「## その他の画像」と記述したあと、画像の説明を記述してください。 | |
` | |
function scan_(apiKey, prompt, srcFile) { | |
const fileThumb = srcFile.getThumbnail() | |
const baseImage = Utilities.base64Encode(fileThumb.getBytes()); | |
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent?key=${apiKey}` | |
, payload = { | |
'contents': [ | |
{ | |
'parts': [{ | |
"inline_data": { | |
"mime_type": "image/jpeg", | |
"data": baseImage.toString() | |
} | |
}, { | |
'text': prompt | |
}] | |
} | |
], | |
/* "generationConfig": { | |
"stopSequences": [ | |
"Title" | |
], | |
"temperature": 0.0, | |
"maxOutputTokens": 800, | |
"topP": 0.0, | |
"topK": 1 | |
} */ | |
} | |
, options = { | |
'method': 'post', | |
'contentType': 'application/json', | |
'payload': JSON.stringify(payload) | |
}; | |
const res = UrlFetchApp.fetch(url, options) | |
, resJson = JSON.parse(res.getContentText()); | |
return resJson.candidates[0].content.parts[0].text | |
} | |
function formatBody_(kind, body_tmp) { | |
if (kind.match(/^ ## /)) { | |
let lines = body_tmp.slice(body_tmp.findIndex(l => l !== '')) | |
if (kind === ' ## 表が含まれている画像' || kind === ' ## グラフが含まれている画像') { | |
return lines.map(l => l.replace(/(^\|)|(\|$)/g, "")).join('\n') | |
} | |
return lines.join('\n') | |
} | |
return [kind, ...body_tmp].join('\n') | |
} | |
function scan(apiKey, fileId, prompt = '') { | |
const srcFile = DriveApp.getFileById(fileId) | |
const text = scan_(apiKey, prompt || PROMPT_, srcFile) | |
const [kind, ...body_tmp] = text.split('\n') | |
const body = formatBody_(kind, body_tmp) | |
if (kind === ' ## 表が含まれている画像' || kind === ' ## グラフが含まれている画像') { | |
return [kind, Utilities.parseCsv(body, "|"), body] | |
} | |
return [kind, [[body]], body] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment