# Vue (/docs/quickstart/vue)

This page builds a small Vue 3 app, served by Vite, with a connect button and a chat that searches a bookshop catalog through its tools. Vue uses `@aiconnect/core` directly: a composable keeps the client's connection state in a Vue ref, and your components render from it.

The connect window of `@aiconnect/react` is made of React components, so a Vue app shows its own connect button, which calls `client.connect()`.

You need Vue 3, and Chrome with AI Wallet installed and one account connected to try it.

## Install the package [#install-the-package]

```bash
npm install @aiconnect/core
```

<Callout type="warn">
The package is not published on npm yet. Until it is, build a checkout of the AI Connect repository with `npm run build` and point your `package.json` at it with `"@aiconnect/core": "file:../aiconnect/packages/core"`.
</Callout>

## Follow the connection [#follow-the-connection]

Create one client for the page in a module of its own. In a browser it looks for AI Wallet right away and asks whether your site is already approved. `useConnection` gives each component the state as a ref.

```ts title="src/aiConnect.ts"
import { onScopeDispose, shallowRef } from 'vue';
import { AiConnect } from '@aiconnect/core';

// One client for the page. In a browser it looks for AI Wallet right away.
export const client = new AiConnect();

// The connection state as a Vue ref. It follows the client until the component unmounts.
export function useConnection() {
  const state = shallowRef(client.state);
  onScopeDispose(client.subscribe((next) => {
    state.value = next;
  }));
  return state;
}
```

`subscribe` calls your listener on each change only, so the ref starts from `client.state`. A `shallowRef` keeps the state object as the client built it, session included, and Vue updates the components each time the client replaces it. `onScopeDispose` removes the listener when the component unmounts.

`client.installUrl` is the AI Wallet page, https://tensorpass.app/wallet, where a visitor without the wallet gets it.

## Add the connect button [#add-the-connect-button]

The button renders one line per state. The [JavaScript quickstart](/docs/quickstart/javascript#follow-the-connection) explains each of the six states.

```vue title="src/components/ConnectButton.vue"
<script setup lang="ts">
import { client, useConnection } from '../aiConnect';

const state = useConnection();
</script>

<template>
  <p v-if="state.status === 'checking'" role="status">Looking for AI Wallet</p>
  <p v-else-if="state.status === 'noWallet'" role="status">
    Add AI Wallet to your browser, then reload this page.
    <a :href="client.installUrl" target="_blank" rel="noreferrer">Install AI Wallet</a>
  </p>
  <p v-else-if="state.status === 'connecting'" role="status">Approve this site in the AI Wallet window.</p>
  <p v-else-if="state.status === 'connected'" role="status">
    Connected with {{ state.providerName }}
    <button type="button" @click="client.disconnect()">Disconnect</button>
  </p>
  <p v-else role="status">
    {{ state.status === 'error' ? state.message : 'Use your own AI on this site.' }}
    <button type="button" @click="client.connect()">Connect your AI</button>
  </p>
</template>
```

A visitor who declines in the wallet's window lands in the `error` state with the wallet's reason, "The connection was declined", and the button asks the wallet again. A visitor who approved your site on an earlier visit is connected a moment after the page loads, with no click.

`client.disconnect()` removes your site from the visitor's approved sites in the wallet. The next `connect()` asks the visitor again.

## Run a conversation with your tools [#run-a-conversation-with-your-tools]

`runConversation` sends the conversation to the visitor's model, runs the tools it asks for, and returns the messages it added. The composable keeps the transcript and passes it back on the next question.

```ts title="src/useChat.ts"
import { ref } from 'vue';
import { finalAnswer, runConversation } from '@aiconnect/core';
import type { ChatMessage, ProviderSession, ToolCall, ToolSource } from '@aiconnect/core';

// A conversation on the visitor's AI. It keeps the transcript and shows the last answer with its tool calls.
export function useChat(system: string, tools: ToolSource) {
  const answer = ref('');
  const toolCalls = ref<ToolCall[]>([]);
  const busy = ref(false);
  const error = ref('');
  let transcript: ChatMessage[] = [{ role: 'system', content: system }];

  async function ask(session: ProviderSession, question: string) {
    const asked: ChatMessage = { role: 'user', content: question };
    answer.value = '';
    toolCalls.value = [];
    error.value = '';
    busy.value = true;
    try {
      const added = await runConversation({
        session,
        tools,
        messages: [...transcript, asked],
        onText: (delta) => {
          answer.value += delta;
        },
        onToolCall: (call) => {
          toolCalls.value.push(call);
        },
      });
      transcript = [...transcript, asked, ...added];
      // The streamed text covers every step of the loop; the final answer is the model's last message.
      answer.value = finalAnswer(added)?.content ?? answer.value;
    } catch (failure) {
      error.value = failure instanceof Error ? failure.message : String(failure);
    } finally {
      busy.value = false;
    }
  }

  return { answer, toolCalls, busy, error, ask };
}
```

`added` holds the assistant turns and the tool results of the question, so later questions see what the tools returned earlier. A chat request rejects with the wallet's message when it fails, for example when the visitor stops the answer from the wallet or when the provider refuses the call.

## Ask from a component [#ask-from-a-component]

`McpToolSource` reads the tools from your MCP server; the [MCP guide](/docs/guides/mcp-tools) shows how to serve one. The chat reads the session from the connection state when the visitor asks.

```vue title="src/components/BookChat.vue"
<script setup lang="ts">
import { ref } from 'vue';
import { McpToolSource } from '@aiconnect/core';
import { useConnection } from '../aiConnect';
import { useChat } from '../useChat';

const SYSTEM = 'You recommend books from this site\'s catalog. Always search the catalog before recommending.';

// Built once: the source keeps its MCP connection and the tool list it read.
const tools = new McpToolSource('/api/mcp', 'Bookshop');

const state = useConnection();
const { answer, toolCalls, busy, error, ask } = useChat(SYSTEM, tools);
const question = ref('');

function submit() {
  const text = question.value.trim();
  if (state.value.status !== 'connected' || !text) {
    return;
  }
  question.value = '';
  ask(state.value.session, text);
}
</script>

<template>
  <form v-if="state.status === 'connected'" @submit.prevent="submit">
    <label for="question">Your question</label>
    <input id="question" v-model="question" autocomplete="off" placeholder="A calm book for tonight" />
    <button type="submit" :disabled="busy">Ask</button>
    <p v-for="call in toolCalls" :key="call.id" class="tool">{{ call.name }} {{ JSON.stringify(call.arguments) }}</p>
    <p class="answer">{{ answer }}</p>
    <p v-if="error" role="alert">{{ error }}</p>
  </form>
  <p v-else>Connect your AI to ask for a book that fits your mood.</p>
</template>
```

Place both components in your app:

```vue title="src/App.vue"
<script setup lang="ts">
import BookChat from './components/BookChat.vue';
import ConnectButton from './components/ConnectButton.vue';
</script>

<template>
  <main>
    <h1>Bookshop</h1>
    <ConnectButton />
    <BookChat />
  </main>
</template>
```

The answer grows in the page as the model streams it. `ask` does not queue questions, which is why the button stays disabled while `busy` is true.

## Try it without a server [#try-it-without-a-server]

Any object with `listTools` and `callTool` is a tool source. Pass the `demoTools` catalog of the [React quickstart](/docs/quickstart/react#try-it-without-a-server) to `useChat` in place of the `McpToolSource` to try the chat before your MCP server exists.

## Run it [#run-it]

1. Start Vite with `npm run dev`. AI Wallet serves pages on HTTPS and on `http://localhost` or `http://127.0.0.1`.
2. Open the page in Chrome with AI Wallet installed and an account connected in the wallet.
3. Click **Connect your AI** and approve your site in the window the wallet opens.
4. Ask a question. The tool calls show as they run, and the answer streams from the account the wallet chose for your site.

The [core reference](/docs/reference/core) lists every export with its options.
