@meistrari/chat-nuxt 1.2.3 → 1.4.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.
- package/README.md +70 -38
- package/dist/module.d.mts +24 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +38 -29
- package/dist/runtime/components/MeistrariChatEmbed.d.vue.ts +9 -2
- package/dist/runtime/components/MeistrariChatEmbed.vue +11 -4
- package/dist/runtime/components/MeistrariChatEmbed.vue.d.ts +9 -2
- package/dist/runtime/components/app-markdown-render.d.vue.ts +3 -1
- package/dist/runtime/components/app-markdown-render.vue +15 -4
- package/dist/runtime/components/app-markdown-render.vue.d.ts +3 -1
- package/dist/runtime/components/chat/message-bubble.vue +10 -2
- package/dist/runtime/components/chat/thinking-indicator.vue +28 -17
- package/dist/runtime/composables/useChat.js +8 -1
- package/dist/runtime/composables/useChatAction.d.ts +10 -0
- package/dist/runtime/composables/useChatAction.js +12 -0
- package/dist/runtime/composables/useEmbedConfig.d.ts +10 -1
- package/dist/runtime/composables/useEmbedConfig.js +24 -3
- package/dist/runtime/embed/components/ChatConfigurationModal.vue +8 -6
- package/dist/runtime/embed/components/ChatEmbed.d.vue.ts +9 -2
- package/dist/runtime/embed/components/ChatEmbed.vue +22 -13
- package/dist/runtime/embed/components/ChatEmbed.vue.d.ts +9 -2
- package/dist/runtime/embed/components/ChatEmbedInner.vue +5 -1
- package/dist/runtime/server/api/conversations/[id]/messages/[messageId]/retry.post.js +7 -4
- package/dist/runtime/server/api/conversations/[id]/messages/index.get.js +1 -1
- package/dist/runtime/server/api/conversations/[id]/messages/index.post.js +15 -9
- package/dist/runtime/server/api/conversations/[id]/usage.get.js +1 -1
- package/dist/runtime/server/api/conversations/generate-title.post.js +18 -5
- package/dist/runtime/server/api/workspace/usage.get.js +1 -1
- package/dist/runtime/server/db/index.d.ts +20 -3
- package/dist/runtime/server/db/index.js +149 -10
- package/dist/runtime/server/db/schema/_schema.d.ts +2 -0
- package/dist/runtime/server/db/schema/_schema.js +3 -0
- package/dist/runtime/server/db/schema/conversation-usage.d.ts +1 -1
- package/dist/runtime/server/db/schema/conversation-usage.js +3 -2
- package/dist/runtime/server/db/schema/conversations.d.ts +1 -1
- package/dist/runtime/server/db/schema/conversations.js +3 -2
- package/dist/runtime/server/db/schema/external-skills.d.ts +1 -1
- package/dist/runtime/server/db/schema/external-skills.js +3 -2
- package/dist/runtime/server/db/schema/index.d.ts +1 -0
- package/dist/runtime/server/db/schema/index.js +1 -0
- package/dist/runtime/server/db/schema/messages.d.ts +1 -1
- package/dist/runtime/server/db/schema/messages.js +3 -2
- package/dist/runtime/server/db/schema/workspace-settings.d.ts +1 -1
- package/dist/runtime/server/db/schema/workspace-settings.js +3 -2
- package/dist/runtime/server/plugins/init-db.d.ts +2 -0
- package/dist/runtime/server/plugins/init-db.js +11 -0
- package/dist/runtime/server/utils/agent-api.d.ts +1 -1
- package/dist/runtime/server/utils/agent-api.js +2 -5
- package/dist/runtime/server/utils/billing.d.ts +26 -4
- package/dist/runtime/server/utils/billing.js +52 -13
- package/dist/runtime/server/utils/chat-workspace-settings.d.ts +2 -1
- package/dist/runtime/server/utils/chat-workspace-settings.js +11 -2
- package/dist/runtime/utils/chat-loading-messages.d.ts +12 -0
- package/dist/runtime/utils/chat-loading-messages.js +40 -0
- package/dist/runtime/utils/custom-markdown-components.d.ts +9 -0
- package/dist/runtime/utils/custom-markdown-components.js +54 -0
- package/dist/runtime/utils/features.d.ts +6 -0
- package/dist/runtime/utils/features.js +15 -0
- package/dist/types.d.mts +1 -1
- package/drizzle/0000_material_hiroim.sql +28 -0
- package/drizzle/0001_famous_scalphunter.sql +1 -0
- package/drizzle/0002_gorgeous_big_bertha.sql +1 -0
- package/drizzle/0003_massive_forge.sql +2 -0
- package/drizzle/0004_absent_black_tom.sql +2 -0
- package/drizzle/0005_first_kid_colt.sql +1 -0
- package/drizzle/0006_sloppy_mauler.sql +13 -0
- package/drizzle/0007_grey_rhino.sql +1 -0
- package/drizzle/0008_chilly_blue_blade.sql +27 -0
- package/drizzle/0009_cultured_maddog.sql +16 -0
- package/drizzle/0010_small_barracuda.sql +1 -0
- package/drizzle/0011_fat_lady_mastermind.sql +6 -0
- package/drizzle/0012_puzzling_rick_jones.sql +1 -0
- package/drizzle/0013_blue_gwen_stacy.sql +11 -0
- package/drizzle/meta/0000_snapshot.json +239 -0
- package/drizzle/meta/0001_snapshot.json +245 -0
- package/drizzle/meta/0002_snapshot.json +251 -0
- package/drizzle/meta/0003_snapshot.json +263 -0
- package/drizzle/meta/0004_snapshot.json +275 -0
- package/drizzle/meta/0005_snapshot.json +281 -0
- package/drizzle/meta/0006_snapshot.json +320 -0
- package/drizzle/meta/0007_snapshot.json +326 -0
- package/drizzle/meta/0008_snapshot.json +539 -0
- package/drizzle/meta/0009_snapshot.json +646 -0
- package/drizzle/meta/0010_snapshot.json +653 -0
- package/drizzle/meta/0011_snapshot.json +689 -0
- package/drizzle/meta/0012_snapshot.json +695 -0
- package/drizzle/meta/0013_snapshot.json +699 -0
- package/drizzle/meta/_journal.json +104 -0
- package/package.json +5 -3
- package/dist/runtime/server/tasks/send-daily-usage-report.d.ts +0 -2
- package/dist/runtime/server/tasks/send-daily-usage-report.js +0 -74
- package/dist/runtime/server/utils/auth-api.d.ts +0 -1
- 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,36 +63,60 @@ 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.
|
|
69
|
+
### 2. Module options
|
|
70
|
+
|
|
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`)**.
|
|
72
|
+
|
|
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
|
+
|
|
83
|
+
### 3. Set environment-only secrets
|
|
84
|
+
|
|
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
|
+
|
|
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. |
|
|
93
|
+
|
|
94
|
+
### 4. Features
|
|
67
95
|
|
|
68
|
-
|
|
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.
|
|
69
97
|
|
|
70
|
-
|
|
|
71
|
-
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
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 |
|
|
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` |
|
|
82
103
|
|
|
83
|
-
Set `chatNuxt.validateConfig` to `'warn'` to
|
|
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.
|
|
84
105
|
|
|
85
|
-
###
|
|
106
|
+
### Local mode (no database)
|
|
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`
|
|
86
120
|
|
|
87
121
|
```vue
|
|
88
122
|
<!-- app.vue -->
|
|
@@ -94,23 +128,18 @@ Set `chatNuxt.validateConfig` to `'warn'` to report missing server config during
|
|
|
94
128
|
|
|
95
129
|
`AppStatusToast`, Markstream styles, code block styles, and math delimiter defaults are auto-registered by the module.
|
|
96
130
|
|
|
97
|
-
###
|
|
131
|
+
### 6. Use it
|
|
98
132
|
|
|
99
133
|
```vue
|
|
100
134
|
<!-- pages/index.vue -->
|
|
101
|
-
<script setup lang="ts">
|
|
102
|
-
const { activeOrganization } = useTelaApplicationAuth()
|
|
103
|
-
const workspaceId = computed(() => activeOrganization.value?.id ?? null)
|
|
104
|
-
</script>
|
|
105
|
-
|
|
106
135
|
<template>
|
|
107
|
-
<div
|
|
108
|
-
<MeistrariChatEmbed
|
|
136
|
+
<div h-screen>
|
|
137
|
+
<MeistrariChatEmbed />
|
|
109
138
|
</div>
|
|
110
139
|
</template>
|
|
111
140
|
```
|
|
112
141
|
|
|
113
|
-
The
|
|
142
|
+
The chat workspace is resolved from the authenticated `activeOrganization` provided by `@meistrari/auth-nuxt`.
|
|
114
143
|
|
|
115
144
|
## Embedding in Your Layout
|
|
116
145
|
|
|
@@ -126,8 +155,9 @@ The main use case: drop the chat inside your existing app UI.
|
|
|
126
155
|
<div style="height: 600px" class="rounded-2xl overflow-hidden border">
|
|
127
156
|
<MeistrariChatEmbed
|
|
128
157
|
v-model:conversation-id="activeConversation"
|
|
129
|
-
:workspace-id="workspaceId"
|
|
130
158
|
:hide-sidebar="true"
|
|
159
|
+
:loading-messages="['Buscando contratos', 'Lendo documentos', 'Preparando resposta']"
|
|
160
|
+
loading-messages-mode="ordered"
|
|
131
161
|
/>
|
|
132
162
|
</div>
|
|
133
163
|
</main>
|
|
@@ -145,13 +175,16 @@ Key props for embedding:
|
|
|
145
175
|
|
|
146
176
|
| Prop | Type | Default | Description |
|
|
147
177
|
|------|------|---------|-------------|
|
|
148
|
-
| `workspaceId` | `string` | — | **Required.** Workspace/org identifier |
|
|
149
178
|
| `hideSidebar` | `boolean` | `false` | Hide conversation list sidebar |
|
|
150
179
|
| `conversationId` | `string \| null` | `null` | Controlled conversation (supports v-model) |
|
|
151
180
|
| `initialConversationId` | `string \| null` | `null` | Open this conversation on mount |
|
|
152
181
|
| `workspaceSettings` | `WorkspaceSettings \| null` | `null` | Override settings from host app |
|
|
153
182
|
| `user` | `ChatActor \| null` | `null` | Override current user display info |
|
|
154
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.
|
|
155
188
|
|
|
156
189
|
**Events:**
|
|
157
190
|
|
|
@@ -208,14 +241,14 @@ const features = computed(() => ({
|
|
|
208
241
|
</script>
|
|
209
242
|
|
|
210
243
|
<template>
|
|
211
|
-
<MeistrariChatEmbed :
|
|
244
|
+
<MeistrariChatEmbed :features="features" />
|
|
212
245
|
</template>
|
|
213
246
|
```
|
|
214
247
|
|
|
215
248
|
Without PostHog, you can pass features statically:
|
|
216
249
|
|
|
217
250
|
```vue
|
|
218
|
-
<MeistrariChatEmbed :
|
|
251
|
+
<MeistrariChatEmbed :features="{ showUsageTab: true }" />
|
|
219
252
|
```
|
|
220
253
|
|
|
221
254
|
## Optional: Custom Workspace Settings
|
|
@@ -230,7 +263,6 @@ Pass settings directly from your host app:
|
|
|
230
263
|
|
|
231
264
|
```vue
|
|
232
265
|
<MeistrariChatEmbed
|
|
233
|
-
:workspace-id="workspaceId"
|
|
234
266
|
:workspace-settings="{
|
|
235
267
|
workspaceId: 'ws_123',
|
|
236
268
|
systemMessage: 'You are a helpful assistant for Acme Corp.',
|
|
@@ -326,7 +358,7 @@ Installing `@meistrari/chat-nuxt` installs the runtime packages used by the laye
|
|
|
326
358
|
|
|
327
359
|
| Package | Version | Purpose |
|
|
328
360
|
|---------|---------|---------|
|
|
329
|
-
| `@meistrari/auth-nuxt` |
|
|
361
|
+
| `@meistrari/auth-nuxt` | `3.5.0` | Authentication (required) |
|
|
330
362
|
| `@meistrari/tela-build` | `^1.30.0` | UI component library (Nuxt layer) |
|
|
331
363
|
| `@sentry/nuxt` | `^10.0.0` | Client-side error capture used by chat composables |
|
|
332
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
package/dist/module.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, mkdirSync, symlinkSync } from 'node:fs';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { join, dirname } from 'node:path';
|
|
4
|
-
import { defineNuxtModule, createResolver,
|
|
4
|
+
import { defineNuxtModule, createResolver, addImportsDir, addComponent, addComponentsDir } from 'nuxt/kit';
|
|
5
5
|
|
|
6
6
|
const localRequire = createRequire(import.meta.url);
|
|
7
7
|
const runtimeAliasPackages = [
|
|
@@ -148,17 +148,26 @@ function resolveWorkspaceSettingsResolverEntry(srcDir, fallbackResolverBase) {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
const _chatNuxtRequire = createRequire(import.meta.url);
|
|
151
|
-
const
|
|
152
|
-
|
|
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
|
-
"
|
|
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
|
|
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,29 +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.
|
|
254
|
-
rc.
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
join(runtimeDir, "assets/css/code-theme.css")
|
|
268
|
-
);
|
|
269
|
-
addPlugin({
|
|
270
|
-
src: join(runtimeDir, "plugins/markstream")
|
|
271
|
-
});
|
|
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);
|
|
272
276
|
addImportsDir(join(runtimeDir, "composables"));
|
|
273
277
|
addImportsDir(join(runtimeDir, "utils"));
|
|
274
278
|
addComponent({
|
|
@@ -324,6 +328,7 @@ export default globalThis.ELK;
|
|
|
324
328
|
const serverDir = join(runtimeDir, "server");
|
|
325
329
|
const publicAssetsDir = join(runtimeDir, "public/assets");
|
|
326
330
|
const publicFilesDir = join(runtimeDir, "public/files");
|
|
331
|
+
const dbInitPluginPath = join(runtimeDir, "server/plugins/init-db");
|
|
327
332
|
nuxt.hook("nitro:config", (nitroConfig) => {
|
|
328
333
|
nitroConfig.scanDirs = nitroConfig.scanDirs || [];
|
|
329
334
|
nitroConfig.scanDirs.push(serverDir);
|
|
@@ -332,6 +337,10 @@ export default globalThis.ELK;
|
|
|
332
337
|
"#chat-runtime": runtimeDir,
|
|
333
338
|
"#chat-workspace-settings-resolver": workspaceSettingsResolverEntry
|
|
334
339
|
};
|
|
340
|
+
nitroConfig.plugins = nitroConfig.plugins || [];
|
|
341
|
+
if (!nitroConfig.plugins.includes(dbInitPluginPath)) {
|
|
342
|
+
nitroConfig.plugins.push(dbInitPluginPath);
|
|
343
|
+
}
|
|
335
344
|
nitroConfig.publicAssets = nitroConfig.publicAssets || [];
|
|
336
345
|
nitroConfig.publicAssets.push({
|
|
337
346
|
dir: publicAssetsDir,
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
import type { DeepReadonly } from 'vue';
|
|
1
|
+
import type { Component, DeepReadonly } from 'vue';
|
|
2
2
|
import type { WorkspaceSettings } from '../composables/useWorkspaceSettings.js';
|
|
3
|
+
import type { ChatActionPayload } from '../composables/useChatAction.js';
|
|
4
|
+
import type { ChatLoadingMessageMode } from '../utils/chat-loading-messages.js';
|
|
3
5
|
import type { ChatActor, ChatFeatureConfig } from '../utils/tela-chat.js';
|
|
4
6
|
type __VLS_Props = {
|
|
5
|
-
workspaceId: string;
|
|
6
7
|
workspaceSettings?: DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null;
|
|
7
8
|
user?: ChatActor | null;
|
|
8
9
|
conversationId?: string | null;
|
|
9
10
|
initialConversationId?: string | null;
|
|
10
11
|
hideSidebar?: boolean;
|
|
11
12
|
features?: Partial<ChatFeatureConfig>;
|
|
13
|
+
loadingMessages?: readonly string[] | null;
|
|
14
|
+
loadingMessagesMode?: ChatLoadingMessageMode | null;
|
|
15
|
+
customComponents?: Record<string, Component>;
|
|
16
|
+
customHtmlTags?: readonly string[];
|
|
12
17
|
};
|
|
13
18
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
14
19
|
"update:conversationId": (value: string | null) => any;
|
|
20
|
+
action: (payload: ChatActionPayload) => any;
|
|
15
21
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
16
22
|
"onUpdate:conversationId"?: ((value: string | null) => any) | undefined;
|
|
23
|
+
onAction?: ((payload: ChatActionPayload) => any) | undefined;
|
|
17
24
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
25
|
declare const _default: typeof __VLS_export;
|
|
19
26
|
export default _default;
|
|
@@ -1,25 +1,32 @@
|
|
|
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 },
|
|
11
|
+
customComponents: { type: Object, required: false },
|
|
12
|
+
customHtmlTags: { type: Array, required: false }
|
|
10
13
|
});
|
|
11
|
-
defineEmits(["update:conversationId"]);
|
|
14
|
+
defineEmits(["update:conversationId", "action"]);
|
|
12
15
|
</script>
|
|
13
16
|
|
|
14
17
|
<template>
|
|
15
18
|
<ChatEmbed
|
|
16
|
-
:workspace-id="workspaceId"
|
|
17
19
|
:workspace-settings="workspaceSettings"
|
|
18
20
|
:user="user"
|
|
19
21
|
:conversation-id="conversationId"
|
|
20
22
|
:initial-conversation-id="initialConversationId"
|
|
21
23
|
:hide-sidebar="hideSidebar"
|
|
22
24
|
:features="features"
|
|
25
|
+
:loading-messages="loadingMessages"
|
|
26
|
+
:loading-messages-mode="loadingMessagesMode"
|
|
27
|
+
:custom-components="customComponents"
|
|
28
|
+
:custom-html-tags="customHtmlTags"
|
|
23
29
|
@update:conversation-id="$emit('update:conversationId', $event)"
|
|
30
|
+
@action="$emit('action', $event)"
|
|
24
31
|
/>
|
|
25
32
|
</template>
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
import type { DeepReadonly } from 'vue';
|
|
1
|
+
import type { Component, DeepReadonly } from 'vue';
|
|
2
2
|
import type { WorkspaceSettings } from '../composables/useWorkspaceSettings.js';
|
|
3
|
+
import type { ChatActionPayload } from '../composables/useChatAction.js';
|
|
4
|
+
import type { ChatLoadingMessageMode } from '../utils/chat-loading-messages.js';
|
|
3
5
|
import type { ChatActor, ChatFeatureConfig } from '../utils/tela-chat.js';
|
|
4
6
|
type __VLS_Props = {
|
|
5
|
-
workspaceId: string;
|
|
6
7
|
workspaceSettings?: DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null;
|
|
7
8
|
user?: ChatActor | null;
|
|
8
9
|
conversationId?: string | null;
|
|
9
10
|
initialConversationId?: string | null;
|
|
10
11
|
hideSidebar?: boolean;
|
|
11
12
|
features?: Partial<ChatFeatureConfig>;
|
|
13
|
+
loadingMessages?: readonly string[] | null;
|
|
14
|
+
loadingMessagesMode?: ChatLoadingMessageMode | null;
|
|
15
|
+
customComponents?: Record<string, Component>;
|
|
16
|
+
customHtmlTags?: readonly string[];
|
|
12
17
|
};
|
|
13
18
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
14
19
|
"update:conversationId": (value: string | null) => any;
|
|
20
|
+
action: (payload: ChatActionPayload) => any;
|
|
15
21
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
16
22
|
"onUpdate:conversationId"?: ((value: string | null) => any) | undefined;
|
|
23
|
+
onAction?: ((payload: ChatActionPayload) => any) | undefined;
|
|
17
24
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
25
|
declare const _default: typeof __VLS_export;
|
|
19
26
|
export default _default;
|
|
@@ -4,12 +4,14 @@ type __VLS_Props = {
|
|
|
4
4
|
renderCodeBlocksAsPre?: boolean;
|
|
5
5
|
typewriter?: boolean;
|
|
6
6
|
customComponents?: Record<string, Component>;
|
|
7
|
+
customHtmlTags?: readonly string[];
|
|
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<{}>, {
|
|
9
10
|
content: string | null;
|
|
11
|
+
customComponents: Record<string, Component>;
|
|
12
|
+
customHtmlTags: readonly string[];
|
|
10
13
|
renderCodeBlocksAsPre: boolean;
|
|
11
14
|
typewriter: boolean;
|
|
12
|
-
customComponents: Record<string, Component>;
|
|
13
15
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
14
16
|
declare const _default: typeof __VLS_export;
|
|
15
17
|
export default _default;
|
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
import MarkdownRender, { getMarkdown, parseMarkdownToStructure, removeCustomComponents, setCustomComponents } from "markstream-vue";
|
|
3
3
|
import MermaidDiagram from "./renderers/mermaid-diagram.vue";
|
|
4
4
|
import { transformMarkdownNodes } from "../utils/markdown-nodes";
|
|
5
|
+
import { wrapCustomMarkdownComponents } from "../utils/custom-markdown-components";
|
|
5
6
|
const props = defineProps({
|
|
6
7
|
content: { type: [String, null], required: false, default: "" },
|
|
7
8
|
renderCodeBlocksAsPre: { type: Boolean, required: false, default: false },
|
|
8
9
|
typewriter: { type: Boolean, required: false, default: true },
|
|
9
|
-
customComponents: { type: Object, required: false, default: () => ({}) }
|
|
10
|
+
customComponents: { type: Object, required: false, default: () => ({}) },
|
|
11
|
+
customHtmlTags: { type: Array, required: false, default: () => [] }
|
|
10
12
|
});
|
|
11
13
|
const attrs = useAttrs();
|
|
12
|
-
const markdown = getMarkdown();
|
|
13
14
|
const instance = getCurrentInstance();
|
|
14
15
|
const scopeId = `app-markdown-render-${instance?.uid ?? Math.random().toString(36).slice(2)}`;
|
|
16
|
+
const markdown = computed(() => getMarkdown(scopeId, { customHtmlTags: props.customHtmlTags }));
|
|
15
17
|
function escapeSingleDollarSigns(text) {
|
|
16
18
|
return text.replace(/(```[\s\S]*?```|``+[^`]*``+|`[^`]*`|!?\[(?:[^\[\]]|\[[^\[\]]*\])*\]\([^)]*\))|(?<![$\\])\$(?!\$)/g, (_match, codeSpan) => {
|
|
17
19
|
if (codeSpan)
|
|
@@ -19,14 +21,23 @@ function escapeSingleDollarSigns(text) {
|
|
|
19
21
|
return "\\$";
|
|
20
22
|
});
|
|
21
23
|
}
|
|
24
|
+
const parseOptions = computed(() => {
|
|
25
|
+
const tags = props.customHtmlTags;
|
|
26
|
+
return tags && tags.length > 0 ? { customHtmlTags: [...tags] } : void 0;
|
|
27
|
+
});
|
|
22
28
|
const nodes = computed(() => {
|
|
23
|
-
const parsedNodes = parseMarkdownToStructure(
|
|
29
|
+
const parsedNodes = parseMarkdownToStructure(
|
|
30
|
+
escapeSingleDollarSigns(props.content ?? ""),
|
|
31
|
+
markdown.value,
|
|
32
|
+
parseOptions.value
|
|
33
|
+
);
|
|
24
34
|
return transformMarkdownNodes(parsedNodes);
|
|
25
35
|
});
|
|
36
|
+
const customMarkdownComponents = computed(() => wrapCustomMarkdownComponents(props.customComponents));
|
|
26
37
|
watchEffect(() => {
|
|
27
38
|
setCustomComponents(scopeId, {
|
|
28
39
|
mermaid: MermaidDiagram,
|
|
29
|
-
...
|
|
40
|
+
...customMarkdownComponents.value
|
|
30
41
|
});
|
|
31
42
|
});
|
|
32
43
|
onBeforeUnmount(() => {
|
|
@@ -4,12 +4,14 @@ type __VLS_Props = {
|
|
|
4
4
|
renderCodeBlocksAsPre?: boolean;
|
|
5
5
|
typewriter?: boolean;
|
|
6
6
|
customComponents?: Record<string, Component>;
|
|
7
|
+
customHtmlTags?: readonly string[];
|
|
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<{}>, {
|
|
9
10
|
content: string | null;
|
|
11
|
+
customComponents: Record<string, Component>;
|
|
12
|
+
customHtmlTags: readonly string[];
|
|
10
13
|
renderCodeBlocksAsPre: boolean;
|
|
11
14
|
typewriter: boolean;
|
|
12
|
-
customComponents: Record<string, Component>;
|
|
13
15
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
14
16
|
declare const _default: typeof __VLS_export;
|
|
15
17
|
export default _default;
|
|
@@ -7,6 +7,12 @@ const props = defineProps({
|
|
|
7
7
|
const emit = defineEmits(["retry"]);
|
|
8
8
|
const { user } = useTelaApplicationAuth();
|
|
9
9
|
const { getMemberByEmail } = useWorkspaceMembers();
|
|
10
|
+
const embedConfig = useEmbedConfig();
|
|
11
|
+
const mergedCustomComponents = computed(() => ({
|
|
12
|
+
link: ChatVaultLink,
|
|
13
|
+
...embedConfig?.customComponents.value ?? {}
|
|
14
|
+
}));
|
|
15
|
+
const mergedCustomHtmlTags = computed(() => embedConfig?.customHtmlTags.value ?? []);
|
|
10
16
|
const senderMember = computed(() => {
|
|
11
17
|
const createdBy = props.message.createdBy?.trim();
|
|
12
18
|
if (!createdBy) {
|
|
@@ -241,7 +247,8 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
|
|
|
241
247
|
<AppMarkdownRender
|
|
242
248
|
:id="`message-${message.id}-segment-${index}`"
|
|
243
249
|
:content="segment.content"
|
|
244
|
-
:custom-components="
|
|
250
|
+
:custom-components="mergedCustomComponents"
|
|
251
|
+
:custom-html-tags="mergedCustomHtmlTags"
|
|
245
252
|
:render-code-blocks-as-pre="true"
|
|
246
253
|
:typewriter="shouldAnimate"
|
|
247
254
|
/>
|
|
@@ -259,7 +266,8 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
|
|
|
259
266
|
<ClientOnly v-else>
|
|
260
267
|
<AppMarkdownRender
|
|
261
268
|
:content="displayContent"
|
|
262
|
-
:custom-components="
|
|
269
|
+
:custom-components="mergedCustomComponents"
|
|
270
|
+
:custom-html-tags="mergedCustomHtmlTags"
|
|
263
271
|
:render-code-blocks-as-pre="true"
|
|
264
272
|
:typewriter="shouldAnimate"
|
|
265
273
|
text-black-900
|
|
@@ -1,34 +1,45 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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 =
|
|
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
|
-
|
|
31
|
-
dotCount.value = 0;
|
|
42
|
+
advanceText();
|
|
32
43
|
}, 7e3);
|
|
33
44
|
});
|
|
34
45
|
onUnmounted(() => {
|
|
@@ -307,11 +307,18 @@ export function useChat() {
|
|
|
307
307
|
};
|
|
308
308
|
messages.value = [...messages.value, optimisticUserMessage, optimisticAssistantMessage];
|
|
309
309
|
try {
|
|
310
|
+
const workspaceSettingsSnapshot = embedConfig?.workspaceSettings.value;
|
|
311
|
+
const body = {
|
|
312
|
+
content,
|
|
313
|
+
files,
|
|
314
|
+
model,
|
|
315
|
+
...workspaceSettingsSnapshot !== void 0 ? { workspaceSettingsSnapshot } : {}
|
|
316
|
+
};
|
|
310
317
|
const response = await $fetch(
|
|
311
318
|
chatApi.path(`/conversations/${currentConversation.value.id}/messages`),
|
|
312
319
|
chatApi.withChatHeaders({
|
|
313
320
|
method: "POST",
|
|
314
|
-
body
|
|
321
|
+
body
|
|
315
322
|
})
|
|
316
323
|
);
|
|
317
324
|
messages.value = messages.value.map((msg) => {
|