feat(web): integrate Restate client for audio pipeline invocation#1973
feat(web): integrate Restate client for audio pipeline invocation#1973
Conversation
- 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 EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
✅ Deploy Preview for hyprnote-storybook ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for hyprnote ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughWalkthroughIntegrates 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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 mutationTwo points here:
const base64Data = reader.result?.toString().split(",")[1];can still throw ifreader.resultis evernull/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; + }
- The chained upload +
startAudioPipelineflow is managed manually viasetUploadError/setPipelineId. Given the project’s guidance to lean on TanStack Query for mutations, it might be worth encapsulating this sequence in auseMutationso 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
PipelineStatusandStatusStatehere mirror theStatusStateinterface inapps/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 instanceYou manually re-declare
AudioPipelinehere, whileapps/restate/src/audioPipeline.tsalready exportstype 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,getRestateClientcallsclients.connecton every handler invocation; ifconnectis 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 bindingAll 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/getAudioPipelineResultaccept only apipelineIdand don’t cross-check it againstuserData.user.id. IfAudioPipeline.getStatus/workflowAttachdon’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 frompipelineId→userId(and asserting it here) or forwarding the userId into those calls would tighten privacy guarantees. Also ensure your runtime supportscrypto.randomUUID()as used here instartAudioPipeline.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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.tsapps/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, usecn(import from@hypr/utils). It is similar toclsx. Always pass an array and split by logical grouping.
Usemotion/reactinstead offramer-motion.
Files:
apps/web/src/env.tsapps/web/src/routes/_view/app/file-transcription.tsxapps/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 consistentAdding
RESTATE_INGRESS_URLas 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 schemaThe 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 integrationAdding
@restatedev/restate-sdk-clientsas 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 solidThe
useQuerysetup, terminal-status-basedrefetchInterval, and theisProcessing/errorMessagederivations 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-structuredThe
errorMessageaggregation and single error banner keep the UX simple while surfacing both upload and pipeline failures; the conditional render arounderrorMessageis clear and easy to extend if more error sources are added later.
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
AudioPipelineworkflow fromapps/restate. The file-transcription route now uses server functions to start workflows and poll for status using TanStack Query withrefetchInterval.Key changes:
@restatedev/restate-sdk-clientsdependency andRESTATE_INGRESS_URLenv varstartAudioPipeline,getAudioPipelineStatus,getAudioPipelineResultfile-transcription.tsxto useuseQuerywithrefetchIntervalfor polling (per user feedback to avoid manual polling withsetError)Review & Testing Checklist for Human
PipelineStatusandStatusStatetypes intranscription.tsare duplicated fromapps/restate/src/audioPipeline.ts. Confirm they match and consider extracting to a shared package.refetchIntervalbehavior: Confirm polling stops when status isDONEorERROR, and that the 2-second interval is appropriate.errorMessagederivation combines upload errors, query errors, and pipeline errors - verify all error states display correctly in the UI.Recommended test plan:
RESTATE_INGRESS_URLto point to a running Restate server with the AudioPipeline workflow deployedNotes
transcribeAudioandgetTranscriptionfunctions are preserved for backward compatibility