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