fix(providers): use native ElevenLabs API on desktop to avoid unspeech proxy 401#1107
fix(providers): use native ElevenLabs API on desktop to avoid unspeech proxy 401#1107Hanfeng-Lin wants to merge 4 commits intomoeru-ai:mainfrom
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request resolves persistent HTTP 401 errors for ElevenLabs Free Tier users by intelligently routing API requests based on the application platform. Desktop clients now directly access the native ElevenLabs API, circumventing issues with shared proxy IPs and CORS limitations. Web clients continue to utilize the unspeech proxy to ensure CORS compliance. This change significantly improves the reliability of ElevenLabs integration for desktop users while maintaining functionality for web users. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces platform-specific logic for the ElevenLabs provider to use the native API on desktop, avoiding CORS issues and potential 401 errors from the public proxy. The changes are well-structured and address the problem described. I've identified a few areas for improvement regarding error handling, cleaner JSON payload construction, and a potential bug in array manipulation. My suggestions aim to enhance the robustness and maintainability of the new implementation.
Note: Security Review did not run due to the size of the PR.
| voice?: string | ||
| model?: string | ||
| } | ||
| const voiceId = encodeURIComponent(body.voice ?? '') |
There was a problem hiding this comment.
If body.voice is undefined or an empty string, voiceId will be empty. This will likely cause the fetch call on line 899 to fail with a 404 error, as the URL will be .../text-to-speech/. It would be more robust to add a check for an empty voiceId and return an appropriate error response before making the API call.
| const lo = Math.min(...['Aria', 'Bill'].map((n) => { const i = voices.findIndex(v => v.name.includes(n)); return i !== -1 ? i : voices.length - 1 })) | ||
| const hi = Math.max(...['Aria', 'Bill'].map((n) => { const i = voices.findIndex(v => v.name.includes(n)); return i !== -1 ? i : 0 })) | ||
| return [...voices.slice(0, lo), ...voices.slice(hi + 1), ...voices.slice(lo, hi + 1)] |
There was a problem hiding this comment.
This logic for rearranging voices has a bug when neither 'Aria' nor 'Bill' are found in the voices array. In that case, lo becomes voices.length - 1 and hi becomes 0, causing lo > hi. The spread syntax on the next line will then duplicate parts of the array. A more robust implementation would handle this edge case explicitly.
| const lo = Math.min(...['Aria', 'Bill'].map((n) => { const i = voices.findIndex(v => v.name.includes(n)); return i !== -1 ? i : voices.length - 1 })) | |
| const hi = Math.max(...['Aria', 'Bill'].map((n) => { const i = voices.findIndex(v => v.name.includes(n)); return i !== -1 ? i : 0 })) | |
| return [...voices.slice(0, lo), ...voices.slice(hi + 1), ...voices.slice(lo, hi + 1)] | |
| const ariaIdx = voices.findIndex(v => v.name.includes('Aria')); | |
| const billIdx = voices.findIndex(v => v.name.includes('Bill')); | |
| if (ariaIdx === -1 && billIdx === -1) { | |
| return voices; | |
| } | |
| const lo = Math.min(ariaIdx > -1 ? ariaIdx : Infinity, billIdx > -1 ? billIdx : Infinity); | |
| const hi = Math.max(ariaIdx, billIdx); | |
| return [...voices.slice(0, lo), ...voices.slice(hi + 1), ...voices.slice(lo, hi + 1)]; |
| voice_settings: { | ||
| stability: voiceSettings.stability ?? 0.5, | ||
| similarity_boost: voiceSettings.similarityBoost ?? 0.75, | ||
| style: voiceSettings.style, | ||
| use_speaker_boost: voiceSettings.useSpeakerBoost, | ||
| }, |
There was a problem hiding this comment.
If voiceSettings.style or voiceSettings.useSpeakerBoost are undefined, they will be stringified as null in the JSON payload. While many APIs handle null gracefully, it's safer and cleaner to omit these properties from the payload if they are not defined. You can use conditional spreading to achieve this.
| voice_settings: { | |
| stability: voiceSettings.stability ?? 0.5, | |
| similarity_boost: voiceSettings.similarityBoost ?? 0.75, | |
| style: voiceSettings.style, | |
| use_speaker_boost: voiceSettings.useSpeakerBoost, | |
| }, | |
| voice_settings: { | |
| stability: voiceSettings.stability ?? 0.5, | |
| similarity_boost: voiceSettings.similarityBoost ?? 0.75, | |
| ...(voiceSettings.style !== undefined && { style: voiceSettings.style }), | |
| ...(voiceSettings.useSpeakerBoost !== undefined && { use_speaker_boost: voiceSettings.useSpeakerBoost }) | |
| } |
|
But the request still happens right inside of the browser - provided by Electrom, which is based on Chromium, the CORS issue still exists... |
|
The desktop version has already been tested and confirmed working without CORS issue. The network requests reach api.elevenlabs.io and receive valid responses, which would be impossible if browser-level CORS were enforced. The web end was also tested and the proxy is working as normal. |
|
$ curl -sI -X OPTIONS https://api.elevenlabs.io/v1/text-to-speech/test access-control-allow-origin: * strange things, looks like elevenlabs return CORS headers as of now(or is it just me???) |
|
Thanks @shinohara-rin for pointing out! I've updated the branch to bypass the unspeech proxy and call ElevenLabs' native API directly across all platforms. Previously this was Desktop-only due to CORS issues, but ElevenLabs now returns 'Access-Control-Allow-Origin: *'. Both platforms were tested successfully |
shinohara-rin
left a comment
There was a problem hiding this comment.
It looks a bit dirty to me to handcraft an http client here, this might only work as a temporary fix.
shall we consider supporting the elevenlabs native api? @nekomeowww
with CORS out of the way, we still have the anti-abuse problem to deal with
|
If we're sticking with the native ElevenLabs API (because this solves the 401 redirect issue caused by shared IPs once and for all), I can extract the dirty work related to fetching and create a dedicated adapter file in src/stores/providers/elevenlabs/native.ts, exposing a clean createNativeElevenLabsProvider(). This way, providers.ts can now function like before, simply calling a single line of code. |
e747f73 to
727a922
Compare
⏳ Approval required for deploying to Cloudflare Workers (Preview) for stage-web.
Hey, @nekomeowww, @sumimakito, @luoling8192, @LemonNekoGH, kindly take some time to review and approve this deployment when you are available. Thank you! 🙏 |
This is a follow-up to the previous PR #1100 , addressing the rejection reason.
Problem
The previous PR called ElevenLabs API directly from all platforms, which broke the web version due to CORS — browsers block cross-origin requests to
api.elevenlabs.iosince it doesn't returnAccess-Control-Allow-Originheaders.The underlying issue remains: the public
unspeech.hyp3r.linkproxy causes HTTP 401 errors for Free Tier users because many users share the same proxy IP, triggering ElevenLabs' abuse detection.Solution
Use
isStageTamagotchi()to branch by platform:api.elevenlabs.ioworks, avoiding the shared-IP 401 issue.