@stellartech/voice-widget-directus 1.0.0 → 1.0.1

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 +440 -290
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -547,7 +547,8 @@ function useVoicingApi(api) {
547
547
  texts: [SAMPLE_TEXT],
548
548
  provider,
549
549
  preprocessing: false,
550
- title: `Voice Sample - ${voiceId}`
550
+ title: `Voice Sample - ${voiceId}`,
551
+ audio_files_collection: "AudioFiles"
551
552
  };
552
553
  if (provider === "gemini") {
553
554
  voicingPayload.speakers = [{ voice: voiceId, name: "Sample", style: "neutral" }];
@@ -557,14 +558,28 @@ function useVoicingApi(api) {
557
558
  voicingPayload.voice_id = voiceId;
558
559
  }
559
560
  try {
561
+ console.log("[Voice Widget] Calling flow:", effectiveFlowId);
560
562
  const response = await api.post(`/flows/trigger/${effectiveFlowId}`, voicingPayload);
563
+ console.log("[Voice Widget] Full response:", JSON.stringify(response.data, null, 2));
561
564
  const flowResponse = response.data;
562
- const audioFileId = flowResponse?.data?.audio_file_id || flowResponse?.audio_file_id || null;
565
+ let audioFileId = null;
566
+ if (flowResponse?.data?.audio_file_id) {
567
+ audioFileId = flowResponse.data.audio_file_id;
568
+ } else if (flowResponse?.audio_file_id) {
569
+ audioFileId = flowResponse.audio_file_id;
570
+ } else if (typeof flowResponse === "string") {
571
+ try {
572
+ const parsed = JSON.parse(flowResponse);
573
+ audioFileId = parsed?.data?.audio_file_id || parsed?.audio_file_id;
574
+ } catch {
575
+ }
576
+ }
577
+ console.log("[Voice Widget] Extracted audioFileId:", audioFileId);
563
578
  if (audioFileId) {
564
579
  const url = await resolveAudioFileUrl(audioFileId);
565
580
  return { url, audioFileId };
566
581
  }
567
- throw new Error("No audio file returned from voice generation");
582
+ throw new Error("No audio file returned from voice generation. Response: " + JSON.stringify(flowResponse));
568
583
  } catch (e) {
569
584
  console.error("Failed to generate voice sample:", e);
570
585
  throw new Error(e.response?.data?.detail || e.message || "Failed to generate sample");
@@ -576,10 +591,11 @@ function useVoicingApi(api) {
576
591
  if (!effectiveFlowId) {
577
592
  throw new Error("Voice flow ID is not configured. Set it in Widget Config or in the Flow ID field.");
578
593
  }
594
+ const collection = request.collection || "SM_Lessons";
579
595
  let texts = [];
580
596
  let lessonTitle = `Lesson ${request.lessonId}`;
581
597
  try {
582
- const lessonResponse = await api.get(`/items/SM_Lessons/${request.lessonId}`, {
598
+ const lessonResponse = await api.get(`/items/${collection}/${request.lessonId}`, {
583
599
  params: { fields: ["text", "title"] }
584
600
  });
585
601
  const textContent = lessonResponse.data.data?.text || "";
@@ -596,11 +612,24 @@ function useVoicingApi(api) {
596
612
  console.error("Failed to fetch lesson text content:", e);
597
613
  throw new Error(e.message || "Failed to fetch lesson text content");
598
614
  }
615
+ const voiceConfig = {
616
+ provider: request.provider,
617
+ voice_id: request.voiceId,
618
+ style: request.style,
619
+ preprocessing: request.preprocessing,
620
+ tone_id: request.toneId,
621
+ style_id: request.styleId
622
+ };
599
623
  const voicingPayload = {
600
624
  texts,
601
625
  provider: request.provider,
602
626
  preprocessing: request.preprocessing,
603
- title: `Voiceover - ${lessonTitle}`
627
+ title: `Voiceover - ${lessonTitle}`,
628
+ audio_files_collection: "AudioFiles",
629
+ // Pass these so the flow can include them in callback_data
630
+ lesson_id: request.lessonId,
631
+ collection,
632
+ voice_config: voiceConfig
604
633
  };
605
634
  if (request.provider === "gemini") {
606
635
  voicingPayload.speakers = [{
@@ -617,43 +646,20 @@ function useVoicingApi(api) {
617
646
  const response = await api.post(`/flows/trigger/${effectiveFlowId}`, voicingPayload);
618
647
  const flowResponse = response.data;
619
648
  const data = flowResponse?.data || flowResponse;
620
- let fileUuid = null;
621
- if (data?.audio_file_id) {
649
+ let jobId = null;
650
+ if (data?.callback_data) {
622
651
  try {
623
- const audioFilesResponse = await api.get(`/items/AudioFiles/${data.audio_file_id}`, {
624
- params: { fields: ["file"] }
625
- });
626
- fileUuid = audioFilesResponse.data.data?.file || null;
627
- } catch (e) {
628
- console.warn("[Voice Widget] Could not resolve file UUID:", e);
629
- }
630
- if (fileUuid) {
631
- try {
632
- await api.post("/items/VoiceVariants", {
633
- lesson_id: request.lessonId,
634
- audio_file_id: fileUuid,
635
- voice_config: {
636
- provider: request.provider,
637
- voice_id: request.voiceId,
638
- style: request.style,
639
- preprocessing: request.preprocessing,
640
- tone_id: request.toneId,
641
- style_id: request.styleId
642
- },
643
- callback_data: data.callback_data || null,
644
- status: "published"
645
- });
646
- console.log("[Voice Widget] Saved voice variant with file UUID:", fileUuid);
647
- } catch (saveError) {
648
- console.warn("Failed to save voice variant:", saveError);
649
- }
652
+ const callbackData = JSON.parse(data.callback_data);
653
+ jobId = callbackData.job_id || null;
654
+ } catch {
650
655
  }
651
656
  }
652
657
  return {
653
- audio_file_id: fileUuid || data?.audio_file_id || null,
654
- status: data?.status || null,
658
+ audio_file_id: data?.audio_file_id || null,
659
+ status: data?.status || "processing",
655
660
  callback_data: data?.callback_data || null,
656
- generation_time: data?.generation_time || null
661
+ generation_time: data?.generation_time || null,
662
+ job_id: jobId
657
663
  };
658
664
  } catch (e) {
659
665
  console.error("Failed to generate voiceover:", e);
@@ -668,7 +674,8 @@ function useVoicingApi(api) {
668
674
  const response = await api.get(`/items/AudioFiles/${audioFilesRecordId}`, {
669
675
  params: { fields: ["file"] }
670
676
  });
671
- const fileId = response.data.data?.file;
677
+ const fileField = response.data.data?.file;
678
+ const fileId = typeof fileField === "string" ? fileField : fileField?.id;
672
679
  if (fileId) {
673
680
  return `/assets/${fileId}`;
674
681
  }
@@ -694,14 +701,18 @@ function useVoicingApi(api) {
694
701
  }
695
702
  async function updateVoiceSampleFile(voiceId, audioFilesRecordId, collection = "Voices") {
696
703
  try {
704
+ console.log(`[Voice Widget] updateVoiceSampleFile called with voiceId=${voiceId}, audioFilesRecordId=${audioFilesRecordId}, collection=${collection}`);
697
705
  const response = await api.get(`/items/AudioFiles/${audioFilesRecordId}`, {
698
706
  params: { fields: ["file"] }
699
707
  });
700
- const fileUuid = response.data.data?.file;
708
+ console.log("[Voice Widget] AudioFiles response:", response.data);
709
+ const fileField = response.data.data?.file;
710
+ const fileUuid = typeof fileField === "string" ? fileField : fileField?.id;
701
711
  if (!fileUuid) {
702
- console.warn("[Voice Widget] Could not get file UUID from AudioFiles record");
712
+ console.warn("[Voice Widget] Could not get file UUID from AudioFiles record. fileField:", fileField);
703
713
  return;
704
714
  }
715
+ console.log(`[Voice Widget] Patching ${collection}/${voiceId} with sample_file=${fileUuid}`);
705
716
  await api.patch(`/items/${collection}/${voiceId}`, {
706
717
  sample_file: fileUuid
707
718
  });
@@ -710,6 +721,66 @@ function useVoicingApi(api) {
710
721
  console.error("[Voice Widget] Failed to update voice sample_file:", error);
711
722
  }
712
723
  }
724
+ async function deleteVoiceVariant(variantId) {
725
+ try {
726
+ await api.delete(`/items/VoiceVariants/${variantId}`);
727
+ console.log(`[Voice Widget] Deleted voice variant: ${variantId}`);
728
+ } catch (error) {
729
+ console.error("[Voice Widget] Failed to delete voice variant:", error);
730
+ throw error;
731
+ }
732
+ }
733
+ async function createVoiceVariant(lessonId, audioFileId, voiceConfig) {
734
+ try {
735
+ const response = await api.post("/items/VoiceVariants", {
736
+ lesson_id: lessonId,
737
+ audio_file_id: audioFileId,
738
+ voice_config: voiceConfig,
739
+ status: "draft"
740
+ });
741
+ const variantId = response.data.data?.id;
742
+ console.log(`[Voice Widget] Created VoiceVariant: ${variantId}`);
743
+ return variantId;
744
+ } catch (error) {
745
+ console.error("[Voice Widget] Failed to create VoiceVariant:", error);
746
+ throw error;
747
+ }
748
+ }
749
+ async function linkAudioToLesson(lessonId, audioFileId, collection) {
750
+ try {
751
+ await api.patch(`/items/${collection}/${lessonId}`, {
752
+ audio: audioFileId
753
+ });
754
+ console.log(`[Voice Widget] Linked audio ${audioFileId} to lesson ${lessonId} in ${collection}`);
755
+ } catch (error) {
756
+ console.error("[Voice Widget] Failed to link audio to lesson:", error);
757
+ throw error;
758
+ }
759
+ }
760
+ async function pollVoicingJob(jobId) {
761
+ try {
762
+ const response = await api.get("/items/VoicingJobs", {
763
+ params: {
764
+ filter: { job_id: { _eq: jobId } },
765
+ fields: ["status", "progress", "message", "audio_file_id"],
766
+ limit: 1
767
+ }
768
+ });
769
+ const job = response.data.data?.[0];
770
+ if (job) {
771
+ return {
772
+ status: job.status,
773
+ progress: job.progress,
774
+ message: job.message,
775
+ audio_file_id: job.audio_file_id
776
+ };
777
+ }
778
+ return null;
779
+ } catch (error) {
780
+ console.error("[Voice Widget] Failed to poll voicing job:", error);
781
+ return null;
782
+ }
783
+ }
713
784
  return {
714
785
  fetchVoices,
715
786
  fetchFlowId,
@@ -720,7 +791,11 @@ function useVoicingApi(api) {
720
791
  getAudioUrl,
721
792
  resolveAudioFileUrl,
722
793
  fetchVoiceVariants,
723
- updateVoiceSampleFile
794
+ updateVoiceSampleFile,
795
+ deleteVoiceVariant,
796
+ createVoiceVariant,
797
+ linkAudioToLesson,
798
+ pollVoicingJob
724
799
  };
725
800
  }
726
801
 
@@ -733,49 +808,56 @@ const _hoisted_3 = { class: "widget__progress" };
733
808
  const _hoisted_4 = { class: "widget__progress-bar" };
734
809
  const _hoisted_5 = {
735
810
  key: 0,
811
+ class: "widget__progress-status"
812
+ };
813
+ const _hoisted_6 = {
814
+ key: 1,
736
815
  class: "widget__error"
737
816
  };
738
- const _hoisted_6 = { class: "widget__loading" };
739
- const _hoisted_7 = { class: "widget__error" };
740
- const _hoisted_8 = { class: "widget__header" };
741
- const _hoisted_9 = { class: "widget__header-row" };
742
- const _hoisted_10 = { class: "widget__header-text" };
743
- const _hoisted_11 = { class: "widget__title" };
744
- const _hoisted_12 = ["title"];
745
- const _hoisted_13 = { class: "widget__header-controls" };
746
- const _hoisted_14 = { class: "widget__url-input" };
747
- const _hoisted_15 = ["disabled"];
748
- const _hoisted_16 = { class: "widget__section" };
749
- const _hoisted_17 = { class: "widget__model-buttons" };
750
- const _hoisted_18 = ["onClick"];
751
- const _hoisted_19 = { class: "widget__section" };
752
- const _hoisted_20 = { class: "widget__voice-list" };
753
- const _hoisted_21 = { class: "widget__section" };
817
+ const _hoisted_7 = { class: "widget__loading" };
818
+ const _hoisted_8 = { class: "widget__error" };
819
+ const _hoisted_9 = { class: "widget__header" };
820
+ const _hoisted_10 = { class: "widget__header-row" };
821
+ const _hoisted_11 = { class: "widget__header-text" };
822
+ const _hoisted_12 = { class: "widget__title" };
823
+ const _hoisted_13 = ["title"];
824
+ const _hoisted_14 = { class: "widget__header-controls" };
825
+ const _hoisted_15 = { class: "widget__url-input" };
826
+ const _hoisted_16 = ["disabled"];
827
+ const _hoisted_17 = { class: "widget__section" };
828
+ const _hoisted_18 = { class: "widget__model-buttons" };
829
+ const _hoisted_19 = ["onClick"];
830
+ const _hoisted_20 = { class: "widget__section" };
831
+ const _hoisted_21 = { class: "widget__voice-list" };
754
832
  const _hoisted_22 = { class: "widget__section" };
755
- const _hoisted_23 = { class: "widget__section widget__section--toggle" };
756
- const _hoisted_24 = { class: "widget__toggle" };
757
- const _hoisted_25 = { class: "widget__footer" };
758
- const _hoisted_26 = { class: "widget__footer-right" };
759
- const _hoisted_27 = ["disabled"];
833
+ const _hoisted_23 = { class: "widget__section" };
834
+ const _hoisted_24 = { class: "widget__section widget__section--toggle" };
835
+ const _hoisted_25 = { class: "widget__toggle" };
836
+ const _hoisted_26 = { class: "widget__footer" };
837
+ const _hoisted_27 = { class: "widget__footer-right" };
760
838
  const _hoisted_28 = ["disabled"];
761
- const _hoisted_29 = { class: "widget__header" };
762
- const _hoisted_30 = { class: "widget__header-row" };
763
- const _hoisted_31 = { class: "widget__header-text" };
764
- const _hoisted_32 = { class: "widget__title" };
765
- const _hoisted_33 = { class: "widget__subtitle" };
766
- const _hoisted_34 = {
839
+ const _hoisted_29 = ["disabled"];
840
+ const _hoisted_30 = { class: "widget__header" };
841
+ const _hoisted_31 = { class: "widget__header-row" };
842
+ const _hoisted_32 = { class: "widget__header-text" };
843
+ const _hoisted_33 = { class: "widget__title" };
844
+ const _hoisted_34 = { class: "widget__subtitle" };
845
+ const _hoisted_35 = { class: "widget__variants-list" };
846
+ const _hoisted_36 = ["onClick"];
847
+ const _hoisted_37 = { class: "widget__variant-radio" };
848
+ const _hoisted_38 = ["checked", "onChange"];
849
+ const _hoisted_39 = { class: "widget__variant-player" };
850
+ const _hoisted_40 = { class: "widget__variant-meta" };
851
+ const _hoisted_41 = { class: "widget__variant-voice" };
852
+ const _hoisted_42 = { class: "widget__variant-date" };
853
+ const _hoisted_43 = ["onClick"];
854
+ const _hoisted_44 = {
767
855
  key: 0,
768
- class: "widget__variant-nav"
769
- };
770
- const _hoisted_35 = ["disabled"];
771
- const _hoisted_36 = { class: "widget__variant-counter" };
772
- const _hoisted_37 = ["disabled"];
773
- const _hoisted_38 = { class: "widget__result" };
774
- const _hoisted_39 = { class: "widget__result-info" };
775
- const _hoisted_40 = {
776
- key: 0,
777
- class: "widget__result-date"
856
+ class: "widget__selected-info"
778
857
  };
858
+ const _hoisted_45 = { class: "widget__footer" };
859
+ const _hoisted_46 = { class: "widget__footer-right" };
860
+ const _hoisted_47 = ["disabled"];
779
861
  var _sfc_main = /* @__PURE__ */ defineComponent({
780
862
  __name: "interface",
781
863
  props: {
@@ -804,7 +886,11 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
804
886
  getAudioUrl,
805
887
  resolveAudioFileUrl,
806
888
  fetchVoiceVariants,
807
- updateVoiceSampleFile
889
+ updateVoiceSampleFile,
890
+ deleteVoiceVariant,
891
+ createVoiceVariant,
892
+ linkAudioToLesson,
893
+ pollVoicingJob
808
894
  } = useVoicingApi(api);
809
895
  const currentMode = ref("selection");
810
896
  const loading = ref(true);
@@ -832,13 +918,17 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
832
918
  const hasExistingVoices = ref(false);
833
919
  const allVariants = ref([]);
834
920
  const currentVariantIndex = ref(0);
921
+ const currentJobId = ref(null);
922
+ const isPollingProgress = ref(false);
923
+ const progressStatus = ref("");
924
+ const selectedVariant = computed(() => allVariants.value[currentVariantIndex.value]);
835
925
  const currentVoices = computed(() => {
836
926
  return voices.value.filter((v) => v.provider === selectedModel.value);
837
927
  });
838
928
  const canGenerate = computed(() => {
839
929
  return selectedVoiceId.value !== null && selectedToneId.value !== null && selectedStyleId.value !== null;
840
930
  });
841
- const currentVariantDate = computed(() => {
931
+ computed(() => {
842
932
  const variant = allVariants.value[currentVariantIndex.value];
843
933
  if (!variant?.date_created) return null;
844
934
  return new Date(variant.date_created).toLocaleString();
@@ -906,15 +996,15 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
906
996
  if (!canGenerate.value) return;
907
997
  currentMode.value = "progress";
908
998
  errorMessage.value = null;
909
- processingMessage.value = "Generating voiceover...";
910
- progressPercent.value = 10;
999
+ processingMessage.value = "Starting generation...";
1000
+ progressPercent.value = 5;
1001
+ progressStatus.value = "";
911
1002
  try {
912
1003
  const tonePrompt = getSelectedTonePrompt();
913
1004
  const stylePrompt = getSelectedStylePrompt();
914
1005
  const combinedStyle = `${tonePrompt}. ${stylePrompt}`.trim();
915
1006
  const selectedVoice = voices.value.find((v) => v.id === selectedVoiceId.value);
916
1007
  const providerVoiceId = selectedVoice?.voice_id || selectedVoiceId.value;
917
- progressPercent.value = 30;
918
1008
  const result = await generateFullVoiceover({
919
1009
  provider: selectedModel.value,
920
1010
  voiceId: providerVoiceId,
@@ -922,25 +1012,41 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
922
1012
  preprocessing: preprocessingEnabled.value,
923
1013
  flowId: flowId.value,
924
1014
  lessonId: props.primaryKey,
1015
+ collection: props.collection || "SM_Lessons",
925
1016
  toneId: selectedToneId.value || void 0,
926
1017
  styleId: selectedStyleId.value || void 0
927
1018
  });
928
- progressPercent.value = 90;
929
- if (result.audio_file_id) {
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) {
930
1030
  generatedAudioId.value = result.audio_file_id;
931
1031
  generatedAudioUrl.value = getAudioUrl(result.audio_file_id);
932
1032
  progressPercent.value = 100;
1033
+ await createVoiceVariant(
1034
+ props.primaryKey,
1035
+ result.audio_file_id,
1036
+ voiceConfig
1037
+ );
1038
+ await refreshVariants();
933
1039
  currentMode.value = "result";
934
1040
  } else if (result.status === "processing" && result.callback_data) {
935
1041
  processingMessage.value = "Processing in background...";
936
- await pollForCompletion(result.callback_data);
1042
+ await pollForCompletion(result.callback_data, voiceConfig);
937
1043
  }
938
1044
  } catch (error) {
939
1045
  errorMessage.value = error.message || "Failed to generate voiceover";
940
1046
  progressPercent.value = 0;
941
1047
  }
942
1048
  }
943
- async function pollForCompletion(callbackData) {
1049
+ async function pollForCompletion(callbackData, voiceConfig) {
944
1050
  const maxAttempts = 60;
945
1051
  const pollInterval = 5e3;
946
1052
  for (let i = 0; i < maxAttempts; i++) {
@@ -970,18 +1076,76 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
970
1076
  currentMode.value = "selection";
971
1077
  errorMessage.value = null;
972
1078
  progressPercent.value = 0;
1079
+ progressStatus.value = "";
1080
+ isPollingProgress.value = false;
1081
+ }
1082
+ function selectVariant(index) {
1083
+ currentVariantIndex.value = index;
1084
+ loadVariantAtIndex(index);
973
1085
  }
974
- async function prevVariant() {
975
- if (currentVariantIndex.value > 0) {
976
- currentVariantIndex.value--;
977
- await loadVariantAtIndex(currentVariantIndex.value);
1086
+ async function deleteVariant(variantId) {
1087
+ if (!confirm("Delete this voiceover?")) return;
1088
+ try {
1089
+ await deleteVoiceVariant(variantId);
1090
+ allVariants.value = allVariants.value.filter((v) => v.id !== variantId);
1091
+ if (currentVariantIndex.value >= allVariants.value.length) {
1092
+ currentVariantIndex.value = Math.max(0, allVariants.value.length - 1);
1093
+ }
1094
+ if (allVariants.value.length === 0) {
1095
+ hasExistingVoices.value = false;
1096
+ goBackToSelection();
1097
+ }
1098
+ } catch (error) {
1099
+ console.error("Failed to delete variant:", error);
1100
+ }
1101
+ }
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));
978
1131
  }
979
1132
  }
980
- async function nextVariant() {
981
- if (currentVariantIndex.value < allVariants.value.length - 1) {
982
- currentVariantIndex.value++;
983
- await loadVariantAtIndex(currentVariantIndex.value);
1133
+ async function refreshVariants() {
1134
+ const variants = await fetchVoiceVariants(props.primaryKey);
1135
+ allVariants.value = variants;
1136
+ for (const v of allVariants.value) {
1137
+ const isUuid = v.audio_file_id?.includes("-");
1138
+ v.audioUrl = isUuid ? getAudioUrl(v.audio_file_id) : await resolveAudioFileUrl(v.audio_file_id);
984
1139
  }
1140
+ currentVariantIndex.value = 0;
1141
+ hasExistingVoices.value = variants.length > 0;
1142
+ }
1143
+ function formatVariantDate(dateStr) {
1144
+ if (!dateStr) return "";
1145
+ return new Date(dateStr).toLocaleString();
1146
+ }
1147
+ function getVoiceNameFromConfig(config) {
1148
+ return config?.voice_id || "Unknown";
985
1149
  }
986
1150
  async function loadVariantAtIndex(index) {
987
1151
  const variant = allVariants.value[index];
@@ -1015,25 +1179,41 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1015
1179
  function regenerateVoiceover() {
1016
1180
  generateVoiceover();
1017
1181
  }
1018
- function confirmVoiceover() {
1019
- const value = {
1020
- audio_file_id: generatedAudioId.value,
1021
- model: selectedModel.value,
1022
- voice_id: selectedVoiceId.value,
1023
- tone_id: selectedToneId.value,
1024
- style_id: selectedStyleId.value,
1025
- custom_tone: customTone.value,
1026
- custom_style: customStyle.value,
1027
- preprocessing: preprocessingEnabled.value,
1028
- confirmed_at: (/* @__PURE__ */ new Date()).toISOString()
1029
- };
1030
- emit("input", value);
1182
+ async function confirmVoiceover() {
1183
+ const variant = selectedVariant.value;
1184
+ if (!variant) return;
1185
+ try {
1186
+ await linkAudioToLesson(
1187
+ props.primaryKey,
1188
+ variant.audio_file_id,
1189
+ props.collection || "SM_Lessons"
1190
+ );
1191
+ const value = {
1192
+ audio_file_id: variant.audio_file_id,
1193
+ model: variant.voice_config?.provider || selectedModel.value,
1194
+ voice_id: variant.voice_config?.voice_id || selectedVoiceId.value,
1195
+ tone_id: variant.voice_config?.tone_id || selectedToneId.value,
1196
+ style_id: variant.voice_config?.style_id || selectedStyleId.value,
1197
+ custom_tone: customTone.value,
1198
+ custom_style: customStyle.value,
1199
+ preprocessing: variant.voice_config?.preprocessing ?? preprocessingEnabled.value,
1200
+ confirmed_at: (/* @__PURE__ */ new Date()).toISOString()
1201
+ };
1202
+ emit("input", value);
1203
+ goBackToSelection();
1204
+ } catch (error) {
1205
+ console.error("Failed to link audio:", error);
1206
+ }
1031
1207
  }
1032
1208
  async function viewGeneratedVoices() {
1033
1209
  try {
1034
1210
  const variants = await fetchVoiceVariants(props.primaryKey);
1035
1211
  if (variants.length > 0) {
1036
1212
  allVariants.value = variants;
1213
+ for (const v of allVariants.value) {
1214
+ const isUuid = v.audio_file_id?.includes("-");
1215
+ v.audioUrl = isUuid ? getAudioUrl(v.audio_file_id) : await resolveAudioFileUrl(v.audio_file_id);
1216
+ }
1037
1217
  currentVariantIndex.value = 0;
1038
1218
  await loadVariantAtIndex(0);
1039
1219
  currentMode.value = "result";
@@ -1042,27 +1222,6 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1042
1222
  console.error("Failed to fetch voice variants:", error);
1043
1223
  }
1044
1224
  }
1045
- function getModelName(modelId) {
1046
- const model = VOICE_MODELS.find((m) => m.id === modelId);
1047
- return model?.name || modelId;
1048
- }
1049
- function getVoiceName(voiceId) {
1050
- if (!voiceId) return "Not selected";
1051
- const voice = voices.value.find((v) => v.id === voiceId);
1052
- return voice?.name || voiceId;
1053
- }
1054
- function getToneName(toneId) {
1055
- if (!toneId) return "Not selected";
1056
- if (toneId === "other") return customTone.value || "Custom";
1057
- const tone = tones.value.find((t) => t.id === toneId);
1058
- return tone?.name || toneId;
1059
- }
1060
- function getStyleName(styleId) {
1061
- if (!styleId) return "Not selected";
1062
- if (styleId === "other") return customStyle.value || "Custom";
1063
- const style = styles.value.find((s) => s.id === styleId);
1064
- return style?.name || styleId;
1065
- }
1066
1225
  async function initialize() {
1067
1226
  loading.value = true;
1068
1227
  initError.value = null;
@@ -1172,7 +1331,14 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1172
1331
  /* STYLE */
1173
1332
  )
1174
1333
  ]),
1175
- errorMessage.value ? (openBlock(), createElementBlock("div", _hoisted_5, [
1334
+ progressStatus.value && !errorMessage.value ? (openBlock(), createElementBlock(
1335
+ "p",
1336
+ _hoisted_5,
1337
+ " Status: " + toDisplayString(progressStatus.value),
1338
+ 1
1339
+ /* TEXT */
1340
+ )) : createCommentVNode("v-if", true),
1341
+ errorMessage.value ? (openBlock(), createElementBlock("div", _hoisted_6, [
1176
1342
  createVNode(_component_v_icon, { name: "error" }),
1177
1343
  createElementVNode(
1178
1344
  "span",
@@ -1197,7 +1363,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1197
1363
  { key: 1 },
1198
1364
  [
1199
1365
  createCommentVNode(" Loading State "),
1200
- createElementVNode("div", _hoisted_6, [
1366
+ createElementVNode("div", _hoisted_7, [
1201
1367
  createVNode(_component_v_icon, {
1202
1368
  name: "refresh",
1203
1369
  class: "spinning"
@@ -1218,7 +1384,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1218
1384
  { key: 2 },
1219
1385
  [
1220
1386
  createCommentVNode(" Error State "),
1221
- createElementVNode("div", _hoisted_7, [
1387
+ createElementVNode("div", _hoisted_8, [
1222
1388
  createVNode(_component_v_icon, { name: "error" }),
1223
1389
  createElementVNode(
1224
1390
  "span",
@@ -1240,10 +1406,10 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1240
1406
  { key: 3 },
1241
1407
  [
1242
1408
  createCommentVNode(" Page 1: Selection Mode "),
1243
- createElementVNode("div", _hoisted_8, [
1244
- createElementVNode("div", _hoisted_9, [
1245
- createElementVNode("div", _hoisted_10, [
1246
- createElementVNode("h2", _hoisted_11, [
1409
+ createElementVNode("div", _hoisted_9, [
1410
+ createElementVNode("div", _hoisted_10, [
1411
+ createElementVNode("div", _hoisted_11, [
1412
+ createElementVNode("h2", _hoisted_12, [
1247
1413
  createVNode(_component_v_icon, { name: "mic" }),
1248
1414
  _cache[4] || (_cache[4] = createTextVNode(
1249
1415
  " Voice Generation ",
@@ -1260,7 +1426,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1260
1426
  name: headerExpanded.value ? "expand_less" : "expand_more",
1261
1427
  small: ""
1262
1428
  }, null, 8, ["name"])
1263
- ], 8, _hoisted_12)
1429
+ ], 8, _hoisted_13)
1264
1430
  ]),
1265
1431
  _cache[5] || (_cache[5] = createElementVNode(
1266
1432
  "p",
@@ -1272,9 +1438,9 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1272
1438
  ]),
1273
1439
  withDirectives(createElementVNode(
1274
1440
  "div",
1275
- _hoisted_13,
1441
+ _hoisted_14,
1276
1442
  [
1277
- createElementVNode("div", _hoisted_14, [
1443
+ createElementVNode("div", _hoisted_15, [
1278
1444
  _cache[6] || (_cache[6] = createElementVNode(
1279
1445
  "label",
1280
1446
  { class: "widget__url-label" },
@@ -1287,7 +1453,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1287
1453
  class: "widget__url-field",
1288
1454
  "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => flowId.value = $event),
1289
1455
  disabled: savingUrl.value
1290
- }, null, 8, _hoisted_15), [
1456
+ }, null, 8, _hoisted_16), [
1291
1457
  [vModelText, flowId.value]
1292
1458
  ])
1293
1459
  ])
@@ -1300,7 +1466,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1300
1466
  ])
1301
1467
  ]),
1302
1468
  createCommentVNode(" Model Selection "),
1303
- createElementVNode("div", _hoisted_16, [
1469
+ createElementVNode("div", _hoisted_17, [
1304
1470
  _cache[7] || (_cache[7] = createElementVNode(
1305
1471
  "label",
1306
1472
  { class: "widget__section-label" },
@@ -1308,7 +1474,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1308
1474
  -1
1309
1475
  /* CACHED */
1310
1476
  )),
1311
- createElementVNode("div", _hoisted_17, [
1477
+ createElementVNode("div", _hoisted_18, [
1312
1478
  (openBlock(true), createElementBlock(
1313
1479
  Fragment,
1314
1480
  null,
@@ -1317,7 +1483,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1317
1483
  key: model.id,
1318
1484
  class: normalizeClass(["widget__model-btn", { "widget__model-btn--active": selectedModel.value === model.id }]),
1319
1485
  onClick: ($event) => selectModel(model.id)
1320
- }, toDisplayString(model.name), 11, _hoisted_18);
1486
+ }, toDisplayString(model.name), 11, _hoisted_19);
1321
1487
  }),
1322
1488
  128
1323
1489
  /* KEYED_FRAGMENT */
@@ -1325,7 +1491,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1325
1491
  ])
1326
1492
  ]),
1327
1493
  createCommentVNode(" Voice Selection "),
1328
- createElementVNode("div", _hoisted_19, [
1494
+ createElementVNode("div", _hoisted_20, [
1329
1495
  _cache[8] || (_cache[8] = createElementVNode(
1330
1496
  "label",
1331
1497
  { class: "widget__section-label" },
@@ -1333,7 +1499,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1333
1499
  -1
1334
1500
  /* CACHED */
1335
1501
  )),
1336
- createElementVNode("div", _hoisted_20, [
1502
+ createElementVNode("div", _hoisted_21, [
1337
1503
  (openBlock(true), createElementBlock(
1338
1504
  Fragment,
1339
1505
  null,
@@ -1355,7 +1521,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1355
1521
  ])
1356
1522
  ]),
1357
1523
  createCommentVNode(" Tone Selection "),
1358
- createElementVNode("div", _hoisted_21, [
1524
+ createElementVNode("div", _hoisted_22, [
1359
1525
  _cache[9] || (_cache[9] = createElementVNode(
1360
1526
  "label",
1361
1527
  { class: "widget__section-label" },
@@ -1373,7 +1539,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1373
1539
  }, null, 8, ["items", "selected-id", "custom-value"])
1374
1540
  ]),
1375
1541
  createCommentVNode(" Style Selection "),
1376
- createElementVNode("div", _hoisted_22, [
1542
+ createElementVNode("div", _hoisted_23, [
1377
1543
  _cache[10] || (_cache[10] = createElementVNode(
1378
1544
  "label",
1379
1545
  { class: "widget__section-label" },
@@ -1391,8 +1557,8 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1391
1557
  }, null, 8, ["items", "selected-id", "custom-value"])
1392
1558
  ]),
1393
1559
  createCommentVNode(" Preprocessing Toggle "),
1394
- createElementVNode("div", _hoisted_23, [
1395
- createElementVNode("label", _hoisted_24, [
1560
+ createElementVNode("div", _hoisted_24, [
1561
+ createElementVNode("label", _hoisted_25, [
1396
1562
  withDirectives(createElementVNode(
1397
1563
  "input",
1398
1564
  {
@@ -1422,7 +1588,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1422
1588
  ))
1423
1589
  ]),
1424
1590
  createCommentVNode(" Footer Actions "),
1425
- createElementVNode("div", _hoisted_25, [
1591
+ createElementVNode("div", _hoisted_26, [
1426
1592
  _cache[13] || (_cache[13] = createElementVNode(
1427
1593
  "div",
1428
1594
  { class: "widget__footer-left" },
@@ -1430,17 +1596,17 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1430
1596
  -1
1431
1597
  /* CACHED */
1432
1598
  )),
1433
- createElementVNode("div", _hoisted_26, [
1599
+ createElementVNode("div", _hoisted_27, [
1434
1600
  createElementVNode("button", {
1435
1601
  class: "widget__btn widget__btn--secondary",
1436
1602
  onClick: viewGeneratedVoices,
1437
1603
  disabled: !hasExistingVoices.value
1438
- }, " View Generated Voices ", 8, _hoisted_27),
1604
+ }, " View Generated Voices ", 8, _hoisted_28),
1439
1605
  createElementVNode("button", {
1440
1606
  class: "widget__btn widget__btn--primary",
1441
1607
  onClick: generateVoiceover,
1442
1608
  disabled: !canGenerate.value
1443
- }, " Generate Voiceover ", 8, _hoisted_28)
1609
+ }, " Generate Voiceover ", 8, _hoisted_29)
1444
1610
  ])
1445
1611
  ])
1446
1612
  ],
@@ -1450,163 +1616,147 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1450
1616
  Fragment,
1451
1617
  { key: 4 },
1452
1618
  [
1453
- createCommentVNode(" Page 3: Result Mode "),
1454
- createElementVNode("div", _hoisted_29, [
1455
- createElementVNode("div", _hoisted_30, [
1456
- createElementVNode("div", _hoisted_31, [
1457
- createElementVNode("h2", _hoisted_32, [
1619
+ createCommentVNode(" Page 3: Result Mode - List View "),
1620
+ createElementVNode("div", _hoisted_30, [
1621
+ createElementVNode("div", _hoisted_31, [
1622
+ createElementVNode("div", _hoisted_32, [
1623
+ createElementVNode("h2", _hoisted_33, [
1458
1624
  createVNode(_component_v_icon, { name: "check_circle" }),
1459
1625
  _cache[14] || (_cache[14] = createTextVNode(
1460
- " Voiceover Generated ",
1626
+ " Generated Voiceovers ",
1461
1627
  -1
1462
1628
  /* CACHED */
1463
1629
  ))
1464
1630
  ]),
1465
- createElementVNode("p", _hoisted_33, [
1466
- allVariants.value.length > 1 ? (openBlock(), createElementBlock(
1467
- Fragment,
1468
- { key: 0 },
1469
- [
1470
- createTextVNode(
1471
- " Viewing " + toDisplayString(currentVariantIndex.value + 1) + " of " + toDisplayString(allVariants.value.length) + " generated voiceovers ",
1472
- 1
1473
- /* TEXT */
1474
- )
1475
- ],
1476
- 64
1477
- /* STABLE_FRAGMENT */
1478
- )) : (openBlock(), createElementBlock(
1479
- Fragment,
1480
- { key: 1 },
1481
- [
1482
- createTextVNode(" Listen to your generated voiceover and confirm or regenerate ")
1483
- ],
1484
- 64
1485
- /* STABLE_FRAGMENT */
1486
- ))
1487
- ])
1488
- ])
1489
- ])
1490
- ]),
1491
- createCommentVNode(" Navigation for multiple variants "),
1492
- allVariants.value.length > 1 ? (openBlock(), createElementBlock("div", _hoisted_34, [
1493
- createElementVNode("button", {
1494
- class: "widget__btn widget__btn--icon",
1495
- onClick: prevVariant,
1496
- disabled: currentVariantIndex.value === 0
1497
- }, [
1498
- createVNode(_component_v_icon, { name: "chevron_left" })
1499
- ], 8, _hoisted_35),
1500
- createElementVNode(
1501
- "span",
1502
- _hoisted_36,
1503
- toDisplayString(currentVariantIndex.value + 1) + " / " + toDisplayString(allVariants.value.length),
1504
- 1
1505
- /* TEXT */
1506
- ),
1507
- createElementVNode("button", {
1508
- class: "widget__btn widget__btn--icon",
1509
- onClick: nextVariant,
1510
- disabled: currentVariantIndex.value === allVariants.value.length - 1
1511
- }, [
1512
- createVNode(_component_v_icon, { name: "chevron_right" })
1513
- ], 8, _hoisted_37)
1514
- ])) : createCommentVNode("v-if", true),
1515
- createElementVNode("div", _hoisted_38, [
1516
- createVNode(AudioPlayer, {
1517
- src: generatedAudioUrl.value,
1518
- loading: false,
1519
- size: "large"
1520
- }, null, 8, ["src"]),
1521
- createElementVNode("div", _hoisted_39, [
1522
- createElementVNode("p", null, [
1523
- _cache[15] || (_cache[15] = createElementVNode(
1524
- "strong",
1525
- null,
1526
- "Model:",
1527
- -1
1528
- /* CACHED */
1529
- )),
1530
- createTextVNode(
1531
- " " + toDisplayString(getModelName(selectedModel.value)),
1532
- 1
1533
- /* TEXT */
1534
- )
1535
- ]),
1536
- createElementVNode("p", null, [
1537
- _cache[16] || (_cache[16] = createElementVNode(
1538
- "strong",
1539
- null,
1540
- "Voice:",
1541
- -1
1542
- /* CACHED */
1543
- )),
1544
- createTextVNode(
1545
- " " + toDisplayString(getVoiceName(selectedVoiceId.value)),
1546
- 1
1547
- /* TEXT */
1548
- )
1549
- ]),
1550
- createElementVNode("p", null, [
1551
- _cache[17] || (_cache[17] = createElementVNode(
1552
- "strong",
1553
- null,
1554
- "Tone:",
1555
- -1
1556
- /* CACHED */
1557
- )),
1558
- createTextVNode(
1559
- " " + toDisplayString(getToneName(selectedToneId.value)),
1560
- 1
1561
- /* TEXT */
1562
- )
1563
- ]),
1564
- createElementVNode("p", null, [
1565
- _cache[18] || (_cache[18] = createElementVNode(
1566
- "strong",
1567
- null,
1568
- "Style:",
1569
- -1
1570
- /* CACHED */
1571
- )),
1572
- createTextVNode(
1573
- " " + toDisplayString(getStyleName(selectedStyleId.value)),
1574
- 1
1575
- /* TEXT */
1576
- )
1577
- ]),
1578
- currentVariantDate.value ? (openBlock(), createElementBlock("p", _hoisted_40, [
1579
- _cache[19] || (_cache[19] = createElementVNode(
1580
- "strong",
1581
- null,
1582
- "Generated:",
1583
- -1
1584
- /* CACHED */
1585
- )),
1586
- createTextVNode(
1587
- " " + toDisplayString(currentVariantDate.value),
1631
+ createElementVNode(
1632
+ "p",
1633
+ _hoisted_34,
1634
+ toDisplayString(allVariants.value.length) + " voiceover(s) available. Select one to preview and confirm. ",
1588
1635
  1
1589
1636
  /* TEXT */
1590
1637
  )
1591
- ])) : createCommentVNode("v-if", true)
1638
+ ])
1592
1639
  ])
1593
1640
  ]),
1594
- createElementVNode("div", { class: "widget__footer" }, [
1641
+ createCommentVNode(" Variants List "),
1642
+ createElementVNode("div", _hoisted_35, [
1643
+ (openBlock(true), createElementBlock(
1644
+ Fragment,
1645
+ null,
1646
+ renderList(allVariants.value, (variant, index) => {
1647
+ return openBlock(), createElementBlock("div", {
1648
+ key: variant.id,
1649
+ class: normalizeClass(["widget__variant-item", { "widget__variant-item--selected": currentVariantIndex.value === index }]),
1650
+ onClick: ($event) => selectVariant(index)
1651
+ }, [
1652
+ createElementVNode("div", _hoisted_37, [
1653
+ createElementVNode("input", {
1654
+ type: "radio",
1655
+ checked: currentVariantIndex.value === index,
1656
+ onChange: ($event) => selectVariant(index)
1657
+ }, null, 40, _hoisted_38)
1658
+ ]),
1659
+ createElementVNode("div", _hoisted_39, [
1660
+ createVNode(AudioPlayer, {
1661
+ src: variant.audioUrl,
1662
+ loading: false,
1663
+ size: "small"
1664
+ }, null, 8, ["src"])
1665
+ ]),
1666
+ createElementVNode("div", _hoisted_40, [
1667
+ createElementVNode(
1668
+ "span",
1669
+ _hoisted_41,
1670
+ toDisplayString(variant.voice_config?.provider) + " - " + toDisplayString(getVoiceNameFromConfig(variant.voice_config)),
1671
+ 1
1672
+ /* TEXT */
1673
+ ),
1674
+ createElementVNode(
1675
+ "span",
1676
+ _hoisted_42,
1677
+ toDisplayString(formatVariantDate(variant.date_created)),
1678
+ 1
1679
+ /* TEXT */
1680
+ )
1681
+ ]),
1682
+ createElementVNode("button", {
1683
+ class: "widget__btn widget__btn--icon widget__btn--danger",
1684
+ onClick: withModifiers(($event) => deleteVariant(variant.id), ["stop"]),
1685
+ title: "Delete this voiceover"
1686
+ }, [
1687
+ createVNode(_component_v_icon, {
1688
+ name: "delete",
1689
+ small: ""
1690
+ })
1691
+ ], 8, _hoisted_43)
1692
+ ], 10, _hoisted_36);
1693
+ }),
1694
+ 128
1695
+ /* KEYED_FRAGMENT */
1696
+ ))
1697
+ ]),
1698
+ createCommentVNode(" Selected Variant Details "),
1699
+ selectedVariant.value ? (openBlock(), createElementBlock("div", _hoisted_44, [
1700
+ createElementVNode("p", null, [
1701
+ _cache[15] || (_cache[15] = createElementVNode(
1702
+ "strong",
1703
+ null,
1704
+ "Model:",
1705
+ -1
1706
+ /* CACHED */
1707
+ )),
1708
+ createTextVNode(
1709
+ " " + toDisplayString(selectedVariant.value.voice_config?.provider),
1710
+ 1
1711
+ /* TEXT */
1712
+ )
1713
+ ]),
1714
+ createElementVNode("p", null, [
1715
+ _cache[16] || (_cache[16] = createElementVNode(
1716
+ "strong",
1717
+ null,
1718
+ "Voice:",
1719
+ -1
1720
+ /* CACHED */
1721
+ )),
1722
+ createTextVNode(
1723
+ " " + toDisplayString(getVoiceNameFromConfig(selectedVariant.value.voice_config)),
1724
+ 1
1725
+ /* TEXT */
1726
+ )
1727
+ ]),
1728
+ createElementVNode("p", null, [
1729
+ _cache[17] || (_cache[17] = createElementVNode(
1730
+ "strong",
1731
+ null,
1732
+ "Style:",
1733
+ -1
1734
+ /* CACHED */
1735
+ )),
1736
+ createTextVNode(
1737
+ " " + toDisplayString(selectedVariant.value.voice_config?.style),
1738
+ 1
1739
+ /* TEXT */
1740
+ )
1741
+ ])
1742
+ ])) : createCommentVNode("v-if", true),
1743
+ createElementVNode("div", _hoisted_45, [
1595
1744
  createElementVNode("div", { class: "widget__footer-left" }, [
1596
1745
  createElementVNode("button", {
1597
1746
  class: "widget__btn widget__btn--secondary",
1598
1747
  onClick: goBackToSelection
1599
- }, " Return ")
1748
+ }, " Back ")
1600
1749
  ]),
1601
- createElementVNode("div", { class: "widget__footer-right" }, [
1750
+ createElementVNode("div", _hoisted_46, [
1602
1751
  createElementVNode("button", {
1603
1752
  class: "widget__btn widget__btn--secondary",
1604
1753
  onClick: regenerateVoiceover
1605
1754
  }, " Regenerate "),
1606
1755
  createElementVNode("button", {
1607
1756
  class: "widget__btn widget__btn--primary",
1608
- onClick: confirmVoiceover
1609
- }, " Confirm ")
1757
+ onClick: confirmVoiceover,
1758
+ disabled: !selectedVariant.value
1759
+ }, " Confirm ", 8, _hoisted_47)
1610
1760
  ])
1611
1761
  ])
1612
1762
  ],
@@ -1618,10 +1768,10 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1618
1768
  }
1619
1769
  });
1620
1770
 
1621
- var css = "\n.voice-widget[data-v-f98cb13e] {\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-f98cb13e] {\n margin-bottom: 20px;\n}\n.widget__header-row[data-v-f98cb13e] {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: 16px;\n}\n.widget__header-text[data-v-f98cb13e] {\n flex: 1;\n}\n.widget__title[data-v-f98cb13e] {\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-f98cb13e] {\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-f98cb13e]:hover {\n background: var(--theme--background-accent);\n}\n.widget__subtitle[data-v-f98cb13e] {\n margin: 0;\n font-size: 14px;\n color: var(--theme--foreground-subdued);\n}\n.widget__header-controls[data-v-f98cb13e] {\n display: flex;\n gap: 16px;\n align-items: center;\n}\n.widget__url-input[data-v-f98cb13e] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.widget__url-label[data-v-f98cb13e] {\n font-size: 12px;\n color: var(--theme--foreground-subdued);\n white-space: nowrap;\n}\n.widget__url-field[data-v-f98cb13e] {\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-f98cb13e] {\n margin-bottom: 20px;\n}\n.widget__section-label[data-v-f98cb13e] {\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-f98cb13e] {\n display: flex;\n gap: 8px;\n}\n.widget__model-btn[data-v-f98cb13e] {\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-f98cb13e]:hover {\n border-color: var(--theme--primary);\n}\n.widget__model-btn--active[data-v-f98cb13e] {\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-f98cb13e] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.widget__section--toggle[data-v-f98cb13e] {\n padding: 12px;\n background: var(--theme--background-subdued);\n border-radius: var(--theme--border-radius);\n}\n.widget__toggle[data-v-f98cb13e] {\n display: flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n}\n.widget__toggle input[data-v-f98cb13e] {\n width: 16px;\n height: 16px;\n cursor: pointer;\n}\n.widget__toggle-label[data-v-f98cb13e] {\n font-size: 14px;\n font-weight: 500;\n color: var(--theme--foreground);\n}\n.widget__toggle-note[data-v-f98cb13e] {\n margin: 4px 0 0 24px;\n font-size: 12px;\n color: var(--theme--foreground-subdued);\n}\n.widget__footer[data-v-f98cb13e] {\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-f98cb13e],\n.widget__footer-right[data-v-f98cb13e] {\n display: flex;\n gap: 8px;\n}\n.widget__variant-nav[data-v-f98cb13e] {\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-f98cb13e] {\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-f98cb13e] {\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-f98cb13e] {\n margin-top: 8px;\n font-size: 12px;\n color: var(--theme--foreground-subdued);\n}\n.widget__btn[data-v-f98cb13e] {\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-f98cb13e]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.widget__btn--primary[data-v-f98cb13e] {\n background: var(--theme--primary);\n color: var(--theme--primary-foreground, #fff);\n}\n.widget__btn--primary[data-v-f98cb13e]:hover:not(:disabled) {\n background: var(--theme--primary-accent);\n}\n.widget__btn--secondary[data-v-f98cb13e] {\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-f98cb13e]:hover:not(:disabled) {\n background: var(--theme--background-normal);\n}\n\n/* Processing State */\n.widget__processing[data-v-f98cb13e] {\n padding: 40px 20px;\n text-align: center;\n}\n.widget__progress[data-v-f98cb13e] {\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-f98cb13e] {\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-f98cb13e] {\n height: 100%;\n background: var(--theme--primary);\n transition: width 0.3s ease;\n}\n\n/* Error State */\n.widget__error[data-v-f98cb13e] {\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-f98cb13e] {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n}\n.widget__retry[data-v-f98cb13e] {\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-f98cb13e] {\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-f98cb13e] {\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-f98cb13e] {\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-f98cb13e] {\n margin: 4px 0;\n font-size: 14px;\n color: var(--theme--foreground-subdued);\n}\n.widget__result-info strong[data-v-f98cb13e] {\n color: var(--theme--foreground);\n}\n\n/* Animations */\n.spinning[data-v-f98cb13e] {\n animation: spin-f98cb13e 1s linear infinite;\n}\n@keyframes spin-f98cb13e {\nfrom { transform: rotate(0deg);\n}\nto { transform: rotate(360deg);\n}\n}\n";
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";
1622
1772
  n(css,{});
1623
1773
 
1624
- var InterfaceComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-f98cb13e"], ["__file", "interface.vue"]]);
1774
+ var InterfaceComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-10fc7a16"], ["__file", "interface.vue"]]);
1625
1775
 
1626
1776
  var index = defineInterface({
1627
1777
  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.0",
5
+ "version": "1.0.1",
6
6
  "license": "MIT",
7
7
  "readme": "README.md",
8
8
  "repository": {