@ysolve/ocity-heritage-visualizer 1.0.8 → 1.1.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.
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:
|
|
68
|
-
name:
|
|
67
|
+
id: '1',
|
|
68
|
+
name: 'Castillo de Buñol',
|
|
69
69
|
description: {
|
|
70
70
|
local: {
|
|
71
|
-
short:
|
|
72
|
-
extended:
|
|
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:
|
|
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={[
|
|
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
|
|
100
|
-
|
|
101
|
-
| `heritageItems`
|
|
102
|
-
| `targetLanguage`
|
|
103
|
-
| `descriptionLength` | `"short" \| "extended"` | ✅
|
|
104
|
-
| `autoAdvance`
|
|
105
|
-
| `voiceIds`
|
|
106
|
-
| `avatars`
|
|
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
|
-
#
|
|
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
|
-
|
|
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:
|
|
167
|
-
{ id:
|
|
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={[
|
|
237
|
-
avatars={[
|
|
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
|
```
|