Created
March 23, 2026 04:26
-
-
Save phumberdroz/e51b9ad76b1604226265befb9a9cd5ea to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| [output.post_process] | |
| command = "~/.config/voxtype/gemini-cleanup.sh" | |
| timeout_ms = 12000 |
This file contains hidden or 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
| GEMINI_API_KEY='<REDACTED>' |
This file contains hidden or 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
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| CONFIG_DIR="${HOME}/.config/voxtype" | |
| ENV_FILE="${CONFIG_DIR}/gemini.env" | |
| API_URL="https://generativelanguage.googleapis.com/v1beta/models" | |
| MODEL="${VOXTYPE_GEMINI_MODEL:-gemini-2.5-flash-lite}" | |
| TIMEOUT_SECONDS="${VOXTYPE_GEMINI_TIMEOUT_SECONDS:-10}" | |
| STATE_DIR="${XDG_STATE_HOME:-${HOME}/.local/state}/voxtype/post-process" | |
| LOG_ENABLED="${VOXTYPE_GEMINI_LOG_ENABLED:-1}" | |
| if [[ -f "${ENV_FILE}" ]]; then | |
| # shellcheck disable=SC1090 | |
| source "${ENV_FILE}" | |
| fi | |
| INPUT_TEXT="$(cat)" | |
| if [[ -z "${INPUT_TEXT//[[:space:]]/}" ]]; then | |
| exit 0 | |
| fi | |
| if [[ -z "${GEMINI_API_KEY:-}" ]]; then | |
| printf '%s' "${INPUT_TEXT}" | |
| exit 0 | |
| fi | |
| PROMPT=$'You are cleaning dictation for direct insertion into an app.\n\nRules:\n- Preserve meaning, intent, tone, and level of detail.\n- Make the smallest possible edits.\n- Remove filler words, false starts, repeated fragments, and obvious self-corrections only when they are clearly accidental.\n- Do not summarize, compress, reorder, or rewrite for style unless needed for clarity.\n- Keep wording close to the transcript when it already reads naturally.\n- Fix punctuation, capitalization, spacing, and paragraph breaks.\n- Correct obvious ASR mistakes only when the intended text is clear from context.\n- If the content naturally wants a list, format it as a clean list. Otherwise keep normal prose.\n- Do not add new facts, explanations, titles, markdown fences, or emojis.\n- If you are not confident a phrase should be changed, leave it as-is.\n- Output only the cleaned text.' | |
| JSON_PAYLOAD="$(jq -n \ | |
| --arg prompt "${PROMPT}" \ | |
| --arg transcript "${INPUT_TEXT}" \ | |
| '{ | |
| systemInstruction: { | |
| parts: [ | |
| { text: "You clean raw speech-to-text output for low-latency dictation." } | |
| ] | |
| }, | |
| contents: [ | |
| { | |
| role: "user", | |
| parts: [ | |
| { text: ($prompt + "\n\nTranscript:\n" + $transcript) } | |
| ] | |
| } | |
| ], | |
| generationConfig: { | |
| temperature: 0.05, | |
| topP: 0.95, | |
| maxOutputTokens: 8192 | |
| } | |
| }')" | |
| cleanup() { | |
| [[ -n "${RESP_FILE:-}" && -f "${RESP_FILE}" ]] && rm -f "${RESP_FILE}" | |
| [[ -n "${INPUT_FILE:-}" && -f "${INPUT_FILE}" ]] && rm -f "${INPUT_FILE}" | |
| [[ -n "${OUTPUT_FILE:-}" && -f "${OUTPUT_FILE}" ]] && rm -f "${OUTPUT_FILE}" | |
| [[ -n "${DIFF_FILE:-}" && -f "${DIFF_FILE}" ]] && rm -f "${DIFF_FILE}" | |
| } | |
| trap cleanup EXIT | |
| RESP_FILE="$(mktemp)" | |
| INPUT_FILE="$(mktemp)" | |
| OUTPUT_FILE="$(mktemp)" | |
| DIFF_FILE="$(mktemp)" | |
| printf '%s' "${INPUT_TEXT}" > "${INPUT_FILE}" | |
| TIMESTAMP="$(date -u +%Y%m%dT%H%M%SZ)" | |
| RUN_ID="${TIMESTAMP}-$$" | |
| LOG_DIR="${STATE_DIR}/${RUN_ID}" | |
| START_MS="$(date +%s%3N)" | |
| write_log_bundle() { | |
| local status="$1" | |
| local elapsed_ms="$2" | |
| [[ "${LOG_ENABLED}" == "0" ]] && return 0 | |
| mkdir -p "${LOG_DIR}" | |
| if ! diff -u "${INPUT_FILE}" "${OUTPUT_FILE}" > "${DIFF_FILE}"; then | |
| true | |
| fi | |
| cat > "${LOG_DIR}/single.md" <<EOF | |
| # Voxtype Post-Process Log | |
| ## Metadata | |
| timestamp_utc=${TIMESTAMP} | |
| model=${MODEL} | |
| timeout_seconds=${TIMEOUT_SECONDS} | |
| status=${status} | |
| elapsed_ms=${elapsed_ms} | |
| ## Before | |
| \`\`\`text | |
| $(cat "${INPUT_FILE}") | |
| \`\`\` | |
| ## After | |
| \`\`\`text | |
| $(cat "${OUTPUT_FILE}") | |
| \`\`\` | |
| ## Diff | |
| \`\`\`diff | |
| $(cat "${DIFF_FILE}") | |
| \`\`\` | |
| EOF | |
| } | |
| if ! curl -fsS \ | |
| --max-time "${TIMEOUT_SECONDS}" \ | |
| -X POST \ | |
| "${API_URL}/${MODEL}:generateContent?key=${GEMINI_API_KEY}" \ | |
| -H 'Content-Type: application/json' \ | |
| -d "${JSON_PAYLOAD}" > "${RESP_FILE}"; then | |
| printf '%s' "${INPUT_TEXT}" > "${OUTPUT_FILE}" | |
| END_MS="$(date +%s%3N)" | |
| write_log_bundle "curl_error_fallback" "$((END_MS-START_MS))" | |
| printf '%s' "${INPUT_TEXT}" | |
| exit 0 | |
| fi | |
| OUTPUT_TEXT="$(jq -r '.candidates[0].content.parts[0].text // empty' "${RESP_FILE}")" | |
| if [[ -z "${OUTPUT_TEXT//[[:space:]]/}" ]]; then | |
| printf '%s' "${INPUT_TEXT}" > "${OUTPUT_FILE}" | |
| END_MS="$(date +%s%3N)" | |
| write_log_bundle "empty_response_fallback" "$((END_MS-START_MS))" | |
| printf '%s' "${INPUT_TEXT}" | |
| exit 0 | |
| fi | |
| printf '%s' "${OUTPUT_TEXT}" > "${OUTPUT_FILE}" | |
| END_MS="$(date +%s%3N)" | |
| write_log_bundle "ok" "$((END_MS-START_MS))" | |
| printf '%s' "${OUTPUT_TEXT}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment