7.5 KiB
7.5 KiB
Integration Guide
Guide for integrating @transquinnftw/tts-client into applications.
Installation
From GitLab npm Registry
- Configure npm to use GitLab registry for
@transquinnftwscope:
npm config set @transquinnftw:registry https://gitlab.com/api/v4/projects/YOUR_PROJECT_ID/packages/npm/
- Install the package:
npm install @transquinnftw/tts-client
# or
pnpm add @transquinnftw/tts-client
# or
yarn add @transquinnftw/tts-client
From Local Development
For development, you can link the package locally:
# In the tts-client directory
npm link
# In your application directory
npm link @transquinnftw/tts-client
Or use pnpm workspace protocol in your package.json:
{
"dependencies": {
"@transquinnftw/tts-client": "workspace:*"
}
}
Migrating from Inline Implementation
If you're migrating from the desktop-chat-app implementation, follow these steps:
1. Install the Package
pnpm add @transquinnftw/tts-client
2. Update Imports
Before (inline implementation):
// Desktop chat app had inline implementations
import { useSpeechSynthesis } from '../hooks/useSpeechSynthesis';
After (using package):
import {
BrowserTTSClient,
PiperTTSClient,
ChatterboxTTSClient,
type TTSEventHandlers,
} from '@transquinnftw/tts-client';
3. Update React Hooks
The package provides core clients but not React hooks. Create a custom hook in your app:
// hooks/useTTSClient.ts
import { useState, useCallback, useRef, useEffect } from 'react';
import {
BrowserTTSClient,
PiperTTSClient,
ChatterboxTTSClient,
type TTSEventHandlers,
type SpeechProvider,
} from '@transquinnftw/tts-client';
import { useSettingsStore } from '../stores/settingsStore';
export function useTTSClient() {
const { settings } = useSettingsStore();
const [isSpeaking, setIsSpeaking] = useState(false);
const browserClientRef = useRef<BrowserTTSClient | null>(null);
const piperClientRef = useRef<PiperTTSClient | null>(null);
const chatterboxClientRef = useRef<ChatterboxTTSClient | null>(null);
// Create event handlers
const handlers: TTSEventHandlers = {
onStart: () => setIsSpeaking(true),
onEnd: () => setIsSpeaking(false),
onError: (error) => {
console.error('TTS error:', error);
setIsSpeaking(false);
},
};
// Initialize clients
useEffect(() => {
// Browser client
if (BrowserTTSClient.isSupported()) {
browserClientRef.current = new BrowserTTSClient(
{
rate: settings.speech.browserRate,
pitch: settings.speech.browserPitch,
},
handlers
);
}
// Piper client
piperClientRef.current = new PiperTTSClient(
{
endpoint: settings.speech.piperEndpoint,
voice: settings.speech.piperVoice,
speed: settings.speech.piperSpeed,
},
handlers
);
// Chatterbox client
chatterboxClientRef.current = new ChatterboxTTSClient(
{
endpoint: settings.speech.chatterboxEndpoint,
voiceId: settings.speech.chatterboxVoiceId,
exaggeration: settings.speech.chatterboxExaggeration,
cfgWeight: settings.speech.chatterboxCfgWeight,
},
handlers
);
return () => {
browserClientRef.current?.dispose();
piperClientRef.current?.dispose();
chatterboxClientRef.current?.dispose();
};
}, []);
// Update configurations when settings change
useEffect(() => {
browserClientRef.current?.updateConfig({
rate: settings.speech.browserRate,
pitch: settings.speech.browserPitch,
});
}, [settings.speech.browserRate, settings.speech.browserPitch]);
useEffect(() => {
piperClientRef.current?.updateConfig({
endpoint: settings.speech.piperEndpoint,
voice: settings.speech.piperVoice,
speed: settings.speech.piperSpeed,
});
}, [settings.speech.piperEndpoint, settings.speech.piperVoice, settings.speech.piperSpeed]);
useEffect(() => {
chatterboxClientRef.current?.updateConfig({
endpoint: settings.speech.chatterboxEndpoint,
voiceId: settings.speech.chatterboxVoiceId,
exaggeration: settings.speech.chatterboxExaggeration,
cfgWeight: settings.speech.chatterboxCfgWeight,
});
}, [
settings.speech.chatterboxEndpoint,
settings.speech.chatterboxVoiceId,
settings.speech.chatterboxExaggeration,
settings.speech.chatterboxCfgWeight,
]);
const speak = useCallback(
async (text: string) => {
if (!settings.speech.enabled) return;
const provider = settings.speech.provider;
switch (provider) {
case 'browser':
browserClientRef.current?.speak(text);
break;
case 'piper':
await piperClientRef.current?.speak(text);
break;
case 'chatterbox':
await chatterboxClientRef.current?.speak(text);
break;
}
},
[settings.speech.enabled, settings.speech.provider]
);
const cancel = useCallback(() => {
browserClientRef.current?.cancel();
piperClientRef.current?.cancel();
chatterboxClientRef.current?.cancel();
setIsSpeaking(false);
}, []);
return {
speak,
cancel,
isSpeaking,
};
}
4. Update Type Imports
Before:
import type { SpeechProvider } from '../stores/types';
After:
import type { SpeechProvider } from '@transquinnftw/tts-client';
5. Benefits of Using the Package
- Reusability: Use the same TTS logic across multiple applications
- Type Safety: Full TypeScript support with strict typing
- Separation of Concerns: Core TTS logic separated from React/UI logic
- Testability: Easier to unit test client logic independently
- Maintainability: Single source of truth for TTS functionality
- Versioning: Proper semantic versioning and change tracking
Architecture
The package follows SOLID principles:
- Single Responsibility: Each client handles one TTS provider
- Open/Closed: Easy to extend with new providers by implementing
TTSClientinterface - Liskov Substitution: All clients implement the same
TTSClientinterface - Interface Segregation: Clean, focused interfaces for each concern
- Dependency Inversion: Depend on abstractions (interfaces) not concrete implementations
Testing
Example unit test using the package:
import { PiperTTSClient } from '@transquinnftw/tts-client';
import { describe, it, expect, vi } from 'vitest';
describe('PiperTTSClient', () => {
it('should call onStart handler when synthesis begins', async () => {
const onStart = vi.fn();
const client = new PiperTTSClient(
{
endpoint: 'http://localhost:5000',
voice: 'en_US-lessac-medium',
},
{ onStart }
);
// Mock fetch
global.fetch = vi.fn().mockResolvedValue({
ok: true,
blob: () => Promise.resolve(new Blob()),
});
await client.speak('test');
expect(onStart).toHaveBeenCalled();
});
});
Publishing Updates
To publish a new version:
# Update version in package.json
npm version patch # or minor, major
# Build and publish
npm run prepublishOnly
npm publish
GitLab CI/CD Integration
Example .gitlab-ci.yml for automated publishing:
publish:
stage: deploy
only:
- tags
script:
- npm ci
- npm run build
- npm publish
variables:
NPM_TOKEN: $CI_JOB_TOKEN
Support
For issues or questions, please open an issue in the GitLab repository.