Skip to content

feat(web): integrate Restate client for audio pipeline invocation#1973

Merged
yujonglee merged 1 commit intomainfrom
devin/1764328989-restate-client-integration
Nov 28, 2025
Merged

feat(web): integrate Restate client for audio pipeline invocation#1973
yujonglee merged 1 commit intomainfrom
devin/1764328989-restate-client-integration

Conversation

@yujonglee
Copy link
Contributor

@yujonglee yujonglee commented Nov 28, 2025

feat(web): integrate Restate client for audio pipeline invocation

Summary

This PR integrates the Restate TypeScript SDK client into the web app to invoke the AudioPipeline workflow from apps/restate. The file-transcription route now uses server functions to start workflows and poll for status using TanStack Query with refetchInterval.

Key changes:

  • Added @restatedev/restate-sdk-clients dependency and RESTATE_INGRESS_URL env var
  • Created three server functions: startAudioPipeline, getAudioPipelineStatus, getAudioPipelineResult
  • Refactored file-transcription.tsx to use useQuery with refetchInterval for polling (per user feedback to avoid manual polling with setError)

Review & Testing Checklist for Human

  • Verify type alignment: The PipelineStatus and StatusState types in transcription.ts are duplicated from apps/restate/src/audioPipeline.ts. Confirm they match and consider extracting to a shared package.
  • Test with running Restate server: This integration was not tested end-to-end. Deploy the Restate worker and verify the full flow works (upload → start pipeline → poll status → get transcript).
  • Verify refetchInterval behavior: Confirm polling stops when status is DONE or ERROR, and that the 2-second interval is appropriate.
  • Check error handling: The errorMessage derivation combines upload errors, query errors, and pipeline errors - verify all error states display correctly in the UI.

Recommended test plan:

  1. Set RESTATE_INGRESS_URL to point to a running Restate server with the AudioPipeline workflow deployed
  2. Upload an audio file on the file-transcription page
  3. Verify the pipeline starts and status polling works
  4. Confirm transcript appears when pipeline completes

Notes

- Add @restatedev/restate-sdk-clients dependency
- Add RESTATE_INGRESS_URL environment variable
- Create server functions for Restate workflow invocation:
  - startAudioPipeline: starts the AudioPipeline workflow
  - getAudioPipelineStatus: gets current pipeline status
  - getAudioPipelineResult: waits for and returns final result
- Refactor file-transcription route to use TanStack Query with
  refetchInterval for polling pipeline status instead of manual polling

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@netlify
Copy link

netlify bot commented Nov 28, 2025

Deploy Preview for hyprnote-storybook ready!

Name Link
🔨 Latest commit 919054e
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/69298859de411400087cfbc1
😎 Deploy Preview https://deploy-preview-1973--hyprnote-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Nov 28, 2025

Deploy Preview for hyprnote ready!

Name Link
🔨 Latest commit 919054e
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/692988595a377000088717c9
😎 Deploy Preview https://deploy-preview-1973--hyprnote.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 28, 2025

📝 Walkthrough

Walkthrough

Integrates Restate SDK into the web application to enable an asynchronous audio processing pipeline. Adds environment configuration, dependency, and three server endpoints for starting and monitoring audio processing jobs, with corresponding React Query integration in the file transcription component.

Changes

Cohort / File(s) Summary
Environment & Configuration
apps/web/.env.sample, apps/web/package.json, apps/web/src/env.ts
Added RESTATE_INGRESS_URL environment variable configuration and @restatedev/restate-sdk-clients dependency; updated env schema to validate RESTATE_INGRESS_URL as required server-side string.
Audio Pipeline Server Functions
apps/web/src/functions/transcription.ts
Introduced RESTATE-backed audio pipeline subsystem with PipelineStatus enum, StatusState type, and three new server endpoints: startAudioPipeline (POST), getAudioPipelineStatus (GET), and getAudioPipelineResult (GET). Each endpoint includes Supabase user authorization, RESTATE client interaction, and standardized error handling.
File Transcription UI Integration
apps/web/src/routes/_view/app/file-transcription.tsx
Refactored transcription flow to use React Query polling for pipeline status. Replaced direct transcription logic with pipeline-based async flow: initiates pipeline, stores pipelineId, polls status until completion, and updates transcript on success. Centralized error messaging from both upload and pipeline errors.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as File Transcription<br/>Component
    participant ServerFn as Server Functions
    participant Restate as Restate<br/>Workflow Client
    
    User->>UI: Upload audio file
    UI->>ServerFn: startAudioPipeline(audioUrl)
    activate ServerFn
    ServerFn->>ServerFn: Verify user auth (Supabase)
    ServerFn->>Restate: Create workflow client &<br/>submit job
    Restate-->>ServerFn: Return pipelineId
    deactivate ServerFn
    ServerFn-->>UI: pipelineId
    
    UI->>UI: Store pipelineId
    UI->>UI: Start polling (React Query)
    
    loop Poll every N seconds
        UI->>ServerFn: getAudioPipelineStatus(pipelineId)
        activate ServerFn
        ServerFn->>ServerFn: Verify user auth
        ServerFn->>Restate: Query workflow status
        Restate-->>ServerFn: StatusState (PENDING/DONE/ERROR)
        deactivate ServerFn
        ServerFn-->>UI: StatusState
        alt Status = DONE
            UI->>ServerFn: getAudioPipelineResult(pipelineId)
            activate ServerFn
            ServerFn->>ServerFn: Verify user auth
            ServerFn->>Restate: Retrieve result
            Restate-->>ServerFn: transcript
            deactivate ServerFn
            ServerFn-->>UI: transcript
            UI->>UI: Stop polling &<br/>Update transcript
            UI->>User: Display result
        else Status = ERROR
            UI->>UI: Stop polling &<br/>Show error
            UI->>User: Display error
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Attention areas:
    • Logic density in startAudioPipeline, getAudioPipelineStatus, and getAudioPipelineResult endpoints; verify proper error handling and Supabase authorization patterns
    • Restate client initialization and workflow interaction correctness
    • React Query polling implementation in file-transcription component; verify cleanup (unsubscribe) and state transitions
    • Correctness of pipelineId storage, retrieval, and lifecycle management across component re-renders

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: integrating a Restate client for audio pipeline invocation, which aligns with all modifications across environment config, dependencies, and transcription components.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, covering the integration of Restate SDK, new server functions, environment variables, and UI refactoring with a detailed review checklist.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/1764328989-restate-client-integration

Comment @coderabbitai help to get the list of available commands and usage tips.

@yujonglee yujonglee merged commit 604854a into main Nov 28, 2025
9 of 11 checks passed
@yujonglee yujonglee deleted the devin/1764328989-restate-client-integration branch November 28, 2025 11:36
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/routes/_view/app/file-transcription.tsx (1)

78-141: Tighten FileReader safety and consider modeling upload+pipeline as a mutation

Two points here:

  1. const base64Data = reader.result?.toString().split(",")[1]; can still throw if reader.result is ever null/undefined, since optional chaining doesn’t guard the subsequent .split. A more defensive version avoids a potential runtime TypeError in the async callback:
-        const base64Data = reader.result?.toString().split(",")[1];
-        if (!base64Data) {
-          setUploadError("Failed to read file");
-          return;
-        }
+        if (typeof reader.result !== "string") {
+          setUploadError("Failed to read file");
+          return;
+        }
+        const base64Data = reader.result.split(",")[1];
+        if (!base64Data) {
+          setUploadError("Failed to read file");
+          return;
+        }
  1. The chained upload + startAudioPipeline flow is managed manually via setUploadError/setPipelineId. Given the project’s guidance to lean on TanStack Query for mutations, it might be worth encapsulating this sequence in a useMutation so loading/error state comes from the mutation instead of bespoke state, even if not required for this PR. Based on learnings, this would align better with the preferred form/mutation patterns in this repo.
🧹 Nitpick comments (3)
apps/web/src/functions/transcription.ts (3)

9-28: Centralize pipeline status/state typing to avoid drift

PipelineStatus and StatusState here mirror the StatusState interface in apps/web/src/utils/restate.ts. Duplicating this contract in multiple places risks silent divergence if the workflow evolves; consider defining the status/state model in a single shared module (type + optional runtime schema) and reusing it here and in the Restate helpers, or wiring this schema into wherever you deserialize the workflow status so the validation actually runs instead of being purely a type alias.


29-39: Reuse the canonical AudioPipeline type and consider a shared Restate client instance

You manually re-declare AudioPipeline here, while apps/restate/src/audioPipeline.ts already exports type AudioPipeline = typeof audioPipeline;. If your build setup allows, importing that type alias instead of re-specifying method signatures would keep the web client in lockstep with the workflow’s contract. Also, getRestateClient calls clients.connect on every handler invocation; if connect is non-trivial or manages connection pooling, you may want a module-level singleton client (created once and reused) for efficiency.


41-139: Authn is enforced, but double‑check pipelineId access control and user binding

All three handlers correctly require an authenticated Supabase user before touching the Restate workflow, and the success/error envelopes match what the frontend expects. One thing to verify is authorization: getAudioPipelineStatus/getAudioPipelineResult accept only a pipelineId and don’t cross-check it against userData.user.id. If AudioPipeline.getStatus/workflowAttach don’t themselves validate the calling user, then anyone who obtains a pipelineId could potentially poll or attach to another user’s pipeline; in that case, adding a simple mapping from pipelineIduserId (and asserting it here) or forwarding the userId into those calls would tighten privacy guarantees. Also ensure your runtime supports crypto.randomUUID() as used here in startAudioPipeline.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 689fa97 and 919054e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • apps/web/.env.sample (1 hunks)
  • apps/web/package.json (1 hunks)
  • apps/web/src/env.ts (1 hunks)
  • apps/web/src/functions/transcription.ts (1 hunks)
  • apps/web/src/routes/_view/app/file-transcription.tsx (6 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces

Files:

  • apps/web/src/env.ts
  • apps/web/src/functions/transcription.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/web/src/env.ts
  • apps/web/src/routes/_view/app/file-transcription.tsx
  • apps/web/src/functions/transcription.ts
🧠 Learnings (2)
📚 Learning: 2025-11-24T16:32:23.055Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:23.055Z
Learning: Applies to **/*.{ts,tsx} : Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.

Applied to files:

  • apps/web/src/routes/_view/app/file-transcription.tsx
📚 Learning: 2025-11-24T16:32:19.706Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:19.706Z
Learning: Applies to **/*.{ts,tsx} : Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) for 99% of cases instead of setError and similar patterns.

Applied to files:

  • apps/web/src/routes/_view/app/file-transcription.tsx
🧬 Code graph analysis (1)
apps/web/src/functions/transcription.ts (4)
apps/web/src/utils/restate.ts (1)
  • StatusState (4-15)
apps/restate/src/audioPipeline.ts (1)
  • AudioPipeline (260-260)
apps/web/src/env.ts (1)
  • env (6-37)
apps/web/src/functions/supabase.ts (1)
  • getSupabaseServerClient (20-36)
🪛 dotenv-linter (4.0.0)
apps/web/.env.sample

[warning] 29-29: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: fmt
  • GitHub Check: ci (macos, macos-14)
🔇 Additional comments (5)
apps/web/src/env.ts (1)

21-24: New RESTATE_INGRESS_URL server env looks consistent

Adding RESTATE_INGRESS_URL as a required non-empty server variable matches its usage in the Restate client and keeps the env schema consistent with other required secrets.

apps/web/.env.sample (1)

28-29: Sample RESTATE_INGRESS_URL entry aligns with env schema

The added RESTATE_INGRESS_URL="" keeps the sample in sync with the validated server env. The dotenv-linter quote warning is consistent with how all other variables are written here, so it’s safe to ignore or adjust the linter config later if desired.

apps/web/package.json (1)

13-24: Restate SDK client dependency matches new server integration

Adding @restatedev/restate-sdk-clients as a runtime dependency is consistent with the new Restate-backed audio pipeline functions and looks correct version-wise at a glance. Please just confirm this version is compatible with your deployed Restate server/runtime and infra expectations.

apps/web/src/routes/_view/app/file-transcription.tsx (2)

30-77: Pipeline polling and derived processing/error state look solid

The useQuery setup, terminal-status-based refetchInterval, and the isProcessing/errorMessage derivations cleanly wire the RESTATE pipeline into the UI and should behave correctly across status transitions and errors.


170-176: Unified error banner for upload and pipeline failures is well-structured

The errorMessage aggregation and single error banner keep the UX simple while surfacing both upload and pipeline failures; the conditional render around errorMessage is clear and easy to extend if more error sources are added later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant