@modochats/widget 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/types/src/app.d.ts +30 -0
- package/dist/types/src/app.d.ts.map +1 -0
- package/dist/types/src/constants/index.d.ts +10 -0
- package/dist/types/src/constants/index.d.ts.map +1 -0
- package/dist/types/src/constants/regex.d.ts +3 -0
- package/dist/types/src/constants/regex.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +10 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/models/chatbot.d.ts +24 -0
- package/dist/types/src/models/chatbot.d.ts.map +1 -0
- package/dist/types/src/models/conversation.d.ts +23 -0
- package/dist/types/src/models/conversation.d.ts.map +1 -0
- package/dist/types/src/models/customer-data.d.ts +32 -0
- package/dist/types/src/models/customer-data.d.ts.map +1 -0
- package/dist/types/src/models/message-utils.d.ts +13 -0
- package/dist/types/src/models/message-utils.d.ts.map +1 -0
- package/dist/types/src/services/chat/conversation.d.ts +23 -0
- package/dist/types/src/services/chat/conversation.d.ts.map +1 -0
- package/dist/types/src/services/chat/message-utils.d.ts +13 -0
- package/dist/types/src/services/chat/message-utils.d.ts.map +1 -0
- package/dist/types/src/services/chat/model.d.ts +28 -0
- package/dist/types/src/services/chat/model.d.ts.map +1 -0
- package/dist/types/src/services/chatbot/chatbot.d.ts +24 -0
- package/dist/types/src/services/chatbot/chatbot.d.ts.map +1 -0
- package/dist/types/src/services/checker.d.ts +4 -0
- package/dist/types/src/services/checker.d.ts.map +1 -0
- package/dist/types/src/services/listeners/adders.d.ts +4 -0
- package/dist/types/src/services/listeners/adders.d.ts.map +1 -0
- package/dist/types/src/services/listeners/fn.d.ts +4 -0
- package/dist/types/src/services/listeners/fn.d.ts.map +1 -0
- package/dist/types/src/services/socket/utils.d.ts +3 -0
- package/dist/types/src/services/socket/utils.d.ts.map +1 -0
- package/dist/types/src/services/ui/fn.d.ts +14 -0
- package/dist/types/src/services/ui/fn.d.ts.map +1 -0
- package/dist/types/src/services/ui/html.d.ts +4 -0
- package/dist/types/src/services/ui/html.d.ts.map +1 -0
- package/dist/types/src/services/user/customer-data.d.ts +32 -0
- package/dist/types/src/services/user/customer-data.d.ts.map +1 -0
- package/dist/types/src/services/voice-chat/model.d.ts +13 -0
- package/dist/types/src/services/voice-chat/model.d.ts.map +1 -0
- package/dist/types/src/services/voice-chat/utils.d.ts +10 -0
- package/dist/types/src/services/voice-chat/utils.d.ts.map +1 -0
- package/dist/types/src/tools/fetch.d.ts +3 -0
- package/dist/types/src/tools/fetch.d.ts.map +1 -0
- package/dist/types/src/types/app.d.ts +18 -0
- package/dist/types/src/types/app.d.ts.map +1 -0
- package/dist/types/src/types/conversation.d.ts +15 -0
- package/dist/types/src/types/conversation.d.ts.map +1 -0
- package/dist/types/src/types/socket.d.ts +7 -0
- package/dist/types/src/types/socket.d.ts.map +1 -0
- package/dist/types/src/types/window.d.ts +10 -0
- package/dist/types/src/types/window.d.ts.map +1 -0
- package/dist/types/src/utils/audio.d.ts +4 -0
- package/dist/types/src/utils/audio.d.ts.map +1 -0
- package/dist/types/src/utils/browser.d.ts +3 -0
- package/dist/types/src/utils/browser.d.ts.map +1 -0
- package/dist/types/src/utils/fetch.d.ts +19 -0
- package/dist/types/src/utils/fetch.d.ts.map +1 -0
- package/dist/types/src/utils/uuid.d.ts +7 -0
- package/dist/types/src/utils/uuid.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/app.ts +117 -0
- package/src/constants/index.ts +21 -0
- package/src/constants/regex.ts +2 -0
- package/src/index.ts +16 -0
- package/src/services/chat/conversation.ts +135 -0
- package/src/services/chat/message-utils.ts +221 -0
- package/src/services/chat/model.ts +139 -0
- package/src/services/chatbot/chatbot.ts +66 -0
- package/src/services/checker.ts +10 -0
- package/src/services/listeners/adders.ts +178 -0
- package/src/services/listeners/fn.ts +77 -0
- package/src/services/socket/utils.ts +9 -0
- package/src/services/ui/fn.ts +254 -0
- package/src/services/ui/html.ts +192 -0
- package/src/services/user/customer-data.ts +78 -0
- package/src/services/voice-chat/model.ts +79 -0
- package/src/services/voice-chat/utils.ts +137 -0
- package/src/tools/fetch.ts +7 -0
- package/src/types/app.ts +17 -0
- package/src/types/conversation.ts +14 -0
- package/src/types/socket.ts +7 -0
- package/src/types/window.ts +12 -0
- package/src/utils/audio.ts +67 -0
- package/src/utils/browser.ts +4 -0
- package/src/utils/fetch.ts +98 -0
- package/src/utils/uuid.ts +13 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import {Widget} from "#src/app.js";
|
|
2
|
+
import {registerListeners} from "../listeners/adders.js";
|
|
3
|
+
|
|
4
|
+
const createChatContainer = (widget: Widget) => {
|
|
5
|
+
widget.container = document.createElement("div");
|
|
6
|
+
widget.container.textContent = "Start Chat";
|
|
7
|
+
widget.container.classList.add("modo-widget");
|
|
8
|
+
|
|
9
|
+
// Add fullscreen class if fullscreen mode is enabled
|
|
10
|
+
if (widget.options.fullScreen) {
|
|
11
|
+
widget.container.classList.add("mw-fullscreen");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
document.body.appendChild(widget.container);
|
|
15
|
+
let conBody = document.createElement("div");
|
|
16
|
+
widget.container.appendChild(conBody);
|
|
17
|
+
widget.container.innerHTML = `
|
|
18
|
+
<div dir="rtl" class="mw-chat-inner">
|
|
19
|
+
<div class="mw-chat-body ${widget.options.fullScreen ? "mw-active" : "mw-hidden"}">
|
|
20
|
+
<div class="mw-chat-container">
|
|
21
|
+
<!-- Chat Header -->
|
|
22
|
+
<div class="mw-chat-header">
|
|
23
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
24
|
+
<h3 class="mw-chat-title">پشتیبانی چت</h3>
|
|
25
|
+
<div class="mw-conversation-status-icon mw-hidden">
|
|
26
|
+
<!-- Clean AI/Bot icon -->
|
|
27
|
+
<svg class="mw-ai-chat-icon" style="width: 14px; height: 14px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><!-- Icon from Material Design Icons by Pictogrammers - https://github.com/Templarian/MaterialDesign/blob/master/LICENSE --><path fill="currentColor" d="M22 14h-1c0-3.87-3.13-7-7-7h-1V5.73A2 2 0 1 0 10 4c0 .74.4 1.39 1 1.73V7h-1c-3.87 0-7 3.13-7 7H2c-.55 0-1 .45-1 1v3c0 .55.45 1 1 1h1v1a2 2 0 0 0 2 2h14c1.11 0 2-.89 2-2v-1h1c.55 0 1-.45 1-1v-3c0-.55-.45-1-1-1m-1 3h-2v3H5v-3H3v-1h2v-2c0-2.76 2.24-5 5-5h4c2.76 0 5 2.24 5 5v2h2zM8.5 13.5l2.36 2.36l-1.18 1.18l-1.18-1.18l-1.18 1.18l-1.18-1.18zm7 0l2.36 2.36l-1.18 1.18l-1.18-1.18l-1.18 1.18l-1.18-1.18z"/></svg>
|
|
28
|
+
<!-- Clean Human/Person icon -->
|
|
29
|
+
<svg class="mw-human-chat-icon" viewBox="0 0 24 24" width="18" height="18">
|
|
30
|
+
<path fill="currentColor" d="M12 4C13.66 4 15 5.34 15 7C15 8.66 13.66 10 12 10C10.34 10 9 8.66 9 7C9 5.34 10.34 4 12 4ZM12 12C15.31 12 18 13.34 18 15V18H6V15C6 13.34 8.69 12 12 12Z"/>
|
|
31
|
+
</svg>
|
|
32
|
+
<div class="mw-tooltip">
|
|
33
|
+
<span class="mw-tooltip-text-ai">چت بات هوشمند</span>
|
|
34
|
+
<span class="mw-tooltip-text-human">پشتیبان انسانی</span>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="mw-connection-status mw-disconnected"></div>
|
|
38
|
+
</div>
|
|
39
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
40
|
+
<button class="mw-new-conversation-btn mw-hidden">
|
|
41
|
+
+
|
|
42
|
+
</button>
|
|
43
|
+
<button class="mw-voice-call-btn mw-hidden">
|
|
44
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Google Material Icons by Material Design Authors - https://github.com/material-icons/material-icons/blob/master/LICENSE --><path fill="currentColor" d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24c1.12.37 2.33.57 3.57.57c.55 0 1 .45 1 1V20c0 .55-.45 1-1 1c-9.39 0-17-7.61-17-17c0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1c0 1.25.2 2.45.57 3.57c.11.35.03.74-.25 1.02z"/></svg>
|
|
45
|
+
<div class="mw-voice-call-tooltip mw-hidden">
|
|
46
|
+
<div class="mw-voice-call-tooltip-text">مکالمه با هوش مصنوعی</div>
|
|
47
|
+
</div>
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="mw-chat-messages-con">
|
|
53
|
+
</div>
|
|
54
|
+
<div class="mw-starters-con">
|
|
55
|
+
<div class="mw-starter-welcome">
|
|
56
|
+
<img class="mw-starter-logo" src="" alt="لوگو چت بات" style="display: none;">
|
|
57
|
+
<h2 class="mw-starter-title">پشتیبانی چت</h2>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="mw-starter-items">
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="mw-reply-preview mw-hidden">
|
|
64
|
+
<div class="mw-reply-preview-content">
|
|
65
|
+
<div class="mw-reply-preview-info">
|
|
66
|
+
<span class="mw-reply-preview-label">پاسخ به:</span>
|
|
67
|
+
<span class="mw-reply-preview-text"></span>
|
|
68
|
+
</div>
|
|
69
|
+
<button class="mw-reply-preview-close" title="لغو پاسخ">
|
|
70
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
71
|
+
<path fill="currentColor" d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12L19 6.41Z"/>
|
|
72
|
+
</svg>
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div class="mw-chat-input-area">
|
|
78
|
+
<input type="text" placeholder="پیام خود را تایپ کنید..." class="mw-chat-input">
|
|
79
|
+
<button class="mw-file-upload-btn" title="آپلود فایل">
|
|
80
|
+
<input type="file" class="mw-file-input" hidden />
|
|
81
|
+
<svg class="mw-file-upload-icon" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols Light by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="currentColor" d="M16.346 11.385V6.769h1v4.616zm-5.538 5.457q-.452-.269-.726-.734q-.274-.466-.274-1.031V6.769h1zM11.96 21q-2.271 0-3.846-1.595t-1.575-3.867v-8.73q0-1.587 1.09-2.697Q8.722 3 10.309 3t2.678 1.11t1.091 2.698V14h-1V6.789q-.006-1.166-.802-1.977T10.308 4q-1.163 0-1.966.821q-.804.821-.804 1.987v8.73q-.005 1.853 1.283 3.157Q10.11 20 11.961 20q.556 0 1.056-.124t.945-.372v1.11q-.468.2-.972.293q-.505.093-1.03.093m4.386-1v-2.616h-2.615v-1h2.615V13.77h1v2.615h2.616v1h-2.616V20z"/></svg>
|
|
82
|
+
<svg class="mw-file-remove-icon mw-hidden" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols Light by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="currentColor" d="M11.962 21q-2.273 0-3.848-1.594t-1.575-3.867V7.954L2.091 3.508L2.8 2.8l18.4 18.4l-.708.708l-3.805-3.806q-.664 1.298-1.913 2.098t-2.812.8M7.539 8.954v6.584q-.006 1.852 1.282 3.157T11.961 20q1.356 0 2.413-.727t1.574-1.91l-1.98-1.98q-.087.742-.656 1.295q-.568.553-1.35.553q-.881 0-1.518-.627q-.636-.627-.636-1.527v-3.854zm3.269 3.269v2.854q0 .479.328.816q.328.338.806.338q.474 0 .801-.335t.334-.808v-.596zm5.538 1.33V6.77h1v7.804zm-3.269-3.307V6.79q-.006-1.166-.805-1.977T10.308 4q-.708 0-1.281.32q-.573.319-.961.857l-.714-.713q.529-.68 1.285-1.072T10.307 3q1.587 0 2.679 1.11t1.091 2.698v4.458zm-2.27-3.477v1.189l-1-1.02V6.77z"/></svg>
|
|
83
|
+
</button>
|
|
84
|
+
<button class="mw-send-message-btn" data-is-loading="false">
|
|
85
|
+
<svg class="mw-send-icon" viewBox="0 0 24 24">
|
|
86
|
+
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
|
|
87
|
+
</svg>
|
|
88
|
+
<span class="mw-btn-loading">
|
|
89
|
+
<svg class="mw-loading-spinner" viewBox="0 0 24 24">
|
|
90
|
+
<circle class="mw-spinner-circle" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle>
|
|
91
|
+
<path class="mw-spinner-path" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
92
|
+
</svg>
|
|
93
|
+
</span>
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div class="mw-form-overlay mw-hidden">
|
|
98
|
+
<div class="mw-form-content">
|
|
99
|
+
<h3 class="mw-form-title">اطلاعات تماس</h3>
|
|
100
|
+
<p class="mw-form-subtitle">لطفا برای اطلاع رسانی بهتر پیام ها شماره خود را وارد کنید (اختیاری)</p>
|
|
101
|
+
<div class="mw-form-input-area">
|
|
102
|
+
<input type="tel" placeholder="شماره تلفن (اختیاری)" class="mw-phone-input">
|
|
103
|
+
</div>
|
|
104
|
+
<div class="mw-form-buttons">
|
|
105
|
+
<button class="mw-form-submit-btn">
|
|
106
|
+
ارسال
|
|
107
|
+
</button>
|
|
108
|
+
<button class="mw-form-cancel-btn">
|
|
109
|
+
لغو
|
|
110
|
+
</button>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<!-- Chat Footer -->
|
|
116
|
+
<div class="mw-chat-footer">
|
|
117
|
+
<span class="mw-footer-text">ساخته شده با </span>
|
|
118
|
+
<a href="" class="mw-footer-link" target="_blank" rel="noopener noreferrer" title="">مودوچت</a>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<!-- Voice Agent Overlay -->
|
|
122
|
+
<div class="mw-voice-agent-overlay mw-hidden">
|
|
123
|
+
<div class="mw-voice-agent-content">
|
|
124
|
+
<button class="mw-voice-close-btn">
|
|
125
|
+
<svg viewBox="0 0 24 24" width="24" height="24">
|
|
126
|
+
<path fill="currentColor" d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12L19 6.41Z"/>
|
|
127
|
+
</svg>
|
|
128
|
+
</button>
|
|
129
|
+
|
|
130
|
+
<div class="mw-voice-agent-center">
|
|
131
|
+
<img class="mw-voice-agent-logo" src="" alt="چت بات" />
|
|
132
|
+
<h2 class="mw-voice-agent-title">تماس صوتی</h2>
|
|
133
|
+
<p class="mw-voice-agent-status">درحال اتصال...</p>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div class="mw-voice-agent-controls">
|
|
137
|
+
<button class="mw-voice-disconnect-btn" title="قطع تماس">
|
|
138
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><!-- Icon from Phosphor by Phosphor Icons - https://github.com/phosphor-icons/core/blob/main/LICENSE --><path fill="currentColor" d="M231.59 90.13C175.44 34 80.56 34 24.41 90.13c-20 20-21.92 49.49-4.69 71.71A16 16 0 0 0 32.35 168a15.8 15.8 0 0 0 5.75-1.08l49-17.37l.29-.11a16 16 0 0 0 9.75-11.73l5.9-29.52a76.52 76.52 0 0 1 49.68-.11l6.21 29.75a16 16 0 0 0 9.72 11.59l.29.11l49 17.39a16 16 0 0 0 18.38-5.06c17.19-22.24 15.26-51.73-4.73-71.73M223.67 152l-.3-.12l-48.82-17.33l-6.21-29.74A16 16 0 0 0 158 93a92.56 92.56 0 0 0-60.34.13a16 16 0 0 0-10.32 12l-5.9 29.51l-48.81 17.22c-.1 0-.17.13-.27.17c-12.33-15.91-11-36.23 3.36-50.58c25-25 58.65-37.53 92.28-37.53s67.27 12.51 92.28 37.53c14.33 14.35 15.72 34.67 3.39 50.55m.32 48a8 8 0 0 1-8 8H40a8 8 0 0 1 0-16h176a8 8 0 0 1 8 8Z"/></svg>
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
${
|
|
146
|
+
!widget.options.fullScreen
|
|
147
|
+
? `
|
|
148
|
+
<button class="mw-toggle-chat-btn">
|
|
149
|
+
<img
|
|
150
|
+
class="mw-chat-toggle-image"
|
|
151
|
+
src=""
|
|
152
|
+
alt="شروع گفتگو" />
|
|
153
|
+
<svg
|
|
154
|
+
class="mw-chat-toggle-close"
|
|
155
|
+
viewBox="0 0 24 24"
|
|
156
|
+
width="24"
|
|
157
|
+
height="24">
|
|
158
|
+
<path
|
|
159
|
+
fill="currentColor"
|
|
160
|
+
d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12L19 6.41Z" />
|
|
161
|
+
</svg>
|
|
162
|
+
<!-- Badge for unread messages -->
|
|
163
|
+
<div class="mw-badge mw-hidden">
|
|
164
|
+
<span class="mw-badge-text">0</span>
|
|
165
|
+
</div>
|
|
166
|
+
<!-- Tooltip for toggle button -->
|
|
167
|
+
<div class="mw-toggle-tooltip mw-hidden">
|
|
168
|
+
<div class="mw-tooltip-inner">
|
|
169
|
+
<div
|
|
170
|
+
class="mw-toggle-tooltip-close"
|
|
171
|
+
title="بستن">
|
|
172
|
+
<svg
|
|
173
|
+
viewBox="0 0 24 24"
|
|
174
|
+
width="16"
|
|
175
|
+
height="16">
|
|
176
|
+
<path
|
|
177
|
+
fill="currentColor"
|
|
178
|
+
d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12L19 6.41Z" />
|
|
179
|
+
</svg>
|
|
180
|
+
</div>
|
|
181
|
+
<span class="mw-toggle-tooltip-text">شروع گفتگو</span>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
</button>
|
|
185
|
+
`
|
|
186
|
+
: ""
|
|
187
|
+
}
|
|
188
|
+
</div>
|
|
189
|
+
`;
|
|
190
|
+
registerListeners(widget.container);
|
|
191
|
+
};
|
|
192
|
+
export {createChatContainer};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {Widget} from "#src/app.js";
|
|
2
|
+
import {fetchUpdateUserData} from "#src/utils/fetch.js";
|
|
3
|
+
import {generateUUID} from "#src/utils/uuid.js";
|
|
4
|
+
|
|
5
|
+
class CustomerData {
|
|
6
|
+
private _uniqueId?: string;
|
|
7
|
+
private _userData?: Record<string, any>;
|
|
8
|
+
private widget: Widget;
|
|
9
|
+
phoneNumber?: string;
|
|
10
|
+
|
|
11
|
+
constructor(widget: Widget, userData?: Record<string, any>) {
|
|
12
|
+
this.widget = widget;
|
|
13
|
+
this.initializeUniqueId();
|
|
14
|
+
this.updateUserData(userData);
|
|
15
|
+
this.initializePhoneNumber();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
initializePhoneNumber() {
|
|
19
|
+
const savedPhoneNumber = localStorage.getItem(`modo-chat:${this.widget.publicKey}-user-phone-number`);
|
|
20
|
+
if (savedPhoneNumber) {
|
|
21
|
+
this.phoneNumber = savedPhoneNumber;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Initialize unique ID from localStorage or generate a new one
|
|
27
|
+
* Unique ID is independent of user key/data and is generated by ourselves
|
|
28
|
+
*/
|
|
29
|
+
private initializeUniqueId(): void {
|
|
30
|
+
const savedUniqueId = localStorage.getItem(`modo-chat:${this.widget.publicKey}-user-unique-id`);
|
|
31
|
+
|
|
32
|
+
if (savedUniqueId) {
|
|
33
|
+
this._uniqueId = savedUniqueId;
|
|
34
|
+
} else {
|
|
35
|
+
// Generate a new UUID if no saved unique ID exists
|
|
36
|
+
this._uniqueId = crypto.randomUUID ? crypto.randomUUID() : generateUUID();
|
|
37
|
+
localStorage.setItem(`modo-chat:${this.widget.publicKey}-user-unique-id`, this._uniqueId);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get the current unique ID
|
|
43
|
+
*/
|
|
44
|
+
get uniqueId(): string {
|
|
45
|
+
return this._uniqueId!;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get the current user data
|
|
50
|
+
*/
|
|
51
|
+
get userData(): Record<string, any> {
|
|
52
|
+
return this._userData || {};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Update user data with new values
|
|
57
|
+
* @param newUserData - Object containing new user data to merge
|
|
58
|
+
*/
|
|
59
|
+
async updateUserData(newUserData?: Record<string, any>): Promise<void> {
|
|
60
|
+
if (newUserData && typeof newUserData === "object") {
|
|
61
|
+
this._userData = newUserData;
|
|
62
|
+
} else if (newUserData) console.warn("Invalid user data");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
hasSubmittedPhoneForm(): boolean {
|
|
66
|
+
return Boolean(this.phoneNumber);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
savePhoneNumber(phoneNumber?: string) {
|
|
70
|
+
this.phoneNumber = phoneNumber || "no phone number";
|
|
71
|
+
localStorage.setItem(`modo-chat:${this.widget.publicKey}-user-phone-number`, phoneNumber || "no phone number");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async fetchUpdate() {
|
|
75
|
+
await fetchUpdateUserData(this.widget.chatbot?.uuid as string, this.uniqueId, this.userData);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export {CustomerData};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {VoiceClient, EventType} from "@modochats/voice-client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
initVoiceChatLayout,
|
|
5
|
+
handleVoiceConnected,
|
|
6
|
+
handleVoiceDisconnected,
|
|
7
|
+
handleVoiceConnectionError,
|
|
8
|
+
handleMicrophonePaused,
|
|
9
|
+
handleMicrophoneResumed
|
|
10
|
+
} from "./utils.js";
|
|
11
|
+
|
|
12
|
+
class VoiceChat {
|
|
13
|
+
instance?: VoiceClient;
|
|
14
|
+
isFirstInSession: boolean = true;
|
|
15
|
+
constructor() {
|
|
16
|
+
const widget = window.getMWidget?.();
|
|
17
|
+
this.instance = new VoiceClient({
|
|
18
|
+
apiBase: "https://live.modochats.com",
|
|
19
|
+
// apiBase: "http://localhost:8000",
|
|
20
|
+
chatbotUuid: widget?.chatbot?.uuid as string,
|
|
21
|
+
userUniqueId: widget?.customerData.uniqueId as string
|
|
22
|
+
});
|
|
23
|
+
this.instance.on(EventType.CONNECTED, (event: any) => {
|
|
24
|
+
handleVoiceConnected();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
this.instance.on(EventType.DISCONNECTED, (event: any) => {
|
|
28
|
+
if (event.reason) {
|
|
29
|
+
}
|
|
30
|
+
handleVoiceDisconnected(event.reason);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
this.instance.on(EventType.CONNECTION_ERROR, (event: any) => {
|
|
34
|
+
// console.error("🔴 Connection Error:", event.message);
|
|
35
|
+
handleVoiceConnectionError(event.message);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
this.instance.on(EventType.MICROPHONE_PAUSED, () => {
|
|
39
|
+
handleMicrophonePaused();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
this.instance.on(EventType.MICROPHONE_RESUMED, () => {
|
|
43
|
+
handleMicrophoneResumed();
|
|
44
|
+
});
|
|
45
|
+
// Initialize the voice agent UI
|
|
46
|
+
this.initHtml();
|
|
47
|
+
|
|
48
|
+
// session check
|
|
49
|
+
const hasSeen = sessionStorage.getItem("modochats:voice-agent-seen") === "true";
|
|
50
|
+
if (hasSeen) this.isFirstInSession = false;
|
|
51
|
+
else sessionStorage.setItem("modochats:voice-agent-seen", "true");
|
|
52
|
+
if (this.isFirstInSession) this.showTooltip();
|
|
53
|
+
}
|
|
54
|
+
async connect() {
|
|
55
|
+
try {
|
|
56
|
+
await this.instance?.connect();
|
|
57
|
+
} catch (error) {
|
|
58
|
+
// console.error("Failed to connect:", error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async disconnect() {
|
|
62
|
+
await this.instance?.disconnect();
|
|
63
|
+
}
|
|
64
|
+
initHtml() {
|
|
65
|
+
initVoiceChatLayout();
|
|
66
|
+
}
|
|
67
|
+
toggleLayout() {
|
|
68
|
+
this.toggleLayout();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
showTooltip() {
|
|
72
|
+
const tooltip = document.querySelector(".mw-voice-call-tooltip");
|
|
73
|
+
tooltip?.classList.remove("mw-hidden");
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
tooltip?.classList.add("mw-hidden");
|
|
76
|
+
}, 6000);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export {VoiceChat};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
function toggleVoiceChatLayout() {
|
|
2
|
+
const widget = window.getMWidget?.();
|
|
3
|
+
const voiceOverlay = widget?.container?.querySelector(".mw-voice-agent-overlay");
|
|
4
|
+
|
|
5
|
+
if (voiceOverlay) {
|
|
6
|
+
voiceOverlay.classList.toggle("mw-active");
|
|
7
|
+
voiceOverlay.classList.toggle("mw-hidden");
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function initVoiceChatLayout() {
|
|
12
|
+
const widget = window.getMWidget?.();
|
|
13
|
+
const voiceOverlay = widget?.container?.querySelector(".mw-voice-agent-overlay");
|
|
14
|
+
const voiceCloseBtn = voiceOverlay?.querySelector(".mw-voice-close-btn");
|
|
15
|
+
const voiceDisconnectBtn = voiceOverlay?.querySelector(".mw-voice-disconnect-btn");
|
|
16
|
+
const voiceCallBtn = widget?.container?.querySelector(".mw-voice-call-btn");
|
|
17
|
+
|
|
18
|
+
// Show voice call button
|
|
19
|
+
if (voiceCallBtn) {
|
|
20
|
+
voiceCallBtn.classList.remove("mw-hidden");
|
|
21
|
+
voiceCallBtn.classList.add("mw-visible");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Set logo from chatbot data
|
|
25
|
+
const logoImg = voiceOverlay?.querySelector(".mw-voice-agent-logo") as HTMLImageElement;
|
|
26
|
+
if (logoImg && widget?.chatbot?.image) {
|
|
27
|
+
logoImg.src = widget.chatbot.image;
|
|
28
|
+
logoImg.alt = widget.chatbot.name || "چت بات";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Set title
|
|
32
|
+
const titleEl = voiceOverlay?.querySelector(".mw-voice-agent-title") as HTMLElement;
|
|
33
|
+
if (titleEl) {
|
|
34
|
+
titleEl.textContent = widget?.chatbot?.name || "تماس صوتی";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Call button click handler
|
|
38
|
+
voiceCallBtn?.addEventListener("click", () => {
|
|
39
|
+
if (voiceOverlay) {
|
|
40
|
+
voiceOverlay.classList.remove("mw-hidden");
|
|
41
|
+
voiceOverlay.classList.add("mw-active");
|
|
42
|
+
// Connect to voice instance
|
|
43
|
+
widget?.voiceChat?.connect();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Close button click handler
|
|
48
|
+
voiceCloseBtn?.addEventListener("click", () => {
|
|
49
|
+
if (voiceOverlay) {
|
|
50
|
+
voiceOverlay.classList.remove("mw-active");
|
|
51
|
+
voiceOverlay.classList.add("mw-hidden");
|
|
52
|
+
// Disconnect from voice instance
|
|
53
|
+
widget?.voiceChat?.disconnect();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Disconnect button click handler
|
|
58
|
+
voiceDisconnectBtn?.addEventListener("click", () => {
|
|
59
|
+
if (voiceOverlay) {
|
|
60
|
+
voiceOverlay.classList.remove("mw-active");
|
|
61
|
+
voiceOverlay.classList.add("mw-hidden");
|
|
62
|
+
// Disconnect from voice instance
|
|
63
|
+
widget?.voiceChat?.disconnect();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function updateVoiceChatStatus(status: string, color?: string) {
|
|
69
|
+
const widget = window.getMWidget?.();
|
|
70
|
+
const statusEl = widget?.container?.querySelector(".mw-voice-agent-status") as HTMLElement;
|
|
71
|
+
|
|
72
|
+
if (statusEl) {
|
|
73
|
+
statusEl.textContent = status;
|
|
74
|
+
if (color) {
|
|
75
|
+
statusEl.style.color = color;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function handleVoiceConnected() {
|
|
81
|
+
const widget = window.getMWidget?.();
|
|
82
|
+
const logoEl = widget?.container?.querySelector(".mw-voice-agent-logo") as HTMLElement;
|
|
83
|
+
const statusEl = widget?.container?.querySelector(".mw-voice-agent-status") as HTMLElement;
|
|
84
|
+
|
|
85
|
+
// Add animation classes when connected
|
|
86
|
+
if (logoEl) {
|
|
87
|
+
logoEl.style.animation = "mw-voice-pulse 2s ease-in-out infinite";
|
|
88
|
+
}
|
|
89
|
+
if (statusEl) {
|
|
90
|
+
statusEl.style.animation = "mw-pulse 1.5s ease-in-out infinite";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
updateVoiceChatStatus("متصل ✓", "#68d391"); // Green
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function handleVoiceDisconnected(reason?: string) {
|
|
97
|
+
const widget = window.getMWidget?.();
|
|
98
|
+
const logoEl = widget?.container?.querySelector(".mw-voice-agent-logo") as HTMLElement;
|
|
99
|
+
const statusEl = widget?.container?.querySelector(".mw-voice-agent-status") as HTMLElement;
|
|
100
|
+
|
|
101
|
+
// Remove animations when disconnected
|
|
102
|
+
if (logoEl) {
|
|
103
|
+
logoEl.style.animation = "none";
|
|
104
|
+
}
|
|
105
|
+
if (statusEl) {
|
|
106
|
+
statusEl.style.animation = "none";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const statusText = reason ? `قطع شد: ${reason}` : "قطع شد";
|
|
110
|
+
updateVoiceChatStatus(statusText, "#fc8181"); // Red
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function handleVoiceConnectionError(message: string) {
|
|
114
|
+
updateVoiceChatStatus(`خطا: ${message}`, "#fbb040"); // Warning/Orange
|
|
115
|
+
|
|
116
|
+
// Also show error in console with better visibility
|
|
117
|
+
console.error("🔴 Voice Connection Error:", message);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function handleMicrophonePaused() {
|
|
121
|
+
updateVoiceChatStatus("⏸ میکروفن متوقف شد", "#fbb040"); // Orange
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function handleMicrophoneResumed() {
|
|
125
|
+
updateVoiceChatStatus("🎤 میکروفن فعال", "#68d391"); // Green
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
toggleVoiceChatLayout,
|
|
130
|
+
initVoiceChatLayout,
|
|
131
|
+
updateVoiceChatStatus,
|
|
132
|
+
handleVoiceConnected,
|
|
133
|
+
handleVoiceDisconnected,
|
|
134
|
+
handleVoiceConnectionError,
|
|
135
|
+
handleMicrophonePaused,
|
|
136
|
+
handleMicrophoneResumed
|
|
137
|
+
};
|
package/src/types/app.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface WidgetOptions {
|
|
2
|
+
position: "left" | "right";
|
|
3
|
+
theme: "dark" | "light";
|
|
4
|
+
primaryColor: string;
|
|
5
|
+
title: string;
|
|
6
|
+
foregroundColor: string;
|
|
7
|
+
userData?: Record<string, any>;
|
|
8
|
+
autoInit?: boolean;
|
|
9
|
+
fullScreen: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface FetchPaginationRes<T = any> {
|
|
12
|
+
results: T[];
|
|
13
|
+
next: string | null;
|
|
14
|
+
prev: string | null;
|
|
15
|
+
count: number;
|
|
16
|
+
}
|
|
17
|
+
export {WidgetOptions, FetchPaginationRes};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Cache for preloaded audio elements
|
|
2
|
+
const audioCache = new Map<string, HTMLAudioElement>();
|
|
3
|
+
|
|
4
|
+
const preloadAudio = (audioPath: string): Promise<HTMLAudioElement> => {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
if (audioCache.has(audioPath)) {
|
|
7
|
+
resolve(audioCache.get(audioPath)!);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const audioElement = new Audio(audioPath);
|
|
12
|
+
audioElement.volume = 0.5;
|
|
13
|
+
audioElement.preload = "auto";
|
|
14
|
+
|
|
15
|
+
audioElement.addEventListener("canplaythrough", () => {
|
|
16
|
+
audioCache.set(audioPath, audioElement);
|
|
17
|
+
resolve(audioElement);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
audioElement.addEventListener("error", error => {
|
|
21
|
+
reject(error);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Start loading
|
|
25
|
+
audioElement.load();
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const playAudio = async (audioPath: string) => {
|
|
30
|
+
try {
|
|
31
|
+
// Try to get preloaded audio or create new one
|
|
32
|
+
let audioElement = audioCache.get(audioPath);
|
|
33
|
+
|
|
34
|
+
if (!audioElement) {
|
|
35
|
+
audioElement = new Audio(audioPath);
|
|
36
|
+
audioElement.volume = 0.5;
|
|
37
|
+
audioElement.preload = "auto";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Play the audio
|
|
41
|
+
await audioElement.play();
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.warn("Failed to play audio:", error);
|
|
44
|
+
// Fallback: try to play a simple beep sound using Web Audio API
|
|
45
|
+
try {
|
|
46
|
+
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
|
|
47
|
+
const oscillator = audioContext.createOscillator();
|
|
48
|
+
const gainNode = audioContext.createGain();
|
|
49
|
+
|
|
50
|
+
oscillator.connect(gainNode);
|
|
51
|
+
gainNode.connect(audioContext.destination);
|
|
52
|
+
|
|
53
|
+
oscillator.frequency.setValueAtTime(800, audioContext.currentTime);
|
|
54
|
+
oscillator.type = "sine";
|
|
55
|
+
|
|
56
|
+
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
|
|
57
|
+
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
|
|
58
|
+
|
|
59
|
+
oscillator.start(audioContext.currentTime);
|
|
60
|
+
oscillator.stop(audioContext.currentTime + 0.5);
|
|
61
|
+
} catch (fallbackError) {
|
|
62
|
+
console.warn("Audio fallback also failed:", fallbackError);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export {playAudio, preloadAudio};
|