Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
280 changes: 280 additions & 0 deletions docs/api/ai-vue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
---
title: TanStack AI Vue API
slug: /api/ai-vue
---

Vue hooks for TanStack AI, providing convenient Vue bindings for the headless client.

## Installation

```bash
npm install @tanstack/ai-vue
```

## `useChat(options?)`

Main composable for managing chat state in Vue with full type safety.

```vue
<script setup lang="ts">
import { ref } from "vue";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-vue";
import {
clientTools,
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
// see more in https://tanstack.com/ai/latest/docs/guides/client-tools#defining-client-tools
import { updateUIDef } from "@/tools/definitions";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tool def seems to be buried in one of the other pages, maybe in-lining it would be wise?
Or at least a link to this tool definition?
@AlemTuzlak


const notification = ref<string | null>(null);

// Create client tool implementations,
const updateUI = updateUIDef.client((input) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what is the purpose of this tool. Maybe it makes sense in React to set state causing a re-render, but in Vue it would do nothing as notification ref is not used anywhere.

@AlemTuzlak Was original React intention just to cause a re-render?

notification.value = input.message;
return { success: true };
});

// Create typed tools array (no 'as const' needed!)
const tools = clientTools(updateUI);

const chatOptions = createChatClientOptions({
connection: fetchServerSentEvents("/api/chat"),
tools,
});

// Fully typed messages!
type ChatMessages = InferChatMessages<typeof chatOptions>;

const { messages, sendMessage, isLoading, error, addToolApprovalResponse } = useChat(chatOptions);
</script>

<template>
<div><!-- Chat UI with typed messages --></div>
</template>
```

### Options

Extends `ChatClientOptions` from `@tanstack/ai-client`:

- `connection` - Connection adapter (required)
- `tools?` - Array of client tool implementations (with `.client()` method)
- `initialMessages?` - Initial messages array
- `id?` - Unique identifier for this chat instance
- `body?` - Additional body parameters to send
- `onResponse?` - Callback when response is received
- `onChunk?` - Callback when stream chunk is received
- `onFinish?` - Callback when response finishes
- `onError?` - Callback when error occurs
- `streamProcessor?` - Stream processing configuration

**Note:** Client tools are now automatically executed - no `onToolCall` callback needed!

### Returns

```typescript
interface UseChatReturn {
messages: DeepReadonly<ShallowRef<Array<UIMessage>>>;
sendMessage: (content: string) => Promise<void>;
append: (message: ModelMessage | UIMessage) => Promise<void>;
addToolResult: (result: {
toolCallId: string;
tool: string;
output: any;
state?: "output-available" | "output-error";
errorText?: string;
}) => Promise<void>;
addToolApprovalResponse: (response: {
id: string;
approved: boolean;
}) => Promise<void>;
reload: () => Promise<void>;
stop: () => void;
isLoading: DeepReadonly<ShallowRef<boolean>>;
error: DeepReadonly<ShallowRef<Error | undefined>>;
setMessages: (messages: UIMessage[]) => void;
clear: () => void;
}
```

## Connection Adapters

Re-exported from `@tanstack/ai-client` for convenience:

```typescript
import {
fetchServerSentEvents,
fetchHttpStream,
stream,
type ConnectionAdapter,
} from "@tanstack/ai-vue";
```

## Example: Basic Chat

```vue
<script setup lang="ts">
import { ref } from "vue";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-vue";

const input = ref('')

const { messages, sendMessage, isLoading } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});

const handleSubmit = (e: Event) => {
const val = input.value.trim()
if (((val ?? '') !== '') && !isLoading) {
sendMessage(input.value);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we would need e.target.reset() as well to clear the form on submit.

input.value = '';
}
Comment on lines +127 to +132
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Fix ref access and simplify condition.

The condition on line 129 should use !isLoading.value since isLoading is a ShallowRef<boolean> and requires explicit .value access in the script section (Vue only auto-unwraps refs in templates).

Additionally, the condition on line 128 has unnecessary nested parentheses.

πŸ”Ž Proposed fix
 const handleSubmit = (e: Event) => {
   const val = input.value.trim()
-  if (((val ?? '') !== '') && !isLoading) {
+  if (val !== '' && !isLoading.value) {
     sendMessage(input.value);
     input.value = '';
   }
 };
πŸ€– Prompt for AI Agents
In @docs/api/ai-vue.md around lines 127-132, handleSubmit uses a Ref incorrectly
and has redundant parentheses; compute val as a trimmed string (e.g., const val
= input.value?.trim() ?? ''), check if val !== '' && !isLoading.value, and call
sendMessage(val) (not sendMessage(input.value)) before clearing input.value = ''
so you pass the trimmed text and properly access the ShallowRef isLoading via
.value.

};
</script>
<template>
<div>
<div v-for="message in messages" :key="message.id">
<strong>{{ message.role }}</strong>
<div v-for="part in message.parts" :key="part.id">
<span v-if="part.type === 'text'">{{ part.content }}</span>
<div v-else-if="part.type === 'thinking'" class="text-sm text-gray-500 italic"> πŸ’­ Thinking: {{ part.content }}</div>
<!-- you can custom more parts -->
</div>
</div>
<form @submit.prevent="handleSubmit">
<input v-model="input" :disabled="isLoading"/>
<button type="submit" :disabled="isLoading">
Send
</button>
</form>
<div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Fix closing tag syntax error.

Line 151 has a syntax error - it should be a closing </div> tag, not an opening <div> tag.

πŸ”Ž Proposed fix
     </form>
-  <div>
+  </div>
 </template>
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div>
</form>
</div>
</template>
πŸ€– Prompt for AI Agents
In @docs/api/ai-vue.md around line 151, Replace the incorrect opening "<div>"
tag at the end of the block with the proper closing "</div>" tag; locate the
stray "<div>" (the unmatched tag) and change it to "</div>" so the HTML
structure is properly closed.

</template>
```

## Example: Tool Approval

```vue
<script setup lang="ts">
import { useChat, fetchServerSentEvents } from "@tanstack/ai-vue";

const { messages, sendMessage, addToolApprovalResponse } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});

</script>
<template>
<div>
<template v-for="message in messages" >
<template v-for="part in message.parts" >
<div v-if="part.type === 'tool-call' && part.state === 'approval-requested' && part.approval" :key="part.id">
<p>Approve: {{ part.name }}</p>
<button @click="() => addToolApprovalResponse({ id: part.approval!.id, approved: true })">
Approve
</button>
<button @click="() => addToolApprovalResponse({ id: part.approval!.id, approved: false })">
Deny
</button>
</div>
</template>
</template>
</div>
</template>
```

## Example: Client Tools with Type Safety

```vue
<script setup lang="ts">
import { ref } from 'vue';
import { useChat, fetchServerSentEvents } from "@tanstack/ai-vue";
import {
clientTools,
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
import { updateUIDef, saveToStorageDef } from "@/tools/definitions";

const notification = ref<{message: string; type: 'success' | 'error'}>(null);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what for notification is used in this example

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Fix type definition to include null.

The ref is initialized with null but the type definition doesn't include null in the union, which is a type error.

πŸ”Ž Proposed fix
-const notification = ref<{message: string; type: 'success' | 'error'}>(null);
+const notification = ref<{message: string; type: 'success' | 'error'} | null>(null);
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const notification = ref<{message: string; type: 'success' | 'error'}>(null);
const notification = ref<{message: string; type: 'success' | 'error'} | null>(null);
πŸ€– Prompt for AI Agents
In @docs/api/ai-vue.md around line 198, The type for the reactive "notification"
ref doesn't allow null despite being initialized to null; update the declaration
of notification so its generic includes null (i.e., make the ref type `{
message: string; type: 'success' | 'error' } | null`) so the initial null value
is type-safe while preserving the original object shape.


const updateUI = updateUIDef.client((input) => {
// βœ… input is fully typed!
notification.value = { message: input.message, type: input.type };
return { success: true };
});

const saveToStorage = saveToStorageDef.client((input) => {
localStorage.setItem(input.key, input.value);
return { saved: true };
});

// Create typed tools array (no 'as const' needed!)
const tools = clientTools(updateUI, saveToStorage);

const { messages, sendMessage } = useChat({
connection: fetchServerSentEvents("/api/chat"),
tools, // βœ… Automatic execution, full type safety
});

</script>
<template>
<div>
<template v-for="message in messages" >
<template v-for="part in message.parts" >
<div v-if="part.type === 'tool-call' && part.name === 'updateUI'" :key="part.id">
<p>Tool executed: {{ part.name }}</p>
</div>
</template>
</template>
</div>
</template>
```

## `createChatClientOptions(options)`

Helper to create typed chat options (re-exported from `@tanstack/ai-client`).

```typescript
import {
clientTools,
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";

// Create typed tools array (no 'as const' needed!)
const tools = clientTools(tool1, tool2);

const chatOptions = createChatClientOptions({
connection: fetchServerSentEvents("/api/chat"),
tools,
});

type Messages = InferChatMessages<typeof chatOptions>;
```

## Types

Re-exported from `@tanstack/ai-client`:

- `UIMessage<TTools>` - Message type with tool type parameter
- `MessagePart<TTools>` - Message part with tool type parameter
- `TextPart` - Text content part
- `ThinkingPart` - Thinking content part
- `ToolCallPart<TTools>` - Tool call part (discriminated union)
- `ToolResultPart` - Tool result part
- `ChatClientOptions<TTools>` - Chat client options
- `ConnectionAdapter` - Connection adapter interface
- `InferChatMessages<T>` - Extract message type from options

Re-exported from `@tanstack/ai`:

- `toolDefinition()` - Create isomorphic tool definition
- `ToolDefinitionInstance` - Tool definition type
- `ClientTool` - Client tool type
- `ServerTool` - Server tool type

## Next Steps

- [Getting Started](../getting-started/quick-start) - Learn the basics
- [Tools Guide](../guides/tools) - Learn about the isomorphic tool system
- [Client Tools](../guides/client-tools) - Learn about client-side tools
6 changes: 5 additions & 1 deletion docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@
{
"label": "@tanstack/ai-solid",
"to": "api/ai-solid"
},
{
"label": "@tanstack/ai-vue",
"to": "api/ai-vue"
}
]
},
Expand Down Expand Up @@ -587,4 +591,4 @@
]
}
]
}
}