@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.
Files changed (2) hide show
  1. package/dist/index.js +284 -105
  2. 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 = "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 {
@@ -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
- 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: "" };
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
- const response = await api.get("/items/VoiceVariants", {
677
- params: {
678
- filter: { lesson_id: { _eq: lessonId } },
679
- sort: ["-date_created"],
680
- fields: ["id", "lesson_id", "audio_file_id", "voice_config", "callback_data", "date_created"]
681
- }
682
- });
683
- 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;
684
704
  } catch (error) {
685
- 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);
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 sample_file=${fileUuid}`);
725
+ console.log(`[Voice Widget] Patching ${collection}/${voiceId} with example=${fileUuid}`);
703
726
  await api.patch(`/items/${collection}/${voiceId}`, {
704
- sample_file: fileUuid
727
+ example: fileUuid
705
728
  });
706
- console.log(`[Voice Widget] Updated voice ${voiceId} sample_file to ${fileUuid}`);
729
+ console.log(`[Voice Widget] Updated voice ${voiceId} example to ${fileUuid}`);
707
730
  } catch (error) {
708
- console.error("[Voice Widget] Failed to update voice sample_file:", error);
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("fada8e65-5ce2-477d-b5db-bf7f64a23ffa");
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
- const currentJobId = ref(null);
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
- const effectiveFlowId = flowId.value && flowId.value.trim() ? flowId.value.trim() : void 0;
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].sample_file = url.replace("/assets/", "");
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?.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();
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
- const voiceConfig = {
1007
- provider: selectedModel.value,
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 if (result.status === "processing" && result.callback_data) {
1028
- processingMessage.value = "Processing in background...";
1029
- await pollForCompletion(result.callback_data, voiceConfig);
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
- 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
+ }
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(initialize);
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.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),
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-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";
1759
1938
  n(css,{});
1760
1939
 
1761
- 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"]]);
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.2",
5
+ "version": "1.0.3",
6
6
  "license": "MIT",
7
7
  "readme": "README.md",
8
8
  "repository": {