Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions agents/nextjs/quickstart/example/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type VoiceAgentPageProps = {
setSessionError: (value: string | null) => void;
setStarting: (value: boolean) => void;
starting: boolean;
nextLineId: RefObject<number>;
nextLineIdRef: RefObject<number>;
};

type ConversationMessage = {
Expand Down Expand Up @@ -88,7 +88,7 @@ function VoiceAgentPage({
createError,
creating,
lines,
nextLineId,
nextLineIdRef,
sessionError,
setAgentIdInput,
setAgentLookupError,
Expand Down Expand Up @@ -167,7 +167,7 @@ function VoiceAgentPage({
}

setStarting(true);
nextLineId.current = 0;
nextLineIdRef.current = 0;
setLines([]);

try {
Expand Down Expand Up @@ -458,7 +458,7 @@ export default function Home() {
createError={createError}
creating={creating}
lines={lines}
nextLineId={nextLineId}
nextLineIdRef={nextLineId}
sessionError={sessionError}
setAgentIdInput={setAgentIdInput}
setAgentLookupError={setAgentLookupError}
Expand Down
2 changes: 1 addition & 1 deletion dubbing/nextjs/quickstart/PROMPT.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Before writing any code, invoke the `/text-to-speech` skill to learn the correct ElevenLabs SDK patterns.
Before writing any code, invoke the `/dubbing` skill to learn the correct ElevenLabs SDK patterns.

This example uses the Dubbing Projects API (dubbing v2): a **project** holds the source media and its transcript, and each **language target** produces one dubbed output in a single language.

Expand Down
44 changes: 16 additions & 28 deletions dubbing/nextjs/quickstart/example/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,14 @@ export default function Home() {
const recordStartRef = useRef<number>(0);

useEffect(() => {
return () => {
if (originalUrl) URL.revokeObjectURL(originalUrl);
if (dubbedUrl) URL.revokeObjectURL(dubbedUrl);
};
}, [originalUrl, dubbedUrl]);
if (!originalUrl) return;
return () => URL.revokeObjectURL(originalUrl);
}, [originalUrl]);

useEffect(() => {
if (!dubbedUrl) return;
return () => URL.revokeObjectURL(dubbedUrl);
}, [dubbedUrl]);

const stopTimer = () => {
if (timerRef.current) {
Expand All @@ -79,15 +82,9 @@ export default function Home() {

const startRecording = async () => {
setError(null);
setOriginalUrl(prev => {
if (prev) URL.revokeObjectURL(prev);
return null;
});
setOriginalUrl(null);
setWavFile(null);
setDubbedUrl(prev => {
if (prev) URL.revokeObjectURL(prev);
return null;
});
setDubbedUrl(null);
setProjectId(null);

try {
Expand Down Expand Up @@ -122,10 +119,7 @@ export default function Home() {
setPhase("preparing");
try {
const wav = await recordedBlobToWavFile(rawBlob);
setOriginalUrl(prev => {
if (prev) URL.revokeObjectURL(prev);
return URL.createObjectURL(wav);
});
setOriginalUrl(URL.createObjectURL(wav));
setWavFile(wav);
setPhase("idle");
} catch (e) {
Expand Down Expand Up @@ -175,13 +169,10 @@ export default function Home() {
const startDubbing = async () => {
if (!wavFile) return;
setError(null);
setDubbedUrl(prev => {
if (prev) URL.revokeObjectURL(prev);
return null;
});
setDubbedUrl(null);
setPhase("polling");
setProjectId(null);
setPollNote(null);
setPollNote("Transcribing your recording…");

const body = new FormData();
body.set("audio", wavFile);
Expand Down Expand Up @@ -264,10 +255,7 @@ export default function Home() {
}
const blob = await audioRes.blob();
if (cancelled) return;
setDubbedUrl(prev => {
if (prev) URL.revokeObjectURL(prev);
return URL.createObjectURL(blob);
});
setDubbedUrl(URL.createObjectURL(blob));
setPhase("ready");
} catch (e) {
if (!cancelled) {
Expand Down Expand Up @@ -320,7 +308,7 @@ export default function Home() {
case "preparing":
return "Preparing audio…";
case "polling":
return pollNote ?? "Dubbing in progress… checking every 5s.";
return pollNote ?? "Transcribing your recording…";
case "ready":
return "Dubbing complete.";
case "error":
Expand Down Expand Up @@ -448,7 +436,7 @@ export default function Home() {
)}
onClick={startDubbing}
>
{phase === "polling" ? "Dubbing…" : "Dub recording"}
{phase === "polling" ? "Dubbing…" : "Dub Recording"}
</button>
</div>
) : null}
Expand Down
95 changes: 87 additions & 8 deletions scripts/generate-examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,45 @@ run_with_timeout() {
wait "${cmd_pid}"
}

example_needs_dependencies() {
local example_dir="$1"

if [[ -f "${example_dir}/package.json" && ! -d "${example_dir}/node_modules" ]]; then
return 0
fi
if [[ -f "${example_dir}/requirements.txt" && ! -d "${example_dir}/.venv" ]]; then
return 0
fi
return 1
}

# Mirrors the install step at the end of each setup.sh. Partial installs are
# removed so example_needs_dependencies stays accurate for the prompt preamble.
install_example_dependencies() {
(
cd "$1" || exit 1
if [[ -f package.json && ! -d node_modules ]]; then
if ! pnpm install --config.confirmModulesPurge=false; then
rm -rf node_modules
exit 1
fi
fi
if [[ -f requirements.txt && ! -d .venv ]]; then
if command -v uv >/dev/null 2>&1; then
uv venv .venv && uv pip install --python .venv/bin/python -r requirements.txt
else
python3 -m venv .venv &&
.venv/bin/pip install --upgrade pip &&
.venv/bin/pip install -r requirements.txt
fi
if [[ $? -ne 0 ]]; then
rm -rf .venv
exit 1
fi
fi
)
}

format_duration() {
local total_seconds="$1"

Expand Down Expand Up @@ -354,24 +393,45 @@ for prompt_file in "${PROMPT_FILES[@]}"; do
printf "[%s/%s] %s\n" "${CURRENT_PROMPT}" "${TOTAL_PROMPTS}" "${relative_project_dir}"
fi

# Run setup script if present, but skip if example/ already exists
# Run setup script if present, but skip if example/ already exists.
# A skipped setup still needs dependencies installed for the agent to verify against.
if [[ -f "${project_dir}/setup.sh" ]]; then
setup_cmd=()
dependency_install_only=0
if [[ -d "${project_dir}/example" ]]; then
if (( VERBOSE_OUTPUT == 1 )); then
echo "Skipping setup (example/ already exists): ${relative_project_dir}/setup.sh"
fi
if example_needs_dependencies "${project_dir}/example"; then
if (( VERBOSE_OUTPUT == 1 )); then
echo "Installing dependencies: ${relative_project_dir}/example"
fi
setup_cmd=(install_example_dependencies "${project_dir}/example")
dependency_install_only=1
fi
else
if (( VERBOSE_OUTPUT == 1 )); then
echo "Running setup: ${relative_project_dir}/setup.sh"
fi
setup_cmd=(bash "${project_dir}/setup.sh")
fi
if (( ${#setup_cmd[@]} > 0 )); then
if (( VERBOSE_OUTPUT == 1 )); then
bash "${project_dir}/setup.sh"
"${setup_cmd[@]}"
SETUP_EXIT=$?
else
setup_output="$(bash "${project_dir}/setup.sh" 2>&1)"
setup_output="$("${setup_cmd[@]}" 2>&1)"
SETUP_EXIT=$?
fi
if [[ ${SETUP_EXIT} -ne 0 ]]; then
if [[ ${SETUP_EXIT} -ne 0 && ${dependency_install_only} -eq 1 ]]; then
# The agent can still verify without dependencies, just more slowly.
if (( VERBOSE_OUTPUT == 1 )); then
echo " ! Dependency install failed; continuing without dependencies" >&2
else
echo " ! Dependency install failed; continuing without dependencies" >&2
printf "%s\n" "${setup_output}" | tail -n 5 >&2
fi
elif [[ ${SETUP_EXIT} -ne 0 ]]; then
FAILED_RUNS=$((FAILED_RUNS + 1))
if (( VERBOSE_OUTPUT == 1 )); then
echo " ✗ Setup failed" >&2
Expand All @@ -398,16 +458,35 @@ for prompt_file in "${PROMPT_FILES[@]}"; do
preamble+="IMPORTANT: Check the existing code in example/ first. Only make changes if the requirements below are not already met. If everything is already implemented correctly, confirm that no changes are needed."$'\n\n'
fi
if [[ -f "setup.sh" ]]; then
setup_detail="Prerequisite: \`setup.sh\` has already been run. \`example/\` is ready with dependencies installed"
dependencies_ready=1
if [[ -d "example" ]] && example_needs_dependencies "example"; then
dependencies_ready=0
fi
setup_items=()
if (( dependencies_ready == 1 )); then
setup_items+=("dependencies installed")
fi
if [[ -d "assets" ]]; then
asset_list="$(ls -1 assets/ 2>/dev/null | paste -sd ', ' -)"
if [[ -n "${asset_list}" ]]; then
setup_detail+=" and sample assets (${asset_list})"
setup_items+=("sample assets (${asset_list})")
fi
fi
preamble+="${setup_detail}."$'\n\n'
setup_detail="Prerequisite: \`setup.sh\` has already been run. \`example/\` is ready"
if (( ${#setup_items[@]} == 2 )); then
setup_detail+=" with ${setup_items[0]} and ${setup_items[1]}"
elif (( ${#setup_items[@]} == 1 )); then
setup_detail+=" with ${setup_items[0]}"
fi
preamble+="${setup_detail}."
if (( dependencies_ready == 1 )); then
preamble+=" Check SDK signatures against the packages installed in \`example/\` instead of searching the web or other directories."
else
preamble+=" Its dependencies are not installed."
fi
preamble+=$'\n\n'
fi
preamble+="Implement in \`example/\` only."
preamble+="Implement in \`example/\` only. Verify with typecheck and lint only; do not start dev servers, run browsers, or install extra tooling, because this run has a fixed time limit."
if [[ -f "${REPO_ROOT}/DESIGN.md" ]]; then
preamble+=$'\n\n'"Read \`DESIGN.md\` at the repo root for styling rules. Preserve any existing template header and layout container."
fi
Expand Down
6 changes: 5 additions & 1 deletion sound-effects/typescript/quickstart/example/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ElevenLabsClient, ElevenLabsError } from "@elevenlabs/elevenlabs-js";
import { createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
import { Readable } from "node:stream";
import type { ReadableStream as NodeReadableStream } from "node:stream/web";

const DEFAULT_PROMPT = "Cinematic Braam, Horror";
const OUTPUT_FILE = "output.mp3";
Expand All @@ -25,7 +26,10 @@ async function main(): Promise<void> {
const client = new ElevenLabsClient();
const audio = await client.textToSoundEffects.convert({ text });

const source = Readable.fromWeb(audio);
// The SDK returns the DOM ReadableStream type, which Node's typings reject.
const source = Readable.fromWeb(
audio as unknown as NodeReadableStream<Uint8Array>
);
await pipeline(source, createWriteStream(OUTPUT_FILE));

console.log(`Sound effect saved to ${OUTPUT_FILE}`);
Expand Down
Loading
Loading