@meistrari/chat-nuxt 1.4.0 → 1.5.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 (85) hide show
  1. package/README.md +51 -16
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +12 -8
  4. package/dist/runtime/components/MeistrariChatEmbed.d.vue.ts +2 -18
  5. package/dist/runtime/components/MeistrariChatEmbed.vue +22 -4
  6. package/dist/runtime/components/MeistrariChatEmbed.vue.d.ts +2 -18
  7. package/dist/runtime/components/chat/message-input.d.vue.ts +30 -4
  8. package/dist/runtime/components/chat/message-input.vue +268 -24
  9. package/dist/runtime/components/chat/message-input.vue.d.ts +30 -4
  10. package/dist/runtime/composables/useChat.d.ts +3 -5
  11. package/dist/runtime/composables/useChat.js +18 -4
  12. package/dist/runtime/composables/useChatApi.js +4 -0
  13. package/dist/runtime/composables/useConversations.js +8 -2
  14. package/dist/runtime/composables/useEmbedConfig.d.ts +7 -1
  15. package/dist/runtime/composables/useEmbedConfig.js +8 -0
  16. package/dist/runtime/composables/useFeatureFlags.d.ts +1 -2
  17. package/dist/runtime/composables/useTelaAgentMetadata.d.ts +8 -0
  18. package/dist/runtime/composables/useTelaAgentMetadata.js +65 -0
  19. package/dist/runtime/composables/useWorkspaceSettings.d.ts +1 -19
  20. package/dist/runtime/embed/components/ChatConfigurationModal.d.vue.ts +1 -1
  21. package/dist/runtime/embed/components/ChatConfigurationModal.vue.d.ts +1 -1
  22. package/dist/runtime/embed/components/ChatEmbed.d.vue.ts +3 -23
  23. package/dist/runtime/embed/components/ChatEmbed.vue +34 -9
  24. package/dist/runtime/embed/components/ChatEmbed.vue.d.ts +3 -23
  25. package/dist/runtime/embed/components/ChatEmbedInner.vue +36 -12
  26. package/dist/runtime/plugins/markstream.d.ts +1 -1
  27. package/dist/runtime/plugins/markstream.js +1 -1
  28. package/dist/runtime/server/api/conversations/[id]/cancel.post.d.ts +1 -1
  29. package/dist/runtime/server/api/conversations/[id]/cancel.post.js +37 -17
  30. package/dist/runtime/server/api/conversations/[id]/clear.post.js +28 -13
  31. package/dist/runtime/server/api/conversations/[id]/duplicate.post.js +11 -13
  32. package/dist/runtime/server/api/conversations/[id]/files.get.js +5 -7
  33. package/dist/runtime/server/api/conversations/[id]/index.delete.js +13 -12
  34. package/dist/runtime/server/api/conversations/[id]/index.get.js +6 -13
  35. package/dist/runtime/server/api/conversations/[id]/index.patch.js +11 -10
  36. package/dist/runtime/server/api/conversations/[id]/messages/[messageId]/retry.post.js +46 -80
  37. package/dist/runtime/server/api/conversations/[id]/messages/index.get.d.ts +1 -14
  38. package/dist/runtime/server/api/conversations/[id]/messages/index.get.js +81 -187
  39. package/dist/runtime/server/api/conversations/[id]/messages/index.post.js +68 -86
  40. package/dist/runtime/server/api/conversations/[id]/usage.get.js +4 -8
  41. package/dist/runtime/server/api/conversations/generate-title.post.js +4 -3
  42. package/dist/runtime/server/api/conversations/index.get.js +5 -4
  43. package/dist/runtime/server/api/conversations/index.post.js +5 -4
  44. package/dist/runtime/server/api/tela/agents/[id].get.d.ts +6 -0
  45. package/dist/runtime/server/api/tela/agents/[id].get.js +25 -0
  46. package/dist/runtime/server/api/workspace/settings.patch.js +1 -1
  47. package/dist/runtime/server/db/schema/conversations.d.ts +17 -0
  48. package/dist/runtime/server/db/schema/conversations.js +3 -1
  49. package/dist/runtime/server/db/schema/workspace-settings.d.ts +1 -1
  50. package/dist/runtime/server/utils/chat-context.d.ts +5 -0
  51. package/dist/runtime/server/utils/chat-context.js +20 -0
  52. package/dist/runtime/server/utils/conversation-agent-turn.d.ts +39 -0
  53. package/dist/runtime/server/utils/conversation-agent-turn.js +170 -0
  54. package/dist/runtime/server/utils/conversation-message-files.d.ts +10 -0
  55. package/dist/runtime/server/utils/conversation-message-files.js +77 -0
  56. package/dist/runtime/server/utils/conversation-message-sync.d.ts +31 -0
  57. package/dist/runtime/server/utils/conversation-message-sync.js +132 -0
  58. package/dist/runtime/server/utils/conversation-scope.d.ts +2 -0
  59. package/dist/runtime/server/utils/conversation-scope.js +12 -0
  60. package/dist/runtime/server/utils/tela-agent-api.d.ts +29 -0
  61. package/dist/runtime/server/utils/tela-agent-api.js +107 -0
  62. package/dist/runtime/server/utils/tela-agent-session.d.ts +48 -0
  63. package/dist/runtime/server/utils/tela-agent-session.js +350 -0
  64. package/dist/runtime/types/embed.d.ts +29 -0
  65. package/dist/runtime/types/embed.js +0 -0
  66. package/dist/runtime/types/tela-agent.d.ts +243 -0
  67. package/dist/runtime/types/tela-agent.js +98 -0
  68. package/dist/runtime/types/workspace-settings-data.d.ts +19 -0
  69. package/dist/runtime/types/workspace-settings-data.js +0 -0
  70. package/dist/runtime/types/workspace-settings.d.ts +1 -1
  71. package/dist/runtime/utils/agent-input-readiness.d.ts +14 -0
  72. package/dist/runtime/utils/agent-input-readiness.js +15 -0
  73. package/dist/runtime/utils/features.d.ts +9 -0
  74. package/dist/runtime/utils/features.js +5 -4
  75. package/dist/runtime/utils/file.js +46 -7
  76. package/dist/runtime/utils/markdown-nodes.d.ts +1 -0
  77. package/dist/runtime/utils/markdown-nodes.js +6 -0
  78. package/dist/runtime/utils/tela-chat.d.ts +6 -0
  79. package/dist/runtime/utils/tela-chat.js +12 -1
  80. package/drizzle/0014_faulty_lake.sql +2 -0
  81. package/drizzle/meta/0014_snapshot.json +732 -0
  82. package/drizzle/meta/_journal.json +7 -0
  83. package/package.json +1 -1
  84. package/dist/runtime/types/feature-flags.d.ts +0 -7
  85. package/dist/runtime/types/feature-flags.js +0 -6
@@ -1,8 +1,8 @@
1
1
  <script setup>
2
2
  import { DEFAULT_MODEL } from "../../types/chat";
3
3
  import { formatFileSize, getMimeIcon, getMimeLabel } from "../../utils/file";
4
+ import { getAgentInputReadiness } from "../../utils/agent-input-readiness";
4
5
  import { MAX_FILE_SIZE, MAX_FILES } from "../../composables/useWorkspaceContextFiles";
5
- import { FEATURE_FLAGS } from "../../types/feature-flags";
6
6
  const props = defineProps({
7
7
  disabled: { type: Boolean, required: false },
8
8
  loading: { type: Boolean, required: false },
@@ -13,19 +13,48 @@ const props = defineProps({
13
13
  hasDisclaimer: { type: Boolean, required: false },
14
14
  messages: { type: Array, required: false },
15
15
  conversationFiles: { type: Array, required: false },
16
- workspaceSettings: { type: null, required: false }
16
+ workspaceSettings: { type: null, required: false },
17
+ allowModelSelection: { type: Boolean, required: false },
18
+ agentInputSchema: { type: null, required: false },
19
+ agentInputSchemaLoading: { type: Boolean, required: false },
20
+ showModelSelector: { type: Boolean, required: false },
21
+ showCancelButton: { type: Boolean, required: false }
17
22
  });
18
23
  const emit = defineEmits(["send", "cancel"]);
19
24
  const selectedModel = defineModel("model", { type: null, ...{ default: DEFAULT_MODEL } });
20
25
  const { uploadFiles, uploading, uploadError } = useFileUpload();
21
- const { isFeatureEnabled } = useFeatureFlags();
22
- const isCancelEnabled = computed(() => isFeatureEnabled(FEATURE_FLAGS.ENABLE_CANCEL_GENERATION));
23
- const isModelSelectorEnabled = computed(() => isFeatureEnabled(FEATURE_FLAGS.ENABLE_MODEL_SELECTOR));
26
+ const isCancelEnabled = computed(() => props.showCancelButton === true);
27
+ const isModelSelectorEnabled = computed(() => props.showModelSelector === true && props.allowModelSelection !== false);
24
28
  const content = ref("");
25
29
  const editorRef = ref(null);
26
30
  const fileInputRef = ref(null);
27
31
  const pendingFiles = ref([]);
32
+ const selectedUploadInputName = ref(void 0);
33
+ const agentInputTexts = ref({});
34
+ const isAgentInputsOpen = ref(false);
28
35
  const isLocked = computed(() => props.disabled || props.loading);
36
+ const agentInputVariables = computed(
37
+ () => (props.agentInputSchema ?? []).filter((input) => input.name.trim())
38
+ );
39
+ const fileInputVariables = computed(
40
+ () => agentInputVariables.value.filter((input) => input.type === "file")
41
+ );
42
+ const textInputVariables = computed(
43
+ () => agentInputVariables.value.filter((input) => input.type === "text")
44
+ );
45
+ const hasAgentInputs = computed(() => agentInputVariables.value.length > 0);
46
+ const requiredAgentInputCount = computed(
47
+ () => agentInputVariables.value.filter((input) => input.required).length
48
+ );
49
+ const defaultFileInputName = computed(
50
+ () => fileInputVariables.value.length === 1 ? fileInputVariables.value[0]?.name : void 0
51
+ );
52
+ const agentInputReadiness = computed(() => getAgentInputReadiness({
53
+ inputVariables: agentInputVariables.value,
54
+ inputTexts: agentInputTexts.value,
55
+ files: pendingFiles.value
56
+ }));
57
+ const hasUnmappedAgentFiles = computed(() => agentInputReadiness.value.hasUnmappedFiles);
29
58
  const messagesRef = computed(() => props.messages ?? []);
30
59
  const inputHistory = useInputHistory(messagesRef);
31
60
  let restoringHistory = false;
@@ -92,9 +121,21 @@ const mentionAutocomplete = useMentionAutocomplete({
92
121
  onInsert: handleInput
93
122
  });
94
123
  const canSend = computed(
95
- () => content.value.trim().length > 0 && !isLocked.value && !uploading.value
124
+ () => content.value.trim().length > 0 && !isLocked.value && !uploading.value && !props.agentInputSchemaLoading && agentInputReadiness.value.ready
96
125
  );
97
- function openFileDialog() {
126
+ watch(agentInputVariables, (variables) => {
127
+ const variableNames = new Set(variables.map((input) => input.name));
128
+ agentInputTexts.value = Object.fromEntries(
129
+ Object.entries(agentInputTexts.value).filter(([name]) => variableNames.has(name))
130
+ );
131
+ pendingFiles.value = pendingFiles.value.map((pendingFile) => {
132
+ if (!pendingFile.inputName || variableNames.has(pendingFile.inputName))
133
+ return pendingFile;
134
+ return { ...pendingFile, inputName: void 0 };
135
+ });
136
+ }, { deep: true });
137
+ function openFileDialog(inputName) {
138
+ selectedUploadInputName.value = inputName ?? defaultFileInputName.value;
98
139
  fileInputRef.value?.click();
99
140
  }
100
141
  function handleFileSelect(event) {
@@ -107,28 +148,100 @@ function handleFileSelect(event) {
107
148
  }
108
149
  return true;
109
150
  });
110
- const availableSlots = MAX_FILES - pendingFiles.value.length;
111
- pendingFiles.value = [...pendingFiles.value, ...newFiles.slice(0, availableSlots)];
151
+ queueFiles(newFiles, selectedUploadInputName.value);
152
+ selectedUploadInputName.value = void 0;
112
153
  target.value = "";
113
154
  }
114
- function removeFile(index) {
115
- pendingFiles.value = pendingFiles.value.filter((_, i) => i !== index);
155
+ function createPendingFile(file, inputName) {
156
+ return {
157
+ id: `${file.name}-${file.lastModified}-${file.size}-${Math.random().toString(36).slice(2)}`,
158
+ file,
159
+ inputName
160
+ };
161
+ }
162
+ function queueFiles(files, inputName) {
163
+ const availableSlots = MAX_FILES - pendingFiles.value.length;
164
+ if (availableSlots <= 0)
165
+ return;
166
+ const mappedInputName = inputName ?? defaultFileInputName.value;
167
+ pendingFiles.value = [
168
+ ...pendingFiles.value,
169
+ ...files.slice(0, availableSlots).map((file) => createPendingFile(file, mappedInputName))
170
+ ];
171
+ }
172
+ function removeFile(id) {
173
+ pendingFiles.value = pendingFiles.value.filter((file) => file.id !== id);
174
+ }
175
+ function updatePendingFileInput(id, event) {
176
+ const value = event.target.value;
177
+ pendingFiles.value = pendingFiles.value.map((file) => {
178
+ if (file.id !== id)
179
+ return file;
180
+ return { ...file, inputName: value || void 0 };
181
+ });
182
+ }
183
+ function countFilesForInput(inputName) {
184
+ return pendingFiles.value.filter((file) => file.inputName === inputName).length;
185
+ }
186
+ function handleVariableDrop(event, inputName) {
187
+ const files = event.dataTransfer?.files ? Array.from(event.dataTransfer.files) : [];
188
+ addFiles(files, inputName);
189
+ }
190
+ function getVaultRef(file) {
191
+ return file.vaultReference?.trim() || (file.url.startsWith("vault://") ? file.url : null);
192
+ }
193
+ function buildAgentInputs(files, queuedFiles) {
194
+ if (!hasAgentInputs.value)
195
+ return [];
196
+ const inputs = [];
197
+ for (const input of textInputVariables.value) {
198
+ const text = (agentInputTexts.value[input.name] ?? "").trim();
199
+ if (!text)
200
+ continue;
201
+ inputs.push({
202
+ type: "text",
203
+ name: input.name,
204
+ content: text
205
+ });
206
+ }
207
+ files.forEach((file, index) => {
208
+ const inputName = queuedFiles[index]?.inputName;
209
+ const vaultRef = getVaultRef(file);
210
+ if (!inputName || !vaultRef)
211
+ return;
212
+ inputs.push({
213
+ type: "file",
214
+ name: inputName,
215
+ vaultRef,
216
+ filename: file.name.slice(0, 255)
217
+ });
218
+ });
219
+ return inputs;
116
220
  }
117
221
  async function handleSend() {
118
222
  if (!canSend.value)
119
223
  return;
120
224
  let files = [];
121
- if (pendingFiles.value.length > 0) {
225
+ const queuedFiles = [...pendingFiles.value];
226
+ if (queuedFiles.length > 0) {
122
227
  try {
123
- files = await uploadFiles(pendingFiles.value);
228
+ files = await uploadFiles(queuedFiles.map((item) => item.file));
124
229
  } catch {
125
230
  return;
126
231
  }
127
232
  }
128
233
  const taggedContent = mentionAutocomplete.buildTaggedContent();
129
- emit("send", taggedContent.trim(), files, selectedModel.value);
234
+ const agentInputs = buildAgentInputs(files, queuedFiles);
235
+ emit(
236
+ "send",
237
+ taggedContent.trim(),
238
+ files,
239
+ isModelSelectorEnabled.value ? selectedModel.value : void 0,
240
+ agentInputs.length > 0 ? agentInputs : void 0
241
+ );
130
242
  content.value = "";
131
243
  pendingFiles.value = [];
244
+ agentInputTexts.value = {};
132
245
  mentionAutocomplete.resetMentions();
133
246
  inputHistory.resetNavigation();
134
247
  nextTick(() => {
@@ -273,15 +386,14 @@ function focusEditor() {
273
386
  return;
274
387
  editorRef.value?.focus();
275
388
  }
276
- function addFiles(files) {
389
+ function addFiles(files, inputName) {
277
390
  const validFiles = files.filter((file) => {
278
391
  if (file.size > MAX_FILE_SIZE) {
279
392
  return false;
280
393
  }
281
394
  return true;
282
395
  });
283
- const availableSlots = MAX_FILES - pendingFiles.value.length;
284
- pendingFiles.value = [...pendingFiles.value, ...validFiles.slice(0, availableSlots)];
396
+ queueFiles(validFiles, inputName);
285
397
  }
286
398
  defineExpose({
287
399
  addFiles
@@ -291,11 +403,120 @@ defineExpose({
291
403
  <template>
292
404
  <div :class="centered ? '' : hasDisclaimer ? 'px-24px' : 'px-24px pb-24px'" bg-transparent>
293
405
  <div :class="centered ? 'w-700px' : 'max-w-800px'" mx-auto>
406
+ <Transition name="agent-inputs-panel-motion" mode="out-in" appear>
407
+ <div
408
+ v-if="hasAgentInputs"
409
+ class="agent-inputs-panel"
410
+ mb-6px
411
+ rounded-12px
412
+ b=".5px gray-200"
413
+ bg-white
414
+ overflow-hidden
415
+ >
416
+ <button
417
+ type="button"
418
+ w-full
419
+ flex
420
+ items-center
421
+ gap-8px
422
+ h-40px
423
+ px-12px
424
+ text-left
425
+ hover:bg-gray-50
426
+ transition-colors
427
+ @click.stop="isAgentInputsOpen = !isAgentInputsOpen"
428
+ >
429
+ <span i-ph-brackets-curly text-14px text-gray-500 />
430
+ <span text-13px font-580 text-gray-800>Variáveis</span>
431
+ <span px-6px py-2px rounded-4px bg-gray-100 text-11px text-gray-600>{{ agentInputVariables.length }}</span>
432
+ <span v-if="requiredAgentInputCount" text-11px text-gray-400>{{ requiredAgentInputCount }} obrigatória(s)</span>
433
+ <div flex-1 />
434
+ <span
435
+ i-ph-caret-down-bold
436
+ text-12px
437
+ text-gray-400
438
+ transition-transform
439
+ :class="{ 'rotate-180': isAgentInputsOpen }"
440
+ />
441
+ </button>
442
+
443
+ <Transition name="agent-inputs-content">
444
+ <div v-if="isAgentInputsOpen" class="agent-inputs-content" flex="~ col" gap-10px px-12px pb-12px @click.stop>
445
+ <label
446
+ v-for="input in textInputVariables"
447
+ :key="`text-${input.id || input.name}`"
448
+ flex="~ col"
449
+ gap-6px
450
+ >
451
+ <span flex items-center gap-6px text-12px font-580 text-gray-700>
452
+ <span w-6px h-6px rounded-full bg-blue-400 />
453
+ {{ input.name }}<span v-if="input.required" text-orange-500>*</span>
454
+ </span>
455
+ <textarea
456
+ v-model="agentInputTexts[input.name]"
457
+ class="agent-text-input"
458
+ :placeholder="input.description || input.name"
459
+ :disabled="isLocked"
460
+ rows="2"
461
+ />
462
+ </label>
463
+
464
+ <div
465
+ v-for="input in fileInputVariables"
466
+ :key="`file-${input.id || input.name}`"
467
+ class="agent-file-input-row"
468
+ rounded-10px
469
+ b=".5px dashed gray-300"
470
+ p-10px
471
+ flex
472
+ items-center
473
+ gap-10px
474
+ transition-colors
475
+ :class="isLocked ? 'opacity-60' : 'hover:bg-gray-50 cursor-pointer'"
476
+ @click.stop="openFileDialog(input.name)"
477
+ @dragover.prevent
478
+ @drop.stop.prevent="handleVariableDrop($event, input.name)"
479
+ >
480
+ <span w-6px h-6px rounded-full bg-orange-400 shrink-0 />
481
+ <div flex="~ col" min-w-0 flex-1>
482
+ <span text-12px font-580 text-gray-700 truncate>
483
+ {{ input.name }}<span v-if="input.required" text-orange-500>*</span>
484
+ </span>
485
+ <span v-if="input.description" text-11px text-gray-400 truncate>{{ input.description }}</span>
486
+ </div>
487
+ <span
488
+ v-if="countFilesForInput(input.name) > 0"
489
+ px-6px
490
+ py-2px
491
+ rounded-4px
492
+ bg-orange-50
493
+ text-11px
494
+ text-orange-600
495
+ >
496
+ {{ countFilesForInput(input.name) }}
497
+ </span>
498
+ <TelaTooltip content="Anexar arquivo" side="top">
499
+ <TelaIconButton
500
+ icon="i-ph-paperclip"
501
+ size="sm"
502
+ color="secondary"
503
+ class="rounded-8px! p-4px!"
504
+ icon-class="text-gray-400 text-16px"
505
+ :disabled="isLocked || pendingFiles.length >= MAX_FILES"
506
+ @click.stop="openFileDialog(input.name)"
507
+ />
508
+ </TelaTooltip>
509
+ </div>
510
+ </div>
511
+ </Transition>
512
+ </div>
513
+ </Transition>
514
+
294
515
  <div v-if="pendingFiles.length > 0" flex flex-wrap gap-8px mb-12px>
295
516
  <div
296
- v-for="(file, index) in pendingFiles"
297
- :key="file.name + index"
298
- flex items-center gap-8px
517
+ v-for="pendingFile in pendingFiles"
518
+ :key="pendingFile.id"
519
+ flex items-center gap-8px flex-wrap
299
520
  px-12px py-8px
300
521
  rounded-8px
301
522
  bg-gray-100
@@ -303,9 +524,28 @@ defineExpose({
303
524
  >
304
525
  <div i-ph-file text-gray-600 text-16px />
305
526
  <div flex="~ col" gap-2px>
306
- <span body-12-medium text-black-700 max-w-150px truncate>{{ file.name }}</span>
307
- <span body-11-regular text-gray-500>{{ formatFileSize(file.size) }}</span>
527
+ <span body-12-medium text-black-700 max-w-150px truncate>{{ pendingFile.file.name }}</span>
528
+ <span body-11-regular text-gray-500>{{ formatFileSize(pendingFile.file.size) }}</span>
308
529
  </div>
530
+ <select
531
+ v-if="fileInputVariables.length > 0"
532
+ class="agent-file-variable-select"
533
+ :value="pendingFile.inputName ?? ''"
534
+ :disabled="isLocked"
535
+ @change="updatePendingFileInput(pendingFile.id, $event)"
536
+ @click.stop
537
+ >
538
+ <option value="" disabled>
539
+ Selecionar variável
540
+ </option>
541
+ <option
542
+ v-for="input in fileInputVariables"
543
+ :key="input.id || input.name"
544
+ :value="input.name"
545
+ >
546
+ {{ input.name }}
547
+ </option>
548
+ </select>
309
549
  <TelaIconButton
310
550
  p-4px
311
551
  icon="i-ph-x"
@@ -314,11 +554,15 @@ defineExpose({
314
554
  hover:bg-gray-200
315
555
  transition
316
556
  icon-class="text-gray-500 text-12px"
317
- @click="removeFile(index)"
557
+ @click="removeFile(pendingFile.id)"
318
558
  />
319
559
  </div>
320
560
  </div>
321
561
 
562
+ <div v-if="hasUnmappedAgentFiles" text-orange-600 body-12-regular mb-8px>
563
+ Selecione uma variável para cada arquivo anexado.
564
+ </div>
565
+
322
566
  <div v-if="uploadError" text-red-600 body-12-regular mb-8px>
323
567
  {{ uploadError }}
324
568
  </div>
@@ -424,5 +668,5 @@ defineExpose({
424
668
  </template>
425
669
 
426
670
  <style scoped>
427
- .input-container{border:.5px solid #e5e7eb;box-shadow:0 3px 10px 0 hsla(0,0%,64%,.08),0 12px 20px 0 hsla(0,0%,64%,.06)}.input-locked{background:var(--Base-Gray-gray-100,#f3f4f6);border:.5px solid var(--Base-Neutral-neutral-300,#d9d9d9);box-shadow:0 12px 20px 0 hsla(0,0%,64%,.06)}.editor-content{caret-color:#111827;color:#111827;max-height:200px;outline:none;overflow-y:auto;white-space:pre-wrap;word-break:break-word}.editor-content.is-empty:before{color:#bdc4cc;content:attr(data-placeholder);float:left;height:0;pointer-events:none}.icon-button:disabled{cursor:not-allowed;opacity:.5}.custom-scrollbar{scrollbar-color:#d1d5db transparent;scrollbar-width:thin}.custom-scrollbar::-webkit-scrollbar{width:6px}.custom-scrollbar::-webkit-scrollbar-track{background:transparent}.custom-scrollbar::-webkit-scrollbar-thumb{background-color:#d1d5db;border-radius:3px}.stop-button{align-items:center;background:transparent;border:none;cursor:pointer;display:flex;height:32px;justify-content:center;padding:0;position:relative;transition:opacity .15s ease;width:32px}.stop-button:hover{opacity:.8}.stop-button-ring{border:2px solid #e5e7eb;border-radius:50%;inset:0;position:absolute}.stop-button-ring:before{animation:spin 1s linear infinite;border:2px solid transparent;border-radius:50%;border-top-color:#111827;content:"";inset:-2px;position:absolute}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}
671
+ .input-container{border:.5px solid #e5e7eb;box-shadow:0 3px 10px 0 hsla(0,0%,64%,.08),0 12px 20px 0 hsla(0,0%,64%,.06);position:relative;z-index:2}.input-locked{background:var(--Base-Gray-gray-100,#f3f4f6);border:.5px solid var(--Base-Neutral-neutral-300,#d9d9d9);box-shadow:0 12px 20px 0 hsla(0,0%,64%,.06)}.agent-inputs-panel{box-shadow:0 3px 10px 0 hsla(0,0%,64%,.06);position:relative;transform-origin:bottom center;will-change:max-height,opacity,transform,filter;z-index:1}.agent-inputs-panel-motion-enter-active,.agent-inputs-panel-motion-leave-active{overflow:hidden;transition:max-height .26s cubic-bezier(.2,.8,.2,1),opacity .18s ease,transform .26s cubic-bezier(.2,.8,.2,1),filter .22s ease}.agent-inputs-panel-motion-enter-from,.agent-inputs-panel-motion-leave-to{filter:blur(2px);max-height:0;opacity:0;transform:translateY(46px) scale(.985)}.agent-inputs-panel-motion-enter-to,.agent-inputs-panel-motion-leave-from{filter:blur(0);max-height:560px;opacity:1;transform:translateY(0) scale(1)}.agent-inputs-content{transform-origin:bottom center;will-change:max-height,opacity,transform,filter}.agent-inputs-content-enter-active,.agent-inputs-content-leave-active{overflow:hidden;transition:max-height .22s cubic-bezier(.2,.8,.2,1),opacity .16s ease,transform .22s cubic-bezier(.2,.8,.2,1),filter .22s ease}.agent-inputs-content-enter-from,.agent-inputs-content-leave-to{filter:blur(2px);max-height:0;opacity:0;transform:translateY(14px) scale(.985)}.agent-inputs-content-enter-to,.agent-inputs-content-leave-from{filter:blur(0);max-height:900px;opacity:1;transform:translateY(0) scale(1)}.agent-text-input{border:.5px solid #e5e7eb;border-radius:8px;color:#111827;font-size:13px;line-height:18px;min-height:64px;outline:none;padding:8px 10px;resize:vertical;width:100%}.agent-text-input:focus{border-color:#d1d5db}.agent-text-input:disabled{background-color:#f3f4f6;cursor:not-allowed}.agent-file-variable-select{background:#fff;border:.5px solid #d1d5db;border-radius:6px;color:#374151;font-size:12px;height:28px;max-width:160px;outline:none;padding:0 8px}.editor-content{caret-color:#111827;color:#111827;max-height:200px;outline:none;overflow-y:auto;white-space:pre-wrap;word-break:break-word}.editor-content.is-empty:before{color:#bdc4cc;content:attr(data-placeholder);float:left;height:0;pointer-events:none}.icon-button:disabled{cursor:not-allowed;opacity:.5}.custom-scrollbar{scrollbar-color:#d1d5db transparent;scrollbar-width:thin}.custom-scrollbar::-webkit-scrollbar{width:6px}.custom-scrollbar::-webkit-scrollbar-track{background:transparent}.custom-scrollbar::-webkit-scrollbar-thumb{background-color:#d1d5db;border-radius:3px}.stop-button{align-items:center;background:transparent;border:none;cursor:pointer;display:flex;height:32px;justify-content:center;padding:0;position:relative;transition:opacity .15s ease;width:32px}.stop-button:hover{opacity:.8}.stop-button-ring{border:2px solid #e5e7eb;border-radius:50%;inset:0;position:absolute}.stop-button-ring:before{animation:spin 1s linear infinite;border:2px solid transparent;border-radius:50%;border-top-color:#111827;content:"";inset:-2px;position:absolute}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}
428
672
  </style>
@@ -1,6 +1,7 @@
1
1
  import type { ConversationFile, FileUploadInput, Message, ModelOption } from '../../types/chat.js';
2
2
  import type { DeepReadonly } from 'vue';
3
- import type { WorkspaceSettings } from '../../composables/useWorkspaceSettings.js';
3
+ import type { WorkspaceSettings } from '../../types/workspace-settings-data.js';
4
+ import type { TelaAgentInputVariable } from '../../types/tela-agent.js';
4
5
  type __VLS_Props = {
5
6
  disabled?: boolean;
6
7
  loading?: boolean;
@@ -12,8 +13,13 @@ type __VLS_Props = {
12
13
  messages?: Message[];
13
14
  conversationFiles?: ConversationFile[];
14
15
  workspaceSettings?: DeepReadonly<WorkspaceSettings> | null;
16
+ allowModelSelection?: boolean;
17
+ agentInputSchema?: DeepReadonly<TelaAgentInputVariable[]> | null;
18
+ agentInputSchemaLoading?: boolean;
19
+ showModelSelector?: boolean;
20
+ showCancelButton?: boolean;
15
21
  };
16
- declare function addFiles(files: File[]): void;
22
+ declare function addFiles(files: File[], inputName?: string): void;
17
23
  type __VLS_ModelProps = {
18
24
  'model'?: ModelOption;
19
25
  };
@@ -24,10 +30,30 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
24
30
  "update:model": (value: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6") => any;
25
31
  } & {
26
32
  cancel: () => any;
27
- send: (content: string, files: FileUploadInput[], model: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6") => any;
33
+ send: (content: string, files: FileUploadInput[], model?: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | undefined, agentInputs?: ({
34
+ type: "file";
35
+ name: string;
36
+ vaultRef: string;
37
+ filename: string;
38
+ metadata?: string | undefined;
39
+ } | {
40
+ type: "text";
41
+ name: string;
42
+ content: string;
43
+ })[] | undefined) => any;
28
44
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
29
45
  onCancel?: (() => any) | undefined;
30
- onSend?: ((content: string, files: FileUploadInput[], model: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6") => any) | undefined;
46
+ onSend?: ((content: string, files: FileUploadInput[], model?: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | undefined, agentInputs?: ({
47
+ type: "file";
48
+ name: string;
49
+ vaultRef: string;
50
+ filename: string;
51
+ metadata?: string | undefined;
52
+ } | {
53
+ type: "text";
54
+ name: string;
55
+ content: string;
56
+ })[] | undefined) => any) | undefined;
31
57
  "onUpdate:model"?: ((value: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6") => any) | undefined;
32
58
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
33
59
  declare const _default: typeof __VLS_export;
@@ -1,4 +1,5 @@
1
- import type { Conversation, ConversationWithMessages, Message } from '../types/chat.js';
1
+ import type { Conversation, ConversationWithMessages, FileUploadInput, Message } from '../types/chat.js';
2
+ import type { TelaAgentExecutionInput } from '../types/tela-agent.js';
2
3
  export declare function useChat(): {
3
4
  currentConversation: any;
4
5
  messages: any;
@@ -12,10 +13,7 @@ export declare function useChat(): {
12
13
  animatedTitle: any;
13
14
  loadConversation: (id: string) => Promise<void>;
14
15
  setConversation: (conv: Conversation | ConversationWithMessages) => void;
15
- sendMessage: (content: string, files?: {
16
- url: string;
17
- name: string;
18
- }[], model?: string) => Promise<Message | null>;
16
+ sendMessage: (content: string, files?: FileUploadInput[], model?: string, agentInputs?: TelaAgentExecutionInput[]) => Promise<Message | null>;
19
17
  retryMessage: (messageId: string) => Promise<boolean>;
20
18
  cancelConversation: () => Promise<boolean>;
21
19
  startPolling: any;
@@ -1,6 +1,11 @@
1
1
  import * as Sentry from "@sentry/nuxt";
2
2
  import { exportConversationToMarkdown } from "../utils/export-conversation.js";
3
+ import { resolveChatStateScope } from "../utils/tela-chat.js";
3
4
  import { useConversationFileActions } from "./useConversationFiles.js";
5
+ function resolveTelaAgentInputs(messageInputs, configuredInputs) {
6
+ const inputs = messageInputs ?? configuredInputs ?? void 0;
7
+ return inputs?.length ? inputs : void 0;
8
+ }
4
9
  function getStepKey(step) {
5
10
  if (typeof step === "string") {
6
11
  return `text:${step.slice(0, 100)}`;
@@ -147,7 +152,11 @@ export function useChat() {
147
152
  const embedConfig = useEmbedConfig();
148
153
  const { user: authUser, activeOrganization } = useTelaApplicationAuth();
149
154
  const { addFilesToConversation } = useConversationFileActions();
150
- const chatScope = computed(() => embedConfig?.workspaceId.value.trim() || activeOrganization.value?.id?.trim() || "default");
155
+ const isTelaAgentMode = computed(() => !!embedConfig?.telaAgentId.value);
156
+ const chatScope = computed(() => resolveChatStateScope(
157
+ embedConfig?.workspaceId.value || activeOrganization.value?.id,
158
+ embedConfig?.telaAgentId.value
159
+ ));
151
160
  const currentConversationBuckets = useState("chat-current-conversation", () => ({}));
152
161
  const messageBuckets = useState("chat-messages", () => ({}));
153
162
  const currentConversation = computed({
@@ -271,7 +280,7 @@ export function useChat() {
271
280
  loading.value = false;
272
281
  }
273
282
  };
274
- const sendMessage = async (content, files, model) => {
283
+ const sendMessage = async (content, files, model, agentInputs) => {
275
284
  if (!currentConversation.value)
276
285
  return null;
277
286
  sending.value = true;
@@ -308,11 +317,16 @@ export function useChat() {
308
317
  messages.value = [...messages.value, optimisticUserMessage, optimisticAssistantMessage];
309
318
  try {
310
319
  const workspaceSettingsSnapshot = embedConfig?.workspaceSettings.value;
320
+ const resolvedAgentInputs = resolveTelaAgentInputs(agentInputs, embedConfig?.telaAgentInputs?.value);
311
321
  const body = {
312
322
  content,
313
323
  files,
314
- model,
315
- ...workspaceSettingsSnapshot !== void 0 ? { workspaceSettingsSnapshot } : {}
324
+ ...isTelaAgentMode.value ? {
325
+ ...resolvedAgentInputs ? { agentInputs: resolvedAgentInputs } : {}
326
+ } : {
327
+ model,
328
+ ...workspaceSettingsSnapshot !== void 0 ? { workspaceSettingsSnapshot } : {}
329
+ }
316
330
  };
317
331
  const response = await $fetch(
318
332
  chatApi.path(`/conversations/${currentConversation.value.id}/messages`),
@@ -9,6 +9,10 @@ export function useChatApi() {
9
9
  if (workspaceId) {
10
10
  mergedHeaders["x-chat-workspace-id"] = workspaceId;
11
11
  }
12
+ const telaAgentId = embedConfig?.telaAgentId.value;
13
+ if (telaAgentId) {
14
+ mergedHeaders["x-chat-tela-agent-id"] = telaAgentId;
15
+ }
12
16
  return Object.keys(mergedHeaders).length > 0 ? mergedHeaders : void 0;
13
17
  }
14
18
  function withChatHeaders(options) {
@@ -1,4 +1,5 @@
1
1
  import * as Sentry from "@sentry/nuxt";
2
+ import { resolveChatStateScope } from "../utils/tela-chat.js";
2
3
  function sortConversationsByUpdatedAt(conversations) {
3
4
  return [...conversations].sort(
4
5
  (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
@@ -33,7 +34,11 @@ export function useConversations() {
33
34
  const mutationVersionBuckets = useState("conversations-mutation-version", () => ({}));
34
35
  const deletedIdBuckets = useState("conversations-deleted-ids", () => ({}));
35
36
  const mutatedIdBuckets = useState("conversations-mutated-ids", () => ({}));
36
- const workspaceScope = computed(() => embedConfig?.workspaceId.value.trim() || activeOrganization.value?.id?.trim() || "default");
37
+ const isTelaAgentMode = computed(() => !!embedConfig?.telaAgentId.value);
38
+ const workspaceScope = computed(() => resolveChatStateScope(
39
+ embedConfig?.workspaceId.value || activeOrganization.value?.id,
40
+ embedConfig?.telaAgentId.value
41
+ ));
37
42
  const conversations = computed({
38
43
  get: () => conversationBuckets.value[workspaceScope.value] ?? [],
39
44
  set: (value) => {
@@ -166,7 +171,7 @@ export function useConversations() {
166
171
  chatApi.path("/conversations"),
167
172
  chatApi.withChatHeaders({
168
173
  method: "POST",
169
- body: options?.model ? { model: options.model } : void 0
174
+ body: !isTelaAgentMode.value && options?.model ? { model: options.model } : void 0
170
175
  })
171
176
  );
172
177
  commitConversations([conversation, ...conversations.value], conversation.id);
@@ -222,6 +227,7 @@ export function useConversations() {
222
227
  model: updated.model,
223
228
  appliedSettingsAt: updated.appliedSettingsAt,
224
229
  appliedSettingsSnapshot: cloneAppliedSettingsSnapshot(updated.appliedSettingsSnapshot),
230
+ telaAgentId: updated.telaAgentId,
225
231
  createdBy: updated.createdBy,
226
232
  createdAt: updated.createdAt,
227
233
  updatedAt: updated.updatedAt
@@ -1,8 +1,12 @@
1
1
  import type { Component, DeepReadonly, MaybeRefOrGetter, Ref } from 'vue';
2
+ import type { TelaAgentExecutionInput } from '../types/tela-agent.js';
3
+ import type { WorkspaceSettings } from '../types/workspace-settings-data.js';
2
4
  import type { ChatLoadingMessageMode } from '../utils/chat-loading-messages.js';
3
- import type { WorkspaceSettings } from './useWorkspaceSettings.js';
5
+ type ConfiguredTelaAgentInputs = DeepReadonly<TelaAgentExecutionInput[]> | null | undefined;
4
6
  export type EmbedConfig = {
5
7
  workspaceId: Ref<string>;
8
+ telaAgentId: Ref<string | null>;
9
+ telaAgentInputs: Ref<ConfiguredTelaAgentInputs>;
6
10
  workspaceSettings: Ref<DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null | undefined>;
7
11
  hideSidebar: Ref<boolean>;
8
12
  loadingMessages: Ref<readonly string[] | null | undefined>;
@@ -12,6 +16,8 @@ export type EmbedConfig = {
12
16
  };
13
17
  type EmbedConfigInput = {
14
18
  workspaceId: MaybeRefOrGetter<string>;
19
+ telaAgentId?: MaybeRefOrGetter<string | null | undefined>;
20
+ telaAgentInputs?: MaybeRefOrGetter<ConfiguredTelaAgentInputs>;
15
21
  workspaceSettings?: MaybeRefOrGetter<DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null | undefined>;
16
22
  hideSidebar?: MaybeRefOrGetter<boolean | undefined>;
17
23
  loadingMessages?: MaybeRefOrGetter<readonly string[] | null | undefined>;
@@ -8,6 +8,8 @@ function useGlobalEmbedConfigState() {
8
8
  return {
9
9
  provided: useState("chat-embed-config-provided", () => false),
10
10
  workspaceId: useState("chat-embed-config-workspace-id", () => ""),
11
+ telaAgentId: useState("chat-embed-config-tela-agent-id", () => null),
12
+ telaAgentInputs: useState("chat-embed-config-tela-agent-inputs", () => void 0),
11
13
  workspaceSettings: useState("chat-embed-config-workspace-settings", () => void 0),
12
14
  hideSidebar: useState("chat-embed-config-hide-sidebar", () => void 0),
13
15
  loadingMessages: useState("chat-embed-config-loading-messages", () => void 0),
@@ -19,6 +21,8 @@ function useGlobalEmbedConfigState() {
19
21
  export function provideEmbedConfig(input, options = {}) {
20
22
  const config = {
21
23
  workspaceId: computed(() => toValue(input.workspaceId)),
24
+ telaAgentId: computed(() => toValue(input.telaAgentId) ?? null),
25
+ telaAgentInputs: computed(() => toValue(input.telaAgentInputs)),
22
26
  workspaceSettings: computed(() => toValue(input.workspaceSettings)),
23
27
  hideSidebar: computed(() => toValue(input.hideSidebar) ?? true),
24
28
  loadingMessages: computed(() => toValue(input.loadingMessages)),
@@ -31,6 +35,8 @@ export function provideEmbedConfig(input, options = {}) {
31
35
  watchEffect(() => {
32
36
  globalConfig.provided.value = true;
33
37
  globalConfig.workspaceId.value = config.workspaceId.value;
38
+ globalConfig.telaAgentId.value = config.telaAgentId.value;
39
+ globalConfig.telaAgentInputs.value = config.telaAgentInputs.value;
34
40
  globalConfig.workspaceSettings.value = config.workspaceSettings.value;
35
41
  globalConfig.hideSidebar.value = config.hideSidebar.value;
36
42
  globalConfig.loadingMessages.value = config.loadingMessages.value;
@@ -50,6 +56,8 @@ export function useEmbedConfig() {
50
56
  return null;
51
57
  return {
52
58
  workspaceId: computed(() => globalConfig.workspaceId.value),
59
+ telaAgentId: computed(() => globalConfig.telaAgentId.value),
60
+ telaAgentInputs: computed(() => globalConfig.telaAgentInputs.value),
53
61
  workspaceSettings: computed(() => globalConfig.workspaceSettings.value),
54
62
  hideSidebar: computed(() => globalConfig.hideSidebar.value ?? true),
55
63
  loadingMessages: computed(() => globalConfig.loadingMessages.value),
@@ -1,4 +1,3 @@
1
- import type { FeatureFlagKey } from '../types/feature-flags.js';
2
1
  type AuthUser = {
3
2
  id: string;
4
3
  email: string;
@@ -14,6 +13,6 @@ export declare function identifyPosthogUser(user: AuthUser): void;
14
13
  export declare function resetPosthogIdentity(): void;
15
14
  export declare function setPosthogWorkspace(workspace: Workspace): void;
16
15
  export declare function useFeatureFlags(): {
17
- isFeatureEnabled: (flag: FeatureFlagKey) => boolean;
16
+ isFeatureEnabled: (flag: string) => boolean;
18
17
  };
19
18
  export {};
@@ -0,0 +1,8 @@
1
+ import type { TelaAgent } from '../types/tela-agent.js';
2
+ export declare function useTelaAgentMetadata(): {
3
+ agentId: any;
4
+ metadata: any;
5
+ loading: any;
6
+ error: any;
7
+ fetchAgentMetadata: () => Promise<TelaAgent | null>;
8
+ };