@stellartech/voice-widget-directus 1.0.3 → 1.0.5

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 (3) hide show
  1. package/README.md +46 -0
  2. package/dist/index.js +79 -27
  3. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @stellartech/voice-widget-directus
2
+
3
+ Voice generation widget with model/voice selection and audio preview for Directus.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @stellartech/voice-widget-directus
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - Multiple TTS provider support (Gemini, ElevenLabs)
14
+ - Voice selection with audio sample previews
15
+ - Tone and style customization
16
+ - Full voiceover generation with progress tracking
17
+ - Async generation with background processing
18
+ - Audio playback and variant management
19
+
20
+ ## Requirements
21
+
22
+ - Directus ^11.0.0
23
+ - Vue ^3.4.0
24
+
25
+ ## Configuration
26
+
27
+ The widget requires the following collections in your Directus instance:
28
+
29
+ - `Voices` - Available voice options with samples
30
+ - `VoiceTones` - Tone presets for voice generation
31
+ - `VoiceStyles` - Style presets for voice generation
32
+ - `VoiceVariants` - Generated voice variants storage
33
+ - `AudioFiles` - Audio file records
34
+
35
+ ## Usage
36
+
37
+ After installation, the interface will be available in your Directus instance as a custom interface type. Configure it on a JSON field to enable voice generation for your content.
38
+
39
+ ### Widget Options
40
+
41
+ - **Voices Collection**: Collection containing voice definitions
42
+ - **Flow ID**: Directus Flow ID for voice generation proxy
43
+
44
+ ## License
45
+
46
+ MIT
package/dist/index.js CHANGED
@@ -530,6 +530,9 @@ function useVoicingApi(api) {
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.");
532
532
  }
533
+ if (!collection) {
534
+ throw new Error("collection is required but was not provided");
535
+ }
533
536
  const voicingPayload = {
534
537
  texts: [SAMPLE_TEXT],
535
538
  provider,
@@ -538,7 +541,7 @@ function useVoicingApi(api) {
538
541
  audio_files_collection: "AudioFiles",
539
542
  // Include lesson context for callback tracking
540
543
  lesson_id: lessonId || null,
541
- collection: collection || "SM_Lessons",
544
+ collection,
542
545
  voice_config: {
543
546
  provider,
544
547
  voice_id: voiceId,
@@ -588,7 +591,10 @@ function useVoicingApi(api) {
588
591
  if (!effectiveFlowId) {
589
592
  throw new Error("Voice flow ID is not configured. Set it in Widget Config or in the Flow ID field.");
590
593
  }
591
- const collection = request.collection || "SM_Lessons";
594
+ if (!request.collection) {
595
+ throw new Error("collection is required but was not provided");
596
+ }
597
+ const collection = request.collection;
592
598
  let texts = [];
593
599
  let lessonTitle = `Lesson ${request.lessonId}`;
594
600
  try {
@@ -990,6 +996,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
990
996
  ref(null);
991
997
  const isPollingProgress = ref(false);
992
998
  const progressStatus = ref("");
999
+ const pendingVariantId = ref(null);
993
1000
  let samplePollingInterval = null;
994
1001
  let voiceoverPollingInterval = null;
995
1002
  const selectedVariant = computed(() => allVariants.value[currentVariantIndex.value]);
@@ -1037,12 +1044,15 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1037
1044
  voices.value[voiceIndex].example_status = "processing";
1038
1045
  }
1039
1046
  const effectiveFlowId = flowId.value && flowId.value.trim() ? flowId.value.trim() : void 0;
1047
+ if (!props.collection) {
1048
+ throw new Error("collection is required but was not provided");
1049
+ }
1040
1050
  generateVoiceSample(
1041
1051
  voice.voice,
1042
1052
  selectedModel.value,
1043
1053
  effectiveFlowId,
1044
1054
  props.primaryKey,
1045
- props.collection || "SM_Lessons"
1055
+ props.collection
1046
1056
  ).then(() => {
1047
1057
  console.log("[Voice Widget] Sample generation request sent for", voice.voice);
1048
1058
  }).catch((error) => {
@@ -1111,15 +1121,31 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1111
1121
  return;
1112
1122
  }
1113
1123
  voiceoverPollCount++;
1114
- console.log(`[Voice Widget] Poll #${voiceoverPollCount} for lesson ${voiceoverLessonId}...`);
1124
+ console.log(`[Voice Widget] Poll #${voiceoverPollCount} for lesson ${voiceoverLessonId}, pendingVariantId=${pendingVariantId.value}...`);
1115
1125
  try {
1116
1126
  const allVariantsNow = await fetchVoiceVariants(voiceoverLessonId);
1117
1127
  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) {
1128
+ let isNewVariantComplete = false;
1129
+ if (pendingVariantId.value) {
1130
+ const pendingVariant = allVariantsNow.find((v) => v.id === pendingVariantId.value);
1131
+ if (pendingVariant?.audio_file_id) {
1132
+ console.log("[Voice Widget] Pending variant completed with audio:", pendingVariant.audio_file_id);
1133
+ isNewVariantComplete = true;
1134
+ } else {
1135
+ console.log(`[Voice Widget] Poll #${voiceoverPollCount} - pending variant not yet complete`);
1136
+ }
1137
+ } else {
1138
+ const processingThatCompleted = allVariantsNow.find((v) => v.status === "processing" && v.audio_file_id);
1139
+ if (processingThatCompleted) {
1140
+ console.log("[Voice Widget] Found completed processing variant:", processingThatCompleted.id);
1141
+ isNewVariantComplete = true;
1142
+ }
1143
+ }
1144
+ if (isNewVariantComplete) {
1121
1145
  console.log("[Voice Widget] COMPLETED! Transitioning to result...");
1122
1146
  stopVoiceoverPolling();
1147
+ pendingVariantId.value = null;
1148
+ const completedWithAudio = allVariantsNow.filter((v) => v.audio_file_id);
1123
1149
  allVariants.value = completedWithAudio;
1124
1150
  for (const v of allVariants.value) {
1125
1151
  if (v.audio_file_id) {
@@ -1181,8 +1207,13 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1181
1207
  tone_id: selectedToneId.value,
1182
1208
  style_id: selectedStyleId.value
1183
1209
  };
1184
- await createPendingVoiceVariant(props.primaryKey, voiceConfig);
1210
+ const newPendingId = await createPendingVoiceVariant(props.primaryKey, voiceConfig);
1211
+ pendingVariantId.value = newPendingId;
1212
+ console.log("[Voice Widget] Created pending variant:", newPendingId);
1185
1213
  startVoiceoverPolling();
1214
+ if (!props.collection) {
1215
+ throw new Error("collection is required but was not provided");
1216
+ }
1186
1217
  const result = await generateFullVoiceover({
1187
1218
  provider: selectedModel.value,
1188
1219
  voiceId: providerVoiceId,
@@ -1190,7 +1221,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1190
1221
  preprocessing: preprocessingEnabled.value,
1191
1222
  flowId: flowId.value,
1192
1223
  lessonId: props.primaryKey,
1193
- collection: props.collection || "SM_Lessons",
1224
+ collection: props.collection,
1194
1225
  toneId: selectedToneId.value || void 0,
1195
1226
  styleId: selectedStyleId.value || void 0
1196
1227
  });
@@ -1225,6 +1256,8 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1225
1256
  progressPercent.value = 0;
1226
1257
  progressStatus.value = "";
1227
1258
  isPollingProgress.value = false;
1259
+ pendingVariantId.value = null;
1260
+ stopVoiceoverPolling();
1228
1261
  }
1229
1262
  function selectVariant(index) {
1230
1263
  currentVariantIndex.value = index;
@@ -1272,11 +1305,12 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1272
1305
  generatedAudioUrl.value = isUuid ? getAudioUrl(audioFileId) : await resolveAudioFileUrl(audioFileId);
1273
1306
  const config = variant.voice_config;
1274
1307
  if (config) {
1308
+ isRestoringFromVariant.value = true;
1275
1309
  if (config.provider) {
1276
1310
  selectedModel.value = config.provider;
1277
1311
  }
1278
1312
  if (config.voice_id) {
1279
- const matchingVoice = voices.value.find((v) => v.name === config.voice_id);
1313
+ const matchingVoice = voices.value.find((v) => v.voice === config.voice_id || v.name === config.voice_id);
1280
1314
  if (matchingVoice) {
1281
1315
  selectedVoiceId.value = matchingVoice.id;
1282
1316
  }
@@ -1290,19 +1324,27 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1290
1324
  if (typeof config.preprocessing === "boolean") {
1291
1325
  preprocessingEnabled.value = config.preprocessing;
1292
1326
  }
1327
+ isRestoringFromVariant.value = false;
1293
1328
  }
1294
1329
  }
1295
- function regenerateVoiceover() {
1330
+ async function regenerateVoiceover() {
1331
+ const variant = selectedVariant.value;
1332
+ if (variant) {
1333
+ await loadVariantAtIndex(currentVariantIndex.value);
1334
+ }
1296
1335
  generateVoiceover();
1297
1336
  }
1298
1337
  async function confirmVoiceover() {
1299
1338
  const variant = selectedVariant.value;
1300
1339
  if (!variant) return;
1301
1340
  try {
1341
+ if (!props.collection) {
1342
+ throw new Error("collection is required but was not provided");
1343
+ }
1302
1344
  await linkAudioToLesson(
1303
1345
  props.primaryKey,
1304
1346
  variant.audio_file_id,
1305
- props.collection || "SM_Lessons"
1347
+ props.collection
1306
1348
  );
1307
1349
  const value = {
1308
1350
  audio_file_id: variant.audio_file_id,
@@ -1392,20 +1434,29 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1392
1434
  console.log("[Voice Widget] Checking variants for lesson:", props.primaryKey);
1393
1435
  const variants = await fetchVoiceVariants(props.primaryKey);
1394
1436
  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);
1437
+ const processingVariants = variants.filter((v) => v.status === "processing" && !v.audio_file_id);
1438
+ if (processingVariants.length > 0) {
1439
+ console.log("[Voice Widget] Found", processingVariants.length, "processing voiceovers, showing progress");
1440
+ currentMode.value = "progress";
1441
+ processingMessage.value = "Generating voiceover...";
1442
+ progressPercent.value = 50;
1443
+ startVoiceoverPolling();
1444
+ } else {
1445
+ const completedVariants = variants.filter((v) => v.audio_file_id);
1446
+ hasExistingVoices.value = completedVariants.length > 0;
1447
+ console.log("[Voice Widget] Completed variants with audio:", completedVariants.length);
1448
+ if (completedVariants.length > 0) {
1449
+ allVariants.value = completedVariants;
1450
+ for (const v of allVariants.value) {
1451
+ if (v.audio_file_id) {
1452
+ const isUuid = v.audio_file_id?.includes("-");
1453
+ v.audioUrl = isUuid ? getAudioUrl(v.audio_file_id) : await resolveAudioFileUrl(v.audio_file_id);
1454
+ }
1404
1455
  }
1456
+ currentVariantIndex.value = 0;
1457
+ currentMode.value = "result";
1458
+ console.log("[Voice Widget] AUTO-SHOWING RESULT PAGE with", completedVariants.length, "variants");
1405
1459
  }
1406
- currentVariantIndex.value = 0;
1407
- currentMode.value = "result";
1408
- console.log("[Voice Widget] AUTO-SHOWING RESULT PAGE with", completedVariants.length, "variants");
1409
1460
  }
1410
1461
  }
1411
1462
  initFromValue();
@@ -1454,8 +1505,9 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1454
1505
  }
1455
1506
  }
1456
1507
  watch(() => props.value, initFromValue, { deep: true });
1508
+ const isRestoringFromVariant = ref(false);
1457
1509
  watch(selectedModel, () => {
1458
- if (currentVoices.value.length > 0) {
1510
+ if (!isRestoringFromVariant.value && currentVoices.value.length > 0) {
1459
1511
  selectedVoiceId.value = currentVoices.value[0].id;
1460
1512
  }
1461
1513
  });
@@ -1934,10 +1986,10 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
1934
1986
  }
1935
1987
  });
1936
1988
 
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";
1989
+ var css = "\n.voice-widget[data-v-e86a7906] {\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-e86a7906] {\n margin-bottom: 20px;\n}\n.widget__header-row[data-v-e86a7906] {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: 16px;\n}\n.widget__header-text[data-v-e86a7906] {\n flex: 1;\n}\n.widget__title[data-v-e86a7906] {\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-e86a7906] {\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-e86a7906]:hover {\n background: var(--theme--background-accent);\n}\n.widget__subtitle[data-v-e86a7906] {\n margin: 0;\n font-size: 14px;\n color: var(--theme--foreground-subdued);\n}\n.widget__header-controls[data-v-e86a7906] {\n display: flex;\n gap: 16px;\n align-items: center;\n}\n.widget__url-input[data-v-e86a7906] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.widget__url-label[data-v-e86a7906] {\n font-size: 12px;\n color: var(--theme--foreground-subdued);\n white-space: nowrap;\n}\n.widget__url-field[data-v-e86a7906] {\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-e86a7906] {\n margin-bottom: 20px;\n}\n.widget__section-label[data-v-e86a7906] {\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-e86a7906] {\n display: flex;\n gap: 8px;\n}\n.widget__model-btn[data-v-e86a7906] {\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-e86a7906]:hover {\n border-color: var(--theme--primary);\n}\n.widget__model-btn--active[data-v-e86a7906] {\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-e86a7906] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.widget__section--toggle[data-v-e86a7906] {\n padding: 12px;\n background: var(--theme--background-subdued);\n border-radius: var(--theme--border-radius);\n}\n.widget__toggle[data-v-e86a7906] {\n display: flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n}\n.widget__toggle input[data-v-e86a7906] {\n width: 16px;\n height: 16px;\n cursor: pointer;\n}\n.widget__toggle-label[data-v-e86a7906] {\n font-size: 14px;\n font-weight: 500;\n color: var(--theme--foreground);\n}\n.widget__toggle-note[data-v-e86a7906] {\n margin: 4px 0 0 24px;\n font-size: 12px;\n color: var(--theme--foreground-subdued);\n}\n.widget__footer[data-v-e86a7906] {\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-e86a7906],\n.widget__footer-right[data-v-e86a7906] {\n display: flex;\n gap: 8px;\n}\n.widget__variant-nav[data-v-e86a7906] {\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-e86a7906] {\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-e86a7906] {\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-e86a7906] {\n margin-top: 8px;\n font-size: 12px;\n color: var(--theme--foreground-subdued);\n}\n.widget__btn[data-v-e86a7906] {\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-e86a7906]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.widget__btn--primary[data-v-e86a7906] {\n background: var(--theme--primary);\n color: var(--theme--primary-foreground, #fff);\n}\n.widget__btn--primary[data-v-e86a7906]:hover:not(:disabled) {\n background: var(--theme--primary-accent);\n}\n.widget__btn--secondary[data-v-e86a7906] {\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-e86a7906]:hover:not(:disabled) {\n background: var(--theme--background-normal);\n}\n\n/* Processing State */\n.widget__processing[data-v-e86a7906] {\n padding: 40px 20px;\n text-align: center;\n}\n.widget__progress[data-v-e86a7906] {\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-e86a7906] {\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-e86a7906] {\n height: 100%;\n background: var(--theme--primary);\n transition: width 0.3s ease;\n}\n\n/* Error State */\n.widget__error[data-v-e86a7906] {\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-e86a7906] {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n}\n.widget__retry[data-v-e86a7906] {\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-e86a7906] {\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-e86a7906] {\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-e86a7906] {\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-e86a7906] {\n margin: 4px 0;\n font-size: 14px;\n color: var(--theme--foreground-subdued);\n}\n.widget__result-info strong[data-v-e86a7906] {\n color: var(--theme--foreground);\n}\n\n/* Variants List View */\n.widget__variants-list[data-v-e86a7906] {\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-e86a7906] {\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-e86a7906]:hover {\n border-color: var(--theme--primary);\n}\n.widget__variant-item--selected[data-v-e86a7906] {\n border-color: var(--theme--primary);\n background: var(--theme--primary-background);\n}\n.widget__variant-radio[data-v-e86a7906] {\n flex-shrink: 0;\n}\n.widget__variant-radio input[data-v-e86a7906] {\n width: 16px;\n height: 16px;\n cursor: pointer;\n}\n.widget__variant-player[data-v-e86a7906] {\n flex: 1;\n min-width: 200px;\n}\n.widget__variant-meta[data-v-e86a7906] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 120px;\n}\n.widget__variant-voice[data-v-e86a7906] {\n font-size: 13px;\n font-weight: 500;\n color: var(--theme--foreground);\n}\n.widget__variant-date[data-v-e86a7906] {\n font-size: 11px;\n color: var(--theme--foreground-subdued);\n}\n.widget__btn--danger[data-v-e86a7906] {\n color: var(--theme--danger);\n background: transparent;\n border: none;\n}\n.widget__btn--danger[data-v-e86a7906]:hover {\n background: var(--theme--danger-background);\n}\n.widget__selected-info[data-v-e86a7906] {\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-e86a7906] {\n margin: 4px 0;\n font-size: 14px;\n color: var(--theme--foreground-subdued);\n}\n.widget__selected-info strong[data-v-e86a7906] {\n color: var(--theme--foreground);\n}\n.widget__progress-status[data-v-e86a7906] {\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-e86a7906] {\n animation: spin-e86a7906 1s linear infinite;\n}\n@keyframes spin-e86a7906 {\nfrom { transform: rotate(0deg);\n}\nto { transform: rotate(360deg);\n}\n}\n";
1938
1990
  n(css,{});
1939
1991
 
1940
- var InterfaceComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-214c3276"], ["__file", "interface.vue"]]);
1992
+ var InterfaceComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-e86a7906"], ["__file", "interface.vue"]]);
1941
1993
 
1942
1994
  var index = defineInterface({
1943
1995
  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.3",
5
+ "version": "1.0.5",
6
6
  "license": "MIT",
7
7
  "readme": "README.md",
8
8
  "repository": {