@ysolve/ocity-heritage-visualizer 1.0.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -64,15 +64,16 @@ import avatarImage2 from './assets/avatar2.webp';
64
64
  function App() {
65
65
  const heritageItems = [
66
66
  {
67
- id: "1",
68
- name: "Castillo de Buñol",
67
+ id: '1',
68
+ name: 'Castillo de Buñol',
69
69
  description: {
70
70
  local: {
71
- short: "Castillo medieval del siglo XI",
72
- extended: "El castillo de Buñol es una fortaleza cristiana construida sobre un asentamiento islámico..."
73
- }
71
+ short: 'Castillo medieval del siglo XI',
72
+ extended:
73
+ 'El castillo de Buñol es una fortaleza cristiana construida sobre un asentamiento islámico...',
74
+ },
74
75
  },
75
- imageUrl: "https://example.com/castle.jpg"
76
+ imageUrl: 'https://example.com/castle.jpg',
76
77
  },
77
78
  // Más patrimonios...
78
79
  ];
@@ -83,7 +84,7 @@ function App() {
83
84
  targetLanguage="es"
84
85
  descriptionLength="extended"
85
86
  autoAdvance={false}
86
- voiceIds={["21m00Tcm4TlvDq8ikWAM"]} // IDs de voces de ElevenLabs
87
+ voiceIds={['21m00Tcm4TlvDq8ikWAM']} // IDs de voces de ElevenLabs
87
88
  avatars={[avatarImage1, avatarImage2]}
88
89
  />
89
90
  );
@@ -96,14 +97,15 @@ export default App;
96
97
 
97
98
  ### `StoryVisualizerProps`
98
99
 
99
- | Prop | Tipo | Requerido | Descripción |
100
- |------|------|-----------|-------------|
101
- | `heritageItems` | `HeritageItem[]` | ✅ | Array de objetos de patrimonio |
102
- | `targetLanguage` | `"es" \| "en" \| "fr"` | ✅ | Idioma de narración |
103
- | `descriptionLength` | `"short" \| "extended"` | ✅ | Longitud de la descripción |
104
- | `autoAdvance` | `boolean` | ❌ | Autoavance al siguiente patrimonio |
105
- | `voiceIds` | `string[]` | ✅ | IDs de voces de ElevenLabs |
106
- | `avatars` | `string[]` | ✅ | URLs de imágenes de avatares |
100
+ | Prop | Tipo | Requerido | Descripción |
101
+ | ------------------- | ----------------------- | --------- | ---------------------------------- |
102
+ | `heritageItems` | `HeritageItem[]` | ✅ | Array de objetos de patrimonio |
103
+ | `targetLanguage` | `"es" \| "en" \| "fr"` | ✅ | Idioma de narración |
104
+ | `descriptionLength` | `"short" \| "extended"` | ✅ | Longitud de la descripción |
105
+ | `autoAdvance` | `boolean` | ❌ | Autoavance al siguiente patrimonio |
106
+ | `voiceIds` | `string[]` | ✅ | IDs de voces de ElevenLabs |
107
+ | `avatars` | `string[]` | ✅ | URLs de imágenes de avatares |
108
+ | `ttsPreset` | `string` | ❌ | ID del preset TTS a usar |
107
109
 
108
110
  ### Tipo `HeritageItem`
109
111
 
@@ -138,7 +140,12 @@ Los estilos se incluyen en el archivo CSS de la librería. Puedes sobrescribirlo
138
140
  El componente requiere ciertas variables de entorno para funcionar correctamente:
139
141
 
140
142
  ```env
141
- # ElevenLabs API
143
+ # Qwen3-TTS-Flash API via ocity_tts Server (Proveedor principal)
144
+ # URL del servidor ocity_tts que actúa como proxy a Alibaba DashScope
145
+ # Default: http://localhost:3000 (si no se especifica)
146
+ VITE_QWEN_TTS_API_URL=http://localhost:3000
147
+
148
+ # ElevenLabs API (Proveedor fallback)
142
149
  VITE_ELEVENLABS_API_BASE=https://api.elevenlabs.io/v1
143
150
  VITE_ELEVENLABS_API_KEY=tu_api_key_aqui
144
151
 
@@ -153,7 +160,152 @@ VITE_DEFAULT_IMAGE_URL=https://tu-cdn.com/default.jpg
153
160
  VITE_DEBUG_MODE=false
154
161
  ```
155
162
 
156
- ## 🎤 Obtener IDs de Voces de ElevenLabs
163
+ ### Notas sobre variables de entorno
164
+
165
+ - **`VITE_QWEN_TTS_API_URL`**: URL del servidor ocity_tts (proveedor principal). Default: `http://localhost:3000`. Sin esta URL disponible, se usará automáticamente ElevenLabs como proveedor único.
166
+ - **`VITE_ELEVENLABS_API_KEY`**: Requerida como proveedor fallback o principal si Qwen no está disponible.
167
+ - Las demás variables son obligatorias para el correcto funcionamiento del componente.
168
+
169
+ ## Sistema de Fallback de TTS
170
+
171
+ Este componente implementa automaticamente un sistema de fallback entre dos proveedores de TTS:
172
+
173
+ 1. **Proveedor Principal**: Qwen3-TTS-Flash (a través de ocity_tts)
174
+ - Si el servidor está disponible (`VITE_QWEN_TTS_API_URL`), se intenta sintetizar primero con este proveedor
175
+ - Voces soportadas: Peter, Bodega, Ebona
176
+ - El servidor ocity_tts maneja la autenticación con Alibaba (sin CORS)
177
+
178
+ 2. **Proveedor Fallback**: ElevenLabs
179
+ - Si la síntesis con Qwen falla, automáticamente se intenta con ElevenLabs
180
+ - Si ambos fallan, el usuario ve un error informativo
181
+
182
+ **Mapeo de voces**:
183
+
184
+ - Voz Mujer (ElevenLabs) ↔ Ebona (Qwen3)
185
+ - Voz Hombre Joven (ElevenLabs) ↔ Bodega (Qwen3)
186
+
187
+ ### Levantar ocity_tts
188
+
189
+ Para usar Qwen3-TTS-Flash, necesitas tener el servidor ocity_tts corriendo:
190
+
191
+ ```bash
192
+ cd ../ocity_tts
193
+ npm install
194
+ ALIBABA_API_KEY=sk_your_key_here npm run dev
195
+ # Corre en http://localhost:3000
196
+ ```
197
+
198
+ Luego, en ocity_history_visualizer, asegúrate de que `VITE_QWEN_TTS_API_URL=http://localhost:3000` esté configurado.
199
+
200
+ ## Sistema de Presets TTS
201
+
202
+ El componente soporta presets predefinidos de parámetros TTS que aplican estilos emocionales y de narración consistentes. Los presets pueden combinarse con parámetros individuales, donde **los parámetros individuales sobrescriben los valores del preset**.
203
+
204
+ ### Presets Disponibles
205
+
206
+ El servidor ocity_tts expone un endpoint para obtener todos los presets disponibles:
207
+
208
+ ```bash
209
+ curl http://localhost:3000/api/v1/tts/presets
210
+ ```
211
+
212
+ **Presets incluidos:**
213
+
214
+ | ID | Nombre | Descripción | Emoción | Velocidad | Pitch | Propósito |
215
+ | ----------------------- | --------------------- | --------------------- | ------------ | --------- | ----- | --------------------- |
216
+ | `formal_narrative` | Formal Narrative | Narración documental | calm | 1.0 | 1.0 | documentary narration |
217
+ | `conversational` | Conversational | Presentación amigable | cheerful | 1.1 | 1.05 | presentation |
218
+ | `dramatic_storytelling` | Dramatic Storytelling | Narración cautivadora | enthusiastic | 0.9 | 1.1 | storytelling |
219
+ | `audiobook` | Audiobook | Narración profesional | calm | 0.85 | 0.95 | audiobook |
220
+ | `educational` | Educational | Tono educativo claro | calm | 0.95 | 1.0 | educational content |
221
+
222
+ ### Usando Presets en el Componente
223
+
224
+ Pasa el ID del preset a través de la prop `ttsPreset`:
225
+
226
+ ```tsx
227
+ <StoryVisualizer
228
+ heritageItems={heritageItems}
229
+ targetLanguage="es"
230
+ descriptionLength="extended"
231
+ autoAdvance={false}
232
+ voiceIds={['21m00Tcm4TlvDq8ikWAM']}
233
+ avatars={[avatarImage]}
234
+ ttsPreset="dramatic_storytelling" // ← Usar preset específico
235
+ />
236
+ ```
237
+
238
+ ### Selector de Presets en Aplicación
239
+
240
+ El componente de demostración (en `demo/src/App.tsx`) incluye un selector de presets que permite cambiar entre presets en tiempo de ejecución:
241
+
242
+ ```tsx
243
+ import { useEffect, useState } from 'react';
244
+
245
+ function App() {
246
+ const [presets, setPresets] = useState([]);
247
+ const [selectedPreset, setSelectedPreset] = useState<string | undefined>();
248
+
249
+ useEffect(() => {
250
+ // Obtener presets disponibles del servidor ocity_tts
251
+ fetch('http://localhost:3000/api/v1/tts/presets')
252
+ .then((res) => res.json())
253
+ .then((data) => {
254
+ setPresets(data.presets);
255
+ if (data.presets.length > 0) {
256
+ setSelectedPreset(data.presets[0].id);
257
+ }
258
+ });
259
+ }, []);
260
+
261
+ return (
262
+ <>
263
+ <select
264
+ value={selectedPreset || ''}
265
+ onChange={(e) => setSelectedPreset(e.target.value)}
266
+ >
267
+ {presets.map((p) => (
268
+ <option key={p.id} value={p.id}>
269
+ {p.label}
270
+ </option>
271
+ ))}
272
+ </select>
273
+
274
+ <StoryVisualizer
275
+ heritageItems={heritageItems}
276
+ targetLanguage="es"
277
+ descriptionLength="extended"
278
+ autoAdvance={false}
279
+ voiceIds={['21m00Tcm4TlvDq8ikWAM']}
280
+ avatars={[avatarImage]}
281
+ ttsPreset={selectedPreset}
282
+ />
283
+ </>
284
+ );
285
+ }
286
+ ```
287
+
288
+ ### Diferencias Audibles entre Presets
289
+
290
+ Cada preset produce diferencias audibles en la narración:
291
+
292
+ - **Formal Narrative** → Narración tranquila y medida (documentales)
293
+ - **Conversational** → Tono más rápido y alegre (presentaciones)
294
+ - **Dramatic Storytelling** → Más lento pero entusiasta (historias cautivadoras)
295
+ - **Audiobook** → Muy lento y profesional (audiolibros)
296
+ - **Educational** → Moderadamente lento y claro (contenido educativo)
297
+
298
+ ### Caché Inteligente
299
+
300
+ El sistema de caché diferencia entre presets diferentes, por lo que la misma narración con diferentes presets se cachea por separado:
301
+
302
+ ```
303
+ Cache key: qwen-${voiceId}-${language}-${preset}-${text}
304
+ ```
305
+
306
+ Esto significa que cambiar presets solicita síntesis nuevas pero evita re-procesar si vuelves a usar el mismo preset.
307
+
308
+ ## Obtener IDs de Voces de ElevenLabs
157
309
 
158
310
  1. Inicia sesión en [ElevenLabs](https://elevenlabs.io/)
159
311
  2. Ve a "Voice Library" o "My Voices"
@@ -163,8 +315,8 @@ Ejemplo de voces disponibles:
163
315
 
164
316
  ```typescript
165
317
  const voices = [
166
- { id: "21m00Tcm4TlvDq8ikWAM", name: "Rachel - Voz Femenina" },
167
- { id: "Nh2zY9kknu6z4pZy6FhD", name: "Adam - Voz Masculina" },
318
+ { id: '21m00Tcm4TlvDq8ikWAM', name: 'Rachel - Voz Femenina' },
319
+ { id: 'Nh2zY9kknu6z4pZy6FhD', name: 'Adam - Voz Masculina' },
168
320
  ];
169
321
  ```
170
322
 
@@ -220,8 +372,8 @@ function DynamicExample() {
220
372
 
221
373
  useEffect(() => {
222
374
  fetch('https://api.o-city.org/heritages')
223
- .then(res => res.json())
224
- .then(data => setHeritages(data));
375
+ .then((res) => res.json())
376
+ .then((data) => setHeritages(data));
225
377
  }, []);
226
378
 
227
379
  if (heritages.length === 0) {
@@ -233,8 +385,8 @@ function DynamicExample() {
233
385
  heritageItems={heritages}
234
386
  targetLanguage="es"
235
387
  descriptionLength="extended"
236
- voiceIds={["21m00Tcm4TlvDq8ikWAM"]}
237
- avatars={["./avatar.webp"]}
388
+ voiceIds={['21m00Tcm4TlvDq8ikWAM']}
389
+ avatars={['./avatar.webp']}
238
390
  />
239
391
  );
240
392
  }
@@ -245,22 +397,26 @@ function DynamicExample() {
245
397
  Si deseas contribuir o desarrollar localmente:
246
398
 
247
399
  1. Clona el repositorio:
400
+
248
401
  ```bash
249
402
  git clone https://github.com/tu-usuario/ocity_history_visualizer.git
250
403
  cd ocity_history_visualizer
251
404
  ```
252
405
 
253
406
  2. Instala las dependencias:
407
+
254
408
  ```bash
255
409
  npm install
256
410
  ```
257
411
 
258
412
  3. Inicia el servidor de desarrollo:
413
+
259
414
  ```bash
260
415
  npm run dev
261
416
  ```
262
417
 
263
418
  4. Construye la librería:
419
+
264
420
  ```bash
265
421
  npm run build:lib
266
422
  ```