diff --git a/evi/evi-next-js-function-calling/.nvmrc b/evi/evi-next-js-function-calling/.nvmrc deleted file mode 100644 index 603606bc..00000000 --- a/evi/evi-next-js-function-calling/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -18.17.0 diff --git a/evi/evi-next-js-function-calling/.prettierrc.json b/evi/evi-next-js-function-calling/.prettierrc.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/evi/evi-next-js-function-calling/.prettierrc.json @@ -0,0 +1 @@ +{} diff --git a/evi/evi-next-js-function-calling/README.md b/evi/evi-next-js-function-calling/README.md index b43abe42..f460d289 100644 --- a/evi/evi-next-js-function-calling/README.md +++ b/evi/evi-next-js-function-calling/README.md @@ -3,6 +3,8 @@

Empathic Voice Interface | Next.js Function Calling Example

+![preview.png](preview.png) + ## Overview This project features a sample implementation of Hume's [Empathic Voice Interface](https://dev.hume.ai/docs/empathic-voice-interface-evi/overview) using Hume's [React SDK](https://github.com/HumeAI/empathic-voice-api-js/tree/main/packages/react). Here, we have a simple EVI that calls a function to get the weather for a given location. @@ -13,32 +15,73 @@ See the [Tool Use guide](https://dev.hume.ai/docs/empathic-voice-interface-evi/f 1. [Create a tool](https://dev.hume.ai/docs/empathic-voice-interface-evi/tool-use#create-a-tool) with the following payload: + Sample JSON Request Body + ```json { "name": "get_current_weather", - "description": "This tool is for getting the current weather.", - "parameters": "{ \"type\": \"object\", \"properties\": { \"location\": { \"type\": \"string\", \"description\": \"The city and state, e.g. San Francisco, CA\" }, \"format\": { \"type\": \"string\", \"enum\": [\"celsius\", \"fahrenheit\"], \"description\": \"The temperature unit to use. Infer this from the users location.\" } }, \"required\": [\"location\", \"format\"] }" + "description": "This tool is for getting the current weather in a given locale.", + "version_description": "Fetches current weather and uses celsius or fahrenheit based on location of user.", + "parameters": "{ \"type\": \"object\", \"properties\": { \"location\": { \"type\": \"string\", \"description\": \"The city and state, e.g. San Francisco, CA\" }, \"format\": { \"type\": \"string\", \"enum\": [\"celsius\", \"fahrenheit\"], \"description\": \"The temperature unit to use. Infer this from the users location.\" } }, \"required\": [\"location\", \"format\"] }", + "fallback_content": "The weather API is unavailable. Unable to fetch the current weather." } ``` + Sample cURL Request + + ```cURL + curl https://api.hume.ai/v0/evi/tools \ + -H "X-Hume-Api-Key: " \ + --json '{ + "name": "get_current_weather", + "description": "This tool is for getting the current weather in a given locale.", + "version_description": "Fetches current weather and uses celsius or fahrenheit based on location of user.", + "parameters": "{ \"type\": \"object\", \"properties\": { \"location\": { \"type\": \"string\", \"description\": \"The city and state, e.g. San Francisco, CA\" }, \"format\": { \"type\": \"string\", \"enum\": [\"celsius\", \"fahrenheit\"], \"description\": \"The temperature unit to use. Infer this from the users location.\" } }, \"required\": [\"location\", \"format\"] }", + "fallback_content": "The weather API is unavailable. Unable to fetch the current weather." + }' + ``` + 2. [Create a configuration](https://dev.hume.ai/docs/empathic-voice-interface-evi/tool-use#create-a-configuration) equipped with that tool: + Sample JSON Request Body + ```json { + "evi_version": "2", "name": "Weather Assistant Config", "language_model": { "model_provider": "ANTHROPIC", - "model_resource": "claude-3-5-sonnet-20240620" + "model_resource": "claude-3-7-sonnet-latest" }, "tools": [ { - "id": "", - "version": 0 + "id": "" } ] } ``` + Sample cURL Request + + ```cURL + curl -X POST https://api.hume.ai/v0/evi/configs \ + -H "X-Hume-Api-Key: " \ + -H "Content-Type: application/json" \ + -d '{ + "evi_version": "2", + "name": "Weather Assistant Config", + "language_model": { + "model_provider": "ANTHROPIC", + "model_resource": "claude-3-7-sonnet-latest" + }, + "tools": [ + { + "id": "" + } + ] + }' + ``` + ## Instructions 1. Clone this examples repository: @@ -51,7 +94,7 @@ See the [Tool Use guide](https://dev.hume.ai/docs/empathic-voice-interface-evi/f 2. Install dependencies: ```shell - pnpm install + npm install ``` 3. Set up your API key and Secret key: @@ -67,13 +110,13 @@ See the [Tool Use guide](https://dev.hume.ai/docs/empathic-voice-interface-evi/f You can copy the `.env.example` file to use as a template. -4. Add your Config ID to the `.env` file. This ID is from the EVI configuration you created earlier that includes your weather tool. +4. Add your Config ID to the `.env` file. This ID should be from the EVI configuration you created earlier that includes your weather tool. ```shell echo "NEXT_PUBLIC_HUME_CONFIG_ID=your_config_id_here" >> .env ``` -5. Add the Geocoding API key to the `.env` file. You can obtain it for free from [geocode.maps.co](https://geocode.maps.co/). +5. Add your Geocoding API key to the `.env` file. You can obtain it for free from [geocode.maps.co](https://geocode.maps.co/). ```shell echo "GEOCODING_API_KEY=your_geocoding_api_key_here" >> .env @@ -82,7 +125,7 @@ See the [Tool Use guide](https://dev.hume.ai/docs/empathic-voice-interface-evi/f 6. Run the project: ```shell - pnpm run dev + npm run dev ``` This will start the Next.js development server, and you can access the application at `http://localhost:3000`. diff --git a/evi/evi-next-js-function-calling/app/api/fetchWeather/route.ts b/evi/evi-next-js-function-calling/app/api/fetchWeather/route.ts index d11baade..f1e045e1 100644 --- a/evi/evi-next-js-function-calling/app/api/fetchWeather/route.ts +++ b/evi/evi-next-js-function-calling/app/api/fetchWeather/route.ts @@ -1,14 +1,18 @@ -import { NextResponse } from 'next/server'; -import { fetchWeather } from '@/utils/fetchWeather'; +import { NextResponse } from "next/server"; +import { fetchWeather } from "@/utils/fetchWeather"; export async function POST(request: Request) { const { parameters } = await request.json(); + console.log(parameters); try { const currentWeather = await fetchWeather(parameters); return NextResponse.json({ success: true, data: currentWeather }); } catch (error) { - console.error('Error in fetchWeather API route:', error); - return NextResponse.json({ success: false, error: 'Weather tool error' }, { status: 500 }); + console.error("Error in fetchWeather API route:", error); + return NextResponse.json( + { success: false, error: "Weather tool error" }, + { status: 500 }, + ); } -} \ No newline at end of file +} diff --git a/evi/evi-next-js-function-calling/app/components/ClientComponent.tsx b/evi/evi-next-js-function-calling/app/components/ClientComponent.tsx deleted file mode 100644 index 0e4c797d..00000000 --- a/evi/evi-next-js-function-calling/app/components/ClientComponent.tsx +++ /dev/null @@ -1,57 +0,0 @@ -'use client'; -import { - VoiceProvider, - ToolCallHandler -} from '@humeai/voice-react'; -import Messages from './Controls'; -import Controls from './Messages'; - -const handleToolCall: ToolCallHandler = async ( - message, - send, -) => { - if (message.name === 'get_current_weather') { - try { - const response = await fetch('/api/fetchWeather', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ parameters: message.parameters }), - }); - - const result = await response.json(); - - if (result.success) { - return send.success(result.data); - } else { - return send.error(result.error); - } - } catch (error) { - return send.error({ - error: 'Weather tool error', - code: 'weather_tool_error', - level: 'warn', - content: 'There was an error with the weather tool', - }); - } - } - - return send.error({ - error: 'Tool not found', - code: 'tool_not_found', - level: 'warn', - content: 'The tool you requested was not found', - }); -}; - -export default function ClientComponent({ accessToken }: { accessToken: string }) { - return ( - - - - - ); -} diff --git a/evi/evi-next-js-function-calling/app/components/Controls.tsx b/evi/evi-next-js-function-calling/app/components/Controls.tsx deleted file mode 100644 index 860ffba4..00000000 --- a/evi/evi-next-js-function-calling/app/components/Controls.tsx +++ /dev/null @@ -1,28 +0,0 @@ -"use client"; -import { useVoice, VoiceReadyState } from "@humeai/voice-react"; -import React from "react"; - -export default function Controls() { - const { connect, disconnect, status } = useVoice(); - - const handleConnect = () => { - try { - void connect(); - } catch (error) { - console.error("Error connecting:", error); - } - }; - - const handleDisconnect = () => { - disconnect(); - }; - - return ( - - ); -} diff --git a/evi/evi-next-js-function-calling/app/components/Messages.tsx b/evi/evi-next-js-function-calling/app/components/Messages.tsx deleted file mode 100644 index a141fd1b..00000000 --- a/evi/evi-next-js-function-calling/app/components/Messages.tsx +++ /dev/null @@ -1,25 +0,0 @@ -"use client"; -import { useVoice } from "@humeai/voice-react"; - -export default function Controls() { - const { messages } = useVoice(); - - return ( -
- {messages.map((msg, index) => { - // Don't do anything if the message is not a User or Assistant message - if (msg.type !== "user_message" && msg.type !== "assistant_message") { - return null; - } - - // Render User and Assistant messages - const { role, content } = msg.message; - return ( -
-
{role}: {content}
-
- ); - })} -
- ); -} diff --git a/evi/evi-next-js-function-calling/app/error.tsx b/evi/evi-next-js-function-calling/app/error.tsx index dbda55a8..c4e8b016 100644 --- a/evi/evi-next-js-function-calling/app/error.tsx +++ b/evi/evi-next-js-function-calling/app/error.tsx @@ -1,30 +1,12 @@ -'use client' // Error boundaries must be Client Components - -import { useEffect } from 'react' - -export default function Error({ - error, - reset, -}: { - error: Error & { digest?: string } - reset: () => void -}) { - useEffect(() => { - // Log the error to an error reporting service - console.error(error) - }, [error]) - +"use client"; + +export default function Error() { return ( -
-

Something went wrong!

- +
+
+

An unexpected error occurred

+

Please try again later

+
- ) -} \ No newline at end of file + ); +} diff --git a/evi/evi-next-js-function-calling/app/globals.css b/evi/evi-next-js-function-calling/app/globals.css index 875c01e8..ff533c3b 100644 --- a/evi/evi-next-js-function-calling/app/globals.css +++ b/evi/evi-next-js-function-calling/app/globals.css @@ -2,32 +2,58 @@ @tailwind components; @tailwind utilities; -:root { - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; -} - -@media (prefers-color-scheme: dark) { +@layer base { :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 240 5.9% 10%; + --radius: 0.5rem; } -} -body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); + .dark { + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 240 4.9% 83.9%; + } } -@layer utilities { - .text-balance { - text-wrap: balance; +@layer base { + * { + @apply border-border font-sans; + } + body { + @apply bg-background text-foreground; } } diff --git a/evi/evi-next-js-function-calling/app/layout.tsx b/evi/evi-next-js-function-calling/app/layout.tsx index 3314e478..b390940a 100644 --- a/evi/evi-next-js-function-calling/app/layout.tsx +++ b/evi/evi-next-js-function-calling/app/layout.tsx @@ -1,12 +1,13 @@ import type { Metadata } from "next"; -import { Inter } from "next/font/google"; +import { GeistSans } from "geist/font/sans"; +import { GeistMono } from "geist/font/mono"; import "./globals.css"; - -const inter = Inter({ subsets: ["latin"] }); +import { Nav } from "@/components/Nav"; +import { cn } from "@/utils"; export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Hume AI - EVI - Next.js Starter", + description: "A Next.js starter using Hume AI's Empathic Voice Interface", }; export default function RootLayout({ @@ -16,7 +17,16 @@ export default function RootLayout({ }>) { return ( - {children} + +