@meistrari/chat-nuxt
v3.3.0
Published
Nuxt module that adds a complete AI chat interface to your app. Server-side chat behavior is delegated to the dedicated Chat API; the Nuxt module owns the UI, auth bridge, and local proxy routes.
Maintainers
Keywords
Readme
@meistrari/chat-nuxt
Nuxt module that adds a complete AI chat interface to your app. Server-side chat behavior is delegated to the dedicated Chat API; the Nuxt module owns the UI, auth bridge, and local proxy routes.
Public Surface
@meistrari/chat-nuxt is an external package. Its supported consumer surface is intentionally small:
- Nuxt module registration through
@meistrari/chat-nuxt. - Module options under
chatNuxt. - Auto-registered
<MeistrariChatEmbed>. - Public component import from
@meistrari/chat-nuxt/components/MeistrariChatEmbed. - Public types from the package root and
@meistrari/chat-nuxt/types/*.
Runtime internals under src/runtime/**, generated Nuxt aliases, and workspace-only schema package imports are implementation details unless they are explicitly exported above.
Installation
pnpm add @meistrari/chat-nuxtRequired: Vue dedupe via pnpm overrides
@meistrari/tela-build pins [email protected] as a direct dependency, which collides with the Vue that Nuxt installs. Without an override, pnpm installs two Vue copies and you'll hit runtime errors such as root.ce._hasShadowRoot is not a function when custom elements register against one Vue and mount against the other.
Until tela-build is published with Vue as a peer dependency, pin a single Vue version in your consumer package.json:
{
"pnpm": {
"overrides": {
"vue": "3.5.33"
}
}
}Keep this entry until @meistrari/tela-build moves Vue to peerDependencies — at that point the override becomes unnecessary and should be removed.
Setup
1. Register the modules
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@meistrari/auth-nuxt', // must come before chat-nuxt
'@meistrari/chat-nuxt',
],
ssr: false,
chatNuxt: {
chatApiUrl: process.env.CHAT_API_URL,
validateConfig: 'warn',
},
telaAuth: {
apiUrl: process.env.AUTH_API_URL ?? '',
application: {
enabled: true,
applicationId: process.env.APPLICATION_ID ?? '',
redirectUri: process.env.AUTH_REDIRECT_URI ?? 'http://localhost:3000/auth/callback',
dashboardUrl: process.env.AUTH_DASHBOARD_URL ?? '',
},
},
})2. Module options
chatApiUrl accepts the value inline, or you can leave the option empty and set CHAT_API_URL. Resolution order: module option → existing runtimeConfig → env var → empty.
| Option | Required | Env fallback | Description |
|--------|----------|--------------|-------------|
| chatApiUrl | ✅ Always | CHAT_API_URL | Dedicated Chat API base URL used by Nuxt server proxy routes. |
| validateConfig | 🔧 Optional | — | false (default), 'warn', or 'error'. Warns or fails fast when chatApiUrl is missing. |
Set chatNuxt.validateConfig to 'warn' to log a warning when chatApiUrl is missing, or 'error' to fail fast at module setup. The check is off by default since some deployments inject runtime config after build.
UI features are configured per component instance, not at the module level: the embed takes a :features prop (ChatFeatureConfig) and <ChatConfigurationModal> takes show-credentials-tab.
3. Auth modes
@meistrari/chat-nuxt can run with either auth mode exposed by @meistrari/auth-nuxt:
- Application auth (
telaAuth.application.enabled: true) delegates chat identity and workspace state touseTelaApplicationAuth(). - First-party auth delegates chat identity to
useTelaSession()and workspace state touseTelaOrganization().
The module chooses the bridge during Nuxt setup. Host apps should keep registering @meistrari/auth-nuxt before @meistrari/chat-nuxt.
4. Setup app.vue
<!-- app.vue -->
<template>
<NuxtPage />
<AppStatusToast />
</template>AppStatusToast, Markstream styles, code block styles, and math delimiter defaults are auto-registered by the module.
5. Use it
<!-- pages/index.vue -->
<template>
<div h-screen>
<MeistrariChatEmbed />
</div>
</template>The chat workspace is resolved from the authenticated activeOrganization provided by @meistrari/auth-nuxt.
Chat runtime modes
<MeistrariChatEmbed /> has two mutually exclusive runtimes:
- Default chat mode uses the Chat API and the workspace's configured Tela agent. Runtime configuration, entitlements, and workspace-specific agent selection are owned by
@meistrari/chat-api. Workspaces whose legacy customization has not been migrated to a Tela agent are not ready to chat: sends fail withworkspace_agent_not_readyand the embed surfaces guidance pointing to the settings modal, where the Agente tab offers a one-click agent creation. - Tela agent mode uses a configured Tela agent directly. Pass
telaAgentId; the embed sends user messages toPOST /agent/:id/run, pollsGET /agent/sessions/:sessionId, and ends sessions withDELETE /agent/sessions/:sessionId.
<MeistrariChatEmbed tela-agent-id="agent_123" />conversationScope works in both runtimes. In default chat mode, it isolates conversations inside the authenticated workspace while the Chat API resolves the correct Tela agent:
<MeistrariChatEmbed :conversation-scope="`user:${userId}`" />Host apps can also provide Tela agent inputs directly. When telaAgentInputs is non-null, the chat sends those inputs with each agent run and does not render the agent variables panel.
<MeistrariChatEmbed
tela-agent-id="agent_123"
:conversation-scope="`customer:${customerId}`"
:tela-agent-inputs="[
{ type: 'text', name: 'customer_id', content: customerId },
]"
/>In Tela agent mode, workspaceSettings and user are not accepted by the public prop contract - the Tela agent owns runtime configuration and identity for execution. features is still accepted for shared UI controls.
Embedding in Your Layout
The main use case: drop the chat inside your existing app UI.
<template>
<div class="my-app-layout">
<MyHeader />
<main>
<!-- Chat embedded in a card -->
<div style="height: 600px" class="rounded-2xl overflow-hidden border">
<MeistrariChatEmbed
v-model:conversation-id="activeConversation"
:hide-sidebar="true"
:loading-messages="['Buscando contratos', 'Lendo documentos', 'Preparando resposta']"
loading-messages-mode="ordered"
/>
</div>
</main>
</div>
</template>Key props for embedding:
:hide-sidebar="true"— removes the conversation list, keeps just the chatv-model:conversation-id— sync the active conversation with your app's state:initial-conversation-id— open a specific conversation on mountconversation-scope— isolate conversation history inside the same workspace or Tela agent
<MeistrariChatEmbed> Props
All prop, feature-flag, and event payload types are importable from the package root (or the types/embed subpath):
import type {
ChatActionPayload,
ChatFeatureConfig,
MeistrariChatEmbedProps,
MessageFeedbackConfig,
MessageFeedbackPayload,
MessageFeedbackRating,
} from '@meistrari/chat-nuxt'| Prop | Type | Default | Description |
|------|------|---------|-------------|
| hideSidebar | boolean | false | Hide conversation list sidebar |
| sidebar | ChatSidebarConfig | undefined | Sidebar layout: { position?, width?, bottomHeight?, collapsible?, defaultCollapsed? }. position ('left'|'right', default 'left') picks the edge; width is the sidebar width in pixels (default 240); bottomHeight is the sidebar-bottom pane height as a percentage 0–100 of the conversation area (default 50); collapsible (default false) renders a native collapse handle and animates the sidebar open/closed; defaultCollapsed (default false) starts it collapsed |
| hideSettings | boolean | false | Hide workspace settings actions while keeping the chat header and content unchanged |
| conversationId | string \| null | null | Controlled conversation (supports v-model) |
| initialConversationId | string \| null | null | Open this conversation on mount |
| conversationScope | string \| null | null | Isolate conversation history by technical scope within the current workspace and runtime |
| defaultConversationCreatorFilter | 'all' \| 'mine' | 'all' | Initial sidebar creator filter |
| telaAgentId | string | — | Use Tela agent mode for this embed |
| telaAgentInputs | TelaAgentExecutionInput[] \| null | undefined | Inputs sent with each Tela agent run; hides the variables panel when non-null |
| workspaceSettings | WorkspaceSettings \| null | null | Override settings from host app |
| user | ChatActor \| null | null | Override current user display info |
| features | Partial<ChatFeatureConfig> | {} | Toggle optional features |
| loadingMessages | readonly string[] \| null | Default chat messages | Override pending-response messages |
| loadingMessagesMode | 'ordered' \| 'random' \| null | 'ordered' | Show custom messages sequentially or randomly |
| customComponents | Record<string, Component> | undefined | Custom markdown renderers keyed by markdown node or tag name |
| customHtmlTags | readonly string[] | undefined | Extra HTML tags allowed by the markdown renderer |
| feedbackConfig | MessageFeedbackConfig \| null | undefined | Enables 👍/👎 feedback on assistant messages; providing the prop (even {}) shows the controls |
| messageFeedback | Record<string, MessageFeedbackRating> \| null | undefined | Host-persisted votes keyed by message id; when provided, it is the single source of truth for the selected thumbs |
loadingMessagesMode only applies when loadingMessages has at least one non-empty message.
Use conversationScope when you need multiple chat surfaces to share the same authenticated workspace but keep separate histories. This is useful for per-user inboxes, record detail pages, customer workspaces, template previews, workflow steps, or any app route where the same chat should only show conversations for that route's domain object. The scope is applied together with the workspace and, when present, the telaAgentId.
Scoped embeds only see conversations created with the same scope. Unscoped embeds only see unscoped conversations, so adding a scope will not mix with existing workspace-level history. Scope values are technical identifiers: they are trimmed, blank strings become null, and server requests accept 1-200 characters from A-Z, a-z, 0-9, ., _, :, /, and -.
<!-- Default chat mode: workspace + scope -->
<MeistrariChatEmbed :conversation-scope="`user:${userId}`" />
<MeistrariChatEmbed :conversation-scope="`customer:${customerId}`" />
<!-- Tela agent mode: workspace + telaAgentId + scope -->
<MeistrariChatEmbed
tela-agent-id="agent_123"
:conversation-scope="`template:${templateId}`"
/>Events:
| Event | Payload | Description |
|-------|---------|-------------|
| update:conversationId | string \| null | Active conversation changed |
| action | ChatActionPayload | Custom action triggered from chat content ({ name, data, messageId? }) |
| messageFeedback | MessageFeedbackPayload | User voted on an assistant message (see Message Feedback) |
Slots:
| Slot | Description |
|------|-------------|
| sidebar-bottom | Splits the desktop sidebar into two panes and renders your content in the bottom one, below the conversation list. Each pane scrolls independently. Size it with sidebar.bottomHeight (percentage, default 50). Not rendered on mobile or when hideSidebar is set. |
<MeistrariChatEmbed :sidebar="{ position: 'right', width: 300, bottomHeight: 30 }">
<template #sidebar-bottom>
<MyPinnedDocuments />
</template>
</MeistrariChatEmbed>Optional: Feature Flags
<MeistrariChatEmbed> accepts a :features prop with three optional toggles. All default to false. The host owns the policy - hardcode them, gate by user role, or wire them through a feature-flag provider of your choice.
| Flag | Effect |
|------|--------|
| showUsageTab | Usage tab in the chat topbar (per-conversation token/cost stats) |
| showDebugOption | Debug entry in the topbar overflow menu |
| showCancelButton | "Stop generation" button while a response streams |
Static example:
<MeistrariChatEmbed :features="{ showUsageTab: true, showCancelButton: true }" />With PostHog
chat-nuxt ships a small PostHog helper for hosts that want to gate flags dynamically. The flag names live in your app — chat-nuxt has no opinion about them.
- Add to
nuxt.config.ts:
runtimeConfig: {
public: {
posthogPublicKey: process.env.POSTHOG_PUBLIC_KEY ?? '',
posthogHost: process.env.POSTHOG_HOST ?? '',
posthogDefaults: process.env.POSTHOG_DEFAULTS ?? '2026-01-30',
},
},- Initialize in
app.vue:
<script setup lang="ts">
const { posthogPublicKey, posthogHost, posthogDefaults } = useRuntimeConfig().public
// Application auth hosts:
const { user, activeOrganization } = useTelaApplicationAuth()
// First-party auth hosts can use this instead:
// const session = useTelaSession()
// const organization = useTelaOrganization()
// const user = session.user
// const activeOrganization = organization.activeOrganization
// await organization.getActiveOrganization()
onMounted(async () => {
if (!posthogPublicKey || !posthogHost) return
await initPosthog(posthogPublicKey, posthogHost, posthogDefaults)
if (user.value) identifyPosthogUser(user.value)
if (activeOrganization.value) setPosthogWorkspace(activeOrganization.value)
})
</script>- Thread the flags into the embed:
<script setup lang="ts">
const { isFeatureEnabled } = useFeatureFlags()
const features = computed(() => ({
showUsageTab: isFeatureEnabled('enable-usage-tab'),
showDebugOption: isFeatureEnabled('enable-debug-mode'),
showCancelButton: isFeatureEnabled('enable-cancel-generation'),
}))
</script>
<template>
<MeistrariChatEmbed :features="features" />
</template>Optional: Message Feedback
Assistant messages can carry 👍/👎 controls. The package renders the controls and collects the vote; persistence is owned by the host — nothing is stored by chat-nuxt.
Passing feedbackConfig (even {}) enables the controls:
<script setup lang="ts">
import type { MessageFeedbackPayload, MessageFeedbackRating } from '@meistrari/chat-nuxt'
const votes = ref<Record<string, MessageFeedbackRating>>({})
async function onFeedback(payload: MessageFeedbackPayload) {
// payload: { conversationId, messageId, rating, reasons, comment }
await saveVoteToMyBackend(payload)
votes.value = { ...votes.value, [payload.messageId]: payload.rating }
}
</script>
<template>
<MeistrariChatEmbed
:feedback-config="{
negativeReasons: ['Resposta incorreta', 'Fora do tema', 'Incompleta'],
requireReason: true,
}"
:message-feedback="votes"
@message-feedback="onFeedback"
/>
</template>MessageFeedbackConfig fields:
| Field | Effect |
|-------|--------|
| positiveReasons | Reason chips offered on a positive vote. Empty or omitted: 👍 submits directly |
| negativeReasons | Reason chips offered on a negative vote |
| requireReason | Blocks a bare negative vote: requires at least one reason chip when negativeReasons are configured, otherwise a non-empty comment |
messageFeedback controls which thumb renders selected:
- Provided (controlled): only host-persisted votes render. Update the map as
message-feedbackevents are saved — optimistically or after persistence. - Omitted: votes are kept in component-local state only.
Optional: Custom Workspace Settings
Agent-level configuration (system prompt, context files, tools, skills) lives in the workspace's Tela agent and is edited in the Tela app. The module's built-in settings UI manages knowledge sources (Tela workstations) and workspace credentials, persisted by the Chat API service.
To override the settings the embed reads, pass them directly from your host app:
<MeistrariChatEmbed
:workspace-settings="{
workspaceId: 'ws_123',
systemMessage: 'You are a helpful assistant for Acme Corp.',
contextFiles: null,
canvasTools: null,
knowledgeSources: null,
externalSkills: null,
updatedAt: new Date(),
}"
/>The server/chat/resolve-workspace-settings.ts host resolver from earlier versions is no longer invoked: the module proxies chat requests to the Chat API, which owns workspace settings resolution.
Agent Runtime Configuration
chat-nuxt proxies chat requests to @meistrari/chat-api; it does not start agent sessions directly or resolve runtime environment variables. Agent runtime configuration, entitlements, and Tela agent selection are owned by chat-api and the Tela agent configuration. Host apps do not need Nuxt-side agent environment resolver files for this module.
A skill that calls a host backend must implement its own callback contract with the host backend. In short, callback authorization belongs to the skill and host backend, not chat-nuxt. The host backend remains responsible for validating invocation grants, allowed tools, schemas, replay protection, confirmation for writes, and audit logging.
<ChatConfigurationModal>
A reusable workspace settings modal that any host app can open from any button. It has three tabs:
agent(Agente) — the workspace's Tela agent status. Shows the generic-chat state, the dedicated agent (id, copy, open in Tela), or — for workspaces whose legacy customization has not been migrated — a setup-required warning with a create-agent action. Agent configuration itself (system prompt, context files, tools, skills) is edited in the Tela app, not here.knowledge-sources(Tela workstations) — the workstations injected as knowledge sources into new chat sessions.credentials(Credenciais) — workspace credentials, shown whenshowCredentialsTabis set.
By default, the modal uses useWorkspaceSettings() to fetch and persist settings. If the host passes the settings prop, the host owns workspace settings state and persistence.
<script setup lang="ts">
import type { ChatConfigurationTab } from '@meistrari/chat-nuxt/types/workspace-settings'
const open = ref(false)
const initialTab = ref<ChatConfigurationTab>('agent')
function openSettings(tab: ChatConfigurationTab = 'agent') {
initialTab.value = tab
open.value = true
}
</script>
<template>
<button @click="openSettings()">
Configurações
</button>
<ChatConfigurationModal
v-model:open="open"
:initial-tab="initialTab"
/>
</template>| Prop | Type | Default | Description |
|------|------|---------|-------------|
| open | boolean | — | Modal visibility (supports v-model:open) |
| settings | WorkspaceSettings \| null | undefined | Optional host-controlled settings. Omit this prop to let the modal fetch and persist via useWorkspaceSettings(); pass null or a settings object to make the host own state and persistence |
| initialTab | ChatConfigurationTab | 'agent' | Tab to open on: 'agent' \| 'knowledge-sources' \| 'credentials' |
| saving | boolean | false | Host-controlled spinner/disable while persistence is in flight |
| showCredentialsTab | boolean | false | Show the workspace credentials tab |
| Event | Payload | Description |
|-------|---------|-------------|
| update:open | boolean | Modal open state changed |
| save | ChatConfigurationSavePayload | Emitted only when the host passes settings; carries the knowledge-source updates plus onSaved; host persists (e.g. via useWorkspaceSettings().updateSettings()), calls onSaved after persistence succeeds, and closes the modal |
Only the knowledge-sources tab produces a save payload. The agent tab acts immediately through the module's /api/workspace/agent endpoints, and the credentials tab manages its own data via /api/workspace/credentials — neither is part of the save payload.
What's Included
The module auto-registers everything — no manual imports needed:
- Components: full chat UI, message bubbles, tool widgets (code, search, files), markdown rendering, file previews
- Composables:
useChat,useConversations,useWorkspaceSettings,useFileUpload,useFeatureFlags, and more - Server routes: thin proxy routes that forward chat requests to the Chat API service
Package Dependencies
Installing @meistrari/chat-nuxt installs the runtime packages used by the layer. The host Nuxt app still owns the Nuxt and Vue versions.
| Package | Version | Purpose |
|---------|---------|---------|
| @meistrari/auth-nuxt | 3.5.0 | Authentication (required) |
| @meistrari/logger | ^2.1.3 | Structured logging for the server proxy routes |
| @meistrari/tela-build | ^1.30.0 | UI component library (Nuxt layer) |
| @sentry/nuxt | ^10.0.0 | Client-side error capture used by chat composables |
| @iconify/vue + @iconify-json/ph | ^5.0.0 / ^1.2.2 | Icon rendering support (Phosphor set) |
| @vueuse/core | ^12.8.0 | Runtime composables used by chat UI |
| @vueuse/components | ^12.8.0 | VueUse component peer expected by the shared runtime |
| beautiful-mermaid + mermaid | ^1.1.3 / ^11.13.0 | Mermaid diagram rendering in markdown |
| dompurify | ^3.3.2 | HTML sanitization for rendered markdown |
| h3 | ^1.15.0 | Server route utilities provided by Nuxt/Nitro |
| markstream-vue | 0.0.3-beta.6 | Markdown streaming renderer |
| motion | ^11.13.0 | Animation component runtime |
| nitropack | ^2.10.0 | Runtime config and task utilities provided by Nuxt/Nitro |
| pdfjs-dist | ^5.4.530 | PDF file previews |
| posthog-js | ^1.364.2 | Optional PostHog feature-flag helper |
| virtua | ^0.42.0 | Virtualized list runtime expected by shared UI |
| vue-i18n | ^11.0.0 | Internationalization runtime expected by shared UI |
| xlsx | ^0.18.5 | Spreadsheet file previews |
| zod | ^4.1.13 | Schema validation for API payloads |
Peer dependencies:
| Package | Version | Purpose |
|---------|---------|---------|
| nuxt | ^3.17.0 | Host framework |
| vue | ^3.5.0 | Host Vue runtime |
