> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spatialreal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Speech-to-Avatar Walkthrough

> Step-by-step code walkthrough for the Speech-to-Avatar quickstart

This page explains the quickstart in steps and keeps each section copy-friendly.

<Card title="Run Quickstart First" icon="rocket" href="/overview/speech-to-avatar">
  Use the clone-and-run flow first, then come back here for code details.
</Card>

<Steps>
  <Step title="Configure environment variables">
    Set the required app/avatar/token values used by the frontend runtime.

    <div className="walkthrough-code">
      ```bash title=".env" theme={null}
      VITE_SPATIALREAL_AVATAR_ID=6aed28f9-674c-4ffb-89ee-b447b28aa3ed # https://app.spatialreal.ai/avatars/library
      VITE_SPATIALREAL_APP_ID=your_app_id # https://app.spatialreal.ai/apps
      VITE_SPATIALREAL_SESSION_TOKEN=your_temporary_session_token # https://app.spatialreal.ai/apps -> details -> Generate Temporary Token
      ```
    </div>
  </Step>

  <Step title="Enable AvatarKit Vite plugin">
    The plugin is required so AvatarKit WASM assets are correctly handled in dev/build.

    <div className="walkthrough-code">
      ```ts title="vite.config.ts" theme={null}
      import { defineConfig } from 'vite'
      import vue from '@vitejs/plugin-vue'
      import { avatarkitVitePlugin } from '@spatialwalk/avatarkit/vite'

      export default defineConfig({
        plugins: [vue(), avatarkitVitePlugin()],
        server: { port: 3000 },
      })
      ```
    </div>
  </Step>

  <Step title="Implement connect + send flow in App.vue">
    This file handles SDK init, avatar loading, connection, and one-shot PCM send.

    <div className="walkthrough-code">
      ```vue title="src/App.vue" theme={null}
      <script setup lang="ts">
      import { nextTick, onBeforeUnmount, ref } from 'vue'
      import {
        AvatarManager,
        AvatarSDK,
        AvatarView,
        DrivingServiceMode,
        Environment,
      } from '@spatialwalk/avatarkit'

      const container = ref<HTMLDivElement | null>(null)
      const status = ref('Click Connect Avatar to start')
      const connecting = ref(false)
      const sending = ref(false)
      const connected = ref(false)

      let avatarView: AvatarView | null = null

      const appId = import.meta.env.VITE_SPATIALREAL_APP_ID
      const avatarId = import.meta.env.VITE_SPATIALREAL_AVATAR_ID
      const sessionToken = import.meta.env.VITE_SPATIALREAL_SESSION_TOKEN

      if (!sessionToken) throw new Error('Missing VITE_SPATIALREAL_SESSION_TOKEN')

      async function downloadPcmAudio(url: string): Promise<ArrayBuffer> {
        const response = await fetch(url)
        if (!response.ok) throw new Error('Failed to load demo audio URL')
        return response.arrayBuffer()
      }

      async function disposeAvatar(): Promise<void> {
        connected.value = false
        avatarView?.controller.close()
        avatarView?.dispose()
        avatarView = null
      }

      async function connectAvatar(): Promise<void> {
        if (connecting.value || connected.value) return
        connecting.value = true

        try {
          status.value = 'Using session token from .env...'

          if (!AvatarSDK.isInitialized) {
            await AvatarSDK.initialize(appId, {
              environment: Environment.intl,
              drivingServiceMode: DrivingServiceMode.sdk,
            })
          }
          AvatarSDK.setSessionToken(sessionToken)

          await nextTick()
          const mountEl = container.value
          if (!mountEl) throw new Error('Avatar container is not ready')

          if (!avatarView) {
            status.value = 'Loading avatar...'
            const avatar = await AvatarManager.shared.load(avatarId)
            avatarView = new AvatarView(avatar, mountEl)
            avatarView.controller.onConnectionState = (state) => {
              connected.value = state === 'connected'
            }
          }

          status.value = 'Connecting to SpatialReal...'
          await avatarView.controller.initializeAudioContext()
          await avatarView.controller.start()
          await new Promise((resolve) => setTimeout(resolve, 300))
          if (!connected.value) throw new Error('Failed to connect to animation channel')

          status.value = 'Avatar connected. Click Send Audio.'
        } catch (error) {
          status.value = error instanceof Error ? error.message : 'Failed to run demo'
        } finally {
          connecting.value = false
        }
      }

      async function sendAudio(): Promise<void> {
        if (sending.value) return
        if (!connected.value || !avatarView) {
          status.value = 'Please click Connect Avatar first.'
          return
        }

        sending.value = true

        try {
          status.value = 'Downloading demo PCM audio...'
          const audioData = await downloadPcmAudio('https://cdn.spatialwalk.cloud/public/website/quickstart_voice.pcm')

          status.value = 'Sending audio...'
          avatarView.controller.send(audioData, true)
          status.value = `Audio sent (${audioData.byteLength} bytes)`
        } catch (error) {
          status.value = error instanceof Error ? error.message : 'Failed to send audio'
        } finally {
          sending.value = false
        }
      }

      onBeforeUnmount(async () => {
        await disposeAvatar()
      })
      </script>

      <template>
        <div style="min-height:100vh; display:flex; align-items:center; justify-content:center; padding:16px;">
          <div style="width:min(720px, 100%); display:flex; flex-direction:column; gap:10px;">
            <div ref="container" style="width:100%; aspect-ratio:16/10; min-height:320px; border-radius:12px; overflow:hidden; border:1px solid;" />

            <div style="display:flex; gap:8px; flex-wrap:wrap;">
              <button :disabled="connecting || connected" @click="connectAvatar">
                {{ connecting ? 'Connecting...' : connected ? 'Avatar Connected' : 'Connect Avatar' }}
              </button>
              <button :disabled="sending || !connected" @click="sendAudio">
                {{ sending ? 'Sending...' : 'Send Audio' }}
              </button>
            </div>

            <div style="font-size:14px;">{{ status }}</div>
          </div>
        </div>
      </template>
      ```
    </div>
  </Step>

  <Step title="Understand runtime sequence">
    Use this as a mental model when debugging connection or audio send issues.

    <div className="walkthrough-code">
      ```text theme={null}
      initialize SDK -> set session token -> load avatar -> create AvatarView -> start controller
      -> fetch pcm -> controller.send(audio, true)
      ```
    </div>
  </Step>
</Steps>
