@yassirbenmoussa/aicommerce-sdk 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-commerce.min.js +224 -37
- package/dist/index.cjs +459 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +459 -13
- package/dist/index.mjs.map +1 -1
- package/dist/widget.min.js +223 -36
- package/dist/widget.min.js.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -189,6 +189,49 @@ var init_client = __esm({
|
|
|
189
189
|
}
|
|
190
190
|
return response;
|
|
191
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Send an audio message and get product recommendations
|
|
194
|
+
*
|
|
195
|
+
* @param audioBlob - Audio blob (from MediaRecorder or file input)
|
|
196
|
+
* @param context - Optional context for better recommendations
|
|
197
|
+
* @returns Chat response with AI reply and products
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* // Record audio using MediaRecorder
|
|
202
|
+
* const mediaRecorder = new MediaRecorder(stream);
|
|
203
|
+
* const chunks: Blob[] = [];
|
|
204
|
+
* mediaRecorder.ondataavailable = (e) => chunks.push(e.data);
|
|
205
|
+
* mediaRecorder.onstop = async () => {
|
|
206
|
+
* const audioBlob = new Blob(chunks, { type: 'audio/webm' });
|
|
207
|
+
* const response = await client.chatWithAudio(audioBlob);
|
|
208
|
+
* console.log(response.reply);
|
|
209
|
+
* };
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
async chatWithAudio(audioBlob, context) {
|
|
213
|
+
const arrayBuffer = await audioBlob.arrayBuffer();
|
|
214
|
+
const base64 = btoa(
|
|
215
|
+
new Uint8Array(arrayBuffer).reduce(
|
|
216
|
+
(data, byte) => data + String.fromCharCode(byte),
|
|
217
|
+
""
|
|
218
|
+
)
|
|
219
|
+
);
|
|
220
|
+
const request = {
|
|
221
|
+
audioBase64: base64,
|
|
222
|
+
audioMimeType: audioBlob.type || "audio/webm",
|
|
223
|
+
context,
|
|
224
|
+
sessionToken: this.sessionToken || void 0
|
|
225
|
+
};
|
|
226
|
+
const response = await this.request("/api/v1/chat", {
|
|
227
|
+
method: "POST",
|
|
228
|
+
body: JSON.stringify(request)
|
|
229
|
+
});
|
|
230
|
+
if (response.sessionToken) {
|
|
231
|
+
this.sessionToken = response.sessionToken;
|
|
232
|
+
}
|
|
233
|
+
return response;
|
|
234
|
+
}
|
|
192
235
|
/**
|
|
193
236
|
* Create a new chat session
|
|
194
237
|
*
|
|
@@ -550,15 +593,24 @@ function createWidgetStyles(config) {
|
|
|
550
593
|
/* Product Cards */
|
|
551
594
|
.aicommerce-products {
|
|
552
595
|
display: flex;
|
|
553
|
-
gap:
|
|
596
|
+
gap: 16px;
|
|
554
597
|
margin-top: 12px;
|
|
555
598
|
overflow-x: auto;
|
|
556
|
-
padding-bottom:
|
|
599
|
+
padding-bottom: 16px;
|
|
600
|
+
width: 100%;
|
|
601
|
+
max-width: 100%;
|
|
602
|
+
cursor: grab;
|
|
603
|
+
user-select: none;
|
|
604
|
+
-webkit-user-select: none;
|
|
605
|
+
scrollbar-width: none; /* Firefox */
|
|
606
|
+
}
|
|
607
|
+
.aicommerce-products::-webkit-scrollbar {
|
|
608
|
+
display: none; /* Chrome/Safari */
|
|
557
609
|
}
|
|
558
610
|
|
|
559
611
|
.aicommerce-product-card {
|
|
560
612
|
flex-shrink: 0;
|
|
561
|
-
width:
|
|
613
|
+
width: 280px;
|
|
562
614
|
background: var(--aic-bg);
|
|
563
615
|
border-radius: 12px;
|
|
564
616
|
overflow: hidden;
|
|
@@ -574,13 +626,15 @@ function createWidgetStyles(config) {
|
|
|
574
626
|
|
|
575
627
|
.aicommerce-product-image {
|
|
576
628
|
width: 100%;
|
|
577
|
-
|
|
629
|
+
aspect-ratio: 16/9;
|
|
630
|
+
height: auto;
|
|
578
631
|
object-fit: cover;
|
|
579
632
|
}
|
|
580
633
|
|
|
581
634
|
.aicommerce-product-placeholder {
|
|
582
635
|
width: 100%;
|
|
583
|
-
|
|
636
|
+
aspect-ratio: 16/9;
|
|
637
|
+
height: auto;
|
|
584
638
|
background: var(--aic-bg-secondary);
|
|
585
639
|
display: flex;
|
|
586
640
|
align-items: center;
|
|
@@ -610,6 +664,109 @@ function createWidgetStyles(config) {
|
|
|
610
664
|
color: var(--aic-primary);
|
|
611
665
|
}
|
|
612
666
|
|
|
667
|
+
.aicommerce-product-desc {
|
|
668
|
+
font-size: 12px;
|
|
669
|
+
color: var(--aic-text-secondary);
|
|
670
|
+
line-height: 1.4;
|
|
671
|
+
display: -webkit-box;
|
|
672
|
+
-webkit-line-clamp: 2;
|
|
673
|
+
-webkit-box-orient: vertical;
|
|
674
|
+
overflow: hidden;
|
|
675
|
+
margin-top: 4px;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/* Audio Player */
|
|
679
|
+
.aicommerce-audio-player {
|
|
680
|
+
display: flex;
|
|
681
|
+
align-items: center;
|
|
682
|
+
gap: 12px;
|
|
683
|
+
min-width: 240px;
|
|
684
|
+
padding: 4px 0;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.aicommerce-audio-btn {
|
|
688
|
+
width: 40px;
|
|
689
|
+
height: 40px;
|
|
690
|
+
border-radius: 50%;
|
|
691
|
+
display: flex;
|
|
692
|
+
align-items: center;
|
|
693
|
+
justify-content: center;
|
|
694
|
+
border: none;
|
|
695
|
+
cursor: pointer;
|
|
696
|
+
transition: all 0.2s;
|
|
697
|
+
background: rgba(255, 255, 255, 0.25);
|
|
698
|
+
color: white;
|
|
699
|
+
flex-shrink: 0;
|
|
700
|
+
padding: 0;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.aicommerce-audio-btn:hover {
|
|
704
|
+
background: rgba(255, 255, 255, 0.35);
|
|
705
|
+
transform: scale(1.05);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
.aicommerce-audio-btn:active {
|
|
709
|
+
transform: scale(0.95);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/* Invert colors for assistant (since background is white/gray) */
|
|
713
|
+
.aicommerce-assistant .aicommerce-audio-btn {
|
|
714
|
+
background: var(--aic-primary);
|
|
715
|
+
color: white;
|
|
716
|
+
}
|
|
717
|
+
.aicommerce-assistant .aicommerce-audio-btn:hover {
|
|
718
|
+
background: var(--aic-primary-dark);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
.aicommerce-audio-waveform {
|
|
722
|
+
flex: 1;
|
|
723
|
+
display: flex;
|
|
724
|
+
flex-direction: column;
|
|
725
|
+
gap: 6px;
|
|
726
|
+
min-width: 0; /* Prevent overflow */
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
.aicommerce-waveform-bars {
|
|
730
|
+
display: flex;
|
|
731
|
+
align-items: center;
|
|
732
|
+
gap: 2px;
|
|
733
|
+
height: 24px;
|
|
734
|
+
cursor: pointer;
|
|
735
|
+
width: 100%;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.aicommerce-waveform-bar {
|
|
739
|
+
width: 3px;
|
|
740
|
+
border-radius: 2px;
|
|
741
|
+
min-height: 3px;
|
|
742
|
+
transition: background-color 0.1s;
|
|
743
|
+
flex-shrink: 0;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.aicommerce-audio-time {
|
|
747
|
+
display: flex;
|
|
748
|
+
justify-content: space-between;
|
|
749
|
+
font-size: 11px;
|
|
750
|
+
font-weight: 500;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
.aicommerce-user .aicommerce-audio-time {
|
|
754
|
+
color: rgba(255, 255, 255, 0.8);
|
|
755
|
+
}
|
|
756
|
+
.aicommerce-assistant .aicommerce-audio-time {
|
|
757
|
+
color: var(--aic-text-secondary);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/* RTL Support */
|
|
761
|
+
.aicommerce-rtl {
|
|
762
|
+
direction: rtl;
|
|
763
|
+
text-align: right;
|
|
764
|
+
}
|
|
765
|
+
.aicommerce-ltr {
|
|
766
|
+
direction: ltr;
|
|
767
|
+
text-align: left;
|
|
768
|
+
}
|
|
769
|
+
|
|
613
770
|
/* Input Area */
|
|
614
771
|
.aicommerce-input-container {
|
|
615
772
|
padding: 16px 20px;
|
|
@@ -664,6 +821,44 @@ function createWidgetStyles(config) {
|
|
|
664
821
|
cursor: not-allowed;
|
|
665
822
|
}
|
|
666
823
|
|
|
824
|
+
/* Microphone Button */
|
|
825
|
+
.aicommerce-mic {
|
|
826
|
+
width: 44px;
|
|
827
|
+
height: 44px;
|
|
828
|
+
border-radius: 50%;
|
|
829
|
+
background: var(--aic-bg-secondary);
|
|
830
|
+
border: 1px solid var(--aic-border);
|
|
831
|
+
color: var(--aic-text-secondary);
|
|
832
|
+
cursor: pointer;
|
|
833
|
+
display: flex;
|
|
834
|
+
align-items: center;
|
|
835
|
+
justify-content: center;
|
|
836
|
+
transition: all 0.2s;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
.aicommerce-mic:hover:not(:disabled) {
|
|
840
|
+
background: var(--aic-primary-light);
|
|
841
|
+
border-color: var(--aic-primary);
|
|
842
|
+
color: var(--aic-primary);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
.aicommerce-mic.aicommerce-recording {
|
|
846
|
+
background: #ef4444;
|
|
847
|
+
border-color: #ef4444;
|
|
848
|
+
color: white;
|
|
849
|
+
animation: aic-recording-pulse 1s infinite;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.aicommerce-mic:disabled {
|
|
853
|
+
opacity: 0.6;
|
|
854
|
+
cursor: not-allowed;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
@keyframes aic-recording-pulse {
|
|
858
|
+
0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
|
|
859
|
+
50% { transform: scale(1.05); box-shadow: 0 0 0 8px rgba(239, 68, 68, 0); }
|
|
860
|
+
}
|
|
861
|
+
|
|
667
862
|
/* Mobile Responsive */
|
|
668
863
|
@media (max-width: 420px) {
|
|
669
864
|
#aicommerce-widget {
|
|
@@ -735,9 +930,12 @@ function createWidget(config) {
|
|
|
735
930
|
const state = {
|
|
736
931
|
isOpen: false,
|
|
737
932
|
isLoading: true,
|
|
933
|
+
isRecording: false,
|
|
738
934
|
messages: [],
|
|
739
935
|
storeConfig: null
|
|
740
936
|
};
|
|
937
|
+
let mediaRecorder = null;
|
|
938
|
+
let audioChunks = [];
|
|
741
939
|
let container = null;
|
|
742
940
|
let styleElement = null;
|
|
743
941
|
let resolvedConfig;
|
|
@@ -820,20 +1018,26 @@ function createWidget(config) {
|
|
|
820
1018
|
</div>
|
|
821
1019
|
|
|
822
1020
|
<div class="aicommerce-messages">
|
|
823
|
-
${state.messages.map((msg) =>
|
|
1021
|
+
${state.messages.map((msg, index) => {
|
|
1022
|
+
const isRtl = isArabic(msg.content);
|
|
1023
|
+
const isUser = msg.role === "user";
|
|
1024
|
+
return `
|
|
824
1025
|
<div class="aicommerce-message aicommerce-${msg.role}">
|
|
825
|
-
<div class="aicommerce-message-content"
|
|
1026
|
+
<div class="aicommerce-message-content ${isRtl ? "aicommerce-rtl" : "aicommerce-ltr"}">
|
|
1027
|
+
${msg.audioUrl ? renderAudioPlayer(msg, index, isUser) : escapeHtml(msg.content)}
|
|
1028
|
+
</div>
|
|
826
1029
|
${msg.products && msg.products.length > 0 ? `
|
|
827
1030
|
<div class="aicommerce-products">
|
|
828
1031
|
${msg.products.map((product) => `
|
|
829
1032
|
<div class="aicommerce-product-card" data-product-id="${product.id}">
|
|
830
|
-
${product.imageUrl ? `
|
|
831
|
-
<img src="${product.imageUrl}" alt="${escapeHtml(product.name)}" class="aicommerce-product-image" />
|
|
1033
|
+
${product.image || product.imageUrl ? `
|
|
1034
|
+
<img src="${product.image || product.imageUrl}" alt="${escapeHtml(product.name)}" class="aicommerce-product-image" />
|
|
832
1035
|
` : `
|
|
833
1036
|
<div class="aicommerce-product-placeholder">\u{1F4E6}</div>
|
|
834
1037
|
`}
|
|
835
1038
|
<div class="aicommerce-product-info">
|
|
836
|
-
<span class="aicommerce-product-name">${escapeHtml(product.name)}</span>
|
|
1039
|
+
<span class="aicommerce-product-name" title="${escapeHtml(product.name)}">${escapeHtml(product.name)}</span>
|
|
1040
|
+
${product.description ? `<p class="aicommerce-product-desc">${escapeHtml(product.description)}</p>` : ""}
|
|
837
1041
|
<span class="aicommerce-product-price">${formatPrice(product.price, product.currency)}</span>
|
|
838
1042
|
</div>
|
|
839
1043
|
</div>
|
|
@@ -841,7 +1045,8 @@ function createWidget(config) {
|
|
|
841
1045
|
</div>
|
|
842
1046
|
` : ""}
|
|
843
1047
|
</div>
|
|
844
|
-
|
|
1048
|
+
`;
|
|
1049
|
+
}).join("")}
|
|
845
1050
|
${state.isLoading ? `
|
|
846
1051
|
<div class="aicommerce-message aicommerce-assistant">
|
|
847
1052
|
<div class="aicommerce-typing">
|
|
@@ -856,9 +1061,23 @@ function createWidget(config) {
|
|
|
856
1061
|
type="text"
|
|
857
1062
|
class="aicommerce-input"
|
|
858
1063
|
placeholder="Type your message..."
|
|
859
|
-
${state.isLoading ? "disabled" : ""}
|
|
1064
|
+
${state.isLoading || state.isRecording ? "disabled" : ""}
|
|
860
1065
|
/>
|
|
861
|
-
<button class="aicommerce-
|
|
1066
|
+
<button class="aicommerce-mic ${state.isRecording ? "aicommerce-recording" : ""}" ${state.isLoading ? "disabled" : ""} aria-label="${state.isRecording ? "Stop recording" : "Voice input"}">
|
|
1067
|
+
${state.isRecording ? `
|
|
1068
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
1069
|
+
<rect x="6" y="6" width="12" height="12" rx="2"/>
|
|
1070
|
+
</svg>
|
|
1071
|
+
` : `
|
|
1072
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1073
|
+
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
|
|
1074
|
+
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
|
|
1075
|
+
<line x1="12" y1="19" x2="12" y2="23"/>
|
|
1076
|
+
<line x1="8" y1="23" x2="16" y2="23"/>
|
|
1077
|
+
</svg>
|
|
1078
|
+
`}
|
|
1079
|
+
</button>
|
|
1080
|
+
<button class="aicommerce-send" ${state.isLoading || state.isRecording ? "disabled" : ""} aria-label="Send message">
|
|
862
1081
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
863
1082
|
<path d="M22 2L11 13M22 2L15 22L11 13M22 2L2 9L11 13"/>
|
|
864
1083
|
</svg>
|
|
@@ -873,6 +1092,27 @@ function createWidget(config) {
|
|
|
873
1092
|
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
874
1093
|
}
|
|
875
1094
|
}
|
|
1095
|
+
function renderAudioPlayer(msg, index, isUser) {
|
|
1096
|
+
return `
|
|
1097
|
+
<div class="aicommerce-audio-player" data-message-index="${index}">
|
|
1098
|
+
<button class="aicommerce-audio-btn">
|
|
1099
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
|
|
1100
|
+
</button>
|
|
1101
|
+
<div class="aicommerce-audio-waveform">
|
|
1102
|
+
<div class="aicommerce-waveform-bars">
|
|
1103
|
+
${(msg.waveformBars || Array(40).fill(10)).map((height) => `
|
|
1104
|
+
<div class="aicommerce-waveform-bar" style="height: ${height}%; background-color: ${isUser ? "rgba(255,255,255,0.4)" : "rgba(99,102,241,0.3)"}"></div>
|
|
1105
|
+
`).join("")}
|
|
1106
|
+
</div>
|
|
1107
|
+
<div class="aicommerce-audio-time">
|
|
1108
|
+
<span class="aicommerce-current-time">0:00</span>
|
|
1109
|
+
<span>${formatTime(msg.audioDuration || 0)}</span>
|
|
1110
|
+
</div>
|
|
1111
|
+
</div>
|
|
1112
|
+
<audio src="${msg.audioUrl}" preload="metadata"></audio>
|
|
1113
|
+
</div>
|
|
1114
|
+
`;
|
|
1115
|
+
}
|
|
876
1116
|
function attachEventListeners() {
|
|
877
1117
|
if (!container) return;
|
|
878
1118
|
const launcherEl = container.querySelector(".aicommerce-launcher");
|
|
@@ -901,6 +1141,10 @@ function createWidget(config) {
|
|
|
901
1141
|
}
|
|
902
1142
|
});
|
|
903
1143
|
}
|
|
1144
|
+
const micEl = container.querySelector(".aicommerce-mic");
|
|
1145
|
+
if (micEl) {
|
|
1146
|
+
micEl.addEventListener("click", () => handleMicClick());
|
|
1147
|
+
}
|
|
904
1148
|
const productCards = container.querySelectorAll(".aicommerce-product-card");
|
|
905
1149
|
productCards.forEach((card) => {
|
|
906
1150
|
card.addEventListener("click", () => {
|
|
@@ -911,6 +1155,208 @@ function createWidget(config) {
|
|
|
911
1155
|
}
|
|
912
1156
|
});
|
|
913
1157
|
});
|
|
1158
|
+
const sliders = container.querySelectorAll(".aicommerce-products");
|
|
1159
|
+
sliders.forEach((slider) => {
|
|
1160
|
+
let isDown = false;
|
|
1161
|
+
let startX = 0;
|
|
1162
|
+
let scrollLeft = 0;
|
|
1163
|
+
slider.addEventListener("mousedown", (e) => {
|
|
1164
|
+
isDown = true;
|
|
1165
|
+
slider.style.cursor = "grabbing";
|
|
1166
|
+
startX = e.pageX - slider.offsetLeft;
|
|
1167
|
+
scrollLeft = slider.scrollLeft;
|
|
1168
|
+
});
|
|
1169
|
+
slider.addEventListener("mouseleave", () => {
|
|
1170
|
+
isDown = false;
|
|
1171
|
+
slider.style.cursor = "grab";
|
|
1172
|
+
});
|
|
1173
|
+
slider.addEventListener("mouseup", () => {
|
|
1174
|
+
isDown = false;
|
|
1175
|
+
slider.style.cursor = "grab";
|
|
1176
|
+
});
|
|
1177
|
+
slider.addEventListener("mousemove", (e) => {
|
|
1178
|
+
if (!isDown) return;
|
|
1179
|
+
e.preventDefault();
|
|
1180
|
+
const x = e.pageX - slider.offsetLeft;
|
|
1181
|
+
const walk = (x - startX) * 2;
|
|
1182
|
+
slider.scrollLeft = scrollLeft - walk;
|
|
1183
|
+
});
|
|
1184
|
+
});
|
|
1185
|
+
const audioPlayers = container.querySelectorAll(".aicommerce-audio-player");
|
|
1186
|
+
audioPlayers.forEach((player) => {
|
|
1187
|
+
const audio = player.querySelector("audio");
|
|
1188
|
+
const btn = player.querySelector(".aicommerce-audio-btn");
|
|
1189
|
+
const bars = player.querySelectorAll(".aicommerce-waveform-bar");
|
|
1190
|
+
const timeDisplay = player.querySelector(".aicommerce-current-time");
|
|
1191
|
+
if (!audio || !btn) return;
|
|
1192
|
+
btn.addEventListener("click", () => {
|
|
1193
|
+
const isPlaying = !audio.paused;
|
|
1194
|
+
if (!isPlaying) {
|
|
1195
|
+
container?.querySelectorAll("audio").forEach((a) => {
|
|
1196
|
+
if (a !== audio && !a.paused) {
|
|
1197
|
+
a.pause();
|
|
1198
|
+
const parent = a.closest(".aicommerce-audio-player");
|
|
1199
|
+
const otherBtn = parent?.querySelector(".aicommerce-audio-btn");
|
|
1200
|
+
if (otherBtn) otherBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>`;
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
audio.play();
|
|
1204
|
+
btn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>`;
|
|
1205
|
+
} else {
|
|
1206
|
+
audio.pause();
|
|
1207
|
+
btn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>`;
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
audio.addEventListener("timeupdate", () => {
|
|
1211
|
+
if (timeDisplay) timeDisplay.textContent = formatTime(audio.currentTime);
|
|
1212
|
+
if (audio.duration) {
|
|
1213
|
+
const progress = audio.currentTime / audio.duration * 100;
|
|
1214
|
+
bars.forEach((bar, i) => {
|
|
1215
|
+
const barPos = i / bars.length * 100;
|
|
1216
|
+
if (barPos <= progress) {
|
|
1217
|
+
bar.style.backgroundColor = player.closest(".aicommerce-user") ? "rgba(255,255,255,1)" : "var(--aic-primary)";
|
|
1218
|
+
} else {
|
|
1219
|
+
bar.style.backgroundColor = player.closest(".aicommerce-user") ? "rgba(255,255,255,0.4)" : "rgba(99,102,241,0.3)";
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
});
|
|
1224
|
+
audio.addEventListener("ended", () => {
|
|
1225
|
+
btn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>`;
|
|
1226
|
+
});
|
|
1227
|
+
const waveform = player.querySelector(".aicommerce-waveform-bars");
|
|
1228
|
+
if (waveform) {
|
|
1229
|
+
waveform.addEventListener("click", (e) => {
|
|
1230
|
+
const rect = waveform.getBoundingClientRect();
|
|
1231
|
+
const x = e.clientX - rect.left;
|
|
1232
|
+
const percent = x / rect.width;
|
|
1233
|
+
if (audio.duration) {
|
|
1234
|
+
audio.currentTime = percent * audio.duration;
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
async function handleMicClick() {
|
|
1241
|
+
if (state.isRecording) {
|
|
1242
|
+
if (mediaRecorder && mediaRecorder.state !== "inactive") {
|
|
1243
|
+
mediaRecorder.stop();
|
|
1244
|
+
}
|
|
1245
|
+
} else {
|
|
1246
|
+
try {
|
|
1247
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
1248
|
+
audioChunks = [];
|
|
1249
|
+
mediaRecorder = new MediaRecorder(stream, {
|
|
1250
|
+
mimeType: MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4"
|
|
1251
|
+
});
|
|
1252
|
+
mediaRecorder.ondataavailable = (e) => {
|
|
1253
|
+
if (e.data.size > 0) {
|
|
1254
|
+
audioChunks.push(e.data);
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
mediaRecorder.onstop = async () => {
|
|
1258
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
1259
|
+
if (audioChunks.length > 0) {
|
|
1260
|
+
const audioBlob = new Blob(audioChunks, { type: mediaRecorder?.mimeType || "audio/webm" });
|
|
1261
|
+
await handleAudioSend(audioBlob);
|
|
1262
|
+
}
|
|
1263
|
+
state.isRecording = false;
|
|
1264
|
+
render();
|
|
1265
|
+
};
|
|
1266
|
+
mediaRecorder.start();
|
|
1267
|
+
state.isRecording = true;
|
|
1268
|
+
render();
|
|
1269
|
+
} catch (error) {
|
|
1270
|
+
console.error("Failed to start recording:", error);
|
|
1271
|
+
state.messages.push({
|
|
1272
|
+
role: "assistant",
|
|
1273
|
+
content: "Unable to access microphone. Please check your permissions."
|
|
1274
|
+
});
|
|
1275
|
+
render();
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
async function handleAudioSend(audioBlob) {
|
|
1280
|
+
const audioUrl = URL.createObjectURL(audioBlob);
|
|
1281
|
+
let waveformBars = Array(40).fill(10);
|
|
1282
|
+
let audioDuration = 0;
|
|
1283
|
+
try {
|
|
1284
|
+
waveformBars = await analyzeAudio(audioBlob);
|
|
1285
|
+
const audio = new Audio(audioUrl);
|
|
1286
|
+
await new Promise((resolve) => {
|
|
1287
|
+
audio.onloadedmetadata = () => {
|
|
1288
|
+
audioDuration = audio.duration;
|
|
1289
|
+
resolve();
|
|
1290
|
+
};
|
|
1291
|
+
audio.onerror = () => resolve();
|
|
1292
|
+
});
|
|
1293
|
+
} catch (e) {
|
|
1294
|
+
console.error("Audio analysis failed", e);
|
|
1295
|
+
}
|
|
1296
|
+
state.messages.push({
|
|
1297
|
+
role: "user",
|
|
1298
|
+
content: "Voice message",
|
|
1299
|
+
audioUrl,
|
|
1300
|
+
audioDuration,
|
|
1301
|
+
waveformBars
|
|
1302
|
+
});
|
|
1303
|
+
state.isLoading = true;
|
|
1304
|
+
render();
|
|
1305
|
+
try {
|
|
1306
|
+
const response = await client.chatWithAudio(audioBlob);
|
|
1307
|
+
state.messages.push({
|
|
1308
|
+
role: "assistant",
|
|
1309
|
+
content: response.reply,
|
|
1310
|
+
products: response.products
|
|
1311
|
+
});
|
|
1312
|
+
if (resolvedConfig.onMessage) {
|
|
1313
|
+
resolvedConfig.onMessage("Voice message", response);
|
|
1314
|
+
}
|
|
1315
|
+
return response;
|
|
1316
|
+
} catch (error) {
|
|
1317
|
+
state.messages.push({
|
|
1318
|
+
role: "assistant",
|
|
1319
|
+
content: "Sorry, I encountered an error processing your voice message. Please try again."
|
|
1320
|
+
});
|
|
1321
|
+
throw error;
|
|
1322
|
+
} finally {
|
|
1323
|
+
state.isLoading = false;
|
|
1324
|
+
render();
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
function isArabic(text) {
|
|
1328
|
+
return /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/.test(text);
|
|
1329
|
+
}
|
|
1330
|
+
function formatTime(seconds) {
|
|
1331
|
+
const mins = Math.floor(seconds / 60);
|
|
1332
|
+
const secs = Math.floor(seconds % 60);
|
|
1333
|
+
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
1334
|
+
}
|
|
1335
|
+
async function analyzeAudio(blob) {
|
|
1336
|
+
try {
|
|
1337
|
+
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
1338
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
1339
|
+
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
1340
|
+
const channelData = audioBuffer.getChannelData(0);
|
|
1341
|
+
const bars = 40;
|
|
1342
|
+
const step = Math.floor(channelData.length / bars);
|
|
1343
|
+
const calculatedBars = [];
|
|
1344
|
+
for (let i = 0; i < bars; i++) {
|
|
1345
|
+
const start = i * step;
|
|
1346
|
+
const end = start + step;
|
|
1347
|
+
let sum = 0;
|
|
1348
|
+
for (let j = start; j < end; j++) {
|
|
1349
|
+
if (channelData[j]) sum += channelData[j] * channelData[j];
|
|
1350
|
+
}
|
|
1351
|
+
const rms = Math.sqrt(sum / step);
|
|
1352
|
+
const height = Math.min(100, Math.max(10, rms * 400));
|
|
1353
|
+
calculatedBars.push(height);
|
|
1354
|
+
}
|
|
1355
|
+
return calculatedBars;
|
|
1356
|
+
} catch (e) {
|
|
1357
|
+
console.error("Analysis error", e);
|
|
1358
|
+
return Array.from({ length: 40 }, () => 20 + Math.random() * 60);
|
|
1359
|
+
}
|
|
914
1360
|
}
|
|
915
1361
|
async function handleSend(message) {
|
|
916
1362
|
state.messages.push({ role: "user", content: message });
|