npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ysolve/ocity-heritage-visualizer

v1.1.2

Published

Componente React para visualizar patrimonio cultural con texto a voz (TTS) y avatares animados

Readme

O-CITY Heritage Visualizer

Componente React para visualizar patrimonio cultural con texto a voz y avatares animados

npm version License: MIT

📖 Descripción

ocity-heritage-visualizer es un componente React que transforma la información de patrimonio cultural de la plataforma O-CITY en una experiencia audiovisual interactiva.

En lugar de mostrar texto plano e imágenes, genera una "simulación de video" que incluye:

  • 🎤 Narración por voz usando ElevenLabs TTS
  • 🌍 Traducción automática con Google Translate
  • 📝 Subtítulos sincronizados
  • 🎭 Avatar animado (PNGTuber) que acompaña la narración
  • ⏯️ Controles de reproducción completos (play, pause, stop)
  • 🔄 Navegación entre patrimonios con autoavance opcional

✨ Características

  • ✅ Soporte multiidioma (Español, Inglés, Francés)
  • ✅ Reproducción de audio con Text-to-Speech
  • ✅ Animación de avatares sincronizada con el audio
  • ✅ Sistema de caché de audio para mejorar el rendimiento
  • ✅ Completamente tipado con TypeScript
  • ✅ Responsive y accesible

📦 Instalación

npm install @ysolve/ocity-heritage-visualizer

O con yarn:

yarn add @ysolve/ocity-heritage-visualizer

O con pnpm:

pnpm add @ysolve/ocity-heritage-visualizer

🚀 Uso Básico

import { StoryVisualizer } from '@ysolve/ocity-heritage-visualizer';
import '@ysolve/ocity-heritage-visualizer/dist/ocity-heritage-visualizer.css';
// O también puedes usar el alias 'styles':
// import '@ysolve/ocity-heritage-visualizer/styles';

// Importa tus avatares
import avatarImage1 from './assets/avatar1.webp';
import avatarImage2 from './assets/avatar2.webp';

function App() {
  const heritageItems = [
    {
      id: '1',
      name: 'Castillo de Buñol',
      description: {
        local: {
          short: 'Castillo medieval del siglo XI',
          extended:
            'El castillo de Buñol es una fortaleza cristiana construida sobre un asentamiento islámico...',
        },
      },
      imageUrl: 'https://example.com/castle.jpg',
    },
    // Más patrimonios...
  ];

  return (
    <StoryVisualizer
      heritageItems={heritageItems}
      targetLanguage="es"
      descriptionLength="extended"
      autoAdvance={false}
      voiceIds={['21m00Tcm4TlvDq8ikWAM']} // IDs de voces de ElevenLabs
      avatars={[avatarImage1, avatarImage2]}
    />
  );
}

export default App;

🔧 Props

StoryVisualizerProps

| Prop | Tipo | Requerido | Descripción | | ------------------- | ----------------------- | --------- | ---------------------------------- | | heritageItems | HeritageItem[] | ✅ | Array de objetos de patrimonio | | targetLanguage | "es" \| "en" \| "fr" | ✅ | Idioma de narración | | descriptionLength | "short" \| "extended" | ✅ | Longitud de la descripción | | autoAdvance | boolean | ❌ | Autoavance al siguiente patrimonio | | voiceIds | string[] | ✅ | IDs de voces de ElevenLabs | | avatars | string[] | ✅ | URLs de imágenes de avatares | | ttsPreset | string | ❌ | ID del preset TTS a usar |

Tipo HeritageItem

interface HeritageItem {
  id: string;
  name: string;
  description: {
    local: {
      short: string;
      extended: string;
    };
  };
  imageUrl: string;
}

🎨 Personalización de Estilos

Los estilos se incluyen en el archivo CSS de la librería. Puedes sobrescribirlos en tu aplicación:

/* En tu archivo CSS */
.viewer {
  max-width: 1200px;
  /* Tus estilos personalizados */
}

🔑 Configuración de Variables de Entorno

El componente requiere ciertas variables de entorno para funcionar correctamente:

# Qwen3-TTS-Flash API via ocity_tts Server (Proveedor principal)
# URL del servidor ocity_tts que actúa como proxy a Alibaba DashScope
# Default: http://localhost:3000 (si no se especifica)
VITE_QWEN_TTS_API_URL=http://localhost:3000

# ElevenLabs API (Proveedor fallback)
VITE_ELEVENLABS_API_BASE=https://api.elevenlabs.io/v1
VITE_ELEVENLABS_API_KEY=tu_api_key_aqui

# Google Translate API
VITE_GOOGLE_TRANSLATE_BASE=https://translate.googleapis.com

# URLs de imágenes
VITE_IMAGE_BASE_URL=https://tu-cdn.com/images/
VITE_DEFAULT_IMAGE_URL=https://tu-cdn.com/default.jpg

# Modo debug (opcional)
VITE_DEBUG_MODE=false

Notas sobre variables de entorno

  • 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.
  • VITE_ELEVENLABS_API_KEY: Requerida como proveedor fallback o principal si Qwen no está disponible.
  • Las demás variables son obligatorias para el correcto funcionamiento del componente.

Sistema de Fallback de TTS

Este componente implementa automaticamente un sistema de fallback entre dos proveedores de TTS:

  1. Proveedor Principal: Qwen3-TTS-Flash (a través de ocity_tts)

    • Si el servidor está disponible (VITE_QWEN_TTS_API_URL), se intenta sintetizar primero con este proveedor
    • Voces soportadas: Peter, Bodega, Ebona
    • El servidor ocity_tts maneja la autenticación con Alibaba (sin CORS)
  2. Proveedor Fallback: ElevenLabs

    • Si la síntesis con Qwen falla, automáticamente se intenta con ElevenLabs
    • Si ambos fallan, el usuario ve un error informativo

Mapeo de voces:

  • Voz Mujer (ElevenLabs) ↔ Ebona (Qwen3)
  • Voz Hombre Joven (ElevenLabs) ↔ Bodega (Qwen3)

Levantar ocity_tts

Para usar Qwen3-TTS-Flash, necesitas tener el servidor ocity_tts corriendo:

cd ../ocity_tts
npm install
ALIBABA_API_KEY=sk_your_key_here npm run dev
# Corre en http://localhost:3000

Luego, en ocity_history_visualizer, asegúrate de que VITE_QWEN_TTS_API_URL=http://localhost:3000 esté configurado.

Sistema de Presets TTS

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.

Presets Disponibles

El servidor ocity_tts expone un endpoint para obtener todos los presets disponibles:

curl http://localhost:3000/api/v1/tts/presets

Presets incluidos:

| ID | Nombre | Descripción | Emoción | Velocidad | Pitch | Propósito | | ----------------------- | --------------------- | --------------------- | ------------ | --------- | ----- | --------------------- | | formal_narrative | Formal Narrative | Narración documental | calm | 1.0 | 1.0 | documentary narration | | conversational | Conversational | Presentación amigable | cheerful | 1.1 | 1.05 | presentation | | dramatic_storytelling | Dramatic Storytelling | Narración cautivadora | enthusiastic | 0.9 | 1.1 | storytelling | | audiobook | Audiobook | Narración profesional | calm | 0.85 | 0.95 | audiobook | | educational | Educational | Tono educativo claro | calm | 0.95 | 1.0 | educational content |

Usando Presets en el Componente

Pasa el ID del preset a través de la prop ttsPreset:

<StoryVisualizer
  heritageItems={heritageItems}
  targetLanguage="es"
  descriptionLength="extended"
  autoAdvance={false}
  voiceIds={['21m00Tcm4TlvDq8ikWAM']}
  avatars={[avatarImage]}
  ttsPreset="dramatic_storytelling" // ← Usar preset específico
/>

Selector de Presets en Aplicación

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:

import { useEffect, useState } from 'react';

function App() {
  const [presets, setPresets] = useState([]);
  const [selectedPreset, setSelectedPreset] = useState<string | undefined>();

  useEffect(() => {
    // Obtener presets disponibles del servidor ocity_tts
    fetch('http://localhost:3000/api/v1/tts/presets')
      .then((res) => res.json())
      .then((data) => {
        setPresets(data.presets);
        if (data.presets.length > 0) {
          setSelectedPreset(data.presets[0].id);
        }
      });
  }, []);

  return (
    <>
      <select
        value={selectedPreset || ''}
        onChange={(e) => setSelectedPreset(e.target.value)}
      >
        {presets.map((p) => (
          <option key={p.id} value={p.id}>
            {p.label}
          </option>
        ))}
      </select>

      <StoryVisualizer
        heritageItems={heritageItems}
        targetLanguage="es"
        descriptionLength="extended"
        autoAdvance={false}
        voiceIds={['21m00Tcm4TlvDq8ikWAM']}
        avatars={[avatarImage]}
        ttsPreset={selectedPreset}
      />
    </>
  );
}

Diferencias Audibles entre Presets

Cada preset produce diferencias audibles en la narración:

  • Formal Narrative → Narración tranquila y medida (documentales)
  • Conversational → Tono más rápido y alegre (presentaciones)
  • Dramatic Storytelling → Más lento pero entusiasta (historias cautivadoras)
  • Audiobook → Muy lento y profesional (audiolibros)
  • Educational → Moderadamente lento y claro (contenido educativo)

Caché Inteligente

El sistema de caché diferencia entre presets diferentes, por lo que la misma narración con diferentes presets se cachea por separado:

Cache key: qwen-${voiceId}-${language}-${preset}-${text}

Esto significa que cambiar presets solicita síntesis nuevas pero evita re-procesar si vuelves a usar el mismo preset.

Obtener IDs de Voces de ElevenLabs

  1. Inicia sesión en ElevenLabs
  2. Ve a "Voice Library" o "My Voices"
  3. Copia el "Voice ID" de la voz deseada

Ejemplo de voces disponibles:

const voices = [
  { id: '21m00Tcm4TlvDq8ikWAM', name: 'Rachel - Voz Femenina' },
  { id: 'Nh2zY9kknu6z4pZy6FhD', name: 'Adam - Voz Masculina' },
];

📋 Requisitos

  • React 18.0.0 o superior
  • Node.js 16.0.0 o superior

🛠️ Tecnologías Utilizadas

  • React 19 - Framework de UI
  • TypeScript - Tipado estático
  • Vite - Build tool y bundler
  • ElevenLabs API - Síntesis de voz (TTS)
  • Google Translate API - Traducción automática
  • TensorFlow.js - Procesamiento de texto
  • IndexedDB - Caché de audio

📚 Ejemplos Avanzados

Ejemplo con múltiples voces y avatares

import { StoryVisualizer, voices } from '@ysolve/ocity-heritage-visualizer';
import avatar1 from './avatars/narrator1.webp';
import avatar2 from './avatars/narrator2.webp';

function AdvancedExample() {
  return (
    <StoryVisualizer
      heritageItems={heritageData}
      targetLanguage="es"
      descriptionLength="extended"
      autoAdvance={true}
      voiceIds={[
        voices[0].id, // Alterna entre diferentes voces
        voices[1].id,
      ]}
      avatars={[avatar1, avatar2]}
    />
  );
}

Uso con datos dinámicos

import { StoryVisualizer } from '@ysolve/ocity-heritage-visualizer';
import { useEffect, useState } from 'react';

function DynamicExample() {
  const [heritages, setHeritages] = useState([]);

  useEffect(() => {
    fetch('https://api.o-city.org/heritages')
      .then((res) => res.json())
      .then((data) => setHeritages(data));
  }, []);

  if (heritages.length === 0) {
    return <div>Cargando...</div>;
  }

  return (
    <StoryVisualizer
      heritageItems={heritages}
      targetLanguage="es"
      descriptionLength="extended"
      voiceIds={['21m00Tcm4TlvDq8ikWAM']}
      avatars={['./avatar.webp']}
    />
  );
}

🔄 Desarrollo Local

Si deseas contribuir o desarrollar localmente:

  1. Clona el repositorio:
git clone https://github.com/tu-usuario/ocity_history_visualizer.git
cd ocity_history_visualizer
  1. Instala las dependencias:
npm install
  1. Inicia el servidor de desarrollo:
npm run dev
  1. Construye la librería:
npm run build:lib

📝 Scripts Disponibles

  • npm run dev - Inicia el servidor de desarrollo
  • npm run build:lib - Construye la librería para producción
  • npm run lint - Ejecuta el linter
  • npm run preview - Previsualiza la build de producción

📄 Licencia

Este proyecto está bajo la licencia MIT. Ver el archivo LICENSE para más detalles.

🤝 Contribuciones

Las contribuciones son bienvenidas. Por favor:

  1. Haz un fork del proyecto
  2. Crea una rama para tu feature (git checkout -b feature/AmazingFeature)
  3. Commit tus cambios (git commit -m 'Add: amazing feature')
  4. Push a la rama (git push origin feature/AmazingFeature)
  5. Abre un Pull Request

📧 Contacto

Para preguntas o sugerencias, contacta con el equipo de O-CITY.

🗺️ Roadmap

  • [x] Componente base con TTS
  • [x] Traducción automática
  • [x] Sistema de avatares
  • [x] Publicación en npm
  • [ ] Soporte para más idiomas
  • [ ] Personalización avanzada de avatares
  • [ ] Temas personalizables
  • [ ] Integración con más proveedores de TTS

Hecho con ❤️ por el equipo de O-CITY