Skip to main content

Google Slides Integration

One-Line Summary: Global, user-scoped Google OAuth2 flow with encrypted credential storage and LLM-powered HTML-to-Google-Slides conversion.


1. Overview

The Google Slides integration allows users to export their AI-generated slide decks directly to Google Slides presentations. It extends the existing PPTX export with a cloud-native alternative that produces editable Google Slides.

Key design decisions:

  • Global credentials — A single credentials.json is stored app-wide in the google_global_credentials table, encrypted at rest. Admins upload via the /admin page.
  • Per-user tokens — OAuth tokens are scoped to user_identity only. Each user authorizes once; tokens are stored in google_oauth_tokens.
  • DB-backed storage — No files on disk; all secrets live in PostgreSQL/Lakebase, encrypted with Fernet symmetric encryption.
  • LLM code-gen approach — Same architecture as PPTX export: an LLM generates Python code that calls the Google Slides API, which is then executed server-side.

2. Architecture

Frontend (Admin page → GoogleSlidesAuthForm)

├─ Upload credentials.json ──► POST /api/admin/google-credentials
│ └─ validates → encrypts → stores in google_global_credentials

├─ Authorize (popup) ────────► GET /api/export/google-slides/auth/url
│ └─ builds Flow from decrypted creds → returns consent URL
│ Google consent ──────────► GET /api/export/google-slides/auth/callback
│ └─ exchanges code → encrypts token → stores in google_oauth_tokens

└─ Export ───────────────────► POST /api/export/google-slides
│ └─ validates auth → fetches slides → enqueues async job → returns job_id
└─ Poll ────────────────────► GET /api/export/google-slides/poll/{job_id}
└─ returns progress, status, and presentation URL on completion

Data Flow

  1. Admin uploads credentials.json via the Google Slides tab on the /admin page.
  2. Each user clicks "Authorize" to complete the OAuth consent flow in a popup. The resulting token is encrypted and stored per-user (by user_identity).
  3. Export validates auth, fetches slides, and enqueues an async background job. The frontend polls for progress until the job completes with presentation_id and presentation_url.

3. Database Schema

Table: google_global_credentials

CREATE TABLE google_global_credentials (
id SERIAL PRIMARY KEY,
credentials_encrypted TEXT NOT NULL,
uploaded_by VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);

Stores the Fernet-encrypted contents of credentials.json app-wide. Single-row table; upsert on upload.

Table: google_oauth_tokens

CREATE TABLE google_oauth_tokens (
id SERIAL PRIMARY KEY,
user_identity VARCHAR(255) NOT NULL,
token_encrypted TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE (user_identity)
);
ColumnDescription
user_identityDatabricks username (email) or "local_dev"
token_encryptedFernet-encrypted JSON token (access, refresh, expiry)

Unique on user_identity only — one token per user across the app.

Migration

run_migrations() in src/core/database.py migrates any existing config_profiles.google_credentials_encrypted data into google_global_credentials on startup, then nulls out the profile column. See Database Configuration.


4. Encryption

Module: src/core/encryption.py

Uses Fernet symmetric encryption from the cryptography library.

FunctionPurpose
get_encryption_key()Returns the 32-byte Fernet key (cached). Reads from GOOGLE_OAUTH_ENCRYPTION_KEY env var, falls back to .encryption_key file, or generates and persists a new key.
encrypt_data(plaintext)Encrypts a string and returns a base64-encoded ciphertext string.
decrypt_data(ciphertext)Decrypts. Raises InvalidToken if the key doesn't match.

Key management:

  • Production: Set GOOGLE_OAUTH_ENCRYPTION_KEY environment variable (base64-encoded 32-byte key generated by Fernet.generate_key()). If this variable is missing in production (ENVIRONMENT=production), a RuntimeError is raised at first use of encrypt_data/decrypt_data — not at startup.
  • Local dev: The key is auto-generated and persisted to .encryption_key (gitignored).

Generate a production key:

python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

Stale key handling: If a token or credential cannot be decrypted (e.g., after key rotation), the system silently deletes the stale record and prompts the user to re-authorize.


5. API Endpoints

Credential Management (src/api/routes/admin.py)

MethodPathDescription
POST/api/admin/google-credentialsUpload credentials.json file. Validates structure, encrypts, upserts into google_global_credentials.
GET/api/admin/google-credentials/statusReturns {"has_credentials": bool}. Attempts decryption; clears stale data on failure.
DELETE/api/admin/google-credentialsRemoves stored credentials. Returns 204.

Validation: The uploaded JSON must contain either an "installed" or "web" top-level key with client_id and client_secret.

OAuth Flow (/api/export/google-slides/auth)

MethodPathDescription
GET/auth/statusReturns {"authorized": bool}. Gracefully returns false on any error.
GET/auth/urlGenerates and returns the Google OAuth consent URL.
GET/auth/callback?code=...Exchanges auth code for tokens, encrypts, stores. Returns HTML that notifies the opener window.

Export (/api/export/google-slides)

MethodPathDescription
POST/api/export/google-slidesStarts an async Google Slides export job. Returns {job_id, status, total_slides}.
GET/api/export/google-slides/poll/{job_id}Polls export progress. Returns {job_id, status, progress, total_slides}. When status is completed, also includes presentation_id and presentation_url.

Export is asynchronous. The POST validates auth, fetches slides, builds HTML, and enqueues a background job for the slow LLM conversion. The frontend polls the GET endpoint until status reaches completed or error.

Re-export: If a session has already been exported to Google Slides, the endpoint looks up the existing_presentation_id via the session manager and overwrites the existing presentation instead of creating a new one.

Request body:

{
"session_id": "abc123",
"chart_images": [[{"canvas_id": "chart_0", "base64_data": "data:image/png;base64,..."}]]
}

Response body (POST):

{
"job_id": "abc-123",
"status": "pending",
"total_slides": 5
}

Response body (GET poll, completed):

{
"job_id": "abc-123",
"status": "completed",
"progress": 5,
"total_slides": 5,
"presentation_id": "1A2B3C...",
"presentation_url": "https://docs.google.com/presentation/d/1A2B3C.../edit"
}

Auth uses global credentials and user-scoped token.


6. Backend Services

GoogleSlidesAuth (src/services/google_slides_auth.py)

Manages OAuth2 credentials and token lifecycle. Supports two modes:

ModeConstructorPersistence
DB-backedGoogleSlidesAuth.from_global(user_identity, db_session)Encrypted in PostgreSQL
File-backedGoogleSlidesAuth(credentials_path=..., token_path=...)JSON files on disk

Key methods:

  • is_authorized() — Checks for valid (or refreshable) token.
  • get_auth_url(redirect_uri) — Generates consent URL.
  • authorize(code, redirect_uri) — Exchanges code for tokens, persists.
  • build_slides_service() / build_drive_service() — Returns authenticated Google API clients.

HtmlToGoogleSlidesConverter (src/services/html_to_google_slides.py)

LLM-powered converter following the same pattern as HtmlToPptxConverterV3:

  1. Creates a blank Google Slides presentation via the API.
  2. Extracts base64-embedded content images from HTML, converting SVGs to PNG via _svg_to_png() (svgpathtools + Pillow).
  3. For each slide, sends the HTML to the LLM with a detailed system prompt.
  4. LLM generates Python code that calls slides_service.presentations().batchUpdate().
  5. Content images are uploaded to Google Drive and embedded via createImage requests.
  6. Code is executed server-side with sanitization (smart quote replacement, function wrapping, import injection).
  7. On failure, retries once with error context. Falls back to a placeholder text box.

Prompts (src/services/google_slides_prompts_defaults.py)

Shared rules cover:

  • Slide dimensions (16:9 widescreen, 10" x 5.625")
  • Overflow prevention with strict bounds
  • Font size maximums per element type
  • Metric card, table, chart, and layout patterns
  • Google Slides API patterns (batchUpdate nesting, textRange, shapeProperties)

7. Frontend Components

GoogleSlidesAuthForm (frontend/src/components/config/GoogleSlidesAuthForm.tsx)

Rendered on the /admin page. Provides:

  • Drag-and-drop file upload for credentials.json
  • Status indicators (uploaded / not configured)
  • Upload / replace / remove actions
  • "Authorize with Google" button (opens popup)
  • Authorization status (authorized / not authorized)
  • Help text with instructions for obtaining credentials from Google Cloud Console

SlidePanel (frontend/src/components/SlidePanel/SlidePanel.tsx)

handleExportGoogleSlides calls exportToGoogleSlides(sessionId, chartImages) to export the current session's deck.

API Services

  • frontend/src/api/config.tsuploadGoogleCredentials(), getGoogleCredentialsStatus(), deleteGoogleCredentials() (admin endpoints)
  • frontend/src/services/api.tscheckGoogleSlidesAuth(), getGoogleSlidesAuthUrl(), exportToGoogleSlides(sessionId, chartImages)

8. Test Coverage

Test FileTestsCoverage
tests/unit/test_encryption.py5Encrypt/decrypt roundtrip, wrong-key rejection, key sources
tests/unit/config/test_google_oauth.py16Models, credentials API, from_global, auth service, auth endpoint
tests/unit/config/test_admin_routes.pyAdmin credential upload, status, delete
tests/unit/test_database_migrations.py4Migration from profile credentials to global
tests/unit/test_google_slides_converter.py19Static methods: extract, strip fences, chart notes, code prep, image save, SVG-to-PNG, content image extraction
tests/unit/test_prompts_defaults.py7PPTX + Google Slides prompt constant validation
tests/unit/test_app_wiring.py7Model registration, router exports, route registration
tests/unit/test_google_slides_routes.py12Auth endpoints, export endpoint, poll endpoint, helper functions

Run all tests:

pytest tests/unit/ -v --ignore=tests/unit/test_chart_persistence.py \
--ignore=tests/unit/test_deck_integrity.py \
--ignore=tests/unit/test_llm_edit_responses.py

9. Configuration & Setup

Google Cloud Console Setup

  1. Go to Google Cloud Console.
  2. Create a project (or use an existing one).
  3. Enable the Google Slides API and Google Drive API.
  4. Go to Credentials → Create OAuth 2.0 Client ID (Desktop app).
  5. Download the credentials.json file.
  6. Upload it on the admin page (Google Slides tab).

Environment Variables

VariableRequiredDescription
GOOGLE_OAUTH_ENCRYPTION_KEYYes (production)Fernet key for encrypting credentials/tokens. A RuntimeError is raised at first use of encrypt_data/decrypt_data if missing in production.

Dependencies

Added to requirements.txt:

google-api-python-client>=2.100.0
google-auth-oauthlib>=1.2.0
google-auth-httplib2>=0.2.0
cryptography>=42.0.0
svgpathtools>=1.6.0 # Pure-Python SVG-to-PNG conversion for image library assets
Pillow>=10.0.0 # Image rasterization (also used by PPTX export)

10. Cross-References