@meistrari/chat-nuxt 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +71 -48
  2. package/dist/module.d.mts +24 -1
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +37 -19
  5. package/dist/runtime/components/MeistrariChatEmbed.d.vue.ts +3 -1
  6. package/dist/runtime/components/MeistrariChatEmbed.vue +5 -3
  7. package/dist/runtime/components/MeistrariChatEmbed.vue.d.ts +3 -1
  8. package/dist/runtime/components/chat/thinking-indicator.vue +28 -17
  9. package/dist/runtime/composables/useEmbedConfig.d.ts +5 -0
  10. package/dist/runtime/composables/useEmbedConfig.js +11 -3
  11. package/dist/runtime/embed/components/ChatConfigurationModal.vue +8 -6
  12. package/dist/runtime/embed/components/ChatEmbed.d.vue.ts +3 -1
  13. package/dist/runtime/embed/components/ChatEmbed.vue +13 -12
  14. package/dist/runtime/embed/components/ChatEmbed.vue.d.ts +3 -1
  15. package/dist/runtime/embed/components/ChatEmbedInner.vue +5 -1
  16. package/dist/runtime/plugins/markstream.d.ts +2 -0
  17. package/dist/runtime/plugins/markstream.js +5 -0
  18. package/dist/runtime/server/api/conversations/[id]/messages/[messageId]/retry.post.js +7 -4
  19. package/dist/runtime/server/api/conversations/[id]/messages/index.post.js +7 -4
  20. package/dist/runtime/server/api/conversations/[id]/usage.get.js +1 -1
  21. package/dist/runtime/server/api/conversations/generate-title.post.js +18 -5
  22. package/dist/runtime/server/api/workspace/usage.get.js +1 -1
  23. package/dist/runtime/server/db/index.d.ts +20 -3
  24. package/dist/runtime/server/db/index.js +87 -10
  25. package/dist/runtime/server/db/schema/_schema.d.ts +2 -0
  26. package/dist/runtime/server/db/schema/_schema.js +3 -0
  27. package/dist/runtime/server/db/schema/conversation-usage.d.ts +1 -1
  28. package/dist/runtime/server/db/schema/conversation-usage.js +3 -2
  29. package/dist/runtime/server/db/schema/conversations.d.ts +1 -1
  30. package/dist/runtime/server/db/schema/conversations.js +3 -2
  31. package/dist/runtime/server/db/schema/external-skills.d.ts +1 -1
  32. package/dist/runtime/server/db/schema/external-skills.js +3 -2
  33. package/dist/runtime/server/db/schema/index.d.ts +1 -0
  34. package/dist/runtime/server/db/schema/index.js +1 -0
  35. package/dist/runtime/server/db/schema/messages.d.ts +1 -1
  36. package/dist/runtime/server/db/schema/messages.js +3 -2
  37. package/dist/runtime/server/db/schema/workspace-settings.d.ts +1 -1
  38. package/dist/runtime/server/db/schema/workspace-settings.js +3 -2
  39. package/dist/runtime/server/plugins/init-db.d.ts +2 -0
  40. package/dist/runtime/server/plugins/init-db.js +11 -0
  41. package/dist/runtime/server/utils/billing.d.ts +26 -4
  42. package/dist/runtime/server/utils/billing.js +52 -13
  43. package/dist/runtime/server/utils/chat-workspace-settings.js +1 -1
  44. package/dist/runtime/utils/chat-loading-messages.d.ts +12 -0
  45. package/dist/runtime/utils/chat-loading-messages.js +40 -0
  46. package/dist/runtime/utils/features.d.ts +6 -0
  47. package/dist/runtime/utils/features.js +15 -0
  48. package/dist/types.d.mts +1 -1
  49. package/drizzle/0000_material_hiroim.sql +28 -0
  50. package/drizzle/0001_famous_scalphunter.sql +1 -0
  51. package/drizzle/0002_gorgeous_big_bertha.sql +1 -0
  52. package/drizzle/0003_massive_forge.sql +2 -0
  53. package/drizzle/0004_absent_black_tom.sql +2 -0
  54. package/drizzle/0005_first_kid_colt.sql +1 -0
  55. package/drizzle/0006_sloppy_mauler.sql +13 -0
  56. package/drizzle/0007_grey_rhino.sql +1 -0
  57. package/drizzle/0008_chilly_blue_blade.sql +27 -0
  58. package/drizzle/0009_cultured_maddog.sql +16 -0
  59. package/drizzle/0010_small_barracuda.sql +1 -0
  60. package/drizzle/0011_fat_lady_mastermind.sql +6 -0
  61. package/drizzle/0012_puzzling_rick_jones.sql +1 -0
  62. package/drizzle/0013_blue_gwen_stacy.sql +11 -0
  63. package/drizzle/meta/0000_snapshot.json +239 -0
  64. package/drizzle/meta/0001_snapshot.json +245 -0
  65. package/drizzle/meta/0002_snapshot.json +251 -0
  66. package/drizzle/meta/0003_snapshot.json +263 -0
  67. package/drizzle/meta/0004_snapshot.json +275 -0
  68. package/drizzle/meta/0005_snapshot.json +281 -0
  69. package/drizzle/meta/0006_snapshot.json +320 -0
  70. package/drizzle/meta/0007_snapshot.json +326 -0
  71. package/drizzle/meta/0008_snapshot.json +539 -0
  72. package/drizzle/meta/0009_snapshot.json +646 -0
  73. package/drizzle/meta/0010_snapshot.json +653 -0
  74. package/drizzle/meta/0011_snapshot.json +689 -0
  75. package/drizzle/meta/0012_snapshot.json +695 -0
  76. package/drizzle/meta/0013_snapshot.json +699 -0
  77. package/drizzle/meta/_journal.json +104 -0
  78. package/package.json +5 -3
  79. package/dist/runtime/server/tasks/send-daily-usage-report.d.ts +0 -2
  80. package/dist/runtime/server/tasks/send-daily-usage-report.js +0 -74
  81. package/dist/runtime/server/utils/auth-api.d.ts +0 -1
  82. package/dist/runtime/server/utils/auth-api.js +0 -27
package/README.md CHANGED
@@ -41,6 +41,16 @@ export default defineNuxtConfig({
41
41
  ssr: false,
42
42
 
43
43
  chatNuxt: {
44
+ telaApiUrl: process.env.TELA_API_URL,
45
+ agentApiUrl: process.env.AGENT_API_URL,
46
+ authApiUrl: process.env.AUTH_API_URL,
47
+ vaultUrl: process.env.VAULT_URL,
48
+ databaseUrl: '', // leave empty to use the bundled pglite database
49
+ features: {
50
+ usage: true,
51
+ generateTitle: false,
52
+ credentials: false,
53
+ },
44
54
  validateConfig: 'warn',
45
55
  },
46
56
 
@@ -53,73 +63,83 @@ export default defineNuxtConfig({
53
63
  dashboardUrl: process.env.AUTH_DASHBOARD_URL ?? '',
54
64
  },
55
65
  },
56
-
57
- // Prevents duplicate Vue instances with pnpm
58
- vite: {
59
- resolve: {
60
- dedupe: ['vue', '@vue/runtime-core', '@vue/reactivity', '@vue/runtime-dom', '@vue/shared', 'reka-ui', 'radix-vue', '@floating-ui/vue', '@vueuse/core'],
61
- },
62
- },
63
66
  })
64
67
  ```
65
68
 
66
- ### 2. Set environment variables
69
+ ### 2. Module options
67
70
 
68
- The module injects these into Nuxt's `runtimeConfig` automatically just set them in your environment (Infisical, `.env`, etc.):
71
+ Every `chatNuxt.*` URL accepts the value inline, or you can leave the option empty and set the matching env var. Resolution order: **module option existing `runtimeConfig` env var empty (or pglite default for `databaseUrl`)**.
69
72
 
70
- | Variable | Description |
71
- |----------|-------------|
72
- | `DATABASE_URL` | PostgreSQL connection string |
73
- | `TELA_API_URL` | Tela API endpoint |
74
- | `TELA_API_KEY` | Tela API key |
75
- | `AUTH_API_URL` | Auth service URL |
76
- | `AUTH_API_SECRET` | Auth service secret |
77
- | `AUTH_JWKS_URL` | Auth JWKS endpoint |
78
- | `AGENT_API_URL` | Agent API endpoint |
79
- | `VAULT_URL` | Vault service URL |
80
- | `VAULT_S3_BUCKET` | Vault S3 bucket name |
81
- | `CHAT_APP_SERVICE_KEY` | Service key used when resolving workspace credentials |
73
+ | Option | Required | Env fallback | Description |
74
+ |--------|----------|--------------|-------------|
75
+ | `telaApiUrl` | Always | `TELA_API_URL` | Tela platform API base URL. |
76
+ | `agentApiUrl` | Always | `AGENT_API_URL` | Agent runtime base URL the chat streams from. |
77
+ | `authApiUrl` | Always | `AUTH_API_URL` | Auth API base URL used to verify session tokens. |
78
+ | `vaultUrl` | Always | `VAULT_URL` | Vault API base URL used for file uploads and resolution. |
79
+ | `databaseUrl` | 🔧 Optional | `DATABASE_URL` | Postgres connection string. Falls back to a file-backed pglite database for local dev. |
80
+ | `features` | 🔧 Optional | | Feature toggles (see below). `usage` defaults to `true`; the rest default to `false`. |
81
+ | `validateConfig` | 🔧 Optional | | `false` (default), `'warn'`, or `'error'`. Warns or fails fast when required runtime config is missing for the enabled features. |
82
82
 
83
- Set `chatNuxt.validateConfig` to `'warn'` to report missing server config during Nuxt setup, or `'error'` to fail fast in CI or production builds. The check is disabled by default because many deployments inject runtime config after build.
83
+ ### 3. Set environment-only secrets
84
84
 
85
- ### 3. Setup `app.vue`
85
+ These never appear in module options — set them via `.env`, Infisical, etc. Whether each one is required depends on which features are enabled.
86
86
 
87
- ```vue
88
- <!-- app.vue -->
89
- <script setup lang="ts">
90
- import { setDefaultMathOptions } from 'markstream-vue'
91
- import 'markstream-vue/index.css'
92
- import '@meistrari/chat-nuxt/assets/css/markstream.css'
93
- import '@meistrari/chat-nuxt/assets/css/code-theme.css'
87
+ | Variable | Required when | Description |
88
+ |----------|---------------|-------------|
89
+ | `AUTH_API_SECRET` | `features.usage: true` | Bearer token chat-nuxt uses to call the Auth API to resolve workspace billing info for the usage feature. |
90
+ | `TELA_API_KEY` | `features.generateTitle: true` | Authenticates the auto-title-generation endpoint, which executes a Meistrari-owned Tela canvas server-side. |
91
+ | `CHAT_APP_SERVICE_KEY` | `features.credentials: true` | Server-to-server `X-Service-Key` used when chat-nuxt resolves workspace credentials (`POST /workspaces/:id/credentials/resolve`). |
92
+ | `VAULT_S3_BUCKET` | 🔧 Optional | SSRF hardening: when set, `/api/vault/permalink-metadata` only follows S3 redirects to `<bucket>.s3*.amazonaws.com`. Recommended in production. |
94
93
 
95
- setDefaultMathOptions({ strictDelimiters: true })
96
- </script>
94
+ ### 4. Features
95
+
96
+ `usage` defaults to `true` — the chat experience always tracks per-conversation usage out of the box. `generateTitle` and `credentials` default to `false` and layer on optional behavior with their own runtime requirements.
97
+
98
+ | Feature | What it does | Adds to required config |
99
+ |---------|--------------|-------------------------|
100
+ | `usage` | Per-conversation token & cost tracking, daily aggregated reports, and the usage tab UI surface. | `AUTH_API_SECRET` |
101
+ | `generateTitle` | Auto-generates conversation titles from the first user message via a Tela canvas. Without this, titles fall back to a truncated prefix of the message. | `TELA_API_KEY` |
102
+ | `credentials` | Resolves workspace-level credentials and injects them into agent calls so tools can authenticate as the workspace. | `CHAT_APP_SERVICE_KEY` |
103
+
104
+ Set `chatNuxt.validateConfig` to `'warn'` to log a warning when keys for an enabled feature are missing, or `'error'` to fail fast at module setup. The check is off by default since some deployments inject runtime config after build.
105
+
106
+ ### Local mode (no database)
97
107
 
108
+ When `databaseUrl` is empty, chat-nuxt runs an embedded [pglite](https://pglite.dev) instance — Postgres compiled to WASM, in-process, file-backed at `<rootDir>/.data/chat-nuxt.db`. Migrations are applied automatically on first boot. Tables live under a dedicated `chat` Postgres schema so the database can be safely shared with the host app's own Drizzle setup.
109
+
110
+ This mode is intended for local development, demos, and single-instance prototypes. Caveats:
111
+
112
+ - **Single-process only.** Multi-instance deployments (e.g. Railway with `replicas > 1`) will see divergent local databases — use real Postgres.
113
+ - **Node runtime only.** Pglite needs a filesystem. It won't run on Cloudflare Workers / Vercel Edge.
114
+ - **Roughly 4 MB of WASM** is loaded at server startup. Postgres consumers don't pay this cost (the import is dynamic).
115
+
116
+ To explicitly opt into pglite at a custom path: `databaseUrl: 'pglite:/var/data/chat.db'` or `databaseUrl: 'file:./mydata/chat.db'`.
117
+
118
+
119
+ ### 5. Setup `app.vue`
120
+
121
+ ```vue
122
+ <!-- app.vue -->
98
123
  <template>
99
124
  <NuxtPage />
100
125
  <AppStatusToast />
101
126
  </template>
102
127
  ```
103
128
 
104
- `AppStatusToast` is auto-registered by the module — it shows connection/error notifications.
129
+ `AppStatusToast`, Markstream styles, code block styles, and math delimiter defaults are auto-registered by the module.
105
130
 
106
- ### 4. Use it
131
+ ### 6. Use it
107
132
 
108
133
  ```vue
109
134
  <!-- pages/index.vue -->
110
- <script setup lang="ts">
111
- const { activeOrganization } = useTelaApplicationAuth()
112
- const workspaceId = computed(() => activeOrganization.value?.id ?? null)
113
- </script>
114
-
115
135
  <template>
116
- <div v-if="workspaceId" h-screen>
117
- <MeistrariChatEmbed :workspace-id="workspaceId" />
136
+ <div h-screen>
137
+ <MeistrariChatEmbed />
118
138
  </div>
119
139
  </template>
120
140
  ```
121
141
 
122
- The `workspaceId` is typically the organization ID from `@meistrari/auth-nuxt`.
142
+ The chat workspace is resolved from the authenticated `activeOrganization` provided by `@meistrari/auth-nuxt`.
123
143
 
124
144
  ## Embedding in Your Layout
125
145
 
@@ -135,8 +155,9 @@ The main use case: drop the chat inside your existing app UI.
135
155
  <div style="height: 600px" class="rounded-2xl overflow-hidden border">
136
156
  <MeistrariChatEmbed
137
157
  v-model:conversation-id="activeConversation"
138
- :workspace-id="workspaceId"
139
158
  :hide-sidebar="true"
159
+ :loading-messages="['Buscando contratos', 'Lendo documentos', 'Preparando resposta']"
160
+ loading-messages-mode="ordered"
140
161
  />
141
162
  </div>
142
163
  </main>
@@ -154,13 +175,16 @@ Key props for embedding:
154
175
 
155
176
  | Prop | Type | Default | Description |
156
177
  |------|------|---------|-------------|
157
- | `workspaceId` | `string` | — | **Required.** Workspace/org identifier |
158
178
  | `hideSidebar` | `boolean` | `false` | Hide conversation list sidebar |
159
179
  | `conversationId` | `string \| null` | `null` | Controlled conversation (supports v-model) |
160
180
  | `initialConversationId` | `string \| null` | `null` | Open this conversation on mount |
161
181
  | `workspaceSettings` | `WorkspaceSettings \| null` | `null` | Override settings from host app |
162
182
  | `user` | `ChatActor \| null` | `null` | Override current user display info |
163
183
  | `features` | `Partial<ChatFeatureConfig>` | `{}` | Toggle optional features |
184
+ | `loadingMessages` | `string[] \| null` | Default chat messages | Override pending-response messages |
185
+ | `loadingMessagesMode` | `'ordered' \| 'random' \| null` | `'ordered'` | Show custom messages sequentially or randomly |
186
+
187
+ `loadingMessagesMode` only applies when `loadingMessages` has at least one non-empty message.
164
188
 
165
189
  **Events:**
166
190
 
@@ -217,14 +241,14 @@ const features = computed(() => ({
217
241
  </script>
218
242
 
219
243
  <template>
220
- <MeistrariChatEmbed :workspace-id="workspaceId" :features="features" />
244
+ <MeistrariChatEmbed :features="features" />
221
245
  </template>
222
246
  ```
223
247
 
224
248
  Without PostHog, you can pass features statically:
225
249
 
226
250
  ```vue
227
- <MeistrariChatEmbed :workspace-id="workspaceId" :features="{ showUsageTab: true }" />
251
+ <MeistrariChatEmbed :features="{ showUsageTab: true }" />
228
252
  ```
229
253
 
230
254
  ## Optional: Custom Workspace Settings
@@ -239,7 +263,6 @@ Pass settings directly from your host app:
239
263
 
240
264
  ```vue
241
265
  <MeistrariChatEmbed
242
- :workspace-id="workspaceId"
243
266
  :workspace-settings="{
244
267
  workspaceId: 'ws_123',
245
268
  systemMessage: 'You are a helpful assistant for Acme Corp.',
@@ -335,7 +358,7 @@ Installing `@meistrari/chat-nuxt` installs the runtime packages used by the laye
335
358
 
336
359
  | Package | Version | Purpose |
337
360
  |---------|---------|---------|
338
- | `@meistrari/auth-nuxt` | `^3.4.1` | Authentication (required) |
361
+ | `@meistrari/auth-nuxt` | `3.5.0` | Authentication (required) |
339
362
  | `@meistrari/tela-build` | `^1.30.0` | UI component library (Nuxt layer) |
340
363
  | `@sentry/nuxt` | `^10.0.0` | Client-side error capture used by chat composables |
341
364
  | `@iconify/vue` | `^5.0.0` | Icon rendering support |
package/dist/module.d.mts CHANGED
@@ -1,8 +1,31 @@
1
1
  type ChatNuxtConfigValidationMode = false | 'warn' | 'error';
2
+ interface ChatNuxtFeatures {
3
+ /** Track and report per-conversation token/cost usage. */
4
+ usage?: boolean;
5
+ /** Auto-generate conversation titles from the first user message. Requires `telaApiKey`. */
6
+ generateTitle?: boolean;
7
+ /** Resolve workspace-level credentials and inject them into agent calls. Requires `chatAppServiceKey`. */
8
+ credentials?: boolean;
9
+ }
2
10
  interface ChatNuxtModuleOptions {
11
+ /** Tela platform API base URL (e.g. https://api.tela.com). */
12
+ telaApiUrl?: string;
13
+ /** Agent API base URL — the LLM/agent runtime the chat talks to. */
14
+ agentApiUrl?: string;
15
+ /** Vault API base URL used to upload and resolve user-attached files. */
16
+ vaultUrl?: string;
17
+ /** Postgres connection string. Falls back to a file-backed pglite database for local dev. */
18
+ databaseUrl?: string;
19
+ /** Optional feature toggles. `usage` defaults to `true`; `generateTitle` and `credentials` default to `false`. */
20
+ features?: ChatNuxtFeatures;
21
+ /**
22
+ * Whether to fail fast (`'error'`), warn (`'warn'`), or skip (`false`) when
23
+ * required runtime config keys are unset. Required keys depend on which
24
+ * features are enabled.
25
+ */
3
26
  validateConfig?: ChatNuxtConfigValidationMode;
4
27
  }
5
28
  declare const _default: any;
6
29
 
7
30
  export { _default as default };
8
- export type { ChatNuxtModuleOptions };
31
+ export type { ChatNuxtFeatures, ChatNuxtModuleOptions };
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
3
  "configKey": "chatNuxt",
4
- "version": "1.2.2",
4
+ "version": "1.3.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -148,17 +148,26 @@ function resolveWorkspaceSettingsResolverEntry(srcDir, fallbackResolverBase) {
148
148
  }
149
149
 
150
150
  const _chatNuxtRequire = createRequire(import.meta.url);
151
- const requiredRuntimeConfigKeys = [
152
- "databaseUrl",
151
+ const DEFAULT_FEATURES = {
152
+ usage: true,
153
+ generateTitle: false,
154
+ credentials: false
155
+ };
156
+ const ALWAYS_REQUIRED_KEYS = [
153
157
  "telaApiUrl",
154
- "telaApiKey",
155
- "authApiUrl",
156
- "authApiSecret",
157
158
  "agentApiUrl",
158
- "authJwksUrl",
159
- "vaultUrl",
160
- "chatAppServiceKey"
159
+ "vaultUrl"
161
160
  ];
161
+ function buildRequiredKeys(features) {
162
+ const required = [...ALWAYS_REQUIRED_KEYS];
163
+ if (features.generateTitle) {
164
+ required.push("telaApiKey");
165
+ }
166
+ if (features.credentials) {
167
+ required.push("chatAppServiceKey");
168
+ }
169
+ return required;
170
+ }
162
171
  function resolvePackageRoot(packageName) {
163
172
  return dirname(_chatNuxtRequire.resolve(`${packageName}/package.json`));
164
173
  }
@@ -177,11 +186,12 @@ function ensureIconifyPackages(consumerRoot) {
177
186
  }
178
187
  }
179
188
  }
180
- function validateRuntimeConfig(runtimeConfig, mode) {
189
+ function validateRuntimeConfig(runtimeConfig, mode, features) {
181
190
  if (!mode) {
182
191
  return;
183
192
  }
184
- const missingKeys = requiredRuntimeConfigKeys.filter((key) => !runtimeConfig[key]);
193
+ const required = buildRequiredKeys(features);
194
+ const missingKeys = required.filter((key) => !runtimeConfig[key]);
185
195
  if (missingKeys.length === 0) {
186
196
  return;
187
197
  }
@@ -246,20 +256,23 @@ const module$1 = defineNuxtModule({
246
256
  const runtimeDirRegExp = new RegExp(runtimeDir.replace(/\\/g, "/").replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
247
257
  nuxt.options.imports.transform.include.push(runtimeDirRegExp);
248
258
  }
259
+ const features = { ...DEFAULT_FEATURES, ...options.features ?? {} };
260
+ const migrationsDir = resolver.resolve("../drizzle");
249
261
  const rc = nuxt.options.runtimeConfig;
250
- rc.databaseUrl = rc.databaseUrl || process.env.DATABASE_URL || "";
251
- rc.telaApiUrl = rc.telaApiUrl || process.env.TELA_API_URL || "";
262
+ rc.databaseUrl = options.databaseUrl || rc.databaseUrl || process.env.DATABASE_URL || `pglite:${join(nuxt.options.rootDir, ".data/chat-nuxt.db")}`;
263
+ rc.telaApiUrl = options.telaApiUrl || rc.telaApiUrl || process.env.TELA_API_URL || "";
252
264
  rc.telaApiKey = rc.telaApiKey || process.env.TELA_API_KEY || "";
253
- rc.authApiUrl = rc.authApiUrl || process.env.AUTH_API_URL || "";
254
- rc.authApiSecret = rc.authApiSecret || process.env.AUTH_API_SECRET || "";
255
- rc.agentApiUrl = rc.agentApiUrl || process.env.AGENT_API_URL || "";
256
- rc.authJwksUrl = rc.authJwksUrl || process.env.AUTH_JWKS_URL || "";
257
- rc.vaultUrl = rc.vaultUrl || process.env.VAULT_URL || "";
265
+ rc.agentApiUrl = options.agentApiUrl || rc.agentApiUrl || process.env.AGENT_API_URL || "";
266
+ rc.vaultUrl = options.vaultUrl || rc.vaultUrl || process.env.VAULT_URL || "";
258
267
  rc.vaultS3Bucket = rc.vaultS3Bucket || process.env.VAULT_S3_BUCKET || "";
259
268
  rc.chatAppServiceKey = rc.chatAppServiceKey || process.env.CHAT_APP_SERVICE_KEY || "";
260
269
  rc.public = rc.public || {};
261
- rc.public.agentApiUrl = rc.public.agentApiUrl || process.env.AGENT_API_URL || "";
262
- validateRuntimeConfig(rc, options.validateConfig ?? false);
270
+ rc.public.agentApiUrl = rc.agentApiUrl || rc.public.agentApiUrl || process.env.AGENT_API_URL || "";
271
+ rc.public.chatNuxtFeatures = features;
272
+ rc.chatNuxt = rc.chatNuxt || {};
273
+ rc.chatNuxt.migrationsDir = rc.chatNuxt.migrationsDir || migrationsDir;
274
+ rc.chatNuxt.features = features;
275
+ validateRuntimeConfig(rc, options.validateConfig ?? false, features);
263
276
  addImportsDir(join(runtimeDir, "composables"));
264
277
  addImportsDir(join(runtimeDir, "utils"));
265
278
  addComponent({
@@ -315,6 +328,7 @@ export default globalThis.ELK;
315
328
  const serverDir = join(runtimeDir, "server");
316
329
  const publicAssetsDir = join(runtimeDir, "public/assets");
317
330
  const publicFilesDir = join(runtimeDir, "public/files");
331
+ const dbInitPluginPath = join(runtimeDir, "server/plugins/init-db");
318
332
  nuxt.hook("nitro:config", (nitroConfig) => {
319
333
  nitroConfig.scanDirs = nitroConfig.scanDirs || [];
320
334
  nitroConfig.scanDirs.push(serverDir);
@@ -323,6 +337,10 @@ export default globalThis.ELK;
323
337
  "#chat-runtime": runtimeDir,
324
338
  "#chat-workspace-settings-resolver": workspaceSettingsResolverEntry
325
339
  };
340
+ nitroConfig.plugins = nitroConfig.plugins || [];
341
+ if (!nitroConfig.plugins.includes(dbInitPluginPath)) {
342
+ nitroConfig.plugins.push(dbInitPluginPath);
343
+ }
326
344
  nitroConfig.publicAssets = nitroConfig.publicAssets || [];
327
345
  nitroConfig.publicAssets.push({
328
346
  dir: publicAssetsDir,
@@ -1,14 +1,16 @@
1
1
  import type { DeepReadonly } from 'vue';
2
2
  import type { WorkspaceSettings } from '../composables/useWorkspaceSettings.js';
3
+ import type { ChatLoadingMessageMode } from '../utils/chat-loading-messages.js';
3
4
  import type { ChatActor, ChatFeatureConfig } from '../utils/tela-chat.js';
4
5
  type __VLS_Props = {
5
- workspaceId: string;
6
6
  workspaceSettings?: DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null;
7
7
  user?: ChatActor | null;
8
8
  conversationId?: string | null;
9
9
  initialConversationId?: string | null;
10
10
  hideSidebar?: boolean;
11
11
  features?: Partial<ChatFeatureConfig>;
12
+ loadingMessages?: readonly string[] | null;
13
+ loadingMessagesMode?: ChatLoadingMessageMode | null;
12
14
  };
13
15
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
14
16
  "update:conversationId": (value: string | null) => any;
@@ -1,25 +1,27 @@
1
1
  <script setup>
2
2
  defineProps({
3
- workspaceId: { type: String, required: true },
4
3
  workspaceSettings: { type: null, required: false },
5
4
  user: { type: [Object, null], required: false },
6
5
  conversationId: { type: [String, null], required: false },
7
6
  initialConversationId: { type: [String, null], required: false },
8
7
  hideSidebar: { type: Boolean, required: false },
9
- features: { type: Object, required: false }
8
+ features: { type: Object, required: false },
9
+ loadingMessages: { type: [Array, null], required: false },
10
+ loadingMessagesMode: { type: [String, null], required: false }
10
11
  });
11
12
  defineEmits(["update:conversationId"]);
12
13
  </script>
13
14
 
14
15
  <template>
15
16
  <ChatEmbed
16
- :workspace-id="workspaceId"
17
17
  :workspace-settings="workspaceSettings"
18
18
  :user="user"
19
19
  :conversation-id="conversationId"
20
20
  :initial-conversation-id="initialConversationId"
21
21
  :hide-sidebar="hideSidebar"
22
22
  :features="features"
23
+ :loading-messages="loadingMessages"
24
+ :loading-messages-mode="loadingMessagesMode"
23
25
  @update:conversation-id="$emit('update:conversationId', $event)"
24
26
  />
25
27
  </template>
@@ -1,14 +1,16 @@
1
1
  import type { DeepReadonly } from 'vue';
2
2
  import type { WorkspaceSettings } from '../composables/useWorkspaceSettings.js';
3
+ import type { ChatLoadingMessageMode } from '../utils/chat-loading-messages.js';
3
4
  import type { ChatActor, ChatFeatureConfig } from '../utils/tela-chat.js';
4
5
  type __VLS_Props = {
5
- workspaceId: string;
6
6
  workspaceSettings?: DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null;
7
7
  user?: ChatActor | null;
8
8
  conversationId?: string | null;
9
9
  initialConversationId?: string | null;
10
10
  hideSidebar?: boolean;
11
11
  features?: Partial<ChatFeatureConfig>;
12
+ loadingMessages?: readonly string[] | null;
13
+ loadingMessagesMode?: ChatLoadingMessageMode | null;
12
14
  };
13
15
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
14
16
  "update:conversationId": (value: string | null) => any;
@@ -1,34 +1,45 @@
1
1
  <script setup>
2
- const loadingTexts = [
3
- "Analisando sua pergunta",
4
- "Processando informa\xE7\xF5es",
5
- "Consultando base de conhecimento",
6
- "Organizando ideias",
7
- "Elaborando resposta",
8
- "Refinando detalhes",
9
- "Verificando contexto",
10
- "Conectando conceitos",
11
- "Preparando resultado",
12
- "Quase l\xE1",
13
- "Finalizando an\xE1lise",
14
- "Estruturando resposta"
15
- ];
2
+ import {
3
+ DEFAULT_CHAT_LOADING_MESSAGES,
4
+ getNextChatLoadingMessageIndex,
5
+ resolveChatLoadingMessages
6
+ } from "../../utils/chat-loading-messages";
7
+ const embedConfig = useEmbedConfig();
16
8
  const currentTextIndex = ref(0);
17
9
  const dotCount = ref(0);
10
+ const loadingState = computed(() => resolveChatLoadingMessages({
11
+ loadingMessages: embedConfig?.loadingMessages.value,
12
+ loadingMessagesMode: embedConfig?.loadingMessagesMode.value
13
+ }));
18
14
  const currentText = computed(() => {
19
- const base = loadingTexts[currentTextIndex.value];
15
+ const base = loadingState.value.messages[currentTextIndex.value] ?? loadingState.value.messages[0] ?? DEFAULT_CHAT_LOADING_MESSAGES[0];
20
16
  const dots = ".".repeat(dotCount.value);
21
17
  return `${base}${dots}`;
22
18
  });
19
+ watch(loadingState, (state) => {
20
+ if (currentTextIndex.value >= state.messages.length) {
21
+ currentTextIndex.value = 0;
22
+ dotCount.value = 0;
23
+ }
24
+ });
23
25
  let dotInterval;
24
26
  let textInterval;
27
+ function advanceText() {
28
+ currentTextIndex.value = getNextChatLoadingMessageIndex(
29
+ loadingState.value.mode,
30
+ loadingState.value.messages.length,
31
+ currentTextIndex.value
32
+ );
33
+ dotCount.value = 0;
34
+ }
25
35
  onMounted(() => {
36
+ if (loadingState.value.mode === "random")
37
+ advanceText();
26
38
  dotInterval = setInterval(() => {
27
39
  dotCount.value = (dotCount.value + 1) % 4;
28
40
  }, 500);
29
41
  textInterval = setInterval(() => {
30
- currentTextIndex.value = (currentTextIndex.value + 1) % loadingTexts.length;
31
- dotCount.value = 0;
42
+ advanceText();
32
43
  }, 7e3);
33
44
  });
34
45
  onUnmounted(() => {
@@ -1,14 +1,19 @@
1
1
  import type { DeepReadonly, MaybeRefOrGetter, Ref } from 'vue';
2
+ import type { ChatLoadingMessageMode } from '../utils/chat-loading-messages.js';
2
3
  import type { WorkspaceSettings } from './useWorkspaceSettings.js';
3
4
  export type EmbedConfig = {
4
5
  workspaceId: Ref<string>;
5
6
  workspaceSettings: Ref<DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null | undefined>;
6
7
  hideSidebar: Ref<boolean>;
8
+ loadingMessages: Ref<readonly string[] | null | undefined>;
9
+ loadingMessagesMode: Ref<ChatLoadingMessageMode | null | undefined>;
7
10
  };
8
11
  type EmbedConfigInput = {
9
12
  workspaceId: MaybeRefOrGetter<string>;
10
13
  workspaceSettings?: MaybeRefOrGetter<DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null | undefined>;
11
14
  hideSidebar?: MaybeRefOrGetter<boolean | undefined>;
15
+ loadingMessages?: MaybeRefOrGetter<readonly string[] | null | undefined>;
16
+ loadingMessagesMode?: MaybeRefOrGetter<ChatLoadingMessageMode | null | undefined>;
12
17
  };
13
18
  type ProvideEmbedConfigOptions = {
14
19
  syncGlobal?: boolean;
@@ -4,14 +4,18 @@ function useGlobalEmbedConfigState() {
4
4
  provided: useState("chat-embed-config-provided", () => false),
5
5
  workspaceId: useState("chat-embed-config-workspace-id", () => ""),
6
6
  workspaceSettings: useState("chat-embed-config-workspace-settings", () => void 0),
7
- hideSidebar: useState("chat-embed-config-hide-sidebar", () => void 0)
7
+ hideSidebar: useState("chat-embed-config-hide-sidebar", () => void 0),
8
+ loadingMessages: useState("chat-embed-config-loading-messages", () => void 0),
9
+ loadingMessagesMode: useState("chat-embed-config-loading-messages-mode", () => void 0)
8
10
  };
9
11
  }
10
12
  export function provideEmbedConfig(input, options = {}) {
11
13
  const config = {
12
14
  workspaceId: computed(() => toValue(input.workspaceId)),
13
15
  workspaceSettings: computed(() => toValue(input.workspaceSettings)),
14
- hideSidebar: computed(() => toValue(input.hideSidebar) ?? true)
16
+ hideSidebar: computed(() => toValue(input.hideSidebar) ?? true),
17
+ loadingMessages: computed(() => toValue(input.loadingMessages)),
18
+ loadingMessagesMode: computed(() => toValue(input.loadingMessagesMode))
15
19
  };
16
20
  const globalConfig = useGlobalEmbedConfigState();
17
21
  if (options.syncGlobal ?? true) {
@@ -20,6 +24,8 @@ export function provideEmbedConfig(input, options = {}) {
20
24
  globalConfig.workspaceId.value = config.workspaceId.value;
21
25
  globalConfig.workspaceSettings.value = config.workspaceSettings.value;
22
26
  globalConfig.hideSidebar.value = config.hideSidebar.value;
27
+ globalConfig.loadingMessages.value = config.loadingMessages.value;
28
+ globalConfig.loadingMessagesMode.value = config.loadingMessagesMode.value;
23
29
  });
24
30
  }
25
31
  provide(EMBED_CONFIG_KEY, config);
@@ -34,6 +40,8 @@ export function useEmbedConfig() {
34
40
  return {
35
41
  workspaceId: computed(() => globalConfig.workspaceId.value),
36
42
  workspaceSettings: computed(() => globalConfig.workspaceSettings.value),
37
- hideSidebar: computed(() => globalConfig.hideSidebar.value ?? true)
43
+ hideSidebar: computed(() => globalConfig.hideSidebar.value ?? true),
44
+ loadingMessages: computed(() => globalConfig.loadingMessages.value),
45
+ loadingMessagesMode: computed(() => globalConfig.loadingMessagesMode.value)
38
46
  };
39
47
  }
@@ -18,6 +18,7 @@ const {
18
18
  fetchSettings,
19
19
  updateSettings
20
20
  } = useWorkspaceSettings();
21
+ const features = useChatNuxtFeatures();
21
22
  const searchQuery = ref("");
22
23
  const activeTab = ref(props.initialTab);
23
24
  const showDiscardConfirm = ref(false);
@@ -34,14 +35,14 @@ const telaTools = useWorkspaceTelaTools(canvas, projects, searchQuery, currentCa
34
35
  const knowledgeSources = useWorkspaceKnowledgeSources(workstations, canvas, projects, searchQuery, currentKnowledgeSources);
35
36
  const contextFiles = useWorkspaceContextFiles(searchQuery, currentContextFiles);
36
37
  const externalSkills = useWorkspaceExternalSkills(currentExternalSkills, searchQuery);
37
- const settingsTabs = [
38
+ const settingsTabs = computed(() => [
38
39
  { value: "personalization", label: "Personaliza\xE7\xE3o", icon: "i-ph-sliders" },
39
40
  { value: "context-files", label: "Arquivos de contexto", icon: "i-ph-file-text" },
40
41
  { value: "knowledge-sources", label: "Tela workstations", icon: "i-ph-database" },
41
42
  { value: "tela-tools", label: "Skills Tela", icon: "i-ph-puzzle-piece" },
42
43
  { value: "external-skills", label: "Skills Externas", icon: "i-ph-wrench" },
43
- { value: "credentials", label: "Credenciais", icon: "i-ph-password-bold" }
44
- ];
44
+ ...features.credentials ? [{ value: "credentials", label: "Credenciais", icon: "i-ph-password-bold" }] : []
45
+ ]);
45
46
  const hasChanges = computed(
46
47
  () => personalization.hasChanges.value || contextFiles.hasChanges.value || telaTools.hasChanges.value || knowledgeSources.hasChanges.value || externalSkills.hasChanges.value
47
48
  );
@@ -84,9 +85,10 @@ function initializeConfigurationState() {
84
85
  externalSkills.initialize();
85
86
  }
86
87
  function activateTab(tab) {
87
- activeTab.value = tab;
88
+ const isVisible = settingsTabs.value.some((t) => t.value === tab);
89
+ activeTab.value = isVisible ? tab : "personalization";
88
90
  searchQuery.value = "";
89
- if (tab === "external-skills")
91
+ if (activeTab.value === "external-skills")
90
92
  void externalSkills.fetchGitHubConnection();
91
93
  }
92
94
  async function loadSettings() {
@@ -336,7 +338,7 @@ function cancelDiscard() {
336
338
  />
337
339
 
338
340
  <ChatConfigurationCredentialsTab
339
- v-else-if="activeTab === 'credentials'"
341
+ v-else-if="activeTab === 'credentials' && features.credentials"
340
342
  :search-query="searchQuery"
341
343
  @update:search-query="searchQuery = $event"
342
344
  />
@@ -1,15 +1,17 @@
1
1
  import type { DeepReadonly } from 'vue';
2
2
  import type { WorkspaceSettings } from '../../composables/useWorkspaceSettings.js';
3
+ import type { ChatLoadingMessageMode } from '../../utils/chat-loading-messages.js';
3
4
  import type { ChatActor, ChatFeatureConfig } from '../../utils/tela-chat.js';
4
5
  type EmbedUser = ChatActor;
5
6
  type __VLS_Props = {
6
- workspaceId: string;
7
7
  workspaceSettings?: DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null;
8
8
  conversationId?: string | null;
9
9
  initialConversationId?: string | null;
10
10
  hideSidebar?: boolean;
11
11
  user?: EmbedUser | null;
12
12
  features?: Partial<ChatFeatureConfig>;
13
+ loadingMessages?: readonly string[] | null;
14
+ loadingMessagesMode?: ChatLoadingMessageMode | null;
13
15
  };
14
16
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
15
17
  "update:conversationId": (value: string | null) => any;