# [メタ情報] # 識別子: geminiを使った日本語要約_exe # システム名: 未分類 # 技術種別: Misc # 機能名: Misc # 使用言語: AppleScript # 状態: 実行用 # [/メタ情報] 要約: このAppleScriptは、Automatorサービスとして機能し、クリップボード内のテキストをGoogle Gemini APIで要約し、ローカルに保存するワークフローを自動化します。 具体的には、まずクリップボードからテキストを取得し、内容のプレビューと保存の確認を行います。次に、Gemini APIに対し、入力されたテキストを日本語で400字以内に要約し、その後に元の本文も出力するようリクエストを送信します。このAPI通信はPythonスクリプトを通じて行われ、利用可能なモデルを自動選択し、APIが高負荷で一時的に利用できない場合のために最大3回のリトライ機能を備えています。Geminiからの応答が得られた場合、現在のタイムスタンプと要約文の冒頭を元に生成されたファイル名で、指定された保存ディレクトリにUTF-8形式のテキストファイルとして保存します。保存ディレクトリが存在しない場合は自動的に作成されます。APIキー、保存先パス、要約文字数などの設定はスクリプト内でカスタマイズ可能です。 gemini要約.workflow Services Automator ワークフローが受け取る項目:テキスト 選択肢:すべてのアプリケーション ``` -- ===== 設定 ===== property MY_API_KEY : "[MY_API_KEYに入るべき文字列]" property SAVE_DIR : "/Volumes/NO3_SSD/Dropbox/dropbox_1/Geminiによる要約と本文/" property MAX_SUMMARY_CHARS : 400 property TITLE_MAX_CHARS : 40 -- ================= on run set clipText to my getClipboardText() if clipText is missing value or (length of clipText) = 0 then display alert "クリップボードにテキストがありません" return end if set previewText to text 1 thru (my minInt(120, length of clipText)) of clipText set dlg to display dialog "この内容をGeminiで保存しますか(リトライ3回)?" & return & return & previewText buttons {"キャンセル", "続行"} default button "続行" if button returned of dlg is "キャンセル" then return display notification "利用可能なモデルを自動選択して要約中..." with title "通信開始" set finalResult to my askGemini(clipText) if finalResult starts with "Error:" then display alert "Gemini APIエラー" message finalResult return end if my ensureDir(SAVE_DIR) set tsFile to do shell script "date +%Y%m%d-%H%M%S" set tsHuman to do shell script "date '+%Y-%m-%d %H:%M:%S'" set firstLine to item 1 of my splitByNewline(finalResult) set userTitle to my sanitizeFilename(my clampLen(firstLine, TITLE_MAX_CHARS)) set filename to tsFile & "_" & userTitle & ".txt" my writeTextUTF8(SAVE_DIR & filename, "[" & tsHuman & "]" & return & return & finalResult) display notification filename with title "保存完了" end run on askGemini(targetText) set promptText to "以下のテキストを日本語で" & (MAX_SUMMARY_CHARS as string) & "字以内に要約してください。出力は要約文のみ。その後に続けて元の本文も出力してください。" & return & return set fullInput to promptText & targetText set tempInputPath to "/tmp/gemini_in.txt" try my writeTextUTF8(tempInputPath, fullInput) -- Pythonでモデル一覧を取得し、最適なものを1つ選んで実行するスクリプト set pyCode to "import json, sys, subprocess, time try: # 1. 利用可能なモデルをリストから探す list_url = 'https://generativelanguage.googleapis.com/v1beta/models?key=" & MY_API_KEY & "' list_res = subprocess.run(['curl', '-s', list_url], capture_output=True, text=True) models_data = json.loads(list_res.stdout) # generateContentをサポートしているモデルを探す chosen_model = next((m['name'] for m in models_data.get('models', []) if 'generateContent' in m.get('supportedGenerationMethods', [])), None) if not chosen_model: print('Error: あなたのAPIキーで使用可能なモデルが見つかりません。Google AI StudioでAPIの有効化を確認してください。') sys.exit() # 2. 選んだモデルで実行(高負荷エラー対策のリトライループを実装) with open('" & tempInputPath & "', 'r', encoding='utf-8') as f: content = f.read() payload = json.dumps({'contents': [{'parts': [{'text': content}]}]}) exec_url = f'https://generativelanguage.googleapis.com/v1beta/{chosen_model}:generateContent?key=" & MY_API_KEY & "' max_retries = 3 retry_delay = 3 for attempt in range(max_retries): res = subprocess.run(['curl', '-s', '-X', 'POST', '-H', 'Content-Type: application/json', '-d', payload, exec_url], capture_output=True, text=True) data = json.loads(res.stdout) if 'candidates' in data: print(data['candidates'][0]['content']['parts'][0]['text']) break else: err_msg = data.get('error', {}).get('message', '不明なエラー') # 高負荷(high demand / 429 / 503等)を疑うエラー文が含まれる場合はリトライ if 'demand' in err_msg.lower() or 'exhausted' in err_msg.lower() or 'temporarily' in err_msg.lower(): if attempt < max_retries - 1: time.sleep(retry_delay) retry_delay *= 2 continue print('Error: ' + err_msg) break except Exception as e: print('Error: ' + str(e))" -- -1721エラー対策として/usr/bin/envを経由して環境内のpython3を確実に実行 return do shell script "/usr/bin/env python3 -c " & quoted form of pyCode on error errMsg return "Error: 通信失敗 " & errMsg end try end askGemini -- ユーティリティ(省略せずそのままお使いください) on getClipboardText() try return the clipboard as text on error return missing value end try end getClipboardText on ensureDir(d) do shell script "mkdir -p " & quoted form of d end ensureDir on writeTextUTF8(p, t) try set fRef to open for access (POSIX file p) with write permission set eof of fRef to 0 write t to fRef as «class utf8» close access fRef on error try close access (POSIX file p) end try end try end writeTextUTF8 on splitByNewline(s) set AppleScript's text item delimiters to {return, linefeed} set arr to text items of s set AppleScript's text item delimiters to "" return arr end splitByNewline on sanitizeFilename(s) set illegal to "/:\\*?\"<>| " & linefeed & return set out to "" repeat with ch in characters of s if contents of ch is in illegal then set out to out & "_" else set out to out & ch end if end repeat return out end sanitizeFilename on clampLen(s, n) if (length of s) ≤ n then return s return text 1 thru n of s end clampLen on minInt(a, b) if a < b then return a return b end minInt ```