diff --git a/src/common/components/icons/vendors/InworldIcon.tsx b/src/common/components/icons/vendors/InworldIcon.tsx
new file mode 100644
index 000000000..8ea773f4b
--- /dev/null
+++ b/src/common/components/icons/vendors/InworldIcon.tsx
@@ -0,0 +1,10 @@
+import * as React from 'react';
+
+import { SvgIcon, SvgIconProps } from '@mui/joy';
+
+export function InworldIcon(props: SvgIconProps) {
+ return
+
+
+ ;
+}
diff --git a/src/modules/speex/components/SpeexConfigureEngineFull.tsx b/src/modules/speex/components/SpeexConfigureEngineFull.tsx
index ac30cd27e..1281b0f51 100644
--- a/src/modules/speex/components/SpeexConfigureEngineFull.tsx
+++ b/src/modules/speex/components/SpeexConfigureEngineFull.tsx
@@ -13,7 +13,7 @@ import { FormTextField } from '~/common/components/forms/FormTextField';
import { TooltipOutlined } from '~/common/components/TooltipOutlined';
import { useToggleableBoolean } from '~/common/util/hooks/useToggleableBoolean';
-import type { DCredentialsApiKey, DSpeexEngine, DSpeexEngineAny, DSpeexVendorType, DVoiceElevenLabs, DVoiceLocalAI, DVoiceOpenAI, DVoiceWebSpeech } from '../speex.types';
+import type { DCredentialsApiKey, DSpeexEngine, DSpeexEngineAny, DSpeexVendorType, DVoiceElevenLabs, DVoiceInworld, DVoiceLocalAI, DVoiceOpenAI, DVoiceWebSpeech } from '../speex.types';
import { SPEEX_DEFAULTS, SPEEX_PREVIEW_STREAM, SPEEX_PREVIEW_TEXT } from '../speex.config';
import { SpeexVoiceAutocomplete } from './SpeexVoiceAutocomplete';
import { SpeexVoiceSelect } from './SpeexVoiceSelect';
@@ -21,13 +21,14 @@ import { speakText } from '../speex.client';
import { speexVendorTypeLabel } from './SpeexEngineSelect';
-function CredentialsApiKeyInputs({ credentials, onUpdate, vendorType, showHost, hostRequired, hostPlaceholder }: {
+function CredentialsApiKeyInputs({ credentials, onUpdate, vendorType, showHost, hostRequired, hostPlaceholder, keyPlaceholder }: {
credentials: DCredentialsApiKey;
onUpdate: (credentials: DCredentialsApiKey) => void;
vendorType: DSpeexVendorType;
showHost?: boolean;
hostRequired?: boolean;
hostPlaceholder?: string;
+ keyPlaceholder?: string;
}) {
return <>
@@ -35,6 +36,7 @@ function CredentialsApiKeyInputs({ credentials, onUpdate, vendorType, showHost,
autoCompleteId={`tts-${vendorType}-key`}
title='API Key'
description={hostRequired ? 'Optional' : speexVendorTypeLabel(vendorType)}
+ placeholder={keyPlaceholder}
value={credentials.apiKey}
onChange={value => onUpdate({ ...credentials, apiKey: value })}
required={!hostRequired}
@@ -115,6 +117,8 @@ export function SpeexConfigureEngineFull(props: {
{/* Engine-Specific pane */}
{engine.vendorType === 'elevenlabs' ? (
+ ) : engine.vendorType === 'inworld' ? (
+
) : engine.vendorType === 'localai' ? (
) : engine.vendorType === 'openai' ? (
@@ -219,6 +223,83 @@ function ElevenLabsConfig({ engine, onUpdate, mode, isMobile }: {
}
+function InworldConfig({ engine, onUpdate, mode, isMobile }: {
+ engine: DSpeexEngine<'inworld'>,
+ onUpdate: (updates: Partial>) => void;
+ isMobile: boolean;
+ mode: 'full' | 'voice-only';
+}) {
+
+ const { credentials, voice } = engine;
+ const showCredentials = mode === 'full' && !engine.isAutoLinked && credentials.type === 'api-key';
+
+ const handleCredentialsUpdate = React.useCallback((newCredentials: DCredentialsApiKey) => {
+ onUpdate({ credentials: newCredentials });
+ }, [onUpdate]);
+
+ const handleVoiceChange = React.useCallback((ttsVoiceId: DVoiceInworld['ttsVoiceId']) => {
+ const { ttsVoiceId: _, ...restVoice } = voice;
+ onUpdate({
+ voice: {
+ ...restVoice,
+ ...(ttsVoiceId && { ttsVoiceId }),
+ },
+ });
+ }, [onUpdate, voice]);
+
+ const handleSpeedChange = React.useCallback((value: number) => {
+ onUpdate({ voice: { ...voice, ttsSpeakingRate: value } });
+ }, [onUpdate, voice]);
+
+ return <>
+
+ {/* Credentials (only for manually added engines in full mode) */}
+ {showCredentials && (
+
+ )}
+
+ >
+ title='Model'
+ alignEnd
+ options={[
+ { value: 'inworld-tts-1.5-max', label: 'TTS 1.5 Max', description: 'Quality' },
+ { value: 'inworld-tts-1.5-mini', label: 'TTS 1.5 Mini', description: 'Fast' },
+ ]}
+ value={voice.ttsModel ?? SPEEX_DEFAULTS.INWORLD_MODEL}
+ onChange={(value) => onUpdate({ voice: { ...voice, ttsModel: value } })}
+ />
+
+
+
+
+
+
+
+
+ >;
+}
+
+
function LocalAIConfig({ engine, onUpdate, mode, isMobile }: {
engine: DSpeexEngine<'localai'>,
onUpdate: (updates: Partial>) => void;
diff --git a/src/modules/speex/components/SpeexConfigureEngines.tsx b/src/modules/speex/components/SpeexConfigureEngines.tsx
index 737d88abe..44d1538b0 100644
--- a/src/modules/speex/components/SpeexConfigureEngines.tsx
+++ b/src/modules/speex/components/SpeexConfigureEngines.tsx
@@ -18,6 +18,7 @@ import LinkIcon from '@mui/icons-material/Link';
import { ConfirmationModal } from '~/common/components/modals/ConfirmationModal';
import { ElevenLabsIcon } from '~/common/components/icons/vendors/ElevenLabsIcon';
import { FormLabelStart } from '~/common/components/forms/FormLabelStart';
+import { InworldIcon } from '~/common/components/icons/vendors/InworldIcon';
import { LocalAIIcon } from '~/common/components/icons/vendors/LocalAIIcon';
import { OpenAIIcon } from '~/common/components/icons/vendors/OpenAIIcon';
import { TooltipOutlined } from '~/common/components/TooltipOutlined';
@@ -101,6 +102,7 @@ const _styles = {
const ADDABLE_VENDORS: { vendorType: DSpeexVendorType; label: string; description: string, icon?: React.FunctionComponent }[] = [
{ vendorType: 'elevenlabs', label: 'ElevenLabs', description: 'Premium voices', icon: ElevenLabsIcon },
+ { vendorType: 'inworld', label: 'Inworld', description: 'Expressive AI voices', icon: InworldIcon },
{ vendorType: 'localai', label: 'LocalAI', description: 'Self-hosted TTS', icon: LocalAIIcon },
{ vendorType: 'openai', label: 'OpenAI TTS', description: 'Reliable', icon: OpenAIIcon },
] as const;