@stellartech/voice-widget-directus 1.0.1 → 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.
Files changed (2) hide show
  1. package/dist/index.js +288 -122
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -455,20 +455,19 @@ 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 = "fada8e65-5ce2-477d-b5db-bf7f64a23ffa";
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 {
462
462
  const response = await api.get(`/items/${collection}`, {
463
463
  params: {
464
- filter: { status: { _eq: "published" } },
465
464
  sort: ["sort", "name"],
466
- fields: ["id", "name", "voice_id", "provider", "description", "sample_url", "sample_file", "sort"]
465
+ fields: ["*"]
467
466
  }
468
467
  });
469
468
  return response.data.data || [];
470
469
  } catch (error) {
471
- console.error("Failed to fetch voices:", error);
470
+ console.error("Failed to fetch voices:", error?.response?.data || error?.message || error);
472
471
  return [];
473
472
  }
474
473
  }
@@ -486,7 +485,6 @@ function useVoicingApi(api) {
486
485
  try {
487
486
  const response = await api.get(`/items/${collection}`, {
488
487
  params: {
489
- filter: { status: { _eq: "published" } },
490
488
  sort: ["sort", "name"],
491
489
  fields: ["id", "name", "prompt", "sort", "is_custom"]
492
490
  }
@@ -502,19 +500,13 @@ function useVoicingApi(api) {
502
500
  return tones;
503
501
  } catch (error) {
504
502
  console.error("Failed to fetch tones:", error);
505
- return [
506
- { id: "teacher", name: "Teacher", prompt: "Speak in a clear, educational, and patient manner", sort: 1, is_custom: false },
507
- { id: "storyteller", name: "Storyteller", prompt: "Speak in an engaging, narrative style with varied pacing", sort: 2, is_custom: false },
508
- { id: "podcaster", name: "Podcaster", prompt: "Speak in a conversational, informal yet professional tone", sort: 3, is_custom: false },
509
- { id: "other", name: "Other (Custom)", prompt: "", sort: 9999, is_custom: true }
510
- ];
503
+ return [];
511
504
  }
512
505
  }
513
506
  async function fetchStyles(collection = "VoiceStyles") {
514
507
  try {
515
508
  const response = await api.get(`/items/${collection}`, {
516
509
  params: {
517
- filter: { status: { _eq: "published" } },
518
510
  sort: ["sort", "name"],
519
511
  fields: ["id", "name", "prompt", "sort", "is_custom"]
520
512
  }
@@ -530,15 +522,10 @@ function useVoicingApi(api) {
530
522
  return styles;
531
523
  } catch (error) {
532
524
  console.error("Failed to fetch styles:", error);
533
- return [
534
- { id: "happy", name: "Happy", prompt: "enthusiastic and cheerful", sort: 1, is_custom: false },
535
- { id: "chill", name: "Chill", prompt: "relaxed and calm", sort: 2, is_custom: false },
536
- { id: "excited", name: "Excited", prompt: "energetic and animated", sort: 3, is_custom: false },
537
- { id: "other", name: "Other (Custom)", prompt: "", sort: 9999, is_custom: true }
538
- ];
525
+ return [];
539
526
  }
540
527
  }
541
- async function generateVoiceSample(voiceId, provider, flowId = DEFAULT_FLOW_ID) {
528
+ async function generateVoiceSample(voiceId, provider, flowId = DEFAULT_FLOW_ID, lessonId, collection) {
542
529
  const effectiveFlowId = flowId && flowId.trim() ? flowId.trim() : DEFAULT_FLOW_ID;
543
530
  if (!effectiveFlowId) {
544
531
  throw new Error("Voice flow ID is not configured. Set it in Widget Config or in the Flow ID field.");
@@ -548,7 +535,16 @@ function useVoicingApi(api) {
548
535
  provider,
549
536
  preprocessing: false,
550
537
  title: `Voice Sample - ${voiceId}`,
551
- 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
+ }
552
548
  };
553
549
  if (provider === "gemini") {
554
550
  voicingPayload.speakers = [{ voice: voiceId, name: "Sample", style: "neutral" }];
@@ -579,7 +575,8 @@ function useVoicingApi(api) {
579
575
  const url = await resolveAudioFileUrl(audioFileId);
580
576
  return { url, audioFileId };
581
577
  }
582
- throw new Error("No audio file returned from voice generation. Response: " + JSON.stringify(flowResponse));
578
+ console.log("[Voice Widget] Sample processing async, callback will update Voices.example");
579
+ return { url: "", audioFileId: "" };
583
580
  } catch (e) {
584
581
  console.error("Failed to generate voice sample:", e);
585
582
  throw new Error(e.response?.data?.detail || e.message || "Failed to generate sample");
@@ -685,17 +682,30 @@ function useVoicingApi(api) {
685
682
  return `/assets/${audioFilesRecordId}`;
686
683
  }
687
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);
688
689
  try {
689
- const response = await api.get("/items/VoiceVariants", {
690
- params: {
691
- filter: { lesson_id: { _eq: lessonId } },
692
- sort: ["-date_created"],
693
- fields: ["id", "lesson_id", "audio_file_id", "voice_config", "callback_data", "date_created"]
694
- }
695
- });
696
- return response.data.data || [];
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;
697
704
  } catch (error) {
698
- console.error("Failed to fetch voice variants:", 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);
699
709
  return [];
700
710
  }
701
711
  }
@@ -712,13 +722,13 @@ function useVoicingApi(api) {
712
722
  console.warn("[Voice Widget] Could not get file UUID from AudioFiles record. fileField:", fileField);
713
723
  return;
714
724
  }
715
- console.log(`[Voice Widget] Patching ${collection}/${voiceId} with sample_file=${fileUuid}`);
725
+ console.log(`[Voice Widget] Patching ${collection}/${voiceId} with example=${fileUuid}`);
716
726
  await api.patch(`/items/${collection}/${voiceId}`, {
717
- sample_file: fileUuid
727
+ example: fileUuid
718
728
  });
719
- console.log(`[Voice Widget] Updated voice ${voiceId} sample_file to ${fileUuid}`);
729
+ console.log(`[Voice Widget] Updated voice ${voiceId} example to ${fileUuid}`);
720
730
  } catch (error) {
721
- console.error("[Voice Widget] Failed to update voice sample_file:", error);
731
+ console.error("[Voice Widget] Failed to update voice example:", error);
722
732
  }
723
733
  }
724
734
  async function deleteVoiceVariant(variantId) {
@@ -781,6 +791,59 @@ function useVoicingApi(api) {
781
791
  return null;
782
792
  }
783
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
+ }
784
847
  return {
785
848
  fetchVoices,
786
849
  fetchFlowId,
@@ -795,7 +858,10 @@ function useVoicingApi(api) {
795
858
  deleteVoiceVariant,
796
859
  createVoiceVariant,
797
860
  linkAudioToLesson,
798
- pollVoicingJob
861
+ pollVoicingJob,
862
+ setVoiceExampleStatus,
863
+ createPendingVoiceVariant,
864
+ fetchPendingVariants
799
865
  };
800
866
  }
801
867
 
@@ -890,14 +956,17 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
890
956
  deleteVoiceVariant,
891
957
  createVoiceVariant,
892
958
  linkAudioToLesson,
893
- pollVoicingJob
959
+ pollVoicingJob,
960
+ setVoiceExampleStatus,
961
+ createPendingVoiceVariant,
962
+ fetchPendingVariants
894
963
  } = useVoicingApi(api);
895
964
  const currentMode = ref("selection");
896
965
  const loading = ref(true);
897
966
  const initError = ref(null);
898
967
  const headerExpanded = ref(!props.defaultCollapsed);
899
968
  const savingUrl = ref(false);
900
- const flowId = ref("fada8e65-5ce2-477d-b5db-bf7f64a23ffa");
969
+ const flowId = ref("7fa08903-ed7d-4632-81fc-f422d873b8f8");
901
970
  const selectedModel = ref("gemini");
902
971
  const selectedVoiceId = ref(null);
903
972
  const voices = ref([]);
@@ -918,9 +987,11 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
918
987
  const hasExistingVoices = ref(false);
919
988
  const allVariants = ref([]);
920
989
  const currentVariantIndex = ref(0);
921
- const currentJobId = ref(null);
990
+ ref(null);
922
991
  const isPollingProgress = ref(false);
923
992
  const progressStatus = ref("");
993
+ let samplePollingInterval = null;
994
+ let voiceoverPollingInterval = null;
924
995
  const selectedVariant = computed(() => allVariants.value[currentVariantIndex.value]);
925
996
  const currentVoices = computed(() => {
926
997
  return voices.value.filter((v) => v.provider === selectedModel.value);
@@ -960,24 +1031,122 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
960
1031
  }
961
1032
  generatingSampleFor.value = voiceId;
962
1033
  try {
963
- const effectiveFlowId = flowId.value && flowId.value.trim() ? flowId.value.trim() : void 0;
964
- const { url, audioFileId } = await generateVoiceSample(
965
- voice.voice_id,
966
- selectedModel.value,
967
- effectiveFlowId
968
- );
969
- voiceSamples.value[voiceId] = url;
970
- await updateVoiceSampleFile(voiceId, audioFileId, props.voicesCollection);
1034
+ await setVoiceExampleStatus(voiceId, "processing", props.voicesCollection);
971
1035
  const voiceIndex = voices.value.findIndex((v) => v.id === voiceId);
972
1036
  if (voiceIndex !== -1) {
973
- voices.value[voiceIndex].sample_file = url.replace("/assets/", "");
1037
+ voices.value[voiceIndex].example_status = "processing";
974
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();
975
1056
  } catch (error) {
976
1057
  console.error("[Voice Widget] Failed to generate voice:", error?.message ?? error);
977
- } finally {
978
1058
  generatingSampleFor.value = null;
979
1059
  }
980
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
+ }
981
1150
  function getSelectedTonePrompt() {
982
1151
  if (selectedToneId.value === "other") {
983
1152
  return customTone.value;
@@ -1004,7 +1173,16 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1004
1173
  const stylePrompt = getSelectedStylePrompt();
1005
1174
  const combinedStyle = `${tonePrompt}. ${stylePrompt}`.trim();
1006
1175
  const selectedVoice = voices.value.find((v) => v.id === selectedVoiceId.value);
1007
- const providerVoiceId = selectedVoice?.voice_id || selectedVoiceId.value;
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();
1008
1186
  const result = await generateFullVoiceover({
1009
1187
  provider: selectedModel.value,
1010
1188
  voiceId: providerVoiceId,
@@ -1016,17 +1194,8 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1016
1194
  toneId: selectedToneId.value || void 0,
1017
1195
  styleId: selectedStyleId.value || void 0
1018
1196
  });
1019
- const voiceConfig = {
1020
- provider: selectedModel.value,
1021
- voice_id: providerVoiceId,
1022
- style: combinedStyle,
1023
- tone_id: selectedToneId.value,
1024
- style_id: selectedStyleId.value
1025
- };
1026
- if (result.job_id) {
1027
- processingMessage.value = "Processing in background...";
1028
- await startProgressPolling(result.job_id, voiceConfig);
1029
- } else if (result.audio_file_id) {
1197
+ if (result.audio_file_id) {
1198
+ stopVoiceoverPolling();
1030
1199
  generatedAudioId.value = result.audio_file_id;
1031
1200
  generatedAudioUrl.value = getAudioUrl(result.audio_file_id);
1032
1201
  progressPercent.value = 100;
@@ -1037,37 +1206,15 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1037
1206
  );
1038
1207
  await refreshVariants();
1039
1208
  currentMode.value = "result";
1040
- } else if (result.status === "processing" && result.callback_data) {
1041
- processingMessage.value = "Processing in background...";
1042
- await pollForCompletion(result.callback_data, voiceConfig);
1209
+ } else {
1210
+ processingMessage.value = "Generating voiceover...";
1211
+ console.log("[Voice Widget] Async generation started, polling for completion...");
1043
1212
  }
1044
1213
  } catch (error) {
1045
1214
  errorMessage.value = error.message || "Failed to generate voiceover";
1046
1215
  progressPercent.value = 0;
1047
1216
  }
1048
1217
  }
1049
- async function pollForCompletion(callbackData, voiceConfig) {
1050
- const maxAttempts = 60;
1051
- const pollInterval = 5e3;
1052
- for (let i = 0; i < maxAttempts; i++) {
1053
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
1054
- progressPercent.value = Math.min(90, 30 + i / maxAttempts * 60);
1055
- try {
1056
- const variants = await fetchVoiceVariants(props.primaryKey);
1057
- const latest = variants.find((v) => v.callback_data === callbackData);
1058
- if (latest && latest.audio_file_id) {
1059
- generatedAudioId.value = latest.audio_file_id;
1060
- generatedAudioUrl.value = await resolveAudioFileUrl(latest.audio_file_id);
1061
- progressPercent.value = 100;
1062
- currentMode.value = "result";
1063
- return;
1064
- }
1065
- } catch (error) {
1066
- console.error("Poll error:", error);
1067
- }
1068
- }
1069
- errorMessage.value = "Generation timed out. Please try again.";
1070
- }
1071
1218
  function retryGeneration() {
1072
1219
  errorMessage.value = null;
1073
1220
  generateVoiceover();
@@ -1099,37 +1246,6 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1099
1246
  console.error("Failed to delete variant:", error);
1100
1247
  }
1101
1248
  }
1102
- async function startProgressPolling(jobId, voiceConfig) {
1103
- currentJobId.value = jobId;
1104
- isPollingProgress.value = true;
1105
- while (isPollingProgress.value) {
1106
- const job = await pollVoicingJob(jobId);
1107
- if (job) {
1108
- progressPercent.value = job.progress;
1109
- processingMessage.value = job.message;
1110
- progressStatus.value = job.status;
1111
- if (job.status === "completed") {
1112
- isPollingProgress.value = false;
1113
- if (job.audio_file_id) {
1114
- await createVoiceVariant(
1115
- props.primaryKey,
1116
- job.audio_file_id,
1117
- voiceConfig
1118
- );
1119
- }
1120
- await refreshVariants();
1121
- currentMode.value = "result";
1122
- return;
1123
- }
1124
- if (job.status.startsWith("failed")) {
1125
- isPollingProgress.value = false;
1126
- errorMessage.value = job.message || "Generation failed";
1127
- return;
1128
- }
1129
- }
1130
- await new Promise((r) => setTimeout(r, 2e3));
1131
- }
1132
- }
1133
1249
  async function refreshVariants() {
1134
1250
  const variants = await fetchVoiceVariants(props.primaryKey);
1135
1251
  allVariants.value = variants;
@@ -1223,40 +1339,83 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1223
1339
  }
1224
1340
  }
1225
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
+ });
1226
1351
  loading.value = true;
1227
1352
  initError.value = null;
1228
1353
  try {
1229
1354
  if (!props.flowId) {
1230
1355
  flowId.value = await fetchFlowId();
1356
+ console.log("[Voice Widget] Fetched flowId:", flowId.value);
1231
1357
  } else {
1232
1358
  flowId.value = props.flowId;
1359
+ console.log("[Voice Widget] Using props flowId:", flowId.value);
1233
1360
  }
1361
+ console.log("[Voice Widget] Fetching voices, tones, styles...");
1234
1362
  const [loadedVoices, loadedTones, loadedStyles] = await Promise.all([
1235
1363
  fetchVoices(props.voicesCollection),
1236
1364
  fetchTones(props.tonesCollection),
1237
1365
  fetchStyles(props.stylesCollection)
1238
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);
1239
1370
  voices.value = loadedVoices;
1240
1371
  tones.value = loadedTones;
1241
1372
  styles.value = loadedStyles;
1373
+ console.log("[Voice Widget] currentVoices (filtered by model):", currentVoices.value.length);
1242
1374
  if (currentVoices.value.length > 0 && !selectedVoiceId.value) {
1243
1375
  selectedVoiceId.value = currentVoices.value[0].id;
1376
+ console.log("[Voice Widget] Auto-selected voice:", selectedVoiceId.value);
1244
1377
  }
1245
1378
  if (tones.value.length > 0 && !selectedToneId.value) {
1246
1379
  selectedToneId.value = tones.value[0].id;
1380
+ console.log("[Voice Widget] Auto-selected tone:", selectedToneId.value);
1247
1381
  }
1248
1382
  if (styles.value.length > 0 && !selectedStyleId.value) {
1249
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();
1250
1390
  }
1251
1391
  if (props.primaryKey) {
1392
+ console.log("[Voice Widget] Checking variants for lesson:", props.primaryKey);
1252
1393
  const variants = await fetchVoiceVariants(props.primaryKey);
1253
- hasExistingVoices.value = variants.length > 0;
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
+ }
1254
1410
  }
1255
1411
  initFromValue();
1412
+ console.log("[Voice Widget] ===== INITIALIZE SUCCESS =====");
1256
1413
  } catch (error) {
1414
+ console.error("[Voice Widget] ===== INITIALIZE ERROR =====", error);
1257
1415
  initError.value = error.message || "Failed to load configuration";
1258
1416
  } finally {
1259
1417
  loading.value = false;
1418
+ console.log("[Voice Widget] loading set to false, currentMode:", currentMode.value);
1260
1419
  }
1261
1420
  }
1262
1421
  async function initFromValue() {
@@ -1300,7 +1459,14 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1300
1459
  selectedVoiceId.value = currentVoices.value[0].id;
1301
1460
  }
1302
1461
  });
1303
- onMounted(initialize);
1462
+ onMounted(() => {
1463
+ console.log("[Voice Widget] onMounted called");
1464
+ initialize();
1465
+ });
1466
+ onUnmounted(() => {
1467
+ stopSamplePolling();
1468
+ stopVoiceoverPolling();
1469
+ });
1304
1470
  return (_ctx, _cache) => {
1305
1471
  const _component_v_icon = resolveComponent("v-icon");
1306
1472
  return openBlock(), createElementBlock("div", _hoisted_1, [
@@ -1508,8 +1674,8 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1508
1674
  key: voice.id,
1509
1675
  voice,
1510
1676
  "is-selected": selectedVoiceId.value === voice.id,
1511
- loading: generatingSampleFor.value === voice.id,
1512
- "sample-url": voiceSamples.value[voice.id] || (voice.sample_file ? `/assets/${voice.sample_file}` : voice.sample_url),
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),
1513
1679
  provider: selectedModel.value,
1514
1680
  onSelect: selectVoice,
1515
1681
  onGenerateVoice: generateVoice
@@ -1768,10 +1934,10 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1768
1934
  }
1769
1935
  });
1770
1936
 
1771
- var css = "\n.voice-widget[data-v-10fc7a16] {\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-10fc7a16] {\n margin-bottom: 20px;\n}\n.widget__header-row[data-v-10fc7a16] {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: 16px;\n}\n.widget__header-text[data-v-10fc7a16] {\n flex: 1;\n}\n.widget__title[data-v-10fc7a16] {\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-10fc7a16] {\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-10fc7a16]:hover {\n background: var(--theme--background-accent);\n}\n.widget__subtitle[data-v-10fc7a16] {\n margin: 0;\n font-size: 14px;\n color: var(--theme--foreground-subdued);\n}\n.widget__header-controls[data-v-10fc7a16] {\n display: flex;\n gap: 16px;\n align-items: center;\n}\n.widget__url-input[data-v-10fc7a16] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.widget__url-label[data-v-10fc7a16] {\n font-size: 12px;\n color: var(--theme--foreground-subdued);\n white-space: nowrap;\n}\n.widget__url-field[data-v-10fc7a16] {\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-10fc7a16] {\n margin-bottom: 20px;\n}\n.widget__section-label[data-v-10fc7a16] {\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-10fc7a16] {\n display: flex;\n gap: 8px;\n}\n.widget__model-btn[data-v-10fc7a16] {\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-10fc7a16]:hover {\n border-color: var(--theme--primary);\n}\n.widget__model-btn--active[data-v-10fc7a16] {\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-10fc7a16] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.widget__section--toggle[data-v-10fc7a16] {\n padding: 12px;\n background: var(--theme--background-subdued);\n border-radius: var(--theme--border-radius);\n}\n.widget__toggle[data-v-10fc7a16] {\n display: flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n}\n.widget__toggle input[data-v-10fc7a16] {\n width: 16px;\n height: 16px;\n cursor: pointer;\n}\n.widget__toggle-label[data-v-10fc7a16] {\n font-size: 14px;\n font-weight: 500;\n color: var(--theme--foreground);\n}\n.widget__toggle-note[data-v-10fc7a16] {\n margin: 4px 0 0 24px;\n font-size: 12px;\n color: var(--theme--foreground-subdued);\n}\n.widget__footer[data-v-10fc7a16] {\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-10fc7a16],\n.widget__footer-right[data-v-10fc7a16] {\n display: flex;\n gap: 8px;\n}\n.widget__variant-nav[data-v-10fc7a16] {\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-10fc7a16] {\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-10fc7a16] {\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-10fc7a16] {\n margin-top: 8px;\n font-size: 12px;\n color: var(--theme--foreground-subdued);\n}\n.widget__btn[data-v-10fc7a16] {\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-10fc7a16]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.widget__btn--primary[data-v-10fc7a16] {\n background: var(--theme--primary);\n color: var(--theme--primary-foreground, #fff);\n}\n.widget__btn--primary[data-v-10fc7a16]:hover:not(:disabled) {\n background: var(--theme--primary-accent);\n}\n.widget__btn--secondary[data-v-10fc7a16] {\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-10fc7a16]:hover:not(:disabled) {\n background: var(--theme--background-normal);\n}\n\n/* Processing State */\n.widget__processing[data-v-10fc7a16] {\n padding: 40px 20px;\n text-align: center;\n}\n.widget__progress[data-v-10fc7a16] {\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-10fc7a16] {\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-10fc7a16] {\n height: 100%;\n background: var(--theme--primary);\n transition: width 0.3s ease;\n}\n\n/* Error State */\n.widget__error[data-v-10fc7a16] {\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-10fc7a16] {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n}\n.widget__retry[data-v-10fc7a16] {\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-10fc7a16] {\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-10fc7a16] {\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-10fc7a16] {\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-10fc7a16] {\n margin: 4px 0;\n font-size: 14px;\n color: var(--theme--foreground-subdued);\n}\n.widget__result-info strong[data-v-10fc7a16] {\n color: var(--theme--foreground);\n}\n\n/* Variants List View */\n.widget__variants-list[data-v-10fc7a16] {\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-10fc7a16] {\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-10fc7a16]:hover {\n border-color: var(--theme--primary);\n}\n.widget__variant-item--selected[data-v-10fc7a16] {\n border-color: var(--theme--primary);\n background: var(--theme--primary-background);\n}\n.widget__variant-radio[data-v-10fc7a16] {\n flex-shrink: 0;\n}\n.widget__variant-radio input[data-v-10fc7a16] {\n width: 16px;\n height: 16px;\n cursor: pointer;\n}\n.widget__variant-player[data-v-10fc7a16] {\n flex: 1;\n min-width: 200px;\n}\n.widget__variant-meta[data-v-10fc7a16] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 120px;\n}\n.widget__variant-voice[data-v-10fc7a16] {\n font-size: 13px;\n font-weight: 500;\n color: var(--theme--foreground);\n}\n.widget__variant-date[data-v-10fc7a16] {\n font-size: 11px;\n color: var(--theme--foreground-subdued);\n}\n.widget__btn--danger[data-v-10fc7a16] {\n color: var(--theme--danger);\n background: transparent;\n border: none;\n}\n.widget__btn--danger[data-v-10fc7a16]:hover {\n background: var(--theme--danger-background);\n}\n.widget__selected-info[data-v-10fc7a16] {\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-10fc7a16] {\n margin: 4px 0;\n font-size: 14px;\n color: var(--theme--foreground-subdued);\n}\n.widget__selected-info strong[data-v-10fc7a16] {\n color: var(--theme--foreground);\n}\n.widget__progress-status[data-v-10fc7a16] {\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-10fc7a16] {\n animation: spin-10fc7a16 1s linear infinite;\n}\n@keyframes spin-10fc7a16 {\nfrom { transform: rotate(0deg);\n}\nto { transform: rotate(360deg);\n}\n}\n";
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";
1772
1938
  n(css,{});
1773
1939
 
1774
- var InterfaceComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-10fc7a16"], ["__file", "interface.vue"]]);
1940
+ var InterfaceComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-214c3276"], ["__file", "interface.vue"]]);
1775
1941
 
1776
1942
  var index = defineInterface({
1777
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.1",
5
+ "version": "1.0.3",
6
6
  "license": "MIT",
7
7
  "readme": "README.md",
8
8
  "repository": {