@stellartech/voice-widget-directus 1.0.2 → 1.0.3
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/index.js +284 -105
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -455,7 +455,7 @@ const VOICE_MODELS = [
|
|
|
455
455
|
{ id: "elevenlabs", name: "ElevenLabs" }
|
|
456
456
|
];
|
|
457
457
|
const SAMPLE_TEXT = "Hello! This is a sample of my voice. I hope you enjoy listening to how I sound.";
|
|
458
|
-
const DEFAULT_FLOW_ID = "
|
|
458
|
+
const DEFAULT_FLOW_ID = "7fa08903-ed7d-4632-81fc-f422d873b8f8";
|
|
459
459
|
function useVoicingApi(api) {
|
|
460
460
|
async function fetchVoices(collection = "Voices") {
|
|
461
461
|
try {
|
|
@@ -525,7 +525,7 @@ function useVoicingApi(api) {
|
|
|
525
525
|
return [];
|
|
526
526
|
}
|
|
527
527
|
}
|
|
528
|
-
async function generateVoiceSample(voiceId, provider, flowId = DEFAULT_FLOW_ID) {
|
|
528
|
+
async function generateVoiceSample(voiceId, provider, flowId = DEFAULT_FLOW_ID, lessonId, collection) {
|
|
529
529
|
const effectiveFlowId = flowId && flowId.trim() ? flowId.trim() : DEFAULT_FLOW_ID;
|
|
530
530
|
if (!effectiveFlowId) {
|
|
531
531
|
throw new Error("Voice flow ID is not configured. Set it in Widget Config or in the Flow ID field.");
|
|
@@ -535,7 +535,16 @@ function useVoicingApi(api) {
|
|
|
535
535
|
provider,
|
|
536
536
|
preprocessing: false,
|
|
537
537
|
title: `Voice Sample - ${voiceId}`,
|
|
538
|
-
audio_files_collection: "AudioFiles"
|
|
538
|
+
audio_files_collection: "AudioFiles",
|
|
539
|
+
// Include lesson context for callback tracking
|
|
540
|
+
lesson_id: lessonId || null,
|
|
541
|
+
collection: collection || "SM_Lessons",
|
|
542
|
+
voice_config: {
|
|
543
|
+
provider,
|
|
544
|
+
voice_id: voiceId,
|
|
545
|
+
style: "neutral",
|
|
546
|
+
is_sample: true
|
|
547
|
+
}
|
|
539
548
|
};
|
|
540
549
|
if (provider === "gemini") {
|
|
541
550
|
voicingPayload.speakers = [{ voice: voiceId, name: "Sample", style: "neutral" }];
|
|
@@ -566,7 +575,8 @@ function useVoicingApi(api) {
|
|
|
566
575
|
const url = await resolveAudioFileUrl(audioFileId);
|
|
567
576
|
return { url, audioFileId };
|
|
568
577
|
}
|
|
569
|
-
|
|
578
|
+
console.log("[Voice Widget] Sample processing async, callback will update Voices.example");
|
|
579
|
+
return { url: "", audioFileId: "" };
|
|
570
580
|
} catch (e) {
|
|
571
581
|
console.error("Failed to generate voice sample:", e);
|
|
572
582
|
throw new Error(e.response?.data?.detail || e.message || "Failed to generate sample");
|
|
@@ -672,17 +682,30 @@ function useVoicingApi(api) {
|
|
|
672
682
|
return `/assets/${audioFilesRecordId}`;
|
|
673
683
|
}
|
|
674
684
|
async function fetchVoiceVariants(lessonId) {
|
|
685
|
+
const lessonIdStr = String(lessonId);
|
|
686
|
+
console.log("[Voice Widget] ========== fetchVoiceVariants START ==========");
|
|
687
|
+
console.log("[Voice Widget] Lesson ID:", lessonIdStr);
|
|
688
|
+
console.log("[Voice Widget] API object exists:", !!api);
|
|
675
689
|
try {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
690
|
+
console.log('[Voice Widget] Calling api.get("/items/VoiceVariants")...');
|
|
691
|
+
const response = await api.get("/items/VoiceVariants");
|
|
692
|
+
console.log("[Voice Widget] Response status:", response?.status);
|
|
693
|
+
console.log("[Voice Widget] Response data type:", typeof response?.data);
|
|
694
|
+
console.log("[Voice Widget] Response.data.data type:", typeof response?.data?.data);
|
|
695
|
+
const allData = response.data?.data || response.data || [];
|
|
696
|
+
console.log("[Voice Widget] Extracted data length:", allData?.length);
|
|
697
|
+
if (allData.length > 0) {
|
|
698
|
+
console.log("[Voice Widget] First item:", JSON.stringify(allData[0]));
|
|
699
|
+
}
|
|
700
|
+
const filtered = allData.filter((v) => String(v.lesson_id) === lessonIdStr);
|
|
701
|
+
console.log("[Voice Widget] Filtered count:", filtered.length);
|
|
702
|
+
console.log("[Voice Widget] ========== fetchVoiceVariants END ==========");
|
|
703
|
+
return filtered;
|
|
684
704
|
} catch (error) {
|
|
685
|
-
console.error("
|
|
705
|
+
console.error("[Voice Widget] ========== fetchVoiceVariants ERROR ==========");
|
|
706
|
+
console.error("[Voice Widget] Error:", error);
|
|
707
|
+
console.error("[Voice Widget] Error message:", error?.message);
|
|
708
|
+
console.error("[Voice Widget] Error response:", error?.response?.status, error?.response?.data);
|
|
686
709
|
return [];
|
|
687
710
|
}
|
|
688
711
|
}
|
|
@@ -699,13 +722,13 @@ function useVoicingApi(api) {
|
|
|
699
722
|
console.warn("[Voice Widget] Could not get file UUID from AudioFiles record. fileField:", fileField);
|
|
700
723
|
return;
|
|
701
724
|
}
|
|
702
|
-
console.log(`[Voice Widget] Patching ${collection}/${voiceId} with
|
|
725
|
+
console.log(`[Voice Widget] Patching ${collection}/${voiceId} with example=${fileUuid}`);
|
|
703
726
|
await api.patch(`/items/${collection}/${voiceId}`, {
|
|
704
|
-
|
|
727
|
+
example: fileUuid
|
|
705
728
|
});
|
|
706
|
-
console.log(`[Voice Widget] Updated voice ${voiceId}
|
|
729
|
+
console.log(`[Voice Widget] Updated voice ${voiceId} example to ${fileUuid}`);
|
|
707
730
|
} catch (error) {
|
|
708
|
-
console.error("[Voice Widget] Failed to update voice
|
|
731
|
+
console.error("[Voice Widget] Failed to update voice example:", error);
|
|
709
732
|
}
|
|
710
733
|
}
|
|
711
734
|
async function deleteVoiceVariant(variantId) {
|
|
@@ -768,6 +791,59 @@ function useVoicingApi(api) {
|
|
|
768
791
|
return null;
|
|
769
792
|
}
|
|
770
793
|
}
|
|
794
|
+
async function setVoiceExampleStatus(voiceId, status, collection = "Voices") {
|
|
795
|
+
try {
|
|
796
|
+
await api.patch(`/items/${collection}/${voiceId}`, {
|
|
797
|
+
example_status: status
|
|
798
|
+
});
|
|
799
|
+
console.log(`[Voice Widget] Set voice ${voiceId} example_status to ${status}`);
|
|
800
|
+
} catch (error) {
|
|
801
|
+
console.error("[Voice Widget] Failed to set example_status:", error);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
async function createPendingVoiceVariant(lessonId, voiceConfig) {
|
|
805
|
+
try {
|
|
806
|
+
const response = await api.post("/items/VoiceVariants", {
|
|
807
|
+
lesson_id: lessonId,
|
|
808
|
+
voice_config: voiceConfig,
|
|
809
|
+
status: "processing"
|
|
810
|
+
});
|
|
811
|
+
const variantId = response.data.data?.id;
|
|
812
|
+
console.log(`[Voice Widget] Created pending VoiceVariant: ${variantId}`);
|
|
813
|
+
return variantId;
|
|
814
|
+
} catch (error) {
|
|
815
|
+
console.error("[Voice Widget] Failed to create pending VoiceVariant:", error);
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
async function fetchPendingVariants(lessonId) {
|
|
820
|
+
const lessonIdStr = String(lessonId);
|
|
821
|
+
console.log("[Voice Widget] ========== fetchPendingVariants START ==========");
|
|
822
|
+
try {
|
|
823
|
+
const response = await api.get("/items/VoiceVariants", {
|
|
824
|
+
params: {
|
|
825
|
+
fields: "id,lesson_id,voice_config,status,date_created",
|
|
826
|
+
limit: -1
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
const allData = response.data?.data || [];
|
|
830
|
+
console.log("[Voice Widget] fetchPendingVariants: got", allData.length, "total variants");
|
|
831
|
+
console.log("[Voice Widget] All statuses:", allData.map((v) => v.status));
|
|
832
|
+
const pending = allData.filter((v) => {
|
|
833
|
+
const matches = String(v.lesson_id) === lessonIdStr && v.status === "processing";
|
|
834
|
+
if (matches) {
|
|
835
|
+
console.log("[Voice Widget] Found PROCESSING variant:", v.id, "status:", v.status);
|
|
836
|
+
}
|
|
837
|
+
return matches;
|
|
838
|
+
});
|
|
839
|
+
console.log("[Voice Widget] fetchPendingVariants: returning", pending.length, "processing variants");
|
|
840
|
+
console.log("[Voice Widget] ========== fetchPendingVariants END ==========");
|
|
841
|
+
return pending;
|
|
842
|
+
} catch (error) {
|
|
843
|
+
console.error("[Voice Widget] Failed to fetch pending variants:", error);
|
|
844
|
+
return [];
|
|
845
|
+
}
|
|
846
|
+
}
|
|
771
847
|
return {
|
|
772
848
|
fetchVoices,
|
|
773
849
|
fetchFlowId,
|
|
@@ -782,7 +858,10 @@ function useVoicingApi(api) {
|
|
|
782
858
|
deleteVoiceVariant,
|
|
783
859
|
createVoiceVariant,
|
|
784
860
|
linkAudioToLesson,
|
|
785
|
-
pollVoicingJob
|
|
861
|
+
pollVoicingJob,
|
|
862
|
+
setVoiceExampleStatus,
|
|
863
|
+
createPendingVoiceVariant,
|
|
864
|
+
fetchPendingVariants
|
|
786
865
|
};
|
|
787
866
|
}
|
|
788
867
|
|
|
@@ -877,14 +956,17 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
877
956
|
deleteVoiceVariant,
|
|
878
957
|
createVoiceVariant,
|
|
879
958
|
linkAudioToLesson,
|
|
880
|
-
pollVoicingJob
|
|
959
|
+
pollVoicingJob,
|
|
960
|
+
setVoiceExampleStatus,
|
|
961
|
+
createPendingVoiceVariant,
|
|
962
|
+
fetchPendingVariants
|
|
881
963
|
} = useVoicingApi(api);
|
|
882
964
|
const currentMode = ref("selection");
|
|
883
965
|
const loading = ref(true);
|
|
884
966
|
const initError = ref(null);
|
|
885
967
|
const headerExpanded = ref(!props.defaultCollapsed);
|
|
886
968
|
const savingUrl = ref(false);
|
|
887
|
-
const flowId = ref("
|
|
969
|
+
const flowId = ref("7fa08903-ed7d-4632-81fc-f422d873b8f8");
|
|
888
970
|
const selectedModel = ref("gemini");
|
|
889
971
|
const selectedVoiceId = ref(null);
|
|
890
972
|
const voices = ref([]);
|
|
@@ -905,9 +987,11 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
905
987
|
const hasExistingVoices = ref(false);
|
|
906
988
|
const allVariants = ref([]);
|
|
907
989
|
const currentVariantIndex = ref(0);
|
|
908
|
-
|
|
990
|
+
ref(null);
|
|
909
991
|
const isPollingProgress = ref(false);
|
|
910
992
|
const progressStatus = ref("");
|
|
993
|
+
let samplePollingInterval = null;
|
|
994
|
+
let voiceoverPollingInterval = null;
|
|
911
995
|
const selectedVariant = computed(() => allVariants.value[currentVariantIndex.value]);
|
|
912
996
|
const currentVoices = computed(() => {
|
|
913
997
|
return voices.value.filter((v) => v.provider === selectedModel.value);
|
|
@@ -947,24 +1031,122 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
947
1031
|
}
|
|
948
1032
|
generatingSampleFor.value = voiceId;
|
|
949
1033
|
try {
|
|
950
|
-
|
|
951
|
-
const { url, audioFileId } = await generateVoiceSample(
|
|
952
|
-
voice.voice_id,
|
|
953
|
-
selectedModel.value,
|
|
954
|
-
effectiveFlowId
|
|
955
|
-
);
|
|
956
|
-
voiceSamples.value[voiceId] = url;
|
|
957
|
-
await updateVoiceSampleFile(voiceId, audioFileId, props.voicesCollection);
|
|
1034
|
+
await setVoiceExampleStatus(voiceId, "processing", props.voicesCollection);
|
|
958
1035
|
const voiceIndex = voices.value.findIndex((v) => v.id === voiceId);
|
|
959
1036
|
if (voiceIndex !== -1) {
|
|
960
|
-
voices.value[voiceIndex].
|
|
1037
|
+
voices.value[voiceIndex].example_status = "processing";
|
|
961
1038
|
}
|
|
1039
|
+
const effectiveFlowId = flowId.value && flowId.value.trim() ? flowId.value.trim() : void 0;
|
|
1040
|
+
generateVoiceSample(
|
|
1041
|
+
voice.voice,
|
|
1042
|
+
selectedModel.value,
|
|
1043
|
+
effectiveFlowId,
|
|
1044
|
+
props.primaryKey,
|
|
1045
|
+
props.collection || "SM_Lessons"
|
|
1046
|
+
).then(() => {
|
|
1047
|
+
console.log("[Voice Widget] Sample generation request sent for", voice.voice);
|
|
1048
|
+
}).catch((error) => {
|
|
1049
|
+
console.error("[Voice Widget] Failed to start sample generation:", error?.message ?? error);
|
|
1050
|
+
setVoiceExampleStatus(voiceId, null, props.voicesCollection);
|
|
1051
|
+
});
|
|
1052
|
+
setTimeout(() => {
|
|
1053
|
+
generatingSampleFor.value = null;
|
|
1054
|
+
}, 1500);
|
|
1055
|
+
startSamplePolling();
|
|
962
1056
|
} catch (error) {
|
|
963
1057
|
console.error("[Voice Widget] Failed to generate voice:", error?.message ?? error);
|
|
964
|
-
} finally {
|
|
965
1058
|
generatingSampleFor.value = null;
|
|
966
1059
|
}
|
|
967
1060
|
}
|
|
1061
|
+
function startSamplePolling() {
|
|
1062
|
+
if (samplePollingInterval) return;
|
|
1063
|
+
console.log("[Voice Widget] Starting sample completion polling");
|
|
1064
|
+
samplePollingInterval = setInterval(async () => {
|
|
1065
|
+
try {
|
|
1066
|
+
const freshVoices = await fetchVoices(props.voicesCollection);
|
|
1067
|
+
let hasProcessing = false;
|
|
1068
|
+
for (const freshVoice of freshVoices) {
|
|
1069
|
+
const localVoice = voices.value.find((v) => v.id === freshVoice.id);
|
|
1070
|
+
if (localVoice) {
|
|
1071
|
+
if (localVoice.example_status === "processing" && freshVoice.example_status !== "processing") {
|
|
1072
|
+
console.log("[Voice Widget] Sample completed for", freshVoice.voice);
|
|
1073
|
+
localVoice.example_status = freshVoice.example_status;
|
|
1074
|
+
localVoice.example = freshVoice.example;
|
|
1075
|
+
}
|
|
1076
|
+
if (freshVoice.example_status === "processing") {
|
|
1077
|
+
hasProcessing = true;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
if (!hasProcessing) {
|
|
1082
|
+
console.log("[Voice Widget] All samples completed, stopping polling");
|
|
1083
|
+
stopSamplePolling();
|
|
1084
|
+
}
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
console.error("[Voice Widget] Sample polling error:", error);
|
|
1087
|
+
}
|
|
1088
|
+
}, 3e3);
|
|
1089
|
+
}
|
|
1090
|
+
function stopSamplePolling() {
|
|
1091
|
+
if (samplePollingInterval) {
|
|
1092
|
+
clearInterval(samplePollingInterval);
|
|
1093
|
+
samplePollingInterval = null;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
let voiceoverPollCount = 0;
|
|
1097
|
+
let voiceoverLessonId = null;
|
|
1098
|
+
function startVoiceoverPolling() {
|
|
1099
|
+
stopVoiceoverPolling();
|
|
1100
|
+
voiceoverPollCount = 0;
|
|
1101
|
+
voiceoverLessonId = props.primaryKey ? String(props.primaryKey) : null;
|
|
1102
|
+
if (!voiceoverLessonId) {
|
|
1103
|
+
console.error("[Voice Widget] Cannot start polling - no primaryKey!");
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
console.log("[Voice Widget] Starting voiceover completion polling for lesson:", voiceoverLessonId);
|
|
1107
|
+
const checkCompletion = async () => {
|
|
1108
|
+
if (!voiceoverLessonId) {
|
|
1109
|
+
console.error("[Voice Widget] Poll aborted - no lesson ID");
|
|
1110
|
+
stopVoiceoverPolling();
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
voiceoverPollCount++;
|
|
1114
|
+
console.log(`[Voice Widget] Poll #${voiceoverPollCount} for lesson ${voiceoverLessonId}...`);
|
|
1115
|
+
try {
|
|
1116
|
+
const allVariantsNow = await fetchVoiceVariants(voiceoverLessonId);
|
|
1117
|
+
console.log(`[Voice Widget] Poll #${voiceoverPollCount} raw result:`, JSON.stringify(allVariantsNow));
|
|
1118
|
+
const completedWithAudio = allVariantsNow.filter((v) => v.audio_file_id);
|
|
1119
|
+
console.log(`[Voice Widget] Poll #${voiceoverPollCount}: total=${allVariantsNow.length}, withAudio=${completedWithAudio.length}`);
|
|
1120
|
+
if (completedWithAudio.length > 0) {
|
|
1121
|
+
console.log("[Voice Widget] COMPLETED! Transitioning to result...");
|
|
1122
|
+
stopVoiceoverPolling();
|
|
1123
|
+
allVariants.value = completedWithAudio;
|
|
1124
|
+
for (const v of allVariants.value) {
|
|
1125
|
+
if (v.audio_file_id) {
|
|
1126
|
+
const isUuid = v.audio_file_id?.includes("-");
|
|
1127
|
+
v.audioUrl = isUuid ? getAudioUrl(v.audio_file_id) : await resolveAudioFileUrl(v.audio_file_id);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
currentVariantIndex.value = 0;
|
|
1131
|
+
hasExistingVoices.value = true;
|
|
1132
|
+
currentMode.value = "result";
|
|
1133
|
+
console.log("[Voice Widget] Mode set to result, allVariants:", allVariants.value.length);
|
|
1134
|
+
} else {
|
|
1135
|
+
console.log(`[Voice Widget] Poll #${voiceoverPollCount} - waiting for audio_file_id...`);
|
|
1136
|
+
}
|
|
1137
|
+
} catch (error) {
|
|
1138
|
+
console.error("[Voice Widget] Voiceover polling error:", error);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
voiceoverPollingInterval = setInterval(checkCompletion, 2e3);
|
|
1142
|
+
setTimeout(checkCompletion, 500);
|
|
1143
|
+
}
|
|
1144
|
+
function stopVoiceoverPolling() {
|
|
1145
|
+
if (voiceoverPollingInterval) {
|
|
1146
|
+
clearInterval(voiceoverPollingInterval);
|
|
1147
|
+
voiceoverPollingInterval = null;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
968
1150
|
function getSelectedTonePrompt() {
|
|
969
1151
|
if (selectedToneId.value === "other") {
|
|
970
1152
|
return customTone.value;
|
|
@@ -991,7 +1173,16 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
991
1173
|
const stylePrompt = getSelectedStylePrompt();
|
|
992
1174
|
const combinedStyle = `${tonePrompt}. ${stylePrompt}`.trim();
|
|
993
1175
|
const selectedVoice = voices.value.find((v) => v.id === selectedVoiceId.value);
|
|
994
|
-
const providerVoiceId = selectedVoice?.
|
|
1176
|
+
const providerVoiceId = selectedVoice?.voice || selectedVoiceId.value;
|
|
1177
|
+
const voiceConfig = {
|
|
1178
|
+
provider: selectedModel.value,
|
|
1179
|
+
voice_id: providerVoiceId,
|
|
1180
|
+
style: combinedStyle,
|
|
1181
|
+
tone_id: selectedToneId.value,
|
|
1182
|
+
style_id: selectedStyleId.value
|
|
1183
|
+
};
|
|
1184
|
+
await createPendingVoiceVariant(props.primaryKey, voiceConfig);
|
|
1185
|
+
startVoiceoverPolling();
|
|
995
1186
|
const result = await generateFullVoiceover({
|
|
996
1187
|
provider: selectedModel.value,
|
|
997
1188
|
voiceId: providerVoiceId,
|
|
@@ -1003,17 +1194,8 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
1003
1194
|
toneId: selectedToneId.value || void 0,
|
|
1004
1195
|
styleId: selectedStyleId.value || void 0
|
|
1005
1196
|
});
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
voice_id: providerVoiceId,
|
|
1009
|
-
style: combinedStyle,
|
|
1010
|
-
tone_id: selectedToneId.value,
|
|
1011
|
-
style_id: selectedStyleId.value
|
|
1012
|
-
};
|
|
1013
|
-
if (result.job_id) {
|
|
1014
|
-
processingMessage.value = "Processing in background...";
|
|
1015
|
-
await startProgressPolling(result.job_id, voiceConfig);
|
|
1016
|
-
} else if (result.audio_file_id) {
|
|
1197
|
+
if (result.audio_file_id) {
|
|
1198
|
+
stopVoiceoverPolling();
|
|
1017
1199
|
generatedAudioId.value = result.audio_file_id;
|
|
1018
1200
|
generatedAudioUrl.value = getAudioUrl(result.audio_file_id);
|
|
1019
1201
|
progressPercent.value = 100;
|
|
@@ -1024,37 +1206,15 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
1024
1206
|
);
|
|
1025
1207
|
await refreshVariants();
|
|
1026
1208
|
currentMode.value = "result";
|
|
1027
|
-
} else
|
|
1028
|
-
processingMessage.value = "
|
|
1029
|
-
|
|
1209
|
+
} else {
|
|
1210
|
+
processingMessage.value = "Generating voiceover...";
|
|
1211
|
+
console.log("[Voice Widget] Async generation started, polling for completion...");
|
|
1030
1212
|
}
|
|
1031
1213
|
} catch (error) {
|
|
1032
1214
|
errorMessage.value = error.message || "Failed to generate voiceover";
|
|
1033
1215
|
progressPercent.value = 0;
|
|
1034
1216
|
}
|
|
1035
1217
|
}
|
|
1036
|
-
async function pollForCompletion(callbackData, voiceConfig) {
|
|
1037
|
-
const maxAttempts = 60;
|
|
1038
|
-
const pollInterval = 5e3;
|
|
1039
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
1040
|
-
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
1041
|
-
progressPercent.value = Math.min(90, 30 + i / maxAttempts * 60);
|
|
1042
|
-
try {
|
|
1043
|
-
const variants = await fetchVoiceVariants(props.primaryKey);
|
|
1044
|
-
const latest = variants.find((v) => v.callback_data === callbackData);
|
|
1045
|
-
if (latest && latest.audio_file_id) {
|
|
1046
|
-
generatedAudioId.value = latest.audio_file_id;
|
|
1047
|
-
generatedAudioUrl.value = await resolveAudioFileUrl(latest.audio_file_id);
|
|
1048
|
-
progressPercent.value = 100;
|
|
1049
|
-
currentMode.value = "result";
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
} catch (error) {
|
|
1053
|
-
console.error("Poll error:", error);
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
errorMessage.value = "Generation timed out. Please try again.";
|
|
1057
|
-
}
|
|
1058
1218
|
function retryGeneration() {
|
|
1059
1219
|
errorMessage.value = null;
|
|
1060
1220
|
generateVoiceover();
|
|
@@ -1086,37 +1246,6 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
1086
1246
|
console.error("Failed to delete variant:", error);
|
|
1087
1247
|
}
|
|
1088
1248
|
}
|
|
1089
|
-
async function startProgressPolling(jobId, voiceConfig) {
|
|
1090
|
-
currentJobId.value = jobId;
|
|
1091
|
-
isPollingProgress.value = true;
|
|
1092
|
-
while (isPollingProgress.value) {
|
|
1093
|
-
const job = await pollVoicingJob(jobId);
|
|
1094
|
-
if (job) {
|
|
1095
|
-
progressPercent.value = job.progress;
|
|
1096
|
-
processingMessage.value = job.message;
|
|
1097
|
-
progressStatus.value = job.status;
|
|
1098
|
-
if (job.status === "completed") {
|
|
1099
|
-
isPollingProgress.value = false;
|
|
1100
|
-
if (job.audio_file_id) {
|
|
1101
|
-
await createVoiceVariant(
|
|
1102
|
-
props.primaryKey,
|
|
1103
|
-
job.audio_file_id,
|
|
1104
|
-
voiceConfig
|
|
1105
|
-
);
|
|
1106
|
-
}
|
|
1107
|
-
await refreshVariants();
|
|
1108
|
-
currentMode.value = "result";
|
|
1109
|
-
return;
|
|
1110
|
-
}
|
|
1111
|
-
if (job.status.startsWith("failed")) {
|
|
1112
|
-
isPollingProgress.value = false;
|
|
1113
|
-
errorMessage.value = job.message || "Generation failed";
|
|
1114
|
-
return;
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
await new Promise((r) => setTimeout(r, 2e3));
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
1249
|
async function refreshVariants() {
|
|
1121
1250
|
const variants = await fetchVoiceVariants(props.primaryKey);
|
|
1122
1251
|
allVariants.value = variants;
|
|
@@ -1210,40 +1339,83 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
1210
1339
|
}
|
|
1211
1340
|
}
|
|
1212
1341
|
async function initialize() {
|
|
1342
|
+
console.log("[Voice Widget] ===== INITIALIZE START =====");
|
|
1343
|
+
console.log("[Voice Widget] Props:", {
|
|
1344
|
+
collection: props.collection,
|
|
1345
|
+
primaryKey: props.primaryKey,
|
|
1346
|
+
voicesCollection: props.voicesCollection,
|
|
1347
|
+
tonesCollection: props.tonesCollection,
|
|
1348
|
+
stylesCollection: props.stylesCollection,
|
|
1349
|
+
flowId: props.flowId
|
|
1350
|
+
});
|
|
1213
1351
|
loading.value = true;
|
|
1214
1352
|
initError.value = null;
|
|
1215
1353
|
try {
|
|
1216
1354
|
if (!props.flowId) {
|
|
1217
1355
|
flowId.value = await fetchFlowId();
|
|
1356
|
+
console.log("[Voice Widget] Fetched flowId:", flowId.value);
|
|
1218
1357
|
} else {
|
|
1219
1358
|
flowId.value = props.flowId;
|
|
1359
|
+
console.log("[Voice Widget] Using props flowId:", flowId.value);
|
|
1220
1360
|
}
|
|
1361
|
+
console.log("[Voice Widget] Fetching voices, tones, styles...");
|
|
1221
1362
|
const [loadedVoices, loadedTones, loadedStyles] = await Promise.all([
|
|
1222
1363
|
fetchVoices(props.voicesCollection),
|
|
1223
1364
|
fetchTones(props.tonesCollection),
|
|
1224
1365
|
fetchStyles(props.stylesCollection)
|
|
1225
1366
|
]);
|
|
1367
|
+
console.log("[Voice Widget] Loaded voices:", loadedVoices.length, loadedVoices);
|
|
1368
|
+
console.log("[Voice Widget] Loaded tones:", loadedTones.length, loadedTones);
|
|
1369
|
+
console.log("[Voice Widget] Loaded styles:", loadedStyles.length, loadedStyles);
|
|
1226
1370
|
voices.value = loadedVoices;
|
|
1227
1371
|
tones.value = loadedTones;
|
|
1228
1372
|
styles.value = loadedStyles;
|
|
1373
|
+
console.log("[Voice Widget] currentVoices (filtered by model):", currentVoices.value.length);
|
|
1229
1374
|
if (currentVoices.value.length > 0 && !selectedVoiceId.value) {
|
|
1230
1375
|
selectedVoiceId.value = currentVoices.value[0].id;
|
|
1376
|
+
console.log("[Voice Widget] Auto-selected voice:", selectedVoiceId.value);
|
|
1231
1377
|
}
|
|
1232
1378
|
if (tones.value.length > 0 && !selectedToneId.value) {
|
|
1233
1379
|
selectedToneId.value = tones.value[0].id;
|
|
1380
|
+
console.log("[Voice Widget] Auto-selected tone:", selectedToneId.value);
|
|
1234
1381
|
}
|
|
1235
1382
|
if (styles.value.length > 0 && !selectedStyleId.value) {
|
|
1236
1383
|
selectedStyleId.value = styles.value[0].id;
|
|
1384
|
+
console.log("[Voice Widget] Auto-selected style:", selectedStyleId.value);
|
|
1385
|
+
}
|
|
1386
|
+
const processingVoices = voices.value.filter((v) => v.example_status === "processing");
|
|
1387
|
+
if (processingVoices.length > 0) {
|
|
1388
|
+
console.log("[Voice Widget] Found", processingVoices.length, "voices with processing samples");
|
|
1389
|
+
startSamplePolling();
|
|
1237
1390
|
}
|
|
1238
1391
|
if (props.primaryKey) {
|
|
1392
|
+
console.log("[Voice Widget] Checking variants for lesson:", props.primaryKey);
|
|
1239
1393
|
const variants = await fetchVoiceVariants(props.primaryKey);
|
|
1240
|
-
|
|
1394
|
+
console.log("[Voice Widget] All variants for lesson:", variants.length);
|
|
1395
|
+
const completedVariants = variants.filter((v) => v.audio_file_id);
|
|
1396
|
+
hasExistingVoices.value = completedVariants.length > 0;
|
|
1397
|
+
console.log("[Voice Widget] Completed variants with audio:", completedVariants.length);
|
|
1398
|
+
if (completedVariants.length > 0) {
|
|
1399
|
+
allVariants.value = completedVariants;
|
|
1400
|
+
for (const v of allVariants.value) {
|
|
1401
|
+
if (v.audio_file_id) {
|
|
1402
|
+
const isUuid = v.audio_file_id?.includes("-");
|
|
1403
|
+
v.audioUrl = isUuid ? getAudioUrl(v.audio_file_id) : await resolveAudioFileUrl(v.audio_file_id);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
currentVariantIndex.value = 0;
|
|
1407
|
+
currentMode.value = "result";
|
|
1408
|
+
console.log("[Voice Widget] AUTO-SHOWING RESULT PAGE with", completedVariants.length, "variants");
|
|
1409
|
+
}
|
|
1241
1410
|
}
|
|
1242
1411
|
initFromValue();
|
|
1412
|
+
console.log("[Voice Widget] ===== INITIALIZE SUCCESS =====");
|
|
1243
1413
|
} catch (error) {
|
|
1414
|
+
console.error("[Voice Widget] ===== INITIALIZE ERROR =====", error);
|
|
1244
1415
|
initError.value = error.message || "Failed to load configuration";
|
|
1245
1416
|
} finally {
|
|
1246
1417
|
loading.value = false;
|
|
1418
|
+
console.log("[Voice Widget] loading set to false, currentMode:", currentMode.value);
|
|
1247
1419
|
}
|
|
1248
1420
|
}
|
|
1249
1421
|
async function initFromValue() {
|
|
@@ -1287,7 +1459,14 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
1287
1459
|
selectedVoiceId.value = currentVoices.value[0].id;
|
|
1288
1460
|
}
|
|
1289
1461
|
});
|
|
1290
|
-
onMounted(
|
|
1462
|
+
onMounted(() => {
|
|
1463
|
+
console.log("[Voice Widget] onMounted called");
|
|
1464
|
+
initialize();
|
|
1465
|
+
});
|
|
1466
|
+
onUnmounted(() => {
|
|
1467
|
+
stopSamplePolling();
|
|
1468
|
+
stopVoiceoverPolling();
|
|
1469
|
+
});
|
|
1291
1470
|
return (_ctx, _cache) => {
|
|
1292
1471
|
const _component_v_icon = resolveComponent("v-icon");
|
|
1293
1472
|
return openBlock(), createElementBlock("div", _hoisted_1, [
|
|
@@ -1495,8 +1674,8 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
1495
1674
|
key: voice.id,
|
|
1496
1675
|
voice,
|
|
1497
1676
|
"is-selected": selectedVoiceId.value === voice.id,
|
|
1498
|
-
loading: generatingSampleFor.value === voice.id,
|
|
1499
|
-
"sample-url": voiceSamples.value[voice.id] || (voice.
|
|
1677
|
+
loading: generatingSampleFor.value === voice.id || voice.example_status === "processing",
|
|
1678
|
+
"sample-url": voiceSamples.value[voice.id] || (voice.example ? `/assets/${voice.example}` : voice.sample_url),
|
|
1500
1679
|
provider: selectedModel.value,
|
|
1501
1680
|
onSelect: selectVoice,
|
|
1502
1681
|
onGenerateVoice: generateVoice
|
|
@@ -1755,10 +1934,10 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
1755
1934
|
}
|
|
1756
1935
|
});
|
|
1757
1936
|
|
|
1758
|
-
var css = "\n.voice-widget[data-v-
|
|
1937
|
+
var css = "\n.voice-widget[data-v-214c3276] {\n font-family: var(--theme--fonts--sans--font-family);\n padding: 16px;\n border: 1px solid var(--theme--form--field--input--border-color);\n border-radius: var(--theme--border-radius);\n background: var(--theme--background);\n}\n.widget__header[data-v-214c3276] {\n margin-bottom: 20px;\n}\n.widget__header-row[data-v-214c3276] {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: 16px;\n}\n.widget__header-text[data-v-214c3276] {\n flex: 1;\n}\n.widget__title[data-v-214c3276] {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0 0 4px 0;\n font-size: 18px;\n font-weight: 600;\n color: var(--theme--foreground);\n}\n.widget__collapse-btn[data-v-214c3276] {\n background: none;\n border: none;\n padding: 4px;\n cursor: pointer;\n color: var(--theme--foreground-subdued);\n border-radius: 4px;\n}\n.widget__collapse-btn[data-v-214c3276]:hover {\n background: var(--theme--background-accent);\n}\n.widget__subtitle[data-v-214c3276] {\n margin: 0;\n font-size: 14px;\n color: var(--theme--foreground-subdued);\n}\n.widget__header-controls[data-v-214c3276] {\n display: flex;\n gap: 16px;\n align-items: center;\n}\n.widget__url-input[data-v-214c3276] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.widget__url-label[data-v-214c3276] {\n font-size: 12px;\n color: var(--theme--foreground-subdued);\n white-space: nowrap;\n}\n.widget__url-field[data-v-214c3276] {\n padding: 6px 10px;\n border: 1px solid var(--theme--form--field--input--border-color);\n border-radius: var(--theme--border-radius);\n font-size: 12px;\n width: 280px;\n background: var(--theme--form--field--input--background);\n color: var(--theme--foreground);\n}\n.widget__section[data-v-214c3276] {\n margin-bottom: 20px;\n}\n.widget__section-label[data-v-214c3276] {\n display: block;\n margin-bottom: 8px;\n font-size: 14px;\n font-weight: 500;\n color: var(--theme--foreground);\n}\n.widget__model-buttons[data-v-214c3276] {\n display: flex;\n gap: 8px;\n}\n.widget__model-btn[data-v-214c3276] {\n padding: 8px 16px;\n border: 1px solid var(--theme--form--field--input--border-color);\n border-radius: var(--theme--border-radius);\n background: var(--theme--background);\n color: var(--theme--foreground);\n cursor: pointer;\n font-size: 14px;\n transition: all 0.15s ease;\n}\n.widget__model-btn[data-v-214c3276]:hover {\n border-color: var(--theme--primary);\n}\n.widget__model-btn--active[data-v-214c3276] {\n background: var(--theme--primary);\n border-color: var(--theme--primary);\n color: var(--theme--primary-foreground, #fff);\n}\n.widget__voice-list[data-v-214c3276] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.widget__section--toggle[data-v-214c3276] {\n padding: 12px;\n background: var(--theme--background-subdued);\n border-radius: var(--theme--border-radius);\n}\n.widget__toggle[data-v-214c3276] {\n display: flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n}\n.widget__toggle input[data-v-214c3276] {\n width: 16px;\n height: 16px;\n cursor: pointer;\n}\n.widget__toggle-label[data-v-214c3276] {\n font-size: 14px;\n font-weight: 500;\n color: var(--theme--foreground);\n}\n.widget__toggle-note[data-v-214c3276] {\n margin: 4px 0 0 24px;\n font-size: 12px;\n color: var(--theme--foreground-subdued);\n}\n.widget__footer[data-v-214c3276] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-top: 16px;\n border-top: 1px solid var(--theme--border-color-subdued);\n margin-top: 20px;\n}\n.widget__footer-left[data-v-214c3276],\n.widget__footer-right[data-v-214c3276] {\n display: flex;\n gap: 8px;\n}\n.widget__variant-nav[data-v-214c3276] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n margin-bottom: 16px;\n padding: 8px 0;\n}\n.widget__variant-counter[data-v-214c3276] {\n font-size: 14px;\n font-weight: 500;\n color: var(--theme--foreground-subdued);\n min-width: 60px;\n text-align: center;\n}\n.widget__btn--icon[data-v-214c3276] {\n padding: 6px;\n min-width: 32px;\n min-height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.widget__result-date[data-v-214c3276] {\n margin-top: 8px;\n font-size: 12px;\n color: var(--theme--foreground-subdued);\n}\n.widget__btn[data-v-214c3276] {\n padding: 8px 16px;\n border: none;\n border-radius: var(--theme--border-radius);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n.widget__btn[data-v-214c3276]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.widget__btn--primary[data-v-214c3276] {\n background: var(--theme--primary);\n color: var(--theme--primary-foreground, #fff);\n}\n.widget__btn--primary[data-v-214c3276]:hover:not(:disabled) {\n background: var(--theme--primary-accent);\n}\n.widget__btn--secondary[data-v-214c3276] {\n background: var(--theme--background-accent);\n color: var(--theme--foreground);\n border: 1px solid var(--theme--form--field--input--border-color);\n}\n.widget__btn--secondary[data-v-214c3276]:hover:not(:disabled) {\n background: var(--theme--background-normal);\n}\n\n/* Processing State */\n.widget__processing[data-v-214c3276] {\n padding: 40px 20px;\n text-align: center;\n}\n.widget__progress[data-v-214c3276] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n margin-bottom: 16px;\n font-size: 16px;\n color: var(--theme--foreground);\n}\n.widget__progress-bar[data-v-214c3276] {\n height: 8px;\n background: var(--theme--background-accent);\n border-radius: 4px;\n overflow: hidden;\n max-width: 400px;\n margin: 0 auto;\n}\n.widget__progress-fill[data-v-214c3276] {\n height: 100%;\n background: var(--theme--primary);\n transition: width 0.3s ease;\n}\n\n/* Error State */\n.widget__error[data-v-214c3276] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 12px;\n padding: 20px;\n color: var(--theme--danger);\n text-align: center;\n}\n.widget__error-actions[data-v-214c3276] {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n}\n.widget__retry[data-v-214c3276] {\n padding: 6px 12px;\n background: var(--theme--danger);\n color: #fff;\n border: none;\n border-radius: var(--theme--border-radius);\n cursor: pointer;\n}\n\n/* Loading State */\n.widget__loading[data-v-214c3276] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 40px 20px;\n color: var(--theme--foreground-subdued);\n}\n\n/* Result State */\n.widget__result[data-v-214c3276] {\n padding: 20px;\n background: var(--theme--background-subdued);\n border-radius: var(--theme--border-radius);\n margin-bottom: 20px;\n}\n.widget__result-info[data-v-214c3276] {\n margin-top: 16px;\n padding-top: 16px;\n border-top: 1px solid var(--theme--border-color-subdued);\n}\n.widget__result-info p[data-v-214c3276] {\n margin: 4px 0;\n font-size: 14px;\n color: var(--theme--foreground-subdued);\n}\n.widget__result-info strong[data-v-214c3276] {\n color: var(--theme--foreground);\n}\n\n/* Variants List View */\n.widget__variants-list[data-v-214c3276] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n max-height: 400px;\n overflow-y: auto;\n margin-bottom: 16px;\n}\n.widget__variant-item[data-v-214c3276] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px;\n border: 1px solid var(--theme--border-color-subdued);\n border-radius: var(--theme--border-radius);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n.widget__variant-item[data-v-214c3276]:hover {\n border-color: var(--theme--primary);\n}\n.widget__variant-item--selected[data-v-214c3276] {\n border-color: var(--theme--primary);\n background: var(--theme--primary-background);\n}\n.widget__variant-radio[data-v-214c3276] {\n flex-shrink: 0;\n}\n.widget__variant-radio input[data-v-214c3276] {\n width: 16px;\n height: 16px;\n cursor: pointer;\n}\n.widget__variant-player[data-v-214c3276] {\n flex: 1;\n min-width: 200px;\n}\n.widget__variant-meta[data-v-214c3276] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 120px;\n}\n.widget__variant-voice[data-v-214c3276] {\n font-size: 13px;\n font-weight: 500;\n color: var(--theme--foreground);\n}\n.widget__variant-date[data-v-214c3276] {\n font-size: 11px;\n color: var(--theme--foreground-subdued);\n}\n.widget__btn--danger[data-v-214c3276] {\n color: var(--theme--danger);\n background: transparent;\n border: none;\n}\n.widget__btn--danger[data-v-214c3276]:hover {\n background: var(--theme--danger-background);\n}\n.widget__selected-info[data-v-214c3276] {\n padding: 12px;\n background: var(--theme--background-subdued);\n border-radius: var(--theme--border-radius);\n margin-bottom: 16px;\n}\n.widget__selected-info p[data-v-214c3276] {\n margin: 4px 0;\n font-size: 14px;\n color: var(--theme--foreground-subdued);\n}\n.widget__selected-info strong[data-v-214c3276] {\n color: var(--theme--foreground);\n}\n.widget__progress-status[data-v-214c3276] {\n text-align: center;\n font-size: 12px;\n color: var(--theme--foreground-subdued);\n margin-top: 8px;\n}\n\n/* Animations */\n.spinning[data-v-214c3276] {\n animation: spin-214c3276 1s linear infinite;\n}\n@keyframes spin-214c3276 {\nfrom { transform: rotate(0deg);\n}\nto { transform: rotate(360deg);\n}\n}\n";
|
|
1759
1938
|
n(css,{});
|
|
1760
1939
|
|
|
1761
|
-
var InterfaceComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-
|
|
1940
|
+
var InterfaceComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-214c3276"], ["__file", "interface.vue"]]);
|
|
1762
1941
|
|
|
1763
1942
|
var index = defineInterface({
|
|
1764
1943
|
id: "voice-widget",
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stellartech/voice-widget-directus",
|
|
3
3
|
"description": "Voice generation widget with model/voice selection and audio preview for Directus",
|
|
4
4
|
"icon": "mic",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.3",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"readme": "README.md",
|
|
8
8
|
"repository": {
|