インタラクティブ・ストーリー・ジェネレーター
以下のコードスニペットは、Next.js で Mastra を独立したバックエンド統合として用いるインタラクティブ・ストーリー・ジェネレーターアプリにおける Text-to-Speech(TTS)機能の実装例です。この例では、Mastra の client-js SDK を使って Mastra バックエンドへ接続する方法を示します。Mastra と Next.js の統合についての詳細は、Next.js との統合をご覧ください。
TTS 機能を備えたエージェントの作成
次の例は、バックエンドで TTS 機能付きのストーリー生成エージェントをセットアップする方法を示しています。
src/mastra/agents/index.ts
import { openai } from "@ai-sdk/openai";
import { Agent } from "@mastra/core/agent";
import { OpenAIVoice } from "@mastra/voice-openai";
import { Memory } from "@mastra/memory";
const instructions = `
You are an Interactive Storyteller Agent. Your job is to create engaging
short stories with user choices that influence the narrative. // omitted for brevity
`;
export const storyTellerAgent = new Agent({
name: "Story Teller Agent",
instructions: instructions,
model: openai("gpt-4o"),
voice: new OpenAIVoice(),
});
Mastra へのエージェント登録
このスニペットは、Mastra のインスタンスにエージェントを登録する方法を示します。
src/mastra/index.ts
import { PinoLogger } from "@mastra/loggers";
import { Mastra } from "@mastra/core/mastra";
import { storyTellerAgent } from "./agents";
export const mastra = new Mastra({
agents: { storyTellerAgent },
logger: new PinoLogger({
name: "Mastra",
level: "info",
}),
});
フロントエンドからMastraに接続する
ここでは Mastra Client SDK を使用して Mastra サーバーとやり取りします。Mastra Client SDK の詳細はドキュメントをご覧ください。
src/app/page.tsx
import { MastraClient } from "@mastra/client-js";
export const mastraClient = new MastraClient({
baseUrl: "http://localhost:4111", // Mastra のバックエンド URL に置き換えてください
});
物語コンテンツの生成と音声への変換
この例では、Mastra エージェントを参照し、ユーザー入力に基づいて物語の内容を生成し、その内容を音声に変換する方法を示します。
/app/components/StoryManager.tsx
const handleInitialSubmit = async (formData: FormData) => {
setIsLoading(true);
try {
const agent = mastraClient.getAgent("storyTellerAgent");
const message = `Current phase: BEGINNING. Story genre: ${formData.genre}, Protagonist name: ${formData.protagonistDetails.name}, Protagonist age: ${formData.protagonistDetails.age}, Protagonist gender: ${formData.protagonistDetails.gender}, Protagonist occupation: ${formData.protagonistDetails.occupation}, Story Setting: ${formData.setting}`;
const storyResponse = await agent.generate({
messages: [{ role: "user", content: message }],
threadId: storyState.threadId,
resourceId: storyState.resourceId,
});
const storyText = storyResponse.text;
const audioResponse = await agent.voice.speak(storyText);
if (!audioResponse.body) {
throw new Error("No audio stream received");
}
const audio = await readStream(audioResponse.body);
setStoryState((prev) => ({
phase: "beginning",
threadId: prev.threadId,
resourceId: prev.resourceId,
content: {
...prev.content,
beginning: storyText,
},
}));
setAudioBlob(audio);
return audio;
} catch (error) {
console.error("Error generating story beginning:", error);
} finally {
setIsLoading(false);
}
};
音声の再生
このスニペットは、新しい音声データを監視し、テキスト読み上げ音声の再生を行う方法を示します。音声を受信したら、コードは音声のBlobからブラウザで再生可能なURLを生成し、それをaudio要素に設定して自動再生を試みます。
/app/components/StoryManager.tsx
useEffect(() => {
if (!audioRef.current || !audioData) return;
// HTMLのaudio要素への参照を保持
const currentAudio = audioRef.current;
// MastraからのBlob/File形式の音声データをブラウザで再生できるURLに変換
const url = URL.createObjectURL(audioData);
const playAudio = async () => {
try {
currentAudio.src = url;
await currentAudio.load();
await currentAudio.play();
setIsPlaying(true);
} catch (error) {
console.error("Auto-play failed:", error);
}
};
playAudio();
return () => {
if (currentAudio) {
currentAudio.pause();
currentAudio.src = "";
URL.revokeObjectURL(url);
}
};
}, [audioData]);
Interactive Story Generatorの完全な実装は、GitHubリポジトリでご覧いただけます。
View Example on GitHub