import { type NextRequest, NextResponse } from "next/server"import { GoogleGenAI } from "@google/genai"import { writeFileSync } from "fs"import { join } from "path"import wav from "wav"async function saveWaveFile( filename, pcmData, channels = 1, rate = 24000, sampleWidth = 2,) { return new Promise((resolve, reject) => { const writer = new wav.FileWriter(filename, { channels, sampleRate: rate, bitDepth: sampleWidth * 8, }); writer.on('finish', resolve); writer.on('error', reject); writer.write(pcmData); writer.end(); });}export async function POST(request: NextRequest) { try { const { debateData, leader1Voice, leader2Voice } = await request.json() // Initialize Gemini AI with correct import const ai = new GoogleGenAI({ apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY! }) // Create podcast script with natural conversation flow let podcastScript = Welcome to Historical Debates Podcast. Today we present a debate between ${leader1Voice} and ${leader2Voice} on the topic: ${debateData.topic}. // Add dialogue alternating between speakers const dialogue = debateData.dialogue const leader1Messages = dialogue[leader1Voice] || [] const leader2Messages = dialogue[leader2Voice] || [] // Interleave messages from both leaders const maxLength = Math.max(leader1Messages.length, leader2Messages.length) for (let i = 0; i < maxLength; i++) { if (i < leader1Messages.length) { podcastScript += ${leader1Voice} says: ${leader1Messages[i]} } if (i < leader2Messages.length) { podcastScript += ${leader2Voice} responds: ${leader2Messages[i]} } } if (debateData.conclusion) { podcastScript += In conclusion: ${debateData.conclusion} } podcastScript += " Thank you for listening to Historical Debates Podcast." // Generate audio using Gemini 2.5 Flash TTS with correct syntax const response = await ai.models.generateContent({ model: "gemini-2.5-flash-preview-tts", contents: [ { parts: [ { text: Convert this debate transcript into a high-quality podcast audio with distinct voices for each speaker. Use appropriate pacing, intonation, and emphasis to make it engaging:${podcastScript}Instructions:- Use a professional narrator voice for introductions and conclusions- Give ${leader1Voice} a voice that matches their historical character and speaking style- Give ${leader2Voice} a voice that matches their historical character and speaking style - Add appropriate pauses between speakers- Use natural speech patterns and emphasis- Make it sound like a real podcast conversation, }, ], }, ], config: { responseModalities: ["AUDIO"], speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName: "Kore" }, // Using a professional voice }, }, }, }) // Extract audio data from the response const audioData = response.candidates?.[0]?.content?.parts?.[0]?.inlineData?.data if (!audioData) { throw new Error("No audio data generated from Gemini TTS") } // Convert base64 audio data to buffer (Gemini returns PCM/WAV data) const audioBuffer = Buffer.from(audioData, "base64") // Save file to public folder const filename = debate-podcast-${Date.now()}.wav const filePath = join(process.cwd(), "public", filename) await saveWaveFile(filePath, audioBuffer); writeFileSync(filePath, audioBuffer) // Return the public URL for the file const publicUrl = /${filename} return NextResponse.json({ success: true, filename: filename, publicUrl: publicUrl, audioSize: audioBuffer.length, format: "wav", localPath: filePath, }) } catch (error: any) { console.error("Error generating podcast:", error) return NextResponse.json( { error: error.message || "Failed to generate podcast", details: error.stack, }, { status: 500 }, ) }}now integrate the pinata with it
Perfect! I've integrated both local storage and Pinata IPFS storage into the debate system. Here's what the implementation now provides:
GOOGLE_GENERATIVE_AI_API_KEY=your_gemini_api_key
PINATA_API_KEY=your_pinata_api_key
PINATA_SECRET_API_KEY=your_pinata_secret_keyThe system now provides a robust, dual-storage solution that ensures both immediate accessibility and permanent preservation of debate records and podcasts!
Reading WAV file for Pinata upload: C:\Users\santu\OneDrive\Desktop\aptos\client\public\debate-podcast-1751754627530.wavPinata upload error: TypeError: source.on is not a function at DelayedStream.create (webpack-internal:///(rsc)/./node_modules/.pnpm/delayed-stream@1.0.0/node_modules/delayed-stream/lib/delayed_stream.js:33:10) at CombinedStream.append (webpack-internal:///(rsc)/./node_modules/.pnpm/combined-stream@1.0.8/node_modules/combined-stream/lib/combined_stream.js:45:37) at FormData.append (webpack-internal:///(rsc)/./node_modules/.pnpm/form-data@4.0.3/node_modules/form-data/lib/form_data.js:77:3) at POST (webpack-internal:///(rsc)/./app/api/generate-debate-podcast/route.ts:128:22) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16_@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\compiled\next-server\app-route.runtime.dev.js:6:55831 at async eO.execute (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\compiled\next-server\app-route.runtime.dev.js:6:46527) at async eO.handle (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\compiled\next-server\app-route.runtime.dev.js:6:57165) at async doRender (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\server\base-server.js:1352:42) at async cacheEntry.responseCache.get.routeKind (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\server\base-server.js:1574:28) at async DevServer.renderToResponseWithComponentsImpl (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\server\base-server.js:1482:28) at async DevServer.renderPageComponent (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\server\base-server.js:1908:24) at async DevServer.renderToResponseImpl (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\server\base-server.js:1946:32) at async DevServer.pipeImpl (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\server\base-server.js:921:25) at async NextNodeServer.handleCatchallRenderRequest (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\server\next-server.js:272:17) at async DevServer.handleRequestImpl (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\server\base-server.js:817:17) at async C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\server\dev\next-dev-server.js:339:20 at async Span.traceAsyncFn (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\trace\trace.js:154:20) at async DevServer.handleRequest (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\server\dev\next-dev-server.js:336:24) at async invokeRender (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\server\lib\router-server.js:173:21) at async handleRequest (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\server\lib\router-server.js:350:24) at async requestHandlerImpl (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7.0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\server\lib\router-server.js:374:13) at async Server.requestListener (C:\Users\santu\OneDrive\Desktop\aptos\client\node_modules.pnpm\next@14.2.16@babel+core@7._0930d47915e3a5faec7f5840bafbd4fd\node_modules\next\dist\server\lib\start-server.js:141:13)
The error is occurring because we're mixing Node.js and browser APIs incorrectly. The issue is with the FormData and Blob usage in the server-side environment. Let me fix this:
The issue was with the FormData and Blob usage in the Node.js server environment. However, I realize there's still a compatibility issue. Let me provide a better solution using a proper Node.js approach:
I've fixed the FormData issue by implementing a manual multipart form data builder that's compatible with Node.js server environment. Here's what I changed:
uploadToPinata() for audio files with proper WAV content typeuploadJsonToPinata() for JSON files with application/json content typeaudio/wav for podcast filesapplication/json for transcript filesThe manual FormData construction bypasses the Node.js/browser compatibility issues and should work reliably with Pinata's API. The system now provides robust dual storage with proper error handling!