elevenlabs / elevenlabs-js

The official JavaScript (Node) library for ElevenLabs Text to Speech.
https://elevenlabs.io
MIT License
60 stars 7 forks source link

NextJS - Can't resolve 'child_process' #22

Closed osehmathias closed 2 months ago

osehmathias commented 2 months ago

In a very simple implementation:

"use client";

import { ElevenLabsClient, play } from "elevenlabs";

export default async function Audio(props: any) {
  const elevenlabs = new ElevenLabsClient({
    apiKey: "process.env.ELEVENLABS_API_KEY",
  });
  const audio = await elevenlabs.generate({
    voice: "Rachel",
    text: "We support two main models.",
    model_id: "eleven_multilingual_v2",
  });
  await play(audio);
  return <div>Placeholder</div>;
}

This error is persistent.

./node_modules/command-exists/lib/command-exists.js:3:0
Module not found: Can't resolve 'child_process'

https://nextjs.org/docs/messages/module-not-found

Import trace for requested module:
./node_modules/command-exists/index.js
./node_modules/elevenlabs/wrapper/play.js
./node_modules/elevenlabs/wrapper/index.js
./node_modules/elevenlabs/index.js
./components/Audio.tsx
./components/ChatMessageBubble.tsx
./components/ChatWindow.tsx

I can change the config every which way and it doesn't resolve the issue.

For example, in package.config, I can set:

  "browser": {
    "fs": false,
    "child_process": false
  },

However, then it shows the following error.

  cannot destructure property 'signals' of '_os.constants' as it is undefined

These are the details of my project:

  "engines": {
    "node": ">=18"
  },
    "dependencies": {
     ...
     "elevenlabs": "^0.2.2",
     "next": "^14.0.1",
     "react": "18.2.0",
     "react-dom": "18.2.0",
     ...
    },
sponrad commented 3 weeks ago

@osehmathias I see you closed this... did you figure it out? I'm hitting the same issue with nearly the same simple setup.

osehmathias commented 3 weeks ago

I closed it as I realised I ignorantly put this on the client side.

Put the ElevenLabs call in an API route then post to the route.

sponrad commented 3 weeks ago

Ahh of course. Thanks for responding back so fast saved me some time. Cheers

osehmathias commented 3 weeks ago
// app/api/tts/route.ts

import { ElevenLabsClient } from "elevenlabs";
import { NextResponse } from "next/server";

export async function POST(req) {
  const { message } = await req.json();

  const elevenlabs = new ElevenLabsClient({
    apiKey: process.env.ELEVENLABS_API_KEY,
  });

  try {
    const audio = await elevenlabs.generate({
      voice: process.env.ELEVENLABS_VOICE_ID,
      model_id: "eleven_turbo_v2",
      voice_settings: {
        similarity_boost: 0.5,
        stability: 0.5,
        use_speaker_boost: true,
      },
      text: message,
    });

    return new Response(audio, {
      headers: { "Content-Type": "audio/mpeg" },
    });
  } catch (error) {
    console.error(error);
    return NextResponse.json(error, { status: error.statusCode });
  }
}
// in your page / component
...
const audioRef = useRef<HTMLAudioElement>(new Audio());
...
const getAudioResponse = async (text) => {
 const response = await fetch("/api/tts", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          message: text,
        }),
      });
  return await response.blob();
 }

  const botVoiceResponse = await getAudioResponse(text);

  const reader = new FileReader();
  reader.readAsDataURL(botVoiceResponse);
  reader.onload = () => {
        if (audioRef.current) {
          audioRef.current.src = reader.result as string;
        }
      };
....
return ( 
      <audio ref={audioRef} hidden />
  )
]
sponrad commented 3 weeks ago

@osehmathias this is an amazing simple working example, something like this should be in their documentation, thanks so much!