vue-llm: AI SDK для Vue и Nuxt — стриминговый чат, Generative UI и модели прямо в браузере. JavaScript.. JavaScript. llm.. JavaScript. llm. Nuxt.. JavaScript. llm. Nuxt. Open source.. JavaScript. llm. Nuxt. Open source. openai.. JavaScript. llm. Nuxt. Open source. openai. TypeScript.. JavaScript. llm. Nuxt. Open source. openai. TypeScript. vue.. JavaScript. llm. Nuxt. Open source. openai. TypeScript. vue. VueJS.. JavaScript. llm. Nuxt. Open source. openai. TypeScript. vue. VueJS. WebGPU.. JavaScript. llm. Nuxt. Open source. openai. TypeScript. vue. VueJS. WebGPU. искусственный интеллект.

Если вы делали AI-фичу на Vue, то, скорее всего, сталкивались с одним и тем же: большинство готовых инструментов для LLM-интерфейсов написаны с прицелом на React, а во Vue их приходится адаптировать или писать стриминг, tool calling и состояние чата вручную. Я сделал vue-llm — open-source библиотеку, которая закрывает это для Vue 3, Nuxt и даже Vue 2.7.

Сразу ссылки, чтобы можно было пощупать:

– Песочница без API-ключа (модель скачивается и работает прямо в браузере на WebGPU): https://vue-llm.js.org/ru/playground

– Документация на русском: https://vue-llm.js.org/ru/

– GitHub: https://github.com/dadashi44/vue-llm

– npm: npm i vue-llm

Ниже — что умеет библиотека и как это выглядит в коде.

Чат за десять строк

Основа — композабл useChat. Он стримит ответ модели токен за токеном в реактивное состояние: сообщения, статус, ошибка, отмена, перегенерация.

<script setup lang="ts">

import { useChat } from 'vue-llm'

import { createProxyProvider } from 'vue-llm/server'




const { messages, input, handleSubmit, status, stop } = useChat({

  provider: createProxyProvider('/api/chat'),

  model: 'gpt-4o',

})

</script>




<template>

  <div v-for="m in messages" :key="m.id">

    <b>{{ m.role }}:</b>

    <template v-for="(part, i) in m.parts" :key="i">

      <span v-if="part.type === 'text'">{{ part.text }}</span>

    </template>

  </div>

  <form @submit.prevent="handleSubmit">

    <input v-model="input">

    <button v-if="status === 'streaming'" type="button" @click="stop">Стоп</button>

  </form>

</template>

Сообщения состоят из «частей» (parts): текст, рассуждения модели, вызовы инструментов, файлы, источники. Поэтому один и тот же шаблон показывает и обычный ответ, и reasoning у Claude или Qwen3, и вызов tool.

API-ключ при этом остаётся на сервере. Серверная часть — обычный Web-стандартный обработчик Request → Response, который работает в Nuxt/Nitro, Node, Bun, Deno и edge-рантаймах:

// server/api/chat.post.ts (Nuxt)

import { createLLMHandler } from 'vue-llm/server'

import { createAnthropic } from 'vue-llm/providers'




const handler = createLLMHandler({

  provider: createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! }),

  model: 'claude-sonnet-4-5',

  rateLimit: { maxRequests: 30, interval: 60_000 },

})




export default defineEventHandler(event => handler(toWebRequest(event)))

Провайдеры из коробки: OpenAI (и любой OpenAI-совместимый API), Anthropic, Google Gemini, Ollama. Если у вас уже есть свой бэкенд со стримингом, createSSEProvider подключает useChat к нему: вы описываете, как ваши SSE-события превращаются в куски ответа, и всё остальное работает как есть.

Generative UI: модель отвечает компонентами

Самая интересная для меня часть. Когда модель вызывает инструмент, вместо текста можно показать ваш Vue-компонент. Аргументы стримятся в компонент по мере генерации, то есть карточка начинает заполняться ещё до того, как модель дописала вызов.

import { defineTool } from 'vue-llm'




export const getWeather = defineTool<{ city: string }, Weather>({

  name: 'get_weather',

  description: 'Погода в городе',

  parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] },

  execute: ({ city }) => fetch(/api/weather?city=${city}).then(r => r.json()),

})

<MessageParts

  v-for="m in messages"

  :key="m.id"

  :message="m"

  :components="{ get_weather: WeatherCard }"

  @tool-result="addToolResult"

/>

Инструмент с execute выполняется сам. Инструмент без execute ставит чат на паузу и ждёт ответа из интерфейса — так делается human-in-the-loop: модель предлагает забронировать столик, пользователь жмёт «Подтвердить» в карточке, addToolResult передаёт ответ, и разговор продолжается. Живое демо (работает без сети): https://vue-llm.js.org/ru/guide/generative-ui

Под капотом для этого пришлось написать парсер «недописанного» JSON: {"city":"Мос превращается в { city: 'Мос' }, а незаконченные ключи отбрасываются до тех пор, пока не допишутся.

Модели прямо в браузере

createWebLLM запускает открытые модели (Llama 3.2, Qwen, SmolLM, Phi) на WebGPU через WebLLM. Никакого сервера и ключа, данные не покидают устройство.

import { CreateMLCEngine } from '@mlc-ai/web-llm'

import { createWebLLM } from 'vue-llm/providers'




const ai = createWebLLM({

  createEngine: CreateMLCEngine,

  onProgress: ({ progress }) => console.log(${Math.round(progress * 100)}%),

})




useChat({ provider: ai, model: 'Llama-3.2-1B-Instruct-q4f16_1-MLC' })

Это тот же useChat, что и с OpenAI, — меняется только провайдер. Можно, например, давать пользователям без WebGPU серверную модель, а остальным — локальную. Первая загрузка модели занимает от 400 МБ, дальше она берётся из кеша браузера за секунду-две.

Готовый интерфейс чата в стиле shadcn

Писать окно чата с нуля утомительно: автоскролл, который не дёргает пользователя, листающего вверх; Enter для отправки, который не срабатывает при наборе на китайском или японском; вставка картинок из буфера; drag-and-drop файлов. Поэтому библиотека устроена гибридно:

– в npm лежит headless-логика: usePromptInput и useAutoScroll;

– готовые компоненты копируются в ваш проект как в shadcn — одной командой, после чего код ваш:

npx shadcn-vue@latest add https://vue-llm.js.org/r/chat.json

Компоненты используют токены темы shadcn, поэтому сразу выглядят как остальной ваш интерфейс, включая тёмную тему.

Отладка в Vue DevTools

vue-llm/devtools добавляет в Vue DevTools вкладку LLM: список всех useChat, токены, время до первого токена, скорость генерации, вызовы инструментов и таймлайн каждого запроса. Без плагина библиотека ничего не трассирует и не тратит на это ресурсы.

Nuxt

Модуль подключается одной командой:

npx nuxi module add vue-llm

Он делает auto-import композаблов и провайдеров, регистрирует компоненты и в dev-режиме сам подключает вкладку в DevTools. Проверено на Nuxt 3 и 4.

Почему не взять готовый AI SDK

Хороший вопрос, и я сам с него начинал. vue-llm сделан Vue-first: Nuxt-модуль, Vue-компоненты для Generative UI, интеграция с Vue DevTools, провайдер для моделей в браузере, адаптер под существующий SSE-бэкенд и поддержка Vue 2.7 для композаблов (у многих легаси-админок до сих пор Vue 2). Зависимостей в рантайме нет, кроме самого Vue.

Что дальше

Библиотека под MIT, версия 1.3, покрыта тестами на Vue 3 и Vue 2.7. В планах — MCP-клиент (подключать MCP-серверы как инструменты), ответы с цитатами для RAG и голосовой ввод.

Буду рад обратной связи: что неудобно, чего не хватает, где документация непонятна. Issues и PR приветствуются, а если проект пригодится — поставьте звёздочку на GitHub, это правда помогает его продвигать.

– Песочница: https://vue-llm.js.org/ru/playground

– Документация: https://vue-llm.js.org/ru/

– GitHub: https://github.com/dadashi44/vue-llm

Автор: dadash_i

Источник