@meistrari/chat-nuxt 1.1.3 → 1.2.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 (67) 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 +58 -19
  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 +3 -2
  15. package/dist/runtime/composables/useConversations.js +99 -11
  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 -8
  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 +31 -16
  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
@@ -1,7 +1,17 @@
1
1
  import * as Sentry from "@sentry/nuxt";
2
- import * as pdfjsLib from "pdfjs-dist";
3
- import { markRaw } from "vue";
4
- pdfjsLib.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjsLib.version}/build/pdf.worker.min.mjs`;
2
+ import { markRaw, onUnmounted, reactive, watch } from "vue";
3
+ let pdfjsLibPromise = null;
4
+ async function loadPdfjs() {
5
+ if (!import.meta.client)
6
+ return null;
7
+ if (!pdfjsLibPromise) {
8
+ pdfjsLibPromise = import("pdfjs-dist").then((lib) => {
9
+ lib.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${lib.version}/build/pdf.worker.min.mjs`;
10
+ return lib;
11
+ });
12
+ }
13
+ return pdfjsLibPromise;
14
+ }
5
15
  export function usePdf(url) {
6
16
  const state = reactive({
7
17
  pdfDoc: null,
@@ -13,6 +23,11 @@ export function usePdf(url) {
13
23
  async function loadPdf() {
14
24
  if (isUnmounted)
15
25
  return null;
26
+ const pdfjsLib = await loadPdfjs();
27
+ if (!pdfjsLib) {
28
+ state.isLoading = false;
29
+ return null;
30
+ }
16
31
  state.isLoading = true;
17
32
  state.loadError = null;
18
33
  try {
@@ -68,6 +83,9 @@ export function usePdf(url) {
68
83
  async function renderTextLayer(page, viewport, textLayer, highlight, highlightPage, pageNum) {
69
84
  if (isUnmounted)
70
85
  return;
86
+ const pdfjsLib = await loadPdfjs();
87
+ if (!pdfjsLib)
88
+ return;
71
89
  textLayer.innerHTML = "";
72
90
  textLayer.style.width = `${viewport.width}px`;
73
91
  textLayer.style.height = `${viewport.height}px`;
@@ -1,8 +1,7 @@
1
1
  import type { ContextFile } from '../server/db/schema.js';
2
- import type { WorkspaceSettings, WorkspaceSettingsUpdate } from './useWorkspaceSettings.js';
3
2
  export declare const MAX_FILES = 10;
4
3
  export declare const MAX_FILE_SIZE: number;
5
- export declare function useWorkspaceContextFiles(searchQuery: Ref<string>, currentContextFiles: Ref<readonly ContextFile[] | null | undefined> | ComputedRef<readonly ContextFile[] | null | undefined>, updateSettings: (updates: WorkspaceSettingsUpdate) => Promise<WorkspaceSettings>): {
4
+ export declare function useWorkspaceContextFiles(searchQuery: Ref<string>, currentContextFiles: Ref<readonly ContextFile[] | null | undefined> | ComputedRef<readonly ContextFile[] | null | undefined>): {
6
5
  pendingFiles: any;
7
6
  filteredContextFiles: any;
8
7
  filteredPendingFiles: any;
@@ -11,9 +10,10 @@ export declare function useWorkspaceContextFiles(searchQuery: Ref<string>, curre
11
10
  maxFiles: number;
12
11
  handleFileSelect: (event: Event) => void;
13
12
  removePendingFile: (file: File) => void;
14
- removeContextFile: (url: string) => Promise<void>;
13
+ removeContextFile: (url: string) => void;
15
14
  getFileType: (fileName: string) => string;
16
15
  initialize: () => void;
17
16
  getPendingFiles: () => File[];
17
+ getKeptContextFiles: () => ContextFile[];
18
18
  clearPendingFiles: () => void;
19
19
  };
@@ -1,14 +1,16 @@
1
1
  export const MAX_FILES = 10;
2
2
  export const MAX_FILE_SIZE = 10 * 1024 * 1024;
3
- export function useWorkspaceContextFiles(searchQuery, currentContextFiles, updateSettings) {
3
+ export function useWorkspaceContextFiles(searchQuery, currentContextFiles) {
4
4
  const pendingFiles = ref([]);
5
+ const removedUrls = ref(/* @__PURE__ */ new Set());
6
+ const effectiveContextFiles = computed(
7
+ () => (currentContextFiles.value ?? []).filter((file) => !removedUrls.value.has(file.url))
8
+ );
5
9
  const filteredContextFiles = computed(() => {
6
- if (!currentContextFiles.value)
7
- return [];
8
10
  if (!searchQuery.value.trim())
9
- return currentContextFiles.value;
11
+ return effectiveContextFiles.value;
10
12
  const query = searchQuery.value.toLowerCase();
11
- return currentContextFiles.value.filter(
13
+ return effectiveContextFiles.value.filter(
12
14
  (file) => file.name.toLowerCase().includes(query)
13
15
  );
14
16
  });
@@ -21,10 +23,10 @@ export function useWorkspaceContextFiles(searchQuery, currentContextFiles, updat
21
23
  );
22
24
  });
23
25
  const currentFileCount = computed(
24
- () => (currentContextFiles.value?.length ?? 0) + pendingFiles.value.length
26
+ () => effectiveContextFiles.value.length + pendingFiles.value.length
25
27
  );
26
28
  const hasChanges = computed(
27
- () => pendingFiles.value.length > 0
29
+ () => pendingFiles.value.length > 0 || removedUrls.value.size > 0
28
30
  );
29
31
  function handleFileSelect(event) {
30
32
  const target = event.target;
@@ -40,21 +42,27 @@ export function useWorkspaceContextFiles(searchQuery, currentContextFiles, updat
40
42
  if (idx !== -1)
41
43
  pendingFiles.value = pendingFiles.value.filter((_, i) => i !== idx);
42
44
  }
43
- async function removeContextFile(url) {
44
- const newFiles = (currentContextFiles.value ?? []).filter((f) => f.url !== url);
45
- await updateSettings({ contextFiles: newFiles.length > 0 ? newFiles : null });
45
+ function removeContextFile(url) {
46
+ const next = new Set(removedUrls.value);
47
+ next.add(url);
48
+ removedUrls.value = next;
46
49
  }
47
50
  function getFileType(fileName) {
48
51
  return fileName.split(".").pop()?.toUpperCase() ?? "";
49
52
  }
50
53
  function initialize() {
51
54
  pendingFiles.value = [];
55
+ removedUrls.value = /* @__PURE__ */ new Set();
52
56
  }
53
57
  function getPendingFiles() {
54
58
  return [...pendingFiles.value];
55
59
  }
60
+ function getKeptContextFiles() {
61
+ return [...effectiveContextFiles.value];
62
+ }
56
63
  function clearPendingFiles() {
57
64
  pendingFiles.value = [];
65
+ removedUrls.value = /* @__PURE__ */ new Set();
58
66
  }
59
67
  return {
60
68
  pendingFiles: readonly(pendingFiles),
@@ -69,6 +77,7 @@ export function useWorkspaceContextFiles(searchQuery, currentContextFiles, updat
69
77
  getFileType,
70
78
  initialize,
71
79
  getPendingFiles,
80
+ getKeptContextFiles,
72
81
  clearPendingFiles
73
82
  };
74
83
  }
@@ -1,5 +1,5 @@
1
1
  import type { ExternalSkill } from '../server/db/schema.js';
2
- export declare function useWorkspaceExternalSkills(searchQuery?: Ref<string>): {
2
+ export declare function useWorkspaceExternalSkills(currentExternalSkills: Ref<readonly ExternalSkill[] | null | undefined> | ComputedRef<readonly ExternalSkill[] | null | undefined>, searchQuery?: Ref<string>): {
3
3
  displayedSkills: any;
4
4
  filteredSkills: any;
5
5
  hasChanges: any;
@@ -1,15 +1,14 @@
1
- export function useWorkspaceExternalSkills(searchQuery) {
2
- const { settings } = useWorkspaceSettings();
1
+ export function useWorkspaceExternalSkills(currentExternalSkills, searchQuery) {
3
2
  const { user } = useTelaApplicationAuth();
4
3
  const { connection: skillsConnection, fetchConnection: fetchGitHubConnection, connect: connectGitHub } = useGitHubSkills();
5
- const pendingSkills = useState("workspace-external-skills-pending", () => []);
6
- const removedRefs = useState("workspace-external-skills-removed", () => []);
4
+ const pendingSkills = ref([]);
5
+ const removedRefs = ref([]);
7
6
  const skillModalOpen = ref(false);
8
7
  const skillRefInput = ref("");
9
8
  const skillError = ref("");
10
9
  const discoveringSkills = ref(false);
11
10
  const displayedSkills = computed(() => {
12
- const current = (settings.value?.externalSkills ?? []).filter((s) => !removedRefs.value.includes(s.ref));
11
+ const current = (currentExternalSkills.value ?? []).filter((s) => !removedRefs.value.includes(s.ref));
13
12
  return [...current, ...pendingSkills.value];
14
13
  });
15
14
  const filteredSkills = computed(() => {
@@ -38,7 +37,7 @@ export function useWorkspaceExternalSkills(searchQuery) {
38
37
  removedRefs.value = [...removedRefs.value, ref2];
39
38
  }
40
39
  function buildExternalSkills() {
41
- const currentSkills = (settings.value?.externalSkills ?? []).filter((s) => !removedRefs.value.includes(s.ref)).map((s) => ({
40
+ const currentSkills = (currentExternalSkills.value ?? []).filter((s) => !removedRefs.value.includes(s.ref)).map((s) => ({
42
41
  ...s,
43
42
  allowedTools: [...s.allowedTools],
44
43
  addedBy: { ...s.addedBy }
@@ -1,4 +1,4 @@
1
- export declare function useWorkspacePersonalization(): {
1
+ export declare function useWorkspacePersonalization(currentSystemMessage: Ref<string | null | undefined> | ComputedRef<string | null | undefined>): {
2
2
  localSystemMessage: any;
3
3
  hasChanges: any;
4
4
  getSystemMessage: () => string | null;
@@ -1,14 +1,13 @@
1
- export function useWorkspacePersonalization() {
2
- const { settings } = useWorkspaceSettings();
3
- const localSystemMessage = useState("workspace-personalization-system-message", () => "");
1
+ export function useWorkspacePersonalization(currentSystemMessage) {
2
+ const localSystemMessage = ref("");
4
3
  const hasChanges = computed(
5
- () => localSystemMessage.value !== (settings.value?.systemMessage ?? "")
4
+ () => localSystemMessage.value !== (currentSystemMessage.value ?? "")
6
5
  );
7
6
  function getSystemMessage() {
8
7
  return localSystemMessage.value.trim() || null;
9
8
  }
10
9
  function initialize() {
11
- localSystemMessage.value = settings.value?.systemMessage ?? "";
10
+ localSystemMessage.value = currentSystemMessage.value ?? "";
12
11
  }
13
12
  return {
14
13
  localSystemMessage,
@@ -2,7 +2,8 @@ import * as Sentry from "@sentry/nuxt";
2
2
  export function useWorkspaceSettings() {
3
3
  const chatApi = useChatApi();
4
4
  const embedConfig = useEmbedConfig();
5
- const workspaceScope = computed(() => embedConfig?.workspaceId.value.trim() || "default");
5
+ const { activeOrganization } = useTelaApplicationAuth();
6
+ const workspaceScope = computed(() => embedConfig?.workspaceId.value.trim() || activeOrganization.value?.id?.trim() || "default");
6
7
  const settingsBuckets = useState("workspace-settings", () => ({}));
7
8
  const loadingBuckets = useState("workspace-settings-loading", () => ({}));
8
9
  const errorBuckets = useState("workspace-settings-error", () => ({}));
@@ -35,7 +36,8 @@ export function useWorkspaceSettings() {
35
36
  });
36
37
  const isModalOpen = useState("workspace-settings-modal-open", () => false);
37
38
  const initialTab = useState("workspace-settings-initial-tab", () => null);
38
- if (embedConfig !== null && embedConfig.workspaceSettings.value !== void 0) {
39
+ const hasWorkspaceSettingsOverride = computed(() => embedConfig?.workspaceSettings.value !== void 0);
40
+ if (embedConfig !== null && hasWorkspaceSettingsOverride.value) {
39
41
  settings.value = embedConfig.workspaceSettings.value;
40
42
  }
41
43
  if (embedConfig !== null) {
@@ -64,7 +66,9 @@ export function useWorkspaceSettings() {
64
66
  try {
65
67
  const currentSettings = settings.value;
66
68
  let data;
67
- if (embedConfig !== null) {
69
+ if (hasWorkspaceSettingsOverride.value) {
70
+ return settings.value;
71
+ } else if (embedConfig !== null) {
68
72
  data = await $fetch(
69
73
  chatApi.path("/workspace/settings"),
70
74
  chatApi.withChatHeaders()
@@ -102,15 +106,14 @@ export function useWorkspaceSettings() {
102
106
  }
103
107
  }
104
108
  async function updateSettings(updates) {
105
- if (embedConfig !== null)
109
+ if (hasWorkspaceSettingsOverride.value)
106
110
  return settings.value;
107
111
  loading.value = true;
108
112
  error.value = null;
109
113
  try {
110
- const data = await $fetch("/api/workspace/settings", {
111
- method: "PATCH",
112
- body: updates
113
- });
114
+ const path = embedConfig !== null ? chatApi.path("/workspace/settings") : "/api/workspace/settings";
115
+ const options = embedConfig !== null ? chatApi.withChatHeaders({ method: "PATCH", body: updates }) : { method: "PATCH", body: updates };
116
+ const data = await $fetch(path, options);
114
117
  settings.value = data;
115
118
  return data;
116
119
  } catch (e) {
@@ -0,0 +1,25 @@
1
+ import type { DeepReadonly } from 'vue';
2
+ import type { WorkspaceSettings, WorkspaceSettingsUpdate } from '../../composables/useWorkspaceSettings.js';
3
+ export type ChatConfigurationTab = 'personalization' | 'context-files' | 'knowledge-sources' | 'tela-tools' | 'external-skills' | 'credentials';
4
+ export type ChatConfigurationSavePayload = {
5
+ updates: WorkspaceSettingsUpdate;
6
+ onSaved: () => void;
7
+ };
8
+ type __VLS_Props = {
9
+ open: boolean;
10
+ settings?: DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null;
11
+ initialTab?: ChatConfigurationTab;
12
+ saving?: boolean;
13
+ };
14
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
15
+ save: (payload: ChatConfigurationSavePayload) => any;
16
+ "update:open": (value: boolean) => any;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
18
+ onSave?: ((payload: ChatConfigurationSavePayload) => any) | undefined;
19
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
20
+ }>, {
21
+ initialTab: ChatConfigurationTab;
22
+ saving: boolean;
23
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
+ declare const _default: typeof __VLS_export;
25
+ export default _default;
@@ -0,0 +1,373 @@
1
+ <script setup>
2
+ import { useMagicKeys } from "@vueuse/core";
3
+ const props = defineProps({
4
+ open: { type: Boolean, required: true },
5
+ settings: { type: null, required: false },
6
+ initialTab: { type: String, required: false, default: "personalization" },
7
+ saving: { type: Boolean, required: false, default: false }
8
+ });
9
+ const emit = defineEmits(["update:open", "save"]);
10
+ const { uploadFiles, uploading } = useFileUpload();
11
+ const { canvas, projects, workstations, loadingWorkstations } = useTela();
12
+ const { setKnowledgeSources } = useKnowledgeSources();
13
+ const statusToast = useStatusToast();
14
+ const { escape } = useMagicKeys();
15
+ const {
16
+ settings: workspaceSettings,
17
+ loading: workspaceSettingsLoading,
18
+ fetchSettings,
19
+ updateSettings
20
+ } = useWorkspaceSettings();
21
+ const searchQuery = ref("");
22
+ const activeTab = ref(props.initialTab);
23
+ const showDiscardConfirm = ref(false);
24
+ const loadingInitialSettings = ref(false);
25
+ const hasSettingsOverride = computed(() => props.settings !== void 0);
26
+ const mutableSettings = computed(() => hasSettingsOverride.value ? props.settings : workspaceSettings.value);
27
+ const currentSystemMessage = computed(() => mutableSettings.value?.systemMessage ?? null);
28
+ const currentContextFiles = computed(() => mutableSettings.value?.contextFiles ?? null);
29
+ const currentCanvasTools = computed(() => mutableSettings.value?.canvasTools ?? null);
30
+ const currentKnowledgeSources = computed(() => mutableSettings.value?.knowledgeSources ?? null);
31
+ const currentExternalSkills = computed(() => mutableSettings.value?.externalSkills ?? null);
32
+ const personalization = useWorkspacePersonalization(currentSystemMessage);
33
+ const telaTools = useWorkspaceTelaTools(canvas, projects, searchQuery, currentCanvasTools);
34
+ const knowledgeSources = useWorkspaceKnowledgeSources(workstations, canvas, projects, searchQuery, currentKnowledgeSources);
35
+ const contextFiles = useWorkspaceContextFiles(searchQuery, currentContextFiles);
36
+ const externalSkills = useWorkspaceExternalSkills(currentExternalSkills, searchQuery);
37
+ const settingsTabs = [
38
+ { value: "personalization", label: "Personaliza\xE7\xE3o", icon: "i-ph-sliders" },
39
+ { value: "context-files", label: "Arquivos de contexto", icon: "i-ph-file-text" },
40
+ { value: "knowledge-sources", label: "Tela workstations", icon: "i-ph-database" },
41
+ { value: "tela-tools", label: "Skills Tela", icon: "i-ph-puzzle-piece" },
42
+ { value: "external-skills", label: "Skills Externas", icon: "i-ph-wrench" },
43
+ { value: "credentials", label: "Credenciais", icon: "i-ph-password-bold" }
44
+ ];
45
+ const hasChanges = computed(
46
+ () => personalization.hasChanges.value || contextFiles.hasChanges.value || telaTools.hasChanges.value || knowledgeSources.hasChanges.value || externalSkills.hasChanges.value
47
+ );
48
+ const isBusy = computed(() => props.saving || uploading.value || !hasSettingsOverride.value && workspaceSettingsLoading.value);
49
+ const gitHubButtonText = computed(() => externalSkills.getGitHubButtonText());
50
+ watch(() => escape?.value, (pressed) => {
51
+ if (pressed && props.open && !showDiscardConfirm.value)
52
+ handleClose();
53
+ });
54
+ watch(() => props.open, (isOpen) => {
55
+ if (isOpen) {
56
+ searchQuery.value = "";
57
+ activateTab(props.initialTab);
58
+ if (!hasSettingsOverride.value && !mutableSettings.value) {
59
+ void loadSettings();
60
+ return;
61
+ }
62
+ initializeConfigurationState();
63
+ } else {
64
+ loadingInitialSettings.value = false;
65
+ }
66
+ });
67
+ watch(() => props.initialTab, (tab) => {
68
+ if (props.open)
69
+ activateTab(tab);
70
+ });
71
+ watch(currentSystemMessage, () => {
72
+ if (props.open)
73
+ personalization.initialize();
74
+ });
75
+ watch(mutableSettings, (settings, previousSettings) => {
76
+ if (props.open && hasSettingsOverride.value && settings && !previousSettings)
77
+ initializeConfigurationState();
78
+ });
79
+ function initializeConfigurationState() {
80
+ personalization.initialize();
81
+ telaTools.initialize();
82
+ knowledgeSources.initialize();
83
+ contextFiles.initialize();
84
+ externalSkills.initialize();
85
+ }
86
+ function activateTab(tab) {
87
+ activeTab.value = tab;
88
+ searchQuery.value = "";
89
+ if (tab === "external-skills")
90
+ void externalSkills.fetchGitHubConnection();
91
+ }
92
+ async function loadSettings() {
93
+ loadingInitialSettings.value = true;
94
+ try {
95
+ await fetchSettings();
96
+ initializeConfigurationState();
97
+ } catch (error) {
98
+ console.error("Failed to load workspace settings:", error);
99
+ statusToast.update({
100
+ text: "Erro ao carregar configura\xE7\xF5es",
101
+ icon: "i-ph-warning"
102
+ });
103
+ } finally {
104
+ loadingInitialSettings.value = false;
105
+ }
106
+ }
107
+ async function handleSave() {
108
+ if (!hasChanges.value || isBusy.value)
109
+ return;
110
+ try {
111
+ const updates = {};
112
+ if (personalization.hasChanges.value)
113
+ updates.systemMessage = personalization.getSystemMessage();
114
+ if (contextFiles.hasChanges.value) {
115
+ const pendingFilesList = contextFiles.getPendingFiles();
116
+ const uploadedFiles = pendingFilesList.length > 0 ? await uploadFiles(pendingFilesList) : [];
117
+ const newContextFiles = [
118
+ ...contextFiles.getKeptContextFiles(),
119
+ ...uploadedFiles.map((f) => ({ url: f.url, name: f.name, mimeType: f.mimeType }))
120
+ ];
121
+ updates.contextFiles = newContextFiles.length > 0 ? newContextFiles : null;
122
+ }
123
+ if (telaTools.hasChanges.value) {
124
+ const tools = telaTools.buildCanvasTools();
125
+ updates.canvasTools = tools.length > 0 ? tools : null;
126
+ }
127
+ let pendingKnowledgeSources = null;
128
+ if (knowledgeSources.hasChanges.value) {
129
+ const sources = knowledgeSources.buildKnowledgeSources();
130
+ updates.knowledgeSources = sources.length > 0 ? sources : null;
131
+ pendingKnowledgeSources = sources.length > 0 ? sources : null;
132
+ }
133
+ if (externalSkills.hasChanges.value)
134
+ updates.externalSkills = externalSkills.buildExternalSkills();
135
+ const shouldUpdateKnowledgeSources = pendingKnowledgeSources !== null || knowledgeSources.hasChanges.value;
136
+ const shouldClearContextFiles = contextFiles.hasChanges.value;
137
+ const shouldResetExternalSkills = externalSkills.hasChanges.value;
138
+ const onSaved = () => {
139
+ if (shouldUpdateKnowledgeSources)
140
+ setKnowledgeSources(pendingKnowledgeSources);
141
+ if (shouldClearContextFiles)
142
+ contextFiles.clearPendingFiles();
143
+ if (shouldResetExternalSkills)
144
+ externalSkills.initialize();
145
+ };
146
+ if (hasSettingsOverride.value) {
147
+ emit("save", { updates, onSaved });
148
+ return;
149
+ }
150
+ await updateSettings(updates);
151
+ onSaved();
152
+ statusToast.update({
153
+ text: "Configura\xE7\xF5es salvas",
154
+ icon: "i-ph-check"
155
+ });
156
+ emit("update:open", false);
157
+ } catch (error) {
158
+ console.error("Failed to prepare settings update:", error);
159
+ statusToast.update({
160
+ text: "Erro ao salvar configura\xE7\xF5es",
161
+ icon: "i-ph-warning"
162
+ });
163
+ }
164
+ }
165
+ function handleClose() {
166
+ if (isBusy.value)
167
+ return;
168
+ if (hasChanges.value)
169
+ showDiscardConfirm.value = true;
170
+ else
171
+ emit("update:open", false);
172
+ }
173
+ function confirmDiscard() {
174
+ showDiscardConfirm.value = false;
175
+ emit("update:open", false);
176
+ }
177
+ function cancelDiscard() {
178
+ showDiscardConfirm.value = false;
179
+ }
180
+ </script>
181
+
182
+ <template>
183
+ <TelaModal
184
+ :model-value="open"
185
+ modal-width="lg"
186
+ compact
187
+ hide-dividers
188
+ hide-header
189
+ class="!w-[min(1024px,95vw)] !max-w-[min(1024px,95vw)] p-0!"
190
+ @update:model-value="handleClose"
191
+ >
192
+ <div flex class="h-[min(722px,90vh)]" w-full>
193
+ <div w-256px bg-neutral-100 flex="~ col" overflow-hidden rounded-l-16px relative>
194
+ <p text-12px text-secondary px-16px pt-16px leading-16px style="color: #767E88;">
195
+ Configurações do Workspace
196
+ </p>
197
+
198
+ <div flex="~ col" gap-1px px-6px mt-24px>
199
+ <button
200
+ v-for="tab in settingsTabs"
201
+ :key="tab.value"
202
+ flex items-center gap-12px
203
+ px-12px py-7px
204
+ rounded-8px
205
+ cursor-pointer
206
+ transition
207
+ text-left
208
+ w-full
209
+ :class="[
210
+ activeTab === tab.value ? 'bg-black/4' : 'hover:bg-black/2'
211
+ ]"
212
+ @click="activateTab(tab.value)"
213
+ >
214
+ <div
215
+ :class="tab.icon"
216
+ text-16px
217
+ :style="{ color: '#6B7280' }"
218
+ />
219
+ <span
220
+ text-14px
221
+ tracking="-.15px"
222
+ leading-18px
223
+ font-460
224
+ :class="[
225
+ activeTab === tab.value ? 'text-primary' : 'text-secondary'
226
+ ]"
227
+ >
228
+ {{ tab.label }}
229
+ </span>
230
+ </button>
231
+ </div>
232
+
233
+ <div flex-1 />
234
+
235
+ <div absolute bottom-0 left-0 w-full h-56px bg-gradient-to-t from-neutral-50 to-transparent pointer-events-none />
236
+ <div px-16px pb-16px relative z-10>
237
+ <button
238
+ p-6px rounded-full
239
+ bg-white b=".5px gray-200"
240
+ flex items-center justify-center
241
+ cursor-pointer
242
+ hover:bg-gray-50
243
+ transition
244
+ >
245
+ <div i-ph-question text-20px style="color: #767E88;" />
246
+ </button>
247
+ </div>
248
+ </div>
249
+
250
+ <div flex-1 flex="~ col" bg-white relative overflow-hidden rounded-r-16px>
251
+ <button
252
+ absolute top-12px right-12px
253
+ p-8px rounded-12px
254
+ hover:bg-gray-100
255
+ transition cursor-pointer
256
+ z-10
257
+ @click="handleClose"
258
+ >
259
+ <div i-ph-x text-24px text-primary />
260
+ </button>
261
+
262
+ <div flex-1 overflow-auto px-48px pt-32px pb-24px>
263
+ <div
264
+ v-if="loadingInitialSettings"
265
+ h-full min-h-360px flex="~ col" items-center justify-center gap-12px
266
+ >
267
+ <div i-ph-spinner animate-spin text-24px text-gray-500 />
268
+ <p text-14px text-gray-500>
269
+ Carregando configurações...
270
+ </p>
271
+ </div>
272
+
273
+ <ChatConfigurationPersonalizationTab
274
+ v-else-if="activeTab === 'personalization'"
275
+ v-model:system-message="personalization.localSystemMessage.value"
276
+ />
277
+
278
+ <ChatConfigurationContextFilesTab
279
+ v-else-if="activeTab === 'context-files'"
280
+ :search-query="searchQuery"
281
+ :filtered-context-files="contextFiles.filteredContextFiles.value"
282
+ :filtered-pending-files="contextFiles.filteredPendingFiles.value"
283
+ :current-file-count="contextFiles.currentFileCount.value"
284
+ :max-files="contextFiles.maxFiles"
285
+ :get-file-type="contextFiles.getFileType"
286
+ @update:search-query="searchQuery = $event"
287
+ @handle-file-select="contextFiles.handleFileSelect"
288
+ @remove-pending-file="contextFiles.removePendingFile"
289
+ @remove-context-file="contextFiles.removeContextFile"
290
+ />
291
+
292
+ <ChatConfigurationKnowledgeSourcesTab
293
+ v-else-if="activeTab === 'knowledge-sources'"
294
+ :search-query="searchQuery"
295
+ :workstations="workstations"
296
+ :loading-workstations="loadingWorkstations"
297
+ :filtered-workstations="knowledgeSources.filteredWorkstations.value"
298
+ :selected-ids="knowledgeSources.selectedKnowledgeSourceIds.value"
299
+ :get-project-name-for-workstation="knowledgeSources.getProjectNameForWorkstation"
300
+ @update:search-query="searchQuery = $event"
301
+ @toggle="knowledgeSources.toggleKnowledgeSource"
302
+ />
303
+
304
+ <ChatConfigurationTelaToolsTab
305
+ v-else-if="activeTab === 'tela-tools'"
306
+ :search-query="searchQuery"
307
+ :canvas="canvas"
308
+ :filtered-canvas-tools="telaTools.filteredCanvasTools.value"
309
+ :selected-tool-ids="telaTools.selectedToolIds.value"
310
+ :all-tools-selected="telaTools.allToolsSelected.value"
311
+ :get-project-name="telaTools.getProjectName"
312
+ @update:search-query="searchQuery = $event"
313
+ @toggle-tool="telaTools.toggleCanvasTool"
314
+ @toggle-all="telaTools.toggleAllTools"
315
+ />
316
+
317
+ <ChatConfigurationExternalSkillsTab
318
+ v-else-if="activeTab === 'external-skills'"
319
+ :search-query="searchQuery"
320
+ :displayed-skills="externalSkills.displayedSkills.value"
321
+ :filtered-skills="externalSkills.filteredSkills.value"
322
+ :has-changes="externalSkills.hasChanges.value"
323
+ :is-pending="externalSkills.isPending"
324
+ :skill-modal-open="externalSkills.skillModalOpen.value"
325
+ :skill-ref-input="externalSkills.skillRefInput.value"
326
+ :skill-error="externalSkills.skillError.value"
327
+ :discovering-skills="externalSkills.discoveringSkills.value"
328
+ :git-hub-button-text="gitHubButtonText"
329
+ @update:search-query="searchQuery = $event"
330
+ @update:skill-modal-open="externalSkills.skillModalOpen.value = $event"
331
+ @update:skill-ref-input="externalSkills.skillRefInput.value = $event"
332
+ @remove-skill="externalSkills.removeSkill"
333
+ @close-skill-modal="externalSkills.closeSkillModal"
334
+ @handle-add-skill-and-close="externalSkills.handleAddSkillAndClose"
335
+ @connect-git-hub="externalSkills.connectGitHub"
336
+ />
337
+
338
+ <ChatConfigurationCredentialsTab
339
+ v-else-if="activeTab === 'credentials'"
340
+ :search-query="searchQuery"
341
+ @update:search-query="searchQuery = $event"
342
+ />
343
+ </div>
344
+
345
+ <div flex-shrink-0 px-48px pb-48px flex justify-end gap-12px>
346
+ <TelaButton
347
+ size="lg"
348
+ :disabled="!hasChanges || isBusy"
349
+ @click="handleSave"
350
+ >
351
+ <span v-if="isBusy" flex items-center gap-6px>
352
+ <div i-ph-spinner animate-spin />
353
+ Salvando...
354
+ </span>
355
+ <span v-else>Salvar</span>
356
+ </TelaButton>
357
+ </div>
358
+ </div>
359
+ </div>
360
+
361
+ <TelaConfirmationModal
362
+ v-model:open="showDiscardConfirm"
363
+ title="Descartar alterações?"
364
+ variant="danger"
365
+ confirm-button-text="Descartar"
366
+ cancel-button-text="Continuar editando"
367
+ @confirm="confirmDiscard"
368
+ @cancel="cancelDiscard"
369
+ >
370
+ Você tem alterações não salvas. Deseja descartá-las?
371
+ </TelaConfirmationModal>
372
+ </TelaModal>
373
+ </template>