@meistrari/chat-nuxt 1.1.3 → 1.2.1

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 (68) hide show
  1. package/README.md +70 -4
  2. package/dist/module.d.mts +5 -0
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +74 -27
  5. package/dist/runtime/components/chat/conversation-list.d.vue.ts +1 -0
  6. package/dist/runtime/components/chat/conversation-list.vue +22 -2
  7. package/dist/runtime/components/chat/conversation-list.vue.d.ts +1 -0
  8. package/dist/runtime/components/chat/message-list.d.vue.ts +1 -0
  9. package/dist/runtime/components/chat/message-list.vue +23 -2
  10. package/dist/runtime/components/chat/message-list.vue.d.ts +1 -0
  11. package/dist/runtime/composables/useChat.d.ts +2 -1
  12. package/dist/runtime/composables/useChat.js +32 -3
  13. package/dist/runtime/composables/useChatApi.js +2 -1
  14. package/dist/runtime/composables/useConversations.d.ts +6 -3
  15. package/dist/runtime/composables/useConversations.js +114 -12
  16. package/dist/runtime/composables/useEmbedConfig.d.ts +11 -6
  17. package/dist/runtime/composables/useEmbedConfig.js +35 -7
  18. package/dist/runtime/composables/useIDB.js +4 -0
  19. package/dist/runtime/composables/usePdf.d.ts +840 -1
  20. package/dist/runtime/composables/usePdf.js +21 -3
  21. package/dist/runtime/composables/useWorkspaceContextFiles.d.ts +3 -3
  22. package/dist/runtime/composables/useWorkspaceContextFiles.js +19 -10
  23. package/dist/runtime/composables/useWorkspaceExternalSkills.d.ts +1 -1
  24. package/dist/runtime/composables/useWorkspaceExternalSkills.js +5 -6
  25. package/dist/runtime/composables/useWorkspacePersonalization.d.ts +1 -1
  26. package/dist/runtime/composables/useWorkspacePersonalization.js +4 -5
  27. package/dist/runtime/composables/useWorkspaceSettings.js +11 -12
  28. package/dist/runtime/embed/components/ChatConfigurationModal.d.vue.ts +25 -0
  29. package/dist/runtime/embed/components/ChatConfigurationModal.vue +373 -0
  30. package/dist/runtime/embed/components/ChatConfigurationModal.vue.d.ts +25 -0
  31. package/dist/runtime/embed/components/ChatEmbed.vue +12 -1
  32. package/dist/runtime/embed/components/ChatEmbedInner.vue +35 -19
  33. package/dist/runtime/embed/components/configuration/ChatConfigurationContextFilesTab.d.vue.ts +22 -0
  34. package/dist/runtime/embed/components/configuration/ChatConfigurationContextFilesTab.vue +133 -0
  35. package/dist/runtime/embed/components/configuration/ChatConfigurationContextFilesTab.vue.d.ts +22 -0
  36. package/dist/runtime/embed/components/configuration/ChatConfigurationCredentialsTab.d.vue.ts +10 -0
  37. package/dist/runtime/embed/components/configuration/ChatConfigurationCredentialsTab.vue +220 -0
  38. package/dist/runtime/embed/components/configuration/ChatConfigurationCredentialsTab.vue.d.ts +10 -0
  39. package/dist/runtime/embed/components/configuration/ChatConfigurationExternalSkillsTab.d.vue.ts +32 -0
  40. package/dist/runtime/embed/components/configuration/ChatConfigurationExternalSkillsTab.vue +222 -0
  41. package/dist/runtime/embed/components/configuration/ChatConfigurationExternalSkillsTab.vue.d.ts +32 -0
  42. package/dist/runtime/embed/components/configuration/ChatConfigurationKnowledgeSourcesTab.d.vue.ts +18 -0
  43. package/dist/runtime/embed/components/configuration/ChatConfigurationKnowledgeSourcesTab.vue +142 -0
  44. package/dist/runtime/embed/components/configuration/ChatConfigurationKnowledgeSourcesTab.vue.d.ts +18 -0
  45. package/dist/runtime/embed/components/configuration/ChatConfigurationPersonalizationTab.d.vue.ts +10 -0
  46. package/dist/runtime/embed/components/configuration/ChatConfigurationPersonalizationTab.vue +37 -0
  47. package/dist/runtime/embed/components/configuration/ChatConfigurationPersonalizationTab.vue.d.ts +10 -0
  48. package/dist/runtime/embed/components/configuration/ChatConfigurationTelaToolsTab.d.vue.ts +20 -0
  49. package/dist/runtime/embed/components/configuration/ChatConfigurationTelaToolsTab.vue +146 -0
  50. package/dist/runtime/embed/components/configuration/ChatConfigurationTelaToolsTab.vue.d.ts +20 -0
  51. package/dist/runtime/server/api/conversations/[id]/duplicate.post.js +45 -12
  52. package/dist/runtime/types/chat.d.ts +2 -1
  53. package/dist/runtime/types/workspace-settings.d.ts +3 -0
  54. package/dist/runtime/types/workspace-settings.js +0 -0
  55. package/dist/runtime/utils/conversation-list-skeleton.d.ts +31 -0
  56. package/dist/runtime/utils/conversation-list-skeleton.js +12 -0
  57. package/dist/runtime/utils/file.js +42 -42
  58. package/dist/types.d.mts +2 -0
  59. package/package.json +23 -27
  60. package/dist/runtime/composables/PageNavigation.d.ts +0 -13
  61. package/dist/runtime/composables/PageNavigation.js +0 -18
  62. /package/dist/runtime/components/{pdf-preview.d.vue.ts → pdf-preview.client.d.vue.ts} +0 -0
  63. /package/dist/runtime/components/{pdf-preview.vue → pdf-preview.client.vue} +0 -0
  64. /package/dist/runtime/components/{pdf-preview.vue.d.ts → pdf-preview.client.vue.d.ts} +0 -0
  65. /package/dist/runtime/components/{pdf-viewer.d.vue.ts → pdf-viewer.client.d.vue.ts} +0 -0
  66. /package/dist/runtime/components/{pdf-viewer.vue → pdf-viewer.client.vue} +0 -0
  67. /package/dist/runtime/components/{pdf-viewer.vue.d.ts → pdf-viewer.client.vue.d.ts} +0 -0
  68. /package/dist/runtime/public/{github.svg → assets/github.svg} +0 -0
package/README.md CHANGED
@@ -5,7 +5,7 @@ Nuxt module that adds a complete AI chat interface to your app. Powered by Tela'
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- pnpm add @meistrari/chat-nuxt @meistrari/auth-nuxt @meistrari/tela-build markstream-vue
8
+ pnpm add @meistrari/chat-nuxt
9
9
  ```
10
10
 
11
11
  ### Required: Vue dedupe via pnpm overrides
@@ -33,8 +33,6 @@ Keep this entry until `@meistrari/tela-build` moves Vue to `peerDependencies`
33
33
  ```typescript
34
34
  // nuxt.config.ts
35
35
  export default defineNuxtConfig({
36
- extends: ['@meistrari/tela-build'],
37
-
38
36
  modules: [
39
37
  '@meistrari/auth-nuxt', // must come before chat-nuxt
40
38
  '@meistrari/chat-nuxt',
@@ -42,6 +40,10 @@ export default defineNuxtConfig({
42
40
 
43
41
  ssr: false,
44
42
 
43
+ chatNuxt: {
44
+ validateConfig: 'warn',
45
+ },
46
+
45
47
  telaAuth: {
46
48
  apiUrl: process.env.AUTH_API_URL ?? '',
47
49
  application: {
@@ -76,6 +78,9 @@ The module injects these into Nuxt's `runtimeConfig` automatically — just set
76
78
  | `AGENT_API_URL` | Agent API endpoint |
77
79
  | `VAULT_URL` | Vault service URL |
78
80
  | `VAULT_S3_BUCKET` | Vault S3 bucket name |
81
+ | `CHAT_APP_SERVICE_KEY` | Service key used when resolving workspace credentials |
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.
79
84
 
80
85
  ### 3. Setup `app.vue`
81
86
 
@@ -272,6 +277,49 @@ export default async function resolveWorkspaceSettings(
272
277
  }
273
278
  ```
274
279
 
280
+ ## `<ChatConfigurationModal>`
281
+
282
+ A reusable settings modal (system prompt, context files, Tela workstations, Tela skills, external skills, credentials) that any host app can open from any button. 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.
283
+
284
+ ```vue
285
+ <script setup lang="ts">
286
+ import type { ChatConfigurationTab } from '@meistrari/chat-nuxt/types/workspace-settings'
287
+
288
+ const open = ref(false)
289
+ const initialTab = ref<ChatConfigurationTab>('personalization')
290
+
291
+ function openSettings(tab: ChatConfigurationTab = 'personalization') {
292
+ initialTab.value = tab
293
+ open.value = true
294
+ }
295
+ </script>
296
+
297
+ <template>
298
+ <button @click="openSettings()">
299
+ Configurações
300
+ </button>
301
+
302
+ <ChatConfigurationModal
303
+ v-model:open="open"
304
+ :initial-tab="initialTab"
305
+ />
306
+ </template>
307
+ ```
308
+
309
+ | Prop | Type | Default | Description |
310
+ |------|------|---------|-------------|
311
+ | `open` | `boolean` | — | Modal visibility (supports `v-model:open`) |
312
+ | `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 |
313
+ | `initialTab` | `ChatConfigurationTab` | `'personalization'` | Tab to open on |
314
+ | `saving` | `boolean` | `false` | Host-controlled spinner/disable while persistence is in flight |
315
+
316
+ | Event | Payload | Description |
317
+ |-------|---------|-------------|
318
+ | `update:open` | `boolean` | Modal open state changed |
319
+ | `save` | `ChatConfigurationSavePayload` | Emitted only when the host passes `settings`; diff of the changed fields plus `onSaved`; host persists (e.g. via `useWorkspaceSettings().updateSettings()`), calls `onSaved` after persistence succeeds, and closes the modal |
320
+
321
+ The `credentials` tab manages its own data via the module's `/api/workspace/credentials` endpoints and is never part of the `save` payload.
322
+
275
323
  ## What's Included
276
324
 
277
325
  The module auto-registers everything — no manual imports needed:
@@ -281,10 +329,28 @@ The module auto-registers everything — no manual imports needed:
281
329
  - **Server routes**: conversation CRUD, message streaming, workspace settings, vault file storage, GitHub integration
282
330
  - **Database**: conversations, messages, usage tracking (Drizzle ORM + PostgreSQL)
283
331
 
284
- ## Peer Dependencies
332
+ ## Package Dependencies
333
+
334
+ Installing `@meistrari/chat-nuxt` installs the runtime packages used by the layer. The host Nuxt app still owns the Nuxt and Vue versions.
285
335
 
286
336
  | Package | Version | Purpose |
287
337
  |---------|---------|---------|
288
338
  | `@meistrari/auth-nuxt` | `^3.4.1` | Authentication (required) |
289
339
  | `@meistrari/tela-build` | `^1.30.0` | UI component library (Nuxt layer) |
340
+ | `@sentry/nuxt` | `^10.0.0` | Client-side error capture used by chat composables |
341
+ | `@iconify/vue` | `^5.0.0` | Icon rendering support |
342
+ | `@vueuse/core` | `^12.8.0` | Runtime composables used by chat UI |
343
+ | `@vueuse/components` | `^12.8.0` | VueUse component peer expected by the shared runtime |
344
+ | `h3` | `^1.15.0` | Server route utilities provided by Nuxt/Nitro |
290
345
  | `markstream-vue` | `0.0.3-beta.6` | Markdown streaming renderer |
346
+ | `motion` | `^11.13.0` | Animation component runtime |
347
+ | `nitropack` | `^2.10.0` | Runtime config and task utilities provided by Nuxt/Nitro |
348
+ | `virtua` | `^0.42.0` | Virtualized list runtime expected by shared UI |
349
+ | `vue-i18n` | `^11.0.0` | Internationalization runtime expected by shared UI |
350
+
351
+ Peer dependencies:
352
+
353
+ | Package | Version | Purpose |
354
+ |---------|---------|---------|
355
+ | `nuxt` | `^3.17.0` | Host framework |
356
+ | `vue` | `^3.5.0` | Host Vue runtime |
package/dist/module.d.mts CHANGED
@@ -1,3 +1,8 @@
1
+ type ChatNuxtConfigValidationMode = false | 'warn' | 'error';
2
+ interface ChatNuxtModuleOptions {
3
+ validateConfig?: ChatNuxtConfigValidationMode;
4
+ }
1
5
  declare const _default: any;
2
6
 
3
7
  export { _default as default };
8
+ export type { 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.1.3",
4
+ "version": "1.2.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -37,7 +37,7 @@ function isExpectedResolutionError(error) {
37
37
  }
38
38
  return error.code === "MODULE_NOT_FOUND" || error.code === "ERR_PACKAGE_PATH_NOT_EXPORTED";
39
39
  }
40
- function resolvePackageRoot(resolver, packageName) {
40
+ function resolvePackageRoot$1(resolver, packageName) {
41
41
  try {
42
42
  return dirname(resolver.resolve(`${packageName}/package.json`));
43
43
  } catch (error) {
@@ -81,7 +81,7 @@ function resolveTelaBuildLayer() {
81
81
  return localPath;
82
82
  }
83
83
  }
84
- const telaBuildRoot = resolvePackageRoot(localRequire, "@meistrari/tela-build");
84
+ const telaBuildRoot = resolvePackageRoot$1(localRequire, "@meistrari/tela-build");
85
85
  if (!telaBuildRoot) {
86
86
  throw new Error(
87
87
  "[@meistrari/chat-nuxt] Cannot resolve @meistrari/tela-build. Ensure it is installed."
@@ -95,19 +95,19 @@ function resolveRuntimeAliases(telaBuildPath) {
95
95
  const telaBuildRequire = createRequire(join(telaBuildPath, "package.json"));
96
96
  const vueRuntimeEntry = resolvePackageEntry(nuxtRequire, "vue/dist/vue.runtime.esm-bundler.js");
97
97
  const aliases = [
98
- ["vue", vueRuntimeEntry ?? resolvePackageRoot(nuxtRequire, "vue")],
98
+ ["vue", vueRuntimeEntry ?? resolvePackageRoot$1(nuxtRequire, "vue")],
99
99
  ["vue/dist/vue.runtime.esm-bundler.js", vueRuntimeEntry],
100
- ["@vue/runtime-core", resolvePackageEntry(nuxtRequire, "@vue/runtime-core/dist/runtime-core.esm-bundler.js") ?? resolvePackageRoot(nuxtRequire, "@vue/runtime-core")],
101
- ["@vue/runtime-dom", resolvePackageEntry(nuxtRequire, "@vue/runtime-dom/dist/runtime-dom.esm-bundler.js") ?? resolvePackageRoot(nuxtRequire, "@vue/runtime-dom")],
102
- ["@vue/reactivity", resolvePackageEntry(nuxtRequire, "@vue/reactivity/dist/reactivity.esm-bundler.js") ?? resolvePackageRoot(nuxtRequire, "@vue/reactivity")],
103
- ["@vue/shared", resolvePackageEntry(nuxtRequire, "@vue/shared/dist/shared.esm-bundler.js") ?? resolvePackageRoot(nuxtRequire, "@vue/shared")],
104
- ["vue-router", resolvePackageEntry(nuxtRequire, "vue-router/dist/vue-router.mjs") ?? resolvePackageRoot(nuxtRequire, "vue-router")],
105
- ["@vueuse/core", resolvePackageRoot(localRequire, "@vueuse/core")],
106
- ["@vueuse/components", resolvePackageRoot(localRequire, "@vueuse/components")],
107
- ["@vueuse/shared", resolvePackageRoot(vueUseCoreRequire, "@vueuse/shared")],
108
- ["reka-ui", resolvePackageRoot(telaBuildRequire, "reka-ui")],
109
- ["radix-vue", resolvePackageRoot(telaBuildRequire, "radix-vue")],
110
- ["@floating-ui/vue", resolvePackageRoot(telaBuildRequire, "@floating-ui/vue")]
100
+ ["@vue/runtime-core", resolvePackageEntry(nuxtRequire, "@vue/runtime-core/dist/runtime-core.esm-bundler.js") ?? resolvePackageRoot$1(nuxtRequire, "@vue/runtime-core")],
101
+ ["@vue/runtime-dom", resolvePackageEntry(nuxtRequire, "@vue/runtime-dom/dist/runtime-dom.esm-bundler.js") ?? resolvePackageRoot$1(nuxtRequire, "@vue/runtime-dom")],
102
+ ["@vue/reactivity", resolvePackageEntry(nuxtRequire, "@vue/reactivity/dist/reactivity.esm-bundler.js") ?? resolvePackageRoot$1(nuxtRequire, "@vue/reactivity")],
103
+ ["@vue/shared", resolvePackageEntry(nuxtRequire, "@vue/shared/dist/shared.esm-bundler.js") ?? resolvePackageRoot$1(nuxtRequire, "@vue/shared")],
104
+ ["vue-router", resolvePackageEntry(nuxtRequire, "vue-router/dist/vue-router.mjs") ?? resolvePackageRoot$1(nuxtRequire, "vue-router")],
105
+ ["@vueuse/core", resolvePackageRoot$1(localRequire, "@vueuse/core")],
106
+ ["@vueuse/components", resolvePackageRoot$1(localRequire, "@vueuse/components")],
107
+ ["@vueuse/shared", resolvePackageRoot$1(vueUseCoreRequire, "@vueuse/shared")],
108
+ ["reka-ui", resolvePackageRoot$1(telaBuildRequire, "reka-ui")],
109
+ ["radix-vue", resolvePackageRoot$1(telaBuildRequire, "radix-vue")],
110
+ ["@floating-ui/vue", resolvePackageRoot$1(telaBuildRequire, "@floating-ui/vue")]
111
111
  ];
112
112
  const resolvedAliases = aliases.reduce((acc, [name, resolvedPath]) => {
113
113
  if (resolvedPath) {
@@ -148,6 +148,20 @@ function resolveWorkspaceSettingsResolverEntry(srcDir, fallbackResolverBase) {
148
148
  }
149
149
 
150
150
  const _chatNuxtRequire = createRequire(import.meta.url);
151
+ const requiredRuntimeConfigKeys = [
152
+ "databaseUrl",
153
+ "telaApiUrl",
154
+ "telaApiKey",
155
+ "authApiUrl",
156
+ "authApiSecret",
157
+ "agentApiUrl",
158
+ "authJwksUrl",
159
+ "vaultUrl",
160
+ "chatAppServiceKey"
161
+ ];
162
+ function resolvePackageRoot(packageName) {
163
+ return dirname(_chatNuxtRequire.resolve(`${packageName}/package.json`));
164
+ }
151
165
  function ensureIconifyPackages(consumerRoot) {
152
166
  const packages = ["@iconify-json/ph"];
153
167
  for (const pkg of packages) {
@@ -156,14 +170,27 @@ function ensureIconifyPackages(consumerRoot) {
156
170
  continue;
157
171
  }
158
172
  try {
159
- const realPath = dirname(_chatNuxtRequire.resolve(`${pkg}/package.json`));
160
173
  mkdirSync(dirname(consumerPath), { recursive: true });
161
- symlinkSync(realPath, consumerPath);
174
+ symlinkSync(resolvePackageRoot(pkg), consumerPath);
162
175
  } catch (err) {
163
- console.warn(`[@meistrari/chat-nuxt] Failed to symlink ${pkg} \u2014 icons may not render.`, err);
176
+ console.warn(`[@meistrari/chat-nuxt] Failed to link ${pkg} into the consumer app. Icons may not render.`, err);
164
177
  }
165
178
  }
166
179
  }
180
+ function validateRuntimeConfig(runtimeConfig, mode) {
181
+ if (!mode) {
182
+ return;
183
+ }
184
+ const missingKeys = requiredRuntimeConfigKeys.filter((key) => !runtimeConfig[key]);
185
+ if (missingKeys.length === 0) {
186
+ return;
187
+ }
188
+ const message = `[@meistrari/chat-nuxt] Missing runtime config: ${missingKeys.join(", ")}. Set these values in runtimeConfig or the matching environment variables before using chat server routes.`;
189
+ if (mode === "error") {
190
+ throw new Error(message);
191
+ }
192
+ console.warn(message);
193
+ }
167
194
  function isAuthModuleEntry(entry) {
168
195
  if (typeof entry === "string") {
169
196
  return entry === "@meistrari/auth-nuxt";
@@ -181,7 +208,10 @@ const module$1 = defineNuxtModule({
181
208
  name: "@meistrari/chat-nuxt",
182
209
  configKey: "chatNuxt"
183
210
  },
184
- setup(_, nuxt) {
211
+ defaults: {
212
+ validateConfig: false
213
+ },
214
+ setup(options, nuxt) {
185
215
  const hasAuthModule = (nuxt.options.modules ?? []).some(isAuthModuleEntry);
186
216
  if (!hasAuthModule) {
187
217
  throw new Error(
@@ -190,10 +220,12 @@ const module$1 = defineNuxtModule({
190
220
  }
191
221
  ensureIconifyPackages(nuxt.options.rootDir);
192
222
  const resolver = createResolver(import.meta.url);
193
- const runtimeDir = resolver.resolve("./runtime");
223
+ const builtRuntimeDir = resolver.resolve("./runtime");
224
+ const sourceRuntimeDir = resolver.resolve("../src/runtime");
225
+ const runtimeDir = nuxt.options.dev && existsSync(sourceRuntimeDir) ? sourceRuntimeDir : builtRuntimeDir;
194
226
  const workspaceSettingsResolverEntry = resolveWorkspaceSettingsResolverEntry(
195
227
  nuxt.options.srcDir,
196
- resolver.resolve("./runtime/internal/workspace-settings-resolver.stub")
228
+ join(runtimeDir, "internal/workspace-settings-resolver.stub")
197
229
  );
198
230
  const telaBuildPath = resolveTelaBuildLayer();
199
231
  const runtimeAliases = resolveRuntimeAliases(telaBuildPath);
@@ -204,7 +236,8 @@ const module$1 = defineNuxtModule({
204
236
  nuxt.options.alias = {
205
237
  ...nuxt.options.alias,
206
238
  "#chat-workspace-settings-resolver": workspaceSettingsResolverEntry,
207
- "#chat-runtime": runtimeDir
239
+ "#chat-runtime": runtimeDir,
240
+ "@iconify-json/ph": resolvePackageRoot("@iconify-json/ph")
208
241
  };
209
242
  if (nuxt.options.imports !== false) {
210
243
  nuxt.options.imports = nuxt.options.imports || {};
@@ -226,18 +259,19 @@ const module$1 = defineNuxtModule({
226
259
  rc.chatAppServiceKey = rc.chatAppServiceKey || process.env.CHAT_APP_SERVICE_KEY || "";
227
260
  rc.public = rc.public || {};
228
261
  rc.public.agentApiUrl = rc.public.agentApiUrl || process.env.AGENT_API_URL || "";
229
- addImportsDir(resolver.resolve("./runtime/composables"));
230
- addImportsDir(resolver.resolve("./runtime/utils"));
262
+ validateRuntimeConfig(rc, options.validateConfig ?? false);
263
+ addImportsDir(join(runtimeDir, "composables"));
264
+ addImportsDir(join(runtimeDir, "utils"));
231
265
  addComponent({
232
266
  name: "MeistrariChatEmbed",
233
- filePath: resolver.resolve("./runtime/components/MeistrariChatEmbed.vue"),
267
+ filePath: join(runtimeDir, "components/MeistrariChatEmbed.vue"),
234
268
  priority: 1e3
235
269
  });
236
270
  addComponentsDir({
237
- path: resolver.resolve("./runtime/components")
271
+ path: join(runtimeDir, "components")
238
272
  });
239
273
  addComponentsDir({
240
- path: resolver.resolve("./runtime/embed/components"),
274
+ path: join(runtimeDir, "embed/components"),
241
275
  pathPrefix: false
242
276
  });
243
277
  nuxt.hook("vite:extendConfig", (config, { isServer }) => {
@@ -278,7 +312,9 @@ export default globalThis.ELK;
278
312
  }
279
313
  });
280
314
  });
281
- const serverDir = resolver.resolve("./runtime/server");
315
+ const serverDir = join(runtimeDir, "server");
316
+ const publicAssetsDir = join(runtimeDir, "public/assets");
317
+ const publicFilesDir = join(runtimeDir, "public/files");
282
318
  nuxt.hook("nitro:config", (nitroConfig) => {
283
319
  nitroConfig.scanDirs = nitroConfig.scanDirs || [];
284
320
  nitroConfig.scanDirs.push(serverDir);
@@ -287,6 +323,17 @@ export default globalThis.ELK;
287
323
  "#chat-runtime": runtimeDir,
288
324
  "#chat-workspace-settings-resolver": workspaceSettingsResolverEntry
289
325
  };
326
+ nitroConfig.publicAssets = nitroConfig.publicAssets || [];
327
+ nitroConfig.publicAssets.push({
328
+ dir: publicAssetsDir,
329
+ baseURL: "/chat-assets",
330
+ maxAge: 60 * 60 * 24 * 7
331
+ });
332
+ nitroConfig.publicAssets.push({
333
+ dir: publicFilesDir,
334
+ baseURL: "/chat-files",
335
+ maxAge: 60 * 60 * 24 * 7
336
+ });
290
337
  });
291
338
  }
292
339
  });
@@ -4,6 +4,7 @@ type __VLS_Props = {
4
4
  conversations: DeepReadonly<Conversation[]>;
5
5
  currentId?: string;
6
6
  getMemberImage?: (userId: string) => string | undefined;
7
+ loading?: boolean;
7
8
  };
8
9
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
10
  declare const _default: typeof __VLS_export;
@@ -1,13 +1,16 @@
1
1
  <script setup>
2
2
  import { groupConversationsByDate } from "../../utils/format-time";
3
3
  import { exportConversationToMarkdown } from "../../utils/export-conversation";
4
+ import { conversationListSkeletonRows } from "../../utils/conversation-list-skeleton";
4
5
  const props = defineProps({
5
6
  conversations: { type: null, required: true },
6
7
  currentId: { type: String, required: false },
7
- getMemberImage: { type: Function, required: false }
8
+ getMemberImage: { type: Function, required: false },
9
+ loading: { type: Boolean, required: false }
8
10
  });
9
11
  const chatApi = useChatApi();
10
12
  const { deleteConversation, renameConversation, duplicateConversation } = useConversations();
13
+ const { setConversation } = useChat();
11
14
  const router = useRouter();
12
15
  const groupedConversations = computed(() => {
13
16
  return groupConversationsByDate(props.conversations);
@@ -25,6 +28,7 @@ async function handleRename(id, title) {
25
28
  async function handleDuplicate(id) {
26
29
  const duplicated = await duplicateConversation(id);
27
30
  if (duplicated) {
31
+ setConversation(duplicated);
28
32
  router.push(`/${duplicated.id}`);
29
33
  }
30
34
  }
@@ -43,7 +47,23 @@ async function handleExport(id) {
43
47
 
44
48
  <template>
45
49
  <div flex="~ col" gap-16px>
46
- <div v-if="conversations.length === 0" p-16px text-center>
50
+ <div v-if="loading && conversations.length === 0" flex="~ col" gap-4px>
51
+ <div h-20px flex items-center pl-12px pr-4px mb-4px>
52
+ <TelaSkeleton rounded-4px bg-neutral-200 style="width: 68px; height: 12px;" />
53
+ </div>
54
+
55
+ <div flex="~ col" gap-1px>
56
+ <div
57
+ v-for="row in conversationListSkeletonRows"
58
+ :key="row.id"
59
+ flex items-center h-32px px-8px rounded-8px
60
+ >
61
+ <TelaSkeleton rounded-6px bg-neutral-200 :style="{ width: row.width, height: '13px' }" />
62
+ </div>
63
+ </div>
64
+ </div>
65
+
66
+ <div v-else-if="conversations.length === 0" p-16px text-center>
47
67
  <p text-14px text-neutral-400>
48
68
  Nenhuma conversa ainda
49
69
  </p>
@@ -4,6 +4,7 @@ type __VLS_Props = {
4
4
  conversations: DeepReadonly<Conversation[]>;
5
5
  currentId?: string;
6
6
  getMemberImage?: (userId: string) => string | undefined;
7
+ loading?: boolean;
7
8
  };
8
9
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
10
  declare const _default: typeof __VLS_export;
@@ -1,6 +1,7 @@
1
1
  import type { Message } from '../../types/chat.js';
2
2
  type __VLS_Props = {
3
3
  messages: readonly Message[];
4
+ loading?: boolean;
4
5
  };
5
6
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
6
7
  retry: (messageId: string) => any;
@@ -1,9 +1,16 @@
1
1
  <script setup>
2
2
  const props = defineProps({
3
- messages: { type: Array, required: true }
3
+ messages: { type: Array, required: true },
4
+ loading: { type: Boolean, required: false }
4
5
  });
5
6
  const emit = defineEmits(["retry"]);
6
7
  const containerRef = ref(null);
8
+ const skeletonMessages = [
9
+ { id: "user-1", align: "end", width: "58%", height: "44px" },
10
+ { id: "assistant-1", align: "start", width: "92%", height: "116px" },
11
+ { id: "user-2", align: "end", width: "46%", height: "44px" },
12
+ { id: "assistant-2", align: "start", width: "84%", height: "92px" }
13
+ ];
7
14
  function scrollToBottom() {
8
15
  nextTick(() => {
9
16
  if (containerRef.value) {
@@ -23,7 +30,21 @@ onMounted(scrollToBottom);
23
30
  pt-88px pb-24px px-24px
24
31
  >
25
32
  <div max-w-800px mx-auto>
26
- <div flex="~ col" gap-16px>
33
+ <div v-if="loading" flex="~ col" gap-16px>
34
+ <div
35
+ v-for="message in skeletonMessages"
36
+ :key="message.id"
37
+ flex
38
+ :class="message.align === 'end' ? 'justify-end' : 'justify-start'"
39
+ >
40
+ <TelaSkeleton
41
+ rounded-16px
42
+ :style="{ width: message.width, height: message.height }"
43
+ />
44
+ </div>
45
+ </div>
46
+
47
+ <div v-else flex="~ col" gap-16px>
27
48
  <ChatMessageBubble
28
49
  v-for="message in messages"
29
50
  :key="message.id"
@@ -1,6 +1,7 @@
1
1
  import type { Message } from '../../types/chat.js';
2
2
  type __VLS_Props = {
3
3
  messages: readonly Message[];
4
+ loading?: boolean;
4
5
  };
5
6
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
6
7
  retry: (messageId: string) => any;
@@ -1,4 +1,4 @@
1
- import type { Message } from '../types/chat.js';
1
+ import type { Conversation, ConversationWithMessages, Message } from '../types/chat.js';
2
2
  export declare function useChat(): {
3
3
  currentConversation: any;
4
4
  messages: any;
@@ -11,6 +11,7 @@ export declare function useChat(): {
11
11
  generatingTitle: any;
12
12
  animatedTitle: any;
13
13
  loadConversation: (id: string) => Promise<void>;
14
+ setConversation: (conv: Conversation | ConversationWithMessages) => void;
14
15
  sendMessage: (content: string, files?: {
15
16
  url: string;
16
17
  name: string;
@@ -144,8 +144,29 @@ function parseReasoningFromPolling(reasoning) {
144
144
  }
145
145
  export function useChat() {
146
146
  const chatApi = useChatApi();
147
- const currentConversation = ref(null);
148
- const messages = ref([]);
147
+ const embedConfig = useEmbedConfig();
148
+ const { user: authUser, activeOrganization } = useTelaApplicationAuth();
149
+ const chatScope = computed(() => embedConfig?.workspaceId.value.trim() || activeOrganization.value?.id?.trim() || "default");
150
+ const currentConversationBuckets = useState("chat-current-conversation", () => ({}));
151
+ const messageBuckets = useState("chat-messages", () => ({}));
152
+ const currentConversation = computed({
153
+ get: () => currentConversationBuckets.value[chatScope.value] ?? null,
154
+ set: (value) => {
155
+ currentConversationBuckets.value = {
156
+ ...currentConversationBuckets.value,
157
+ [chatScope.value]: value
158
+ };
159
+ }
160
+ });
161
+ const messages = computed({
162
+ get: () => messageBuckets.value[chatScope.value] ?? [],
163
+ set: (value) => {
164
+ messageBuckets.value = {
165
+ ...messageBuckets.value,
166
+ [chatScope.value]: value
167
+ };
168
+ }
169
+ });
149
170
  const loading = ref(false);
150
171
  const sending = ref(false);
151
172
  const error = ref(null);
@@ -213,6 +234,13 @@ export function useChat() {
213
234
  interval: 500,
214
235
  stopCondition
215
236
  });
237
+ const setConversation = (conv) => {
238
+ const conversationMessages = "messages" in conv ? conv.messages : [];
239
+ currentConversation.value = { ...conv, messages: conversationMessages };
240
+ messages.value = conversationMessages;
241
+ titleGenerated.value = false;
242
+ error.value = null;
243
+ };
216
244
  const loadConversation = async (id) => {
217
245
  loading.value = true;
218
246
  error.value = null;
@@ -258,7 +286,7 @@ export function useChat() {
258
286
  files: filesWithSource,
259
287
  externalUuid: null,
260
288
  ttft: null,
261
- createdBy: null,
289
+ createdBy: authUser.value?.email ?? null,
262
290
  createdAt: /* @__PURE__ */ new Date(),
263
291
  updatedAt: null
264
292
  };
@@ -500,6 +528,7 @@ export function useChat() {
500
528
  generatingTitle: readonly(generatingTitle),
501
529
  animatedTitle: readonly(animatedTitle),
502
530
  loadConversation,
531
+ setConversation,
503
532
  sendMessage,
504
533
  retryMessage,
505
534
  cancelConversation,
@@ -1,10 +1,11 @@
1
1
  export function useChatApi() {
2
2
  const embedConfig = useEmbedConfig();
3
+ const { activeOrganization } = useTelaApplicationAuth();
3
4
  function buildHeaders(headers) {
4
5
  const mergedHeaders = {
5
6
  ...headers ?? {}
6
7
  };
7
- const workspaceId = embedConfig?.workspaceId.value.trim();
8
+ const workspaceId = embedConfig?.workspaceId.value.trim() || activeOrganization.value?.id?.trim();
8
9
  if (workspaceId) {
9
10
  mergedHeaders["x-chat-workspace-id"] = workspaceId;
10
11
  }
@@ -1,15 +1,18 @@
1
1
  import type { DeepReadonly } from 'vue';
2
- import type { Conversation, ModelOption } from '../types/chat.js';
2
+ import type { Conversation, ConversationWithMessages, ModelOption } from '../types/chat.js';
3
+ export declare function mergeFetchedConversations(fetchedConversations: Conversation[], localConversations: readonly Conversation[], deletedConversationIds?: readonly string[], locallyMutatedConversationIds?: readonly string[]): Conversation[];
3
4
  export declare function useConversations(): {
4
5
  conversations: any;
5
6
  loading: any;
6
7
  error: any;
7
- fetchConversations: () => Promise<void>;
8
+ fetchConversations: (options?: {
9
+ force?: boolean;
10
+ }) => Promise<void>;
8
11
  createConversation: (options?: {
9
12
  model?: ModelOption | null;
10
13
  }) => Promise<Conversation | null>;
11
14
  deleteConversation: (id: string) => Promise<boolean>;
12
15
  renameConversation: (id: string, title: string) => Promise<boolean>;
13
- duplicateConversation: (id: string) => Promise<Conversation | null>;
16
+ duplicateConversation: (id: string) => Promise<ConversationWithMessages | null>;
14
17
  updateConversationInList: (updated: Conversation | DeepReadonly<Conversation>) => void;
15
18
  };