Skip to content
Open
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
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#For the speech to text model
GROQ_API_KEY=

# LangGraph Configuration
NEXT_PUBLIC_API_URL=http://localhost:2024
NEXT_PUBLIC_ASSISTANT_ID=agent
Expand All @@ -10,4 +13,4 @@ LANGSMITH_API_KEY=
# LANGGRAPH_API_URL="https://my-agent.default.us.langgraph.app"
# This should be the URL of your website + "/api". This is how you connect to the API proxy
# NEXT_PUBLIC_API_URL="https://my-website.com/api"
# LANGSMITH_API_KEY="lsv2_..."
# LANGSMITH_API_KEY="lsv2_..."
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"esbuild": "^0.25.0",
"esbuild-plugin-tailwindcss": "^2.0.1",
"framer-motion": "^12.4.9",
"groq-sdk": "^0.27.0",
"katex": "^0.16.21",
"langgraph-nextjs-api-passthrough": "^0.0.4",
"lodash": "^4.17.21",
Expand Down
59 changes: 28 additions & 31 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 55 additions & 0 deletions src/app/api/transcribe/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// src/app/api/transcribe/route.ts
import { NextRequest, NextResponse } from 'next/server';
import Groq from 'groq-sdk';

const groq = new Groq({
apiKey: process.env.GROQ_API_KEY,
});

export async function POST(request: NextRequest) {
try {
const formData = await request.formData();
const audioFile = formData.get('audio') as File;

if (!audioFile) {
return NextResponse.json(
{ error: 'No audio file provided' },
{ status: 400 }
);
}

console.log('Audio file received:', {
name: audioFile.name,
size: audioFile.size,
type: audioFile.type
});

// Create a proper File object for Groq API
const transcription = await groq.audio.transcriptions.create({
file: audioFile, // Pass the File directly, not a Blob
model: "whisper-large-v3-turbo",
response_format: "text",
language: "fr",
});

console.log('Transcription result:', transcription);

return NextResponse.json({
transcription: typeof transcription === 'string' ? transcription : transcription.text
});

} catch (error) {
console.error('Transcription error:', error);

// More detailed error logging
if (error instanceof Error) {
console.error('Error message:', error.message);
console.error('Error stack:', error.stack);
}

return NextResponse.json(
{ error: 'Failed to transcribe audio', details: error instanceof Error ? error.message : 'Unknown error' },
{ status: 500 }
);
}
}
40 changes: 38 additions & 2 deletions src/components/thread/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// src/components/thread/index.tsx
import { v4 as uuidv4 } from "uuid";
import { ReactNode, useEffect, useRef } from "react";
import { motion } from "framer-motion";
import { cn } from "@/lib/utils";
import { useStreamContext } from "@/providers/Stream";
import { useState, FormEvent } from "react";
import { Button } from "../ui/button";
import { VoiceButton } from "../ui/voice-button";
import { useVoiceRecording } from "@/hooks/use-voice-recording";
import { Checkpoint, Message } from "@langchain/langgraph-sdk";
import { AssistantMessage, AssistantMessageLoading } from "./messages/ai";
import { HumanMessage } from "./messages/human";
Expand Down Expand Up @@ -145,6 +148,28 @@ export function Thread() {

const lastError = useRef<string | undefined>(undefined);

// Voice recording functionality
const voiceRecording = useVoiceRecording({
onTranscriptionComplete: (text: string) => {
// Append transcription to existing input, or replace if input is empty
setInput(prev => {
const trimmedPrev = prev.trim();
if (trimmedPrev.length === 0) {
return text;
}
// Add a space between existing text and new transcription
return trimmedPrev + " " + text;
});
},
onError: (error: string) => {
toast.error("Voice recording error", {
description: error,
richColors: true,
closeButton: true,
});
},
});

const setThreadId = (id: string | null) => {
_setThreadId(id);

Expand Down Expand Up @@ -513,6 +538,15 @@ export function Thread() {
accept="image/jpeg,image/png,image/gif,image/webp,application/pdf"
className="hidden"
/>

{/* Voice Recording Button */}
<VoiceButton
isRecording={voiceRecording.isRecording}
isTranscribing={voiceRecording.isTranscribing}
onToggleRecording={voiceRecording.toggleRecording}
disabled={isLoading}
/>

{stream.isLoading ? (
<Button
key="stop"
Expand All @@ -528,7 +562,9 @@ export function Thread() {
className="ml-auto shadow-md transition-all"
disabled={
isLoading ||
(!input.trim() && contentBlocks.length === 0)
(!input.trim() && contentBlocks.length === 0) ||
voiceRecording.isRecording ||
voiceRecording.isTranscribing
}
>
Send
Expand Down Expand Up @@ -559,4 +595,4 @@ export function Thread() {
</div>
</div>
);
}
}
Loading