Skip to content

Recipes ​

Common Vue patterns for autopilot-client. Each example assumes you have wired up a client in main.ts as shown in Vue overview.

Register a function from inside a component ​

Scope the function to the component's lifetime with useAutopilotScope():

vue
<script setup lang="ts">
import { ref, watchEffect } from 'vue';
import { useAutopilotScope, useAutopilotStore } from '@llmor/autopilot-vue';

const store = useAutopilotStore();
const scope = useAutopilotScope();
const settingsOpen = ref(false);

scope.registerFunction({
  name: 'open_settings',
  description: 'Opens the settings dialog.',
  handler: () => {
    settingsOpen.value = true;
  },
});

// context keys too: claim the key, then keep it in step with the dialog
scope.claimContext('settings');
watchEffect(() => {
  store.context.settings = { open: settingsOpen.value };
});
</script>

Everything registered or claimed through the scope is removed when the component unmounts (or the effectScope stops). registerFunction and claimContext also return a disposer if you need to remove something early.

Don't clean up by name

onScopeDispose(() => client.unregisterFunction('open_settings')) looks equivalent but isn't. When the component is replaced by a fresh instance of itself inside <Suspense> (Nuxt pages, or a keyed router-view wrapped in one), the new instance runs setup and registers open_settings before the old one is unmounted. The old instance would then remove the new one's function. The scope only removes registrations that are still its own.

Sync the current route into context ​

ts
// router-context.ts
import { watchEffect } from 'vue';
import { useRoute } from 'vue-router';
import { useAutopilotStore } from '@llmor/autopilot-vue';

export function useRouteContext() {
  const route = useRoute();
  const store = useAutopilotStore();

  watchEffect(() => {
    store.context.route = {
      path: route.path,
      params: route.params,
      query: route.query,
    };
  });
}

Call useRouteContext() once in your root component.

React to streaming ​

vue
<script setup lang="ts">
import { watch } from 'vue';
import { useAutopilotStore } from '@llmor/autopilot-vue';

const store = useAutopilotStore();

watch(
  () => store.isStreaming,
  (streaming) => {
    document.body.dataset.assistantBusy = String(streaming);
  },
);
</script>

Scroll a chat container to the bottom ​

vue
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue';
import { useAutopilotConversation } from '@llmor/autopilot-vue';

const { visibleMessages, streamingMessage } = useAutopilotConversation();
const scroller = ref<HTMLElement>();

watch(
  [visibleMessages, () => streamingMessage.value?.message],
  async () => {
    await nextTick();
    scroller.value?.scrollTo({ top: scroller.value.scrollHeight });
  },
);
</script>

<template>
  <div ref="scroller" class="chat">
    <article v-for="(msg, i) in visibleMessages" :key="i">{{ msg.message }}</article>
  </div>
</template>

Wait for connection before rendering a feature ​

vue
<script setup lang="ts">
import { useAutopilotStore } from '@llmor/autopilot-vue';
const store = useAutopilotStore();
</script>

<template>
  <Suspense>
    <template #default>
      <AutopilotFeature v-if="store.isConnected" />
      <p v-else>Connecting to the assistant…</p>
    </template>
  </Suspense>
</template>

Show toasts for function errors ​

ts
import { useAutopilotClient } from '@llmor/autopilot-core';
import { useToast } from '@/composables/useToast';

const client = useAutopilotClient();
const toast = useToast();

client.on('functionError', ({ request, error }) => {
  toast.error(`${request.name}: ${error.message}`);
});