@modochats/widget 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/src/models/conversation.js +1 -1
  2. package/dist/src/types/app.js +1 -0
  3. package/package.json +1 -1
  4. package/temp/app.dev.js +5101 -0
  5. package/temp/app.dev.js.map +1 -0
  6. package/temp/app.js +3570 -0
  7. package/.vscode/settings.json +0 -3
  8. package/dist/types/src/app.d.ts +0 -30
  9. package/dist/types/src/app.d.ts.map +0 -1
  10. package/dist/types/src/constants/index.d.ts +0 -10
  11. package/dist/types/src/constants/index.d.ts.map +0 -1
  12. package/dist/types/src/constants/regex.d.ts +0 -3
  13. package/dist/types/src/constants/regex.d.ts.map +0 -1
  14. package/dist/types/src/index.d.ts +0 -10
  15. package/dist/types/src/index.d.ts.map +0 -1
  16. package/dist/types/src/models/chatbot.d.ts +0 -24
  17. package/dist/types/src/models/chatbot.d.ts.map +0 -1
  18. package/dist/types/src/models/conversation.d.ts +0 -23
  19. package/dist/types/src/models/conversation.d.ts.map +0 -1
  20. package/dist/types/src/models/customer-data.d.ts +0 -32
  21. package/dist/types/src/models/customer-data.d.ts.map +0 -1
  22. package/dist/types/src/models/message-utils.d.ts +0 -13
  23. package/dist/types/src/models/message-utils.d.ts.map +0 -1
  24. package/dist/types/src/services/chat/conversation.d.ts +0 -23
  25. package/dist/types/src/services/chat/conversation.d.ts.map +0 -1
  26. package/dist/types/src/services/chat/message-utils.d.ts +0 -13
  27. package/dist/types/src/services/chat/message-utils.d.ts.map +0 -1
  28. package/dist/types/src/services/chat/model.d.ts +0 -28
  29. package/dist/types/src/services/chat/model.d.ts.map +0 -1
  30. package/dist/types/src/services/chatbot/chatbot.d.ts +0 -24
  31. package/dist/types/src/services/chatbot/chatbot.d.ts.map +0 -1
  32. package/dist/types/src/services/checker.d.ts +0 -4
  33. package/dist/types/src/services/checker.d.ts.map +0 -1
  34. package/dist/types/src/services/listeners/adders.d.ts +0 -4
  35. package/dist/types/src/services/listeners/adders.d.ts.map +0 -1
  36. package/dist/types/src/services/listeners/fn.d.ts +0 -4
  37. package/dist/types/src/services/listeners/fn.d.ts.map +0 -1
  38. package/dist/types/src/services/socket/utils.d.ts +0 -3
  39. package/dist/types/src/services/socket/utils.d.ts.map +0 -1
  40. package/dist/types/src/services/ui/fn.d.ts +0 -14
  41. package/dist/types/src/services/ui/fn.d.ts.map +0 -1
  42. package/dist/types/src/services/ui/html.d.ts +0 -4
  43. package/dist/types/src/services/ui/html.d.ts.map +0 -1
  44. package/dist/types/src/services/user/customer-data.d.ts +0 -32
  45. package/dist/types/src/services/user/customer-data.d.ts.map +0 -1
  46. package/dist/types/src/services/voice-chat/model.d.ts +0 -13
  47. package/dist/types/src/services/voice-chat/model.d.ts.map +0 -1
  48. package/dist/types/src/services/voice-chat/utils.d.ts +0 -10
  49. package/dist/types/src/services/voice-chat/utils.d.ts.map +0 -1
  50. package/dist/types/src/tools/fetch.d.ts +0 -3
  51. package/dist/types/src/tools/fetch.d.ts.map +0 -1
  52. package/dist/types/src/types/app.d.ts +0 -18
  53. package/dist/types/src/types/app.d.ts.map +0 -1
  54. package/dist/types/src/types/conversation.d.ts +0 -15
  55. package/dist/types/src/types/conversation.d.ts.map +0 -1
  56. package/dist/types/src/types/socket.d.ts +0 -7
  57. package/dist/types/src/types/socket.d.ts.map +0 -1
  58. package/dist/types/src/types/window.d.ts +0 -10
  59. package/dist/types/src/types/window.d.ts.map +0 -1
  60. package/dist/types/src/utils/audio.d.ts +0 -4
  61. package/dist/types/src/utils/audio.d.ts.map +0 -1
  62. package/dist/types/src/utils/browser.d.ts +0 -3
  63. package/dist/types/src/utils/browser.d.ts.map +0 -1
  64. package/dist/types/src/utils/fetch.d.ts +0 -19
  65. package/dist/types/src/utils/fetch.d.ts.map +0 -1
  66. package/dist/types/src/utils/uuid.d.ts +0 -7
  67. package/dist/types/src/utils/uuid.d.ts.map +0 -1
  68. package/src/app.ts +0 -117
  69. package/src/constants/index.ts +0 -21
  70. package/src/constants/regex.ts +0 -2
  71. package/src/index.ts +0 -16
  72. package/src/services/chat/conversation.ts +0 -135
  73. package/src/services/chat/message-utils.ts +0 -221
  74. package/src/services/chat/model.ts +0 -139
  75. package/src/services/chatbot/chatbot.ts +0 -66
  76. package/src/services/checker.ts +0 -10
  77. package/src/services/listeners/adders.ts +0 -178
  78. package/src/services/listeners/fn.ts +0 -77
  79. package/src/services/socket/utils.ts +0 -9
  80. package/src/services/ui/fn.ts +0 -254
  81. package/src/services/ui/html.ts +0 -192
  82. package/src/services/user/customer-data.ts +0 -78
  83. package/src/services/voice-chat/model.ts +0 -79
  84. package/src/services/voice-chat/utils.ts +0 -137
  85. package/src/tools/fetch.ts +0 -7
  86. package/src/types/app.ts +0 -17
  87. package/src/types/conversation.ts +0 -14
  88. package/src/types/socket.ts +0 -7
  89. package/src/types/window.ts +0 -12
  90. package/src/utils/audio.ts +0 -67
  91. package/src/utils/browser.ts +0 -4
  92. package/src/utils/fetch.ts +0 -98
  93. package/src/utils/uuid.ts +0 -13
  94. package/tsconfig.json +0 -119
@@ -1,66 +0,0 @@
1
- class Chatbot {
2
- name: string;
3
- image: string;
4
- shortDescription: string;
5
- starters: string[] = [];
6
- voiceChat: boolean;
7
- createdAt: string;
8
- updatedAt: string;
9
- deletedAt: string | null;
10
- uuid: string;
11
- allowedHosts: string[] = [];
12
- id: number;
13
- greetingMessage?: string;
14
- uiConfig: {
15
- primaryColor: string;
16
- foregroundColor: string;
17
- theme: "dark" | "light";
18
- };
19
- constructor(data: Record<string, any>) {
20
- this.name = data.name;
21
- this.image = data.image;
22
- this.shortDescription = data.short_description;
23
- this.starters = data.starters;
24
- this.voiceChat = data.voice_agent;
25
- this.createdAt = data.setting.created_at;
26
- this.updatedAt = data.setting.updated_at;
27
- this.deletedAt = data.setting.deleted_at;
28
- this.uuid = data.setting.unique_id;
29
- this.allowedHosts = data.setting.allow_hosts?.split(",") ?? [];
30
- this.id = data.setting.chatbot;
31
- this.allowedHosts.push("modochats.com");
32
- this.uiConfig = {
33
- primaryColor: data.primary_color,
34
- foregroundColor: data.foreground_color,
35
- theme: data.theme
36
- };
37
- this.greetingMessage = data.greeting_message;
38
- }
39
- showTooltip() {
40
- const widget = window.getMWidget?.();
41
- const tooltip = widget?.container?.querySelector(".mw-toggle-tooltip");
42
- const tooltipText = widget?.container?.querySelector(".mw-toggle-tooltip-text");
43
- const hasSeen = localStorage.getItem(`modochats:${widget?.publicKey}-has-seen-greeting-message`) === "true";
44
-
45
- if (tooltip && tooltipText && this.greetingMessage && !hasSeen) {
46
- // Show the tooltip
47
- tooltip.classList.remove("mw-hidden");
48
-
49
- // Update tooltip text with greeting message
50
- tooltipText.textContent = this.greetingMessage;
51
-
52
- // Auto-hide after 5 seconds
53
- // setTimeout(() => {
54
- // tooltip.classList.add("mw-hidden");
55
- // }, 5000);
56
- }
57
- }
58
- hideTooltip() {
59
- const widget = window.getMWidget?.();
60
- const tooltip = widget?.container?.querySelector(".mw-toggle-tooltip");
61
- if (tooltip) {
62
- tooltip.classList.add("mw-hidden");
63
- }
64
- }
65
- }
66
- export {Chatbot};
@@ -1,10 +0,0 @@
1
- import {Widget} from "#src/app.js";
2
- import {parse} from "tldts";
3
-
4
- const checkIfHostIsAllowed = (widget: Widget) => {
5
- const currentHost = parse(window.location.origin).hostname;
6
- const allowedHosts = widget.chatbot?.allowedHosts || [];
7
- if (currentHost) return allowedHosts.includes(currentHost);
8
- };
9
-
10
- export {checkIfHostIsAllowed};
@@ -1,178 +0,0 @@
1
- import {getMessageElement} from "#src/services/chat/message-utils.js";
2
- import {sendMessage, submitPhoneNumberForm} from "./fn.js";
3
-
4
- const registerListeners = (widgetContainer: HTMLDivElement) => {
5
- let chatBody = widgetContainer.querySelector(".mw-chat-body");
6
- const toggleChatBtn = widgetContainer.querySelector(".mw-toggle-chat-btn") as HTMLButtonElement;
7
- let isBodyOpen = false;
8
-
9
- // Set footer link URL with origin parameter and version title
10
- const footerLink = widgetContainer.querySelector(".mw-footer-link") as HTMLAnchorElement;
11
- if (footerLink) {
12
- const widget = window.getMWidget?.();
13
- footerLink.href = `https://modochats.com?utm_source=${encodeURIComponent(window.location.origin)}`;
14
- footerLink.title = `مودوچت v${widget?.version || "0.1"}`;
15
- }
16
-
17
- // toggle chat body visibility (only if not in fullscreen mode)
18
- if (toggleChatBtn) {
19
- toggleChatBtn.addEventListener(
20
- "click",
21
- () => {
22
- const widget = window.getMWidget?.();
23
- isBodyOpen = !isBodyOpen;
24
- if (isBodyOpen) widget?.onOpen();
25
- else widget?.onClose();
26
- chatBody?.classList.toggle("mw-hidden");
27
- toggleChatBtn.classList.toggle("mw-chat-open", isBodyOpen);
28
- },
29
- {capture: false}
30
- );
31
- }
32
-
33
- registerSendMessageListener(widgetContainer);
34
- registerPhoneNumberFormListeners(widgetContainer);
35
- registerNewConversationListener(widgetContainer);
36
- registerFileUploadListener(widgetContainer);
37
- registerReplyPreviewListener(widgetContainer);
38
- registerTooltipCloseListener(widgetContainer);
39
- };
40
-
41
- const registerSendMessageListener = (widgetContainer: HTMLDivElement) => {
42
- const chatInput = widgetContainer.querySelector(".mw-chat-input") as HTMLInputElement;
43
- const sendMessageBtn = widgetContainer.querySelector(".mw-send-message-btn") as HTMLButtonElement;
44
-
45
- let isDisabled = false;
46
- function toggleLoading() {
47
- isDisabled = !isDisabled;
48
- chatInput.disabled = isDisabled;
49
- sendMessageBtn.disabled = isDisabled;
50
- sendMessageBtn.setAttribute("data-is-loading", String(isDisabled));
51
- }
52
-
53
- chatInput.addEventListener("keydown", e => {
54
- if (e.key === "Enter") {
55
- e.preventDefault();
56
- const message = chatInput.value;
57
- toggleLoading();
58
- sendMessage(message)
59
- .then(() => {
60
- chatInput.value = "";
61
- })
62
- .finally(toggleLoading);
63
- }
64
- });
65
- sendMessageBtn.addEventListener("click", e => {
66
- e.preventDefault();
67
- const message = chatInput.value;
68
- toggleLoading();
69
- sendMessage(message)
70
- .then(() => {
71
- chatInput.value = "";
72
- })
73
- .finally(toggleLoading);
74
- });
75
- };
76
-
77
- const registerPhoneNumberFormListeners = (widgetContainer: HTMLDivElement) => {
78
- const formOverlay = widgetContainer.querySelector(".mw-form-overlay") as HTMLDivElement;
79
- const phoneInput = widgetContainer.querySelector(".mw-phone-input") as HTMLInputElement;
80
- const formSubmitBtn = widgetContainer.querySelector(".mw-form-submit-btn") as HTMLButtonElement;
81
- const formCancelBtn = widgetContainer.querySelector(".mw-form-cancel-btn") as HTMLButtonElement;
82
- formSubmitBtn.addEventListener("click", () => {
83
- const phoneNumber = phoneInput.value;
84
- submitPhoneNumberForm(phoneNumber);
85
- });
86
- formCancelBtn.addEventListener("click", () => {
87
- formOverlay.classList.add("mw-hidden");
88
- });
89
- };
90
-
91
- const registerNewConversationListener = (widgetContainer: HTMLDivElement) => {
92
- const newBtn = widgetContainer.querySelector(".mw-new-conversation-btn") as HTMLButtonElement;
93
-
94
- newBtn.addEventListener("click", () => {
95
- const widget = window.getMWidget?.();
96
- if (widget) {
97
- widget.chat!.clear();
98
- }
99
- });
100
- };
101
-
102
- const registerFileUploadListener = (widgetContainer: HTMLDivElement) => {
103
- const fileUploadBtn = widgetContainer.querySelector(".mw-file-upload-btn") as HTMLButtonElement;
104
- const fileInput = widgetContainer.querySelector(".mw-file-input") as HTMLInputElement;
105
- const widget = window?.getMWidget?.();
106
-
107
- // Trigger file input when button is clicked
108
- fileUploadBtn.addEventListener("click", () => {
109
- if (widget?.chat.fileMaster.file) {
110
- // If a file is selected, remove it
111
- widget?.chat.fileMaster.clearFile();
112
- } else {
113
- // Otherwise, open file picker
114
- fileInput.click();
115
- }
116
- });
117
-
118
- // Handle file selection
119
- fileInput.addEventListener("change", () => {
120
- if (fileInput.files && fileInput.files.length > 0) {
121
- widget?.chat.fileMaster.setFile(fileInput.files[0]);
122
- }
123
- });
124
- };
125
-
126
- const registerReplyPreviewListener = (widgetContainer: HTMLDivElement) => {
127
- const replyPreview = widgetContainer.querySelector(".mw-reply-preview") as HTMLDivElement;
128
- const replyPreviewClose = widgetContainer.querySelector(".mw-reply-preview-close") as HTMLButtonElement;
129
- const replyPreviewInfo = widgetContainer.querySelector(".mw-reply-preview-info") as HTMLDivElement;
130
-
131
- if (!replyPreview || !replyPreviewClose || !replyPreviewInfo) return;
132
-
133
- // Close button - clear reply
134
- replyPreviewClose.addEventListener("click", e => {
135
- e.stopPropagation();
136
- const widget = window.getMWidget?.();
137
- if (widget?.chat) {
138
- widget.chat.replyMaster.clearReply();
139
- }
140
- });
141
-
142
- // Click on preview info - scroll to message
143
- replyPreviewInfo.addEventListener("click", () => {
144
- const widget = window.getMWidget?.();
145
- const replyingToEl = getMessageElement(widget?.chat.replyMaster.replyingTo! || {});
146
-
147
- if (replyingToEl) {
148
- // Scroll to the message
149
- const messagesContainer = widgetContainer.querySelector(".mw-chat-messages-con") as HTMLDivElement;
150
- if (messagesContainer) {
151
- replyingToEl.scrollIntoView({behavior: "smooth", block: "center"});
152
-
153
- // Add a highlight effect
154
- replyingToEl.classList.add("mw-message-highlight");
155
- setTimeout(() => {
156
- replyingToEl?.classList.remove("mw-message-highlight");
157
- }, 2000);
158
- }
159
- }
160
- });
161
- };
162
-
163
- const registerTooltipCloseListener = (widgetContainer: HTMLDivElement) => {
164
- const widget = window.getMWidget?.();
165
- const tooltipCloseBtn = widgetContainer.querySelector(".mw-toggle-tooltip-close") as HTMLButtonElement;
166
- const tooltip = widgetContainer.querySelector(".mw-toggle-tooltip") as HTMLDivElement;
167
- if (tooltipCloseBtn) {
168
- tooltipCloseBtn.addEventListener("click", e => {
169
- localStorage.setItem(`modochats:${widget?.publicKey}-has-seen-greeting-message`, "true");
170
- e.stopPropagation();
171
- if (tooltip) {
172
- tooltip.classList.add("mw-hidden");
173
- }
174
- });
175
- }
176
- };
177
-
178
- export {registerListeners, registerNewConversationListener};
@@ -1,77 +0,0 @@
1
- import {PhoneNumberRegex} from "#src/constants/regex.js";
2
-
3
- const sendMessage = async (message: string) => {
4
- if (message.trim().length) {
5
- if (checkIfUserHasPhoneNumber()) {
6
- const widget = window.getMWidget?.();
7
-
8
- if (widget) {
9
- const savedFile = widget.chat.fileMaster.file;
10
- const savedReply = widget.chat.replyMaster.replyingTo?.id;
11
- if (widget?.conversation?.d?.uuid) {
12
- const chatInput = widget.container?.querySelector(".mw-chat-input") as HTMLInputElement;
13
- if (chatInput) chatInput.value = "";
14
- }
15
- widget.chat.fileMaster.clearFile();
16
- widget.chat.replyMaster.clearReply();
17
- await widget?.chat.sendMessage(message, {file: savedFile, replyTo: savedReply});
18
- } else {
19
- console.error("Widget instance not found");
20
- }
21
- } else {
22
- throw new Error("User has not submitted the phone number form");
23
- }
24
- }
25
- };
26
-
27
- const checkIfUserHasPhoneNumber = () => {
28
- const widget = window.getMWidget?.();
29
- if (widget?.customerData?.hasSubmittedPhoneForm()) {
30
- // User has already submitted the phone number form (whether empty or with phone number)
31
- return true;
32
- } else {
33
- // Show phone number form
34
- switchToPhoneNumberFormView();
35
- return false;
36
- }
37
- };
38
-
39
- const switchToPhoneNumberFormView = () => {
40
- const formOverlay = window.getMWidget?.().container?.querySelector(".mw-form-overlay");
41
- if (formOverlay) {
42
- formOverlay.classList.remove("mw-hidden");
43
- formOverlay.classList.add("mw-active");
44
- }
45
- };
46
- const submitPhoneNumberForm = (phoneNumber: string) => {
47
- // Allow empty phone number or valid phone number
48
- const parsedPhoneNumber = phoneNumber.replace(/[۰-۹٠-٩]/g, ch => {
49
- const fa = "۰۱۲۳۴۵۶۷۸۹".indexOf(ch);
50
- if (fa > -1) return String(fa);
51
- const ar = "٠١٢٣٤٥٦٧٨٩".indexOf(ch);
52
- if (ar > -1) return String(ar);
53
- return ch;
54
- });
55
-
56
- if (parsedPhoneNumber.trim() === "" || PhoneNumberRegex.test(parsedPhoneNumber)) {
57
- const widget = window.getMWidget?.();
58
- if (widget) {
59
- // Update the phone number
60
- widget.customerData.savePhoneNumber(phoneNumber.trim() || undefined);
61
-
62
- const formOverlay = widget.container?.querySelector(".mw-form-overlay");
63
- if (formOverlay) {
64
- formOverlay.classList.remove("mw-active");
65
- formOverlay.classList.add("mw-hidden");
66
- }
67
-
68
- (widget.container?.querySelector(".mw-send-message-btn") as HTMLButtonElement)?.click();
69
- } else {
70
- console.error("Widget instance not found");
71
- }
72
- } else {
73
- alert("لطفا شماره تلفن معتبر وارد کنید یا فیلد را خالی بگذارید.");
74
- }
75
- };
76
-
77
- export {sendMessage, submitPhoneNumberForm};
@@ -1,9 +0,0 @@
1
- const onSocketConnectionUpdate = (connected: boolean) => {
2
- const widget = window.getMWidget?.();
3
- const connectionIndicator = widget?.container?.querySelector(".mw-connection-status");
4
-
5
- if (connectionIndicator) {
6
- connectionIndicator.className = `mw-connection-status ${connected ? "mw-connected" : "mw-disconnected"}`;
7
- }
8
- };
9
- export {onSocketConnectionUpdate};
@@ -1,254 +0,0 @@
1
- import {BASE_STORAGE_URL, isDev} from "#src/constants/index.js";
2
- import {ConversationStatus} from "#src/types/conversation.js";
3
-
4
- function switchToConversationLayout() {
5
- const widget = window.getMWidget?.();
6
- widget?.container?.querySelector(".mw-new-conversation-btn")?.classList.remove("mw-hidden");
7
- widget?.container?.querySelector(".mw-starters-con")?.classList.add("mw-hidden");
8
- }
9
- function switchToStarterLayout() {
10
- const widget = window.getMWidget?.();
11
- widget?.container?.querySelector(".mw-new-conversation-btn")?.classList.add("mw-hidden");
12
- widget?.container?.querySelector(".mw-starters-con")?.classList.remove("mw-hidden");
13
- widget?.container?.querySelector(".mw-conversation-status-icon")?.classList.add("mw-hidden");
14
- }
15
-
16
- function setConversationType(type: keyof typeof ConversationStatus) {
17
- const widget = window.getMWidget?.();
18
- widget?.container?.querySelector(".mw-conversation-status-icon")?.classList.remove("mw-hidden");
19
- const statusIcon = widget?.container?.querySelector(".mw-conversation-status-icon");
20
-
21
- if (statusIcon) {
22
- // Remove existing mode classes
23
- statusIcon.classList.remove("mw-ai-mode", "mw-human-mode");
24
-
25
- // Add appropriate mode class
26
- if (type === "AI_CHAT") {
27
- statusIcon.classList.add("mw-ai-mode");
28
- } else {
29
- statusIcon.classList.add("mw-human-mode");
30
- }
31
- }
32
- }
33
-
34
- function loadStarters() {
35
- const widget = window.getMWidget?.();
36
- const startersContainer = widget?.container?.querySelector(".mw-starters-con");
37
- const starterItemsContainer = widget?.container?.querySelector(".mw-starter-items");
38
-
39
- startersContainer?.classList.remove("mw-hidden");
40
-
41
- for (const starter of widget?.chatbot?.starters || []) {
42
- const starterElement = document.createElement("div");
43
- starterElement.className = "mw-starter-item";
44
- starterElement.textContent = starter;
45
- starterElement.addEventListener("click", async () => {
46
- const inputEl = widget?.container?.querySelector(".mw-chat-input") as HTMLInputElement;
47
- const sendMsgBtnEl = widget?.container?.querySelector(".mw-send-message-btn") as HTMLButtonElement;
48
- switchToConversationLayout();
49
- if (inputEl) {
50
- inputEl.value = starter;
51
- sendMsgBtnEl.click();
52
- }
53
- });
54
- starterItemsContainer?.appendChild(starterElement);
55
- }
56
- }
57
-
58
- function updateChatToggleImage() {
59
- const widget = window.getMWidget?.();
60
- const toggleImageEl = widget?.container?.querySelector(".mw-chat-toggle-image") as HTMLImageElement;
61
- const starterLogoEl = widget?.container?.querySelector(".mw-starter-logo") as HTMLImageElement;
62
-
63
- const defaultSvg =
64
- "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M12 2C13.1 2 14 2.9 14 4C14 5.1 13.1 6 12 6C10.9 6 10 5.1 10 4C10 2.9 10.9 2 12 2ZM21 9V7L15 1H5C3.89 1 3 1.89 3 3V21C3 22.1 3.9 23 5 23H11V21H5V3H13V9H21ZM23 18V16H15V18L19 22L15 26V28H23V26H19L23 22L19 18H23Z'/%3E%3C/svg%3E";
65
-
66
- // Update toggle button image
67
- if (toggleImageEl) {
68
- if (widget?.chatbot?.image) {
69
- toggleImageEl.src = widget.chatbot.image;
70
- toggleImageEl.alt = widget.chatbot.name || "شروع گفتگو";
71
-
72
- // Add error handling for failed image loads
73
- toggleImageEl.onerror = () => {
74
- toggleImageEl.src = defaultSvg;
75
- toggleImageEl.alt = "پشتیبانی چت";
76
- };
77
- } else {
78
- // Use default avatar if no image is provided
79
- toggleImageEl.src = defaultSvg;
80
- toggleImageEl.alt = "پشتیبانی چت";
81
- }
82
- }
83
-
84
- // Update starter logo
85
- if (starterLogoEl) {
86
- if (widget?.chatbot?.image) {
87
- starterLogoEl.src = widget.chatbot.image;
88
- starterLogoEl.alt = widget.chatbot.name || "لوگو چت بات";
89
- starterLogoEl.style.display = "block";
90
-
91
- // Add error handling for failed image loads
92
- starterLogoEl.onerror = () => {
93
- starterLogoEl.style.display = "none";
94
- };
95
- } else {
96
- // Hide logo if no image is provided
97
- starterLogoEl.style.display = "none";
98
- }
99
- }
100
- }
101
-
102
- function updateChatTitle() {
103
- const widget = window.getMWidget?.();
104
- const chatTitleEl = widget?.container?.querySelector(".mw-chat-title") as HTMLElement;
105
- const starterTitleEl = widget?.container?.querySelector(".mw-starter-title") as HTMLElement;
106
-
107
- if (chatTitleEl || starterTitleEl) {
108
- // Use options title if no chatbot name is available
109
- const displayTitle = widget?.options?.title || widget?.chatbot?.name || "Modo";
110
-
111
- if (chatTitleEl) {
112
- chatTitleEl.textContent = displayTitle;
113
- }
114
-
115
- if (starterTitleEl) {
116
- starterTitleEl.textContent = displayTitle;
117
- }
118
- }
119
- }
120
-
121
- function applyModoOptions() {
122
- const widget = window.getMWidget?.();
123
- if (!widget?.container || !widget?.options) return;
124
-
125
- const container = widget.container;
126
- const options = widget.options;
127
-
128
- // Apply position option
129
- applyPositionOption(container, options.position!);
130
-
131
- // Apply theme option
132
- applyThemeOption(container, options.theme!);
133
-
134
- // Apply primary color option
135
- applyPrimaryColorOption(container, options.primaryColor!);
136
-
137
- // Apply foreground color option
138
- applyForegroundColorOption(container, options.foregroundColor!);
139
- }
140
-
141
- function applyPositionOption(container: HTMLDivElement, position: "left" | "right") {
142
- const widget = window.getMWidget?.()?.container as HTMLElement;
143
- if (widget) {
144
- if (position === "left") {
145
- widget.style.right = "auto";
146
- widget.style.left = "32px";
147
- widget.style.direction = "ltr";
148
-
149
- // Update chat body position for left alignment
150
- const chatBody = widget.querySelector(".mw-chat-body") as HTMLElement;
151
- if (chatBody) {
152
- chatBody.style.right = "auto";
153
- chatBody.style.left = "0";
154
- }
155
- } else {
156
- // widget.style.left = "auto";
157
- // widget.style.right = "32px";
158
- // widget.style.direction = "rtl";
159
-
160
- // Update chat body position for right alignment
161
- const chatBody = widget.querySelector(".mw-chat-body") as HTMLElement;
162
- if (chatBody) {
163
- chatBody.style.left = "auto";
164
- chatBody.style.right = "0";
165
- }
166
- }
167
- }
168
- }
169
-
170
- function applyThemeOption(container: HTMLDivElement, theme: "dark" | "light") {
171
- // Set the theme attribute on the container or document
172
- const widget = document.querySelector(".modo-widget");
173
- if (theme === "light") {
174
- widget?.setAttribute("data-theme", "light");
175
- } else {
176
- widget?.removeAttribute("data-theme");
177
- }
178
-
179
- // Store theme preference
180
- localStorage.setItem("modo-component:theme", theme);
181
- }
182
-
183
- function applyPrimaryColorOption(container: HTMLDivElement, primaryColor: string) {
184
- // Create CSS custom properties for the primary color
185
- const root = document.querySelector(".modo-widget") as HTMLDivElement;
186
- if (root) {
187
- // Set the primary color
188
- root?.style.setProperty("--primary-color", primaryColor);
189
-
190
- // Generate hover color (slightly darker)
191
- const hoverColor = adjustColorBrightness(primaryColor, -20);
192
- root?.style.setProperty("--primary-hover", hoverColor);
193
-
194
- // Generate gradient using the primary color
195
- const gradientColor = adjustColorBrightness(primaryColor, 15);
196
- root?.style.setProperty("--primary-gradient", `linear-gradient(135deg, ${primaryColor} 0%, ${gradientColor} 100%)`);
197
- } else console.error("modo chat widget not found");
198
- }
199
-
200
- function applyForegroundColorOption(container: HTMLDivElement, foregroundColor: string) {
201
- // Create CSS custom properties for the foreground color
202
- const root = document.querySelector(".modo-widget") as HTMLDivElement;
203
- if (root) {
204
- // Set the foreground color
205
- root?.style.setProperty("--foreground-color", foregroundColor);
206
-
207
- // Set white color to use foreground color for white text elements
208
- root?.style.setProperty("--white", foregroundColor);
209
- } else console.error("modo chat widget not found");
210
- }
211
-
212
- function adjustColorBrightness(hex: string, percent: number): string {
213
- // Remove # if present
214
- hex = hex.replace(/^#/, "");
215
-
216
- // Parse r, g, b values
217
- const num = parseInt(hex, 16);
218
- const amt = Math.round(2.55 * percent);
219
- const R = (num >> 16) + amt;
220
- const G = ((num >> 8) & 0x00ff) + amt;
221
- const B = (num & 0x0000ff) + amt;
222
-
223
- // Ensure values stay within 0-255 range
224
- const newR = Math.max(0, Math.min(255, R));
225
- const newG = Math.max(0, Math.min(255, G));
226
- const newB = Math.max(0, Math.min(255, B));
227
-
228
- return `#${((newR << 16) | (newG << 8) | newB).toString(16).padStart(6, "0")}`;
229
- }
230
- async function loadCss() {
231
- return await new Promise(resolve => {
232
- const link = document.createElement("link");
233
- const source = isDev ? "/css/index.css" : `${BASE_STORAGE_URL}/index.css`;
234
- link.rel = "stylesheet";
235
- link.href = source;
236
- document.head.appendChild(link);
237
- link.addEventListener("load", () => {
238
- resolve("css loaded");
239
- });
240
- });
241
- }
242
- export {
243
- switchToConversationLayout,
244
- switchToStarterLayout,
245
- setConversationType,
246
- loadStarters,
247
- loadCss,
248
- updateChatToggleImage,
249
- updateChatTitle,
250
- applyModoOptions,
251
- applyPositionOption,
252
- applyThemeOption,
253
- applyPrimaryColorOption
254
- };