wellnua-lite-Robert/plugins/withTTSModels.js
Sergei cde44adc5c Add TTS model metadata and documentation
TTS Model (Piper VITS):
- MODEL_CARD: Voice model information
- tokens.txt: Phoneme tokenization
- onnx.json: Model configuration
- Model: en_US-lessac-medium (60MB ONNX - not in git)

Documentation:
- APP_REVIEW_NOTES.txt: App Store review notes
- specs/: Feature specifications
- plugins/: Expo config plugins

.gitignore updates:
- Exclude large ONNX models (60MB+)
- Exclude espeak-ng-data (phoneme data)
- Exclude credentials.json
- Exclude store-screenshots/

Note: TTS models must be downloaded separately.
See specs/ for setup instructions.
2026-01-14 19:10:13 -08:00

97 lines
3.2 KiB
JavaScript

/**
* Expo Config Plugin to bundle TTS model files for iOS
* Uses a Run Script build phase to copy models during build
*/
const { withXcodeProject } = require('@expo/config-plugins');
const path = require('path');
const fs = require('fs');
const withTTSModels = (config) => {
return withXcodeProject(config, async (config) => {
const projectRoot = config.modRequest.projectRoot;
const xcodeProject = config.modResults;
// Source model directory (relative to project root)
const modelSrcDir = path.join(projectRoot, 'assets', 'tts-models');
if (!fs.existsSync(modelSrcDir)) {
console.warn('[withTTSModels] ⚠️ Model directory not found:', modelSrcDir);
return config;
}
console.log('[withTTSModels] Found models at:', modelSrcDir);
// Get the first target
const target = xcodeProject.getFirstTarget();
if (!target) {
console.error('[withTTSModels] ❌ No target found');
return config;
}
// Path relative to ios directory (where Xcode project lives)
const iosDir = path.join(projectRoot, 'ios');
const relativeModelPath = path.relative(iosDir, modelSrcDir);
console.log('[withTTSModels] Relative path from ios/:', relativeModelPath);
// Create a Run Script build phase to copy ONLY Lessac model during build
const scriptName = '[TTS] Copy Model Files';
const scriptContent = [
'# Copy TTS model files to app bundle (ONLY Lessac voice)',
'echo "📦 Copying TTS model to app bundle..."',
'',
'SOURCE_DIR="${SRCROOT}/' + relativeModelPath + '"',
'DEST_DIR="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/assets/tts-models"',
'LESSAC_MODEL="vits-piper-en_US-lessac-medium"',
'',
'# Only copy Lessac model',
'if [ -d "$SOURCE_DIR/$LESSAC_MODEL" ]; then',
' mkdir -p "$DEST_DIR"',
' cp -R "$SOURCE_DIR/$LESSAC_MODEL" "$DEST_DIR/"',
' MODEL_SIZE=$(du -sh "$SOURCE_DIR/$LESSAC_MODEL" | cut -f1)',
' echo "✅ Lessac TTS model copied successfully ($MODEL_SIZE)"',
' echo " From: $SOURCE_DIR/$LESSAC_MODEL"',
' echo " To: $DEST_DIR/$LESSAC_MODEL"',
'else',
' echo "⚠️ Lessac model not found at: $SOURCE_DIR/$LESSAC_MODEL"',
' exit 1',
'fi'
].join('\n');
// Check if script already exists
const buildPhases = xcodeProject.hash.project.objects.PBXShellScriptBuildPhase || {};
let scriptExists = false;
for (const key in buildPhases) {
if (buildPhases[key].name === scriptName) {
scriptExists = true;
console.log('[withTTSModels] Run Script phase already exists');
break;
}
}
if (!scriptExists) {
// Add the Run Script build phase
xcodeProject.addBuildPhase(
[],
'PBXShellScriptBuildPhase',
scriptName,
target.uuid,
{
shellPath: '/bin/sh',
shellScript: scriptContent,
// Run before Copy Bundle Resources phase
runOnlyForDeploymentPostprocessing: 0
}
);
console.log('[withTTSModels] ✅ Added Run Script build phase');
}
console.log('[withTTSModels] ✅ Plugin configured successfully');
return config;
});
};
module.exports = withTTSModels;