본문으로 건너뛰기

@tanstack/ai-vue

헤드리스 클라이언트에 편리한 Vue 3 바인딩을 제공하는 TanStack AI용 Vue 컴포저블입니다.

설치

npm install @tanstack/ai-vue

useChat(options?)

완전한 타입 안전성으로 Vue에서 채팅 상태를 관리하는 기본 컴포저블입니다.

import { useChat, fetchServerSentEvents } from "@tanstack/ai-vue";
import {
createChatClientOptions,
type InferChatMessages,
} from "@tanstack/ai-client";
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
import { ref } from "vue";

const updateUIDef = toolDefinition({
name: "updateUI",
description: "Show a notification in the UI",
inputSchema: z.object({ message: z.string() }),
});

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

// In <script setup>
const updateUI = updateUIDef.client((input) => {
notification.value = input.message;
return { success: true };
});

const tools = [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);

옵션

@tanstack/ai-clientChatClientOptions를 확장합니다(내부 상태 콜백 제외).

  • connection - 연결 어댑터(필수)
  • tools? - 클라이언트 도구 구현 배열(.client() 메서드 포함)
  • initialMessages? - 초기 메시지 배열
  • threadId? - 이 채팅의 유일한 식별자입니다. 영속성이 켜져 있으면 필수입니다. 생략하면 마운트 후 생성됩니다.
  • forwardedProps? - AG-UI RunAgentInput.forwardedProps 필드로 서버에 전달할 클라이언트 제어 JSON입니다(반응형이며 변경 사항은 watch를 통해 자동으로 동기화됩니다).
  • body? - 더 이상 권장되지 않습니다. 대신 forwardedProps를 사용합니다. 이전 버전과의 호환성을 위해 계속 작동하며, 전송 시 값이 forwardedProps에 병합됩니다(반응형).
  • byok? - defineByok의 선택적 BYOK 키링입니다. 전송할 때마다 클라이언트가 확인된 provider를 준비하고 x-byok-* 요청 헤더를 설정합니다. 키는 절대 본문에 포함되지 않습니다.
  • byokProvider? - 이 채팅의 provider slug를 반환하는 선택적 함수입니다. slug를 반환하면 해당 키만 준비하여 전송합니다. 그렇지 않으면 forwardedProps, body, 호출별 sendMessage body의 병합된 provider를 사용합니다. 나중의 소스가 우선합니다. slug가 확인되지 않으면 저장된 모든 키를 첨부하는 대신 전송에서 오류가 발생합니다.
  • context? - 클라이언트 도구 구현에 전달되는 타입이 지정된 클라이언트 로컬 런타임 컨텍스트입니다(반응형). 이 값은 서버로 직렬화되지 않습니다.
  • live? - 실시간 구독 모드를 활성화합니다(자동 구독/구독 취소).
  • onResponse? - 응답을 수신할 때 호출되는 콜백
  • onChunk? - 스트림 청크를 수신할 때 호출되는 콜백
  • onFinish? - 응답이 완료될 때 호출되는 콜백
  • onError? - 오류가 발생할 때 호출되는 콜백
  • onInterruptStateChange? - 인터럽트 상태가 변경될 때 호출되는 콜백입니다. 컨텍스트 소스는 복원된 상태에서는 hydrate, 스트리밍 또는 클라이언트가 시작한 업데이트에서는 live입니다.
  • onCustomEvent? - 사용자 지정 스트림 이벤트용 콜백
  • streamProcessor? - 스트림 처리 구성

참고: 클라이언트 도구는 이제 자동으로 실행되므로 onToolCall 콜백이 필요하지 않습니다!

반환값

import type { DeepReadonly, ShallowRef } from "vue";
import type { UIMessage } from "@tanstack/ai-vue";
import type { ModelMessage } from "@tanstack/ai/client";
import type {
MultimodalContent,
ChatClientState,
ConnectionStatus,
SendMessageOptions,
} from "@tanstack/ai-client";

interface UseChatReturn {
messages: DeepReadonly<ShallowRef<UIMessage[]>>;
sendMessage: (
content: string | MultimodalContent,
options?: SendMessageOptions,
) => 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>>;
status: DeepReadonly<ShallowRef<ChatClientState>>;
isSubscribed: DeepReadonly<ShallowRef<boolean>>;
connectionStatus: DeepReadonly<ShallowRef<ConnectionStatus>>;
sessionGenerating: DeepReadonly<ShallowRef<boolean>>;
setMessages: (messages: UIMessage[]) => void;
clear: () => void;
}

참고: 반응형 상태(messages, isLoading, error, status, isSubscribed, connectionStatus, sessionGenerating)는 DeepReadonly<ShallowRef<T>>로 래핑됩니다. <script setup>에서는 .value로 내부 값을 읽고(예: messages.value), <template>에서는 Vue가 ref의 래핑을 자동으로 해제하므로 이름만 사용합니다(예: v-for="m in messages"). 정리는 onScopeDispose를 통해 자동으로 수행됩니다.

useByok(client)

Vue에서 ByokClient 스냅샷을 구독합니다. 반환값은 DeepReadonly<ShallowRef<ByokSnapshot>>입니다.

import { useByok } from "@tanstack/ai-vue";
import { byok } from "./byok";

const snapshot = useByok(byok);
const openai = snapshot.value.status.openai;
const last4 = openai && "masked" in openai ? openai.masked : "No key";

snapshot.value에는 status, locked, prompt가 있습니다. 자체 UI에서 byok.update(provider, value)를 호출하여 키를 저장합니다. Bring Your Own Key를 참고하세요.

연결 어댑터

편의를 위해 @tanstack/ai-client에서 다시 내보냅니다.

import {
fetchServerSentEvents,
fetchHttpStream,
stream,
type ConnectionAdapter,
} from "@tanstack/ai-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 = () => {
if (input.value.trim() && !isLoading.value) {
sendMessage(input.value);
input.value = "";
}
};
</script>

<template>
<div>
<div>
<div v-for="message in messages" :key="message.id">
<strong>{{ message.role }}:</strong>
<template v-for="(part, idx) in message.parts" :key="idx">
<div
v-if="part.type === 'thinking'"
class="text-sm text-gray-500 italic"
>
Thinking: {{ part.content }}
</div>
<span v-else-if="part.type === 'text'">{{ part.content }}</span>
</template>
</div>
</div>
<form @submit.prevent="handleSubmit">
<input v-model="input" :disabled="isLoading" />
<button type="submit" :disabled="isLoading">Send</button>
</form>
</div>
</template>

예시: 도구 승인

<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" :key="message.id">
<template v-for="part in message.parts" :key="part.id">
<div
v-if="
part.type === 'tool-call' &&
part.state === 'approval-requested' &&
part.approval
"
>
<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>

예시: 타입 안전성을 갖춘 클라이언트 도구

<script setup lang="ts">
import { ref } from "vue";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-vue";
import {
createChatClientOptions,
type InferChatMessages,
} from "@tanstack/ai-client";
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";

const updateUIDef = toolDefinition({
name: "updateUI",
description: "Show a notification in the UI",
inputSchema: z.object({ message: z.string(), type: z.string() }),
});

const saveToStorageDef = toolDefinition({
name: "saveToStorage",
description: "Save a value to localStorage",
inputSchema: z.object({ key: z.string(), value: z.string() }),
});

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

// Create client implementations
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 = [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" :key="message.id">
<template v-for="(part, idx) in message.parts" :key="idx">
<div v-if="part.type === 'tool-call' && part.name === 'updateUI'">
Tool executed: {{ part.name }}
</div>
</template>
</template>
</div>
</template>

생성 컴포저블

이미지, 음성, 전사, 요약, 동영상과 같은 일회성 생성 작업을 위한 Vue 컴포저블입니다. 모두 동일한 패턴을 따릅니다. connection 또는 fetcher를 제공하고 generate()를 호출한 다음 반응형 상태를 읽습니다.

useGeneration(options)

사용자 지정 생성 유형을 위한 기본 컴포저블입니다. 아래의 모든 특화 컴포저블은 이를 기반으로 합니다.

import { useGeneration } from "@tanstack/ai-vue";
import { fetchServerSentEvents } from "@tanstack/ai-client";

const { generate, result, isLoading, error, status, stop, reset } =
useGeneration({
connection: fetchServerSentEvents("/api/generate/custom"),
});

옵션: connection?, fetcher?, threadId?, body?, onResult?, onError?, onProgress?, onChunk?

반환값: generate, result, isLoading, error, status, stop, reset, runId입니다. 모든 반응형 상태는 DeepReadonly<ShallowRef<T>>입니다.

useGenerateImage(options)

이미지 생성 컴포저블입니다. generate()ImageGenerateInput을 받고, 결과는 ImageGenerationResult입니다.

useGenerateSpeech(options)

텍스트 음성 변환 컴포저블입니다. generate()SpeechGenerateInput을 받고, 결과는 TTSResult입니다.

useTranscription(options)

오디오 전사 컴포저블입니다. generate()TranscriptionGenerateInput을 받고, 결과는 TranscriptionResult입니다.

useSummarize(options)

텍스트 요약 컴포저블입니다. generate()SummarizeGenerateInput을 받고, 결과는 SummarizationResult입니다.

useGenerateVideo(options)

작업 폴링을 지원하는 동영상 생성 컴포저블입니다. 추가로 jobIdvideoStatus ref를 반환합니다. onJobCreated?onStatusUpdate? 콜백도 추가로 받습니다.

모든 생성 컴포저블은 onScopeDispose를 통해 자동으로 정리됩니다.

createChatClientOptions(options)

타입이 지정된 채팅 옵션을 생성하는 헬퍼입니다(@tanstack/ai-client에서 다시 내보냄).

import {
createChatClientOptions,
type InferChatMessages,
} from "@tanstack/ai-client";
import { fetchServerSentEvents } from "@tanstack/ai-vue";
import { tool1, tool2 } from "./tools";

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

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

type Messages = InferChatMessages<typeof chatOptions>;

타입

@tanstack/ai-client에서 다시 내보냅니다.

  • UIMessage<TTools> - 도구 타입 매개변수를 사용하는 메시지 타입
  • MessagePart<TTools> - 도구 타입 매개변수를 사용하는 메시지 부분
  • TextPart - 텍스트 콘텐츠 부분
  • ThinkingPart - 사고 콘텐츠 부분
  • ToolCallPart<TTools> - 도구 호출 부분(판별된 유니언)
  • ToolResultPart - 도구 결과 부분
  • ChatClientOptions<TTools, TContext> - 타입이 지정된 클라이언트 런타임 컨텍스트를 사용하는 채팅 클라이언트 옵션
  • ConnectionAdapter - 연결 어댑터 인터페이스
  • InferChatMessages<T> - 옵션에서 메시지 타입 추출
  • ChatRequestBody - 요청 본문 타입
  • GenerationClientState - 생성 수명 주기 상태
  • ImageGenerateInput - 이미지 생성 입력 타입
  • SpeechGenerateInput - 음성 생성 입력 타입
  • TranscriptionGenerateInput - 전사 입력 타입
  • SummarizeGenerateInput - 요약 입력 타입
  • VideoGenerateInput - 동영상 생성 입력 타입
  • VideoGenerateResult - 동영상 생성 결과 타입
  • VideoStatusInfo - 동영상 작업 상태 정보

@tanstack/ai에서 다시 내보냅니다.

  • toolDefinition() - 동형 도구 정의 생성
  • ToolDefinitionInstance - 도구 정의 타입
  • ClientTool - 클라이언트 도구 타입
  • ServerTool - 서버 도구 타입

다음 단계