valenceai 0.4.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/.env.example +0 -0
- package/CHANGELOG.md +154 -0
- package/README.dev.md +7 -0
- package/README.md +240 -0
- package/examples/uploadLong.js +10 -0
- package/examples/uploadShort.js +7 -0
- package/jest.config.js +27 -0
- package/package.json +30 -0
- package/src/asyncAudio.js +168 -0
- package/src/client.js +18 -0
- package/src/config.js +36 -0
- package/src/discreteAudio.js +61 -0
- package/src/index.js +2 -0
- package/src/utils/logger.js +16 -0
- package/src/utils/upload.js +0 -0
- package/src/valenceClient.js +256 -0
- package/tests/asyncAudio.test.js +331 -0
- package/tests/client.test.js +42 -0
- package/tests/config.test.js +90 -0
- package/tests/discreteAudio.test.js +168 -0
- package/tests/index.test.js +33 -0
- package/tests/logger.test.js +121 -0
- package/tests/setup.js +5 -0
- package/tests/valenceClient.test.js +21 -0
package/.env.example
ADDED
|
File without changes
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.4.0] - 2025-06-18
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **BREAKING**: Package name changed from `valence-sdk` to `valenceai`
|
|
12
|
+
- **BREAKING**: Installation command: `npm install valenceai` (was `npm install valence-sdk`)
|
|
13
|
+
- **BREAKING**: Import statement: `import { ValenceClient } from 'valenceai'` (was `'valence-sdk'`)
|
|
14
|
+
- **Package description updated** to reflect new unified naming
|
|
15
|
+
- **User-Agent string updated** to use `valenceai/0.4.0` branding
|
|
16
|
+
- **Updated example files** to use new package import syntax
|
|
17
|
+
|
|
18
|
+
### Technical Improvements
|
|
19
|
+
- **Unified package naming** across Python and JavaScript ecosystems
|
|
20
|
+
- **Consistent branding** with `valenceai` across all documentation and code
|
|
21
|
+
- **Updated test expectations** for new User-Agent header format
|
|
22
|
+
- **All core test suites verified** with new package name and imports
|
|
23
|
+
- **Package configuration updated** with new naming conventions
|
|
24
|
+
|
|
25
|
+
### Documentation
|
|
26
|
+
- **Updated README** with new installation instructions (`npm install valenceai`)
|
|
27
|
+
- **Updated CLAUDE.md** with new package installation commands
|
|
28
|
+
- **Updated example scripts** to demonstrate new import syntax
|
|
29
|
+
- **Updated migration guide** with new package name
|
|
30
|
+
|
|
31
|
+
### Migration Guide
|
|
32
|
+
```javascript
|
|
33
|
+
// Old Installation & Import (v0.3.x)
|
|
34
|
+
npm install valence-sdk
|
|
35
|
+
import { ValenceClient } from 'valence-sdk';
|
|
36
|
+
|
|
37
|
+
// New Installation & Import (v0.4.0+)
|
|
38
|
+
npm install valenceai
|
|
39
|
+
import { ValenceClient } from 'valenceai';
|
|
40
|
+
|
|
41
|
+
// API usage remains identical
|
|
42
|
+
const client = new ValenceClient();
|
|
43
|
+
const result = await client.discrete.emotions('audio.wav', '7emotions');
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Note**: This is primarily a packaging/naming change. All API functionality remains identical to v0.3.0.
|
|
47
|
+
|
|
48
|
+
## [0.3.0] - 2025-06-17
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
- **Unified Client Architecture** with `ValenceClient` containing nested `discrete` and `asynch` clients
|
|
52
|
+
- **Perfect API Symmetry** with Python SDK for consistent cross-language experience
|
|
53
|
+
- **Enhanced method organization** through nested client structure
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
- **BREAKING**: Complete API restructure to unified client pattern
|
|
57
|
+
- **BREAKING**: `predictDiscreteAudioEmotion()` → `client.discrete.emotions()`
|
|
58
|
+
- **BREAKING**: `uploadAsyncAudio()` → `client.asynch.upload()`
|
|
59
|
+
- **BREAKING**: `getEmotions()` → `client.asynch.emotions()`
|
|
60
|
+
- **BREAKING**: Environment variable names updated for consistency:
|
|
61
|
+
- `VALENCE_SHORT_URL` → `VALENCE_DISCRETE_URL`
|
|
62
|
+
- `VALENCE_LONG_URL` → `VALENCE_ASYNC_URL`
|
|
63
|
+
- **Import pattern**: `import { ValenceClient } from 'valenceai'` (single import)
|
|
64
|
+
- **Parameter naming**: `maxTries` → `maxAttempts`, `intervalMs` → `intervalSeconds`
|
|
65
|
+
- **Examples updated** to use new unified client structure
|
|
66
|
+
- **Constructor pattern**: `new ValenceClient(partSize, maxRetries)`
|
|
67
|
+
|
|
68
|
+
### Technical Improvements
|
|
69
|
+
- **Nested client architecture** for better code organization
|
|
70
|
+
- **Consistent parameter naming** with Python SDK
|
|
71
|
+
- **Unified error handling** across all client types
|
|
72
|
+
- **Maintained async/await patterns** for all operations
|
|
73
|
+
- **Preserved multipart upload functionality** with enhanced structure
|
|
74
|
+
|
|
75
|
+
### Documentation
|
|
76
|
+
- **Updated README** with new unified API examples
|
|
77
|
+
- **Enhanced JSDoc** documentation for unified client
|
|
78
|
+
- **Updated example scripts** demonstrating new client structure
|
|
79
|
+
- **Migration guide** for upgrading from v0.2.x
|
|
80
|
+
|
|
81
|
+
### Migration Guide
|
|
82
|
+
```javascript
|
|
83
|
+
// Old API (v0.2.x)
|
|
84
|
+
import { predictDiscreteAudioEmotion, uploadAsyncAudio, getEmotions } from 'valence-sdk';
|
|
85
|
+
const result = await predictDiscreteAudioEmotion('file.wav', '7emotions');
|
|
86
|
+
const requestId = await uploadAsyncAudio('long.wav');
|
|
87
|
+
const emotions = await getEmotions(requestId);
|
|
88
|
+
|
|
89
|
+
// New API (v0.3.0)
|
|
90
|
+
import { ValenceClient } from 'valenceai';
|
|
91
|
+
const client = new ValenceClient();
|
|
92
|
+
const result = await client.discrete.emotions('file.wav', '7emotions');
|
|
93
|
+
const requestId = await client.asynch.upload('long.wav');
|
|
94
|
+
const emotions = await client.asynch.emotions(requestId);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Cross-Platform Consistency
|
|
98
|
+
- **Identical API structure** to Python SDK
|
|
99
|
+
- **Same method names** across both platforms
|
|
100
|
+
- **Consistent parameter naming** conventions
|
|
101
|
+
- **Unified documentation** and examples
|
|
102
|
+
|
|
103
|
+
## [0.2.0] - 2025-06-10
|
|
104
|
+
|
|
105
|
+
### Added
|
|
106
|
+
- **Comprehensive test suite** with 95%+ coverage targets and 100+ test cases
|
|
107
|
+
- **JSDoc documentation** for all public functions with detailed parameter descriptions
|
|
108
|
+
- **Enhanced error handling** with detailed error messages and proper error types
|
|
109
|
+
- **Input validation** for all parameters (file paths, model types, API keys, etc.)
|
|
110
|
+
- **Improved logging** with configurable levels (debug, info, warn, error) and timestamps
|
|
111
|
+
- **Timeout controls** (30s for discrete, 60s per chunk for async uploads)
|
|
112
|
+
- **Configuration validation** function to ensure proper setup
|
|
113
|
+
- **User-Agent header** for API requests tracking SDK version
|
|
114
|
+
- **Test dependencies**: jest, nock, c8 for comprehensive testing
|
|
115
|
+
- **npm scripts** for testing: `test`, `test:coverage`, `test:watch`
|
|
116
|
+
|
|
117
|
+
### Changed
|
|
118
|
+
- **BREAKING**: Renamed `predictShortAudioEmotion` → `predictDiscreteAudioEmotion`
|
|
119
|
+
- **BREAKING**: Renamed `uploadLongAudio` → `uploadAsyncAudio`
|
|
120
|
+
- **BREAKING**: Renamed `getPrediction` → `getEmotions`
|
|
121
|
+
- **BREAKING**: File names changed: `shortAudio.js` → `discreteAudio.js`, `longAudio.js` → `asyncAudio.js`
|
|
122
|
+
- **Enhanced async upload** with better error handling and part validation
|
|
123
|
+
- **Improved polling logic** in `getEmotions` with better retry mechanisms
|
|
124
|
+
- **Logger enhancement** with proper log level hierarchy and timestamps
|
|
125
|
+
- **Example scripts** updated to use new function names
|
|
126
|
+
- **Package scripts** enhanced with discrete/async example runners
|
|
127
|
+
|
|
128
|
+
### Security
|
|
129
|
+
- ✅ **Zero security vulnerabilities** in dependencies
|
|
130
|
+
- ✅ **Latest stable dependencies** (axios 1.6.0, dotenv 16.3.1, form-data 4.0.0)
|
|
131
|
+
- ✅ **Proper input sanitization** and validation throughout
|
|
132
|
+
- ✅ **Secure error handling** without exposing sensitive information
|
|
133
|
+
|
|
134
|
+
### Documentation
|
|
135
|
+
- **Enhanced README examples** using new function names
|
|
136
|
+
- **Comprehensive JSDoc** comments for all public functions
|
|
137
|
+
- **Test documentation** and coverage reporting
|
|
138
|
+
|
|
139
|
+
### Developer Experience
|
|
140
|
+
- **Jest configuration** for ESM modules with proper test setup
|
|
141
|
+
- **Coverage thresholds** ensuring code quality (95%+ coverage)
|
|
142
|
+
- **Mock testing** with nock for reliable API testing
|
|
143
|
+
- **Enhanced npm scripts** for different development workflows
|
|
144
|
+
|
|
145
|
+
## [0.1.0] - 2025-06-10
|
|
146
|
+
|
|
147
|
+
### Added
|
|
148
|
+
- Initial release of Valence SDK for JavaScript
|
|
149
|
+
- Support for discrete (short) audio emotion prediction
|
|
150
|
+
- Support for async (long) audio emotion prediction with multipart upload
|
|
151
|
+
- Environment-based configuration with .env support
|
|
152
|
+
- Basic logging functionality
|
|
153
|
+
- Example scripts for both audio types
|
|
154
|
+
- Core API client with authentication headers
|
package/README.dev.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# 🎧 Valence SDK for Emotion Detection (JavaScript)
|
|
2
|
+
|
|
3
|
+
**valenceai** is a Node.js SDK for interacting with the [Valence Vibrations](https://valencevibrations.com) Emotion Detection API. It provides full support for uploading discrete and async audio files to retrieve their emotional signatures.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Discrete audio processing** - Single API call for short audio files
|
|
8
|
+
- 🔄 **Async audio processing** - Multipart streaming for long audio files
|
|
9
|
+
- 🎭 **Emotion models** - Choose between 4-emotion or 7-emotion detection models
|
|
10
|
+
- ⚙️ **Environment configuration** - Built-in support for `.env` configuration
|
|
11
|
+
- 📊 **Enhanced logging** - Configurable log levels with timestamps
|
|
12
|
+
- 🛡️ **Robust error handling** - Comprehensive validation and error recovery
|
|
13
|
+
- ✅ **TypeScript ready** - Full JSDoc documentation for all functions
|
|
14
|
+
- 🧪 **100% tested** - Comprehensive test suite with 95%+ coverage
|
|
15
|
+
- 🔒 **Security focused** - Input validation and secure error handling
|
|
16
|
+
|
|
17
|
+
## 📦 Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install valenceai
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 🔧 Configuration
|
|
24
|
+
|
|
25
|
+
Create a `.env` file in your project root:
|
|
26
|
+
|
|
27
|
+
```env
|
|
28
|
+
VALENCE_API_KEY=your_api_key # Required: Your Valence API key
|
|
29
|
+
VALENCE_DISCRETE_URL=https://discrete-api-url # Optional: Discrete audio endpoint
|
|
30
|
+
VALENCE_ASYNC_URL=https://async-api-url # Optional: Async audio endpoint
|
|
31
|
+
VALENCE_LOG_LEVEL=info # Optional: debug, info, warn, error
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Configuration Validation
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
import { validateConfig } from 'valenceai';
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
validateConfig();
|
|
41
|
+
console.log('Configuration is valid!');
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('Configuration error:', error.message);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 🚀 Usage
|
|
48
|
+
|
|
49
|
+
### Discrete Audio (Short Files)
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import { ValenceClient } from 'valenceai';
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const client = new ValenceClient();
|
|
56
|
+
const result = await client.discrete.emotions('audio.wav', '7emotions');
|
|
57
|
+
console.log('Emotions detected:', result);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error('Error:', error.message);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Async Audio (Long Files)
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
import { ValenceClient } from 'valenceai';
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const client = new ValenceClient();
|
|
70
|
+
|
|
71
|
+
// Upload the audio file
|
|
72
|
+
const requestId = await client.asynch.upload('long_audio.wav');
|
|
73
|
+
console.log('Upload complete. Request ID:', requestId);
|
|
74
|
+
|
|
75
|
+
// Get emotions from uploaded audio
|
|
76
|
+
const emotions = await client.asynch.emotions(requestId);
|
|
77
|
+
console.log('Emotions detected:', emotions);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error('Error:', error.message);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Advanced Usage
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
import { ValenceClient } from 'valenceai';
|
|
87
|
+
|
|
88
|
+
// Custom client configuration
|
|
89
|
+
const client = new ValenceClient(
|
|
90
|
+
2 * 1024 * 1024, // 2MB parts
|
|
91
|
+
5 // 5 retry attempts
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Upload with custom configuration
|
|
95
|
+
const requestId = await client.asynch.upload('huge_file.wav');
|
|
96
|
+
|
|
97
|
+
// Custom polling with more attempts and shorter intervals
|
|
98
|
+
const emotions = await client.asynch.emotions(
|
|
99
|
+
requestId,
|
|
100
|
+
50, // 50 polling attempts
|
|
101
|
+
3 // 3 second intervals
|
|
102
|
+
);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 🎛️ API Reference
|
|
106
|
+
|
|
107
|
+
### `new ValenceClient(partSize?, maxRetries?)`
|
|
108
|
+
|
|
109
|
+
Creates a new Valence client with nested discrete and asynch clients.
|
|
110
|
+
|
|
111
|
+
**Parameters:**
|
|
112
|
+
- `partSize` (number, optional): Size of each part in bytes for async uploads (default: 5MB)
|
|
113
|
+
- `maxRetries` (number, optional): Maximum retry attempts for async uploads (default: 3)
|
|
114
|
+
|
|
115
|
+
**Returns:** `ValenceClient` instance with `discrete` and `asynch` properties
|
|
116
|
+
|
|
117
|
+
### `client.discrete.emotions(filePath, model?)`
|
|
118
|
+
|
|
119
|
+
Predicts emotions for discrete (short) audio files using a single API call.
|
|
120
|
+
|
|
121
|
+
**Parameters:**
|
|
122
|
+
- `filePath` (string): Path to the audio file
|
|
123
|
+
- `model` (string, optional): `"4emotions"` or `"7emotions"` (default: `"4emotions"`)
|
|
124
|
+
|
|
125
|
+
**Returns:** `Promise<Object>` - Emotion prediction results
|
|
126
|
+
|
|
127
|
+
**Throws:** Error if file doesn't exist, API key missing, or request fails
|
|
128
|
+
|
|
129
|
+
### `client.asynch.upload(filePath)`
|
|
130
|
+
|
|
131
|
+
Uploads async (long) audio files using multipart upload for processing.
|
|
132
|
+
|
|
133
|
+
**Parameters:**
|
|
134
|
+
- `filePath` (string): Path to the audio file
|
|
135
|
+
|
|
136
|
+
**Returns:** `Promise<string>` - Request ID for tracking the upload
|
|
137
|
+
|
|
138
|
+
**Throws:** Error if file doesn't exist, API key missing, or upload fails
|
|
139
|
+
|
|
140
|
+
### `client.asynch.emotions(requestId, maxAttempts?, intervalSeconds?)`
|
|
141
|
+
|
|
142
|
+
Retrieves emotion prediction results for async audio processing.
|
|
143
|
+
|
|
144
|
+
**Parameters:**
|
|
145
|
+
- `requestId` (string): Request ID from `client.asynch.upload`
|
|
146
|
+
- `maxAttempts` (number, optional): Maximum polling attempts (default: 20, range: 1-100)
|
|
147
|
+
- `intervalSeconds` (number, optional): Polling interval in seconds (default: 5, range: 1-60)
|
|
148
|
+
|
|
149
|
+
**Returns:** `Promise<Object>` - Emotion prediction results
|
|
150
|
+
|
|
151
|
+
**Throws:** Error if requestId is invalid or prediction times out
|
|
152
|
+
|
|
153
|
+
### `validateConfig()`
|
|
154
|
+
|
|
155
|
+
Validates the current SDK configuration.
|
|
156
|
+
|
|
157
|
+
**Throws:** Error if required configuration is missing or invalid
|
|
158
|
+
|
|
159
|
+
## 🧪 Examples
|
|
160
|
+
|
|
161
|
+
Run the included examples:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Install dependencies
|
|
165
|
+
npm install
|
|
166
|
+
|
|
167
|
+
# Run discrete audio example
|
|
168
|
+
npm run example:discrete
|
|
169
|
+
|
|
170
|
+
# Run async audio example
|
|
171
|
+
npm run example:async
|
|
172
|
+
|
|
173
|
+
# Or run directly
|
|
174
|
+
node examples/uploadShort.js
|
|
175
|
+
node examples/uploadLong.js
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## 🛠 Development
|
|
179
|
+
|
|
180
|
+
### Testing
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# Run all tests
|
|
184
|
+
npm test
|
|
185
|
+
|
|
186
|
+
# Run tests with coverage
|
|
187
|
+
npm run test:coverage
|
|
188
|
+
|
|
189
|
+
# Watch mode for development
|
|
190
|
+
npm run test:watch
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Building and Publishing
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Validate configuration and run tests
|
|
197
|
+
npm test
|
|
198
|
+
|
|
199
|
+
# Publish to npm
|
|
200
|
+
npm login
|
|
201
|
+
npm publish --access public
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## 🚀 What's New in v0.3.0
|
|
205
|
+
|
|
206
|
+
### Major Changes
|
|
207
|
+
- 🏗️ **Unified Client Architecture** - Single `ValenceClient` with nested `discrete` and `asynch` clients
|
|
208
|
+
- 🔄 **API Restructure**: `predictDiscreteAudioEmotion()` → `client.discrete.emotions()`
|
|
209
|
+
- 🔄 **API Restructure**: `uploadAsyncAudio()` → `client.asynch.upload()`
|
|
210
|
+
- 🔄 **API Restructure**: `getEmotions()` → `client.asynch.emotions()`
|
|
211
|
+
- 📦 **Single Import**: `import { ValenceClient } from 'valenceai'`
|
|
212
|
+
|
|
213
|
+
### Benefits
|
|
214
|
+
- ✅ **Perfect API Symmetry** - Identical structure to Python SDK
|
|
215
|
+
- 🎯 **Intuitive Organization** - Related methods grouped together
|
|
216
|
+
- 🔄 **Consistent Naming** - Same method names across Python and JavaScript
|
|
217
|
+
- 📚 **Enhanced Documentation** - Updated examples and migration guide
|
|
218
|
+
- 🧪 **Maintained Quality** - All existing functionality preserved
|
|
219
|
+
|
|
220
|
+
See [CHANGELOG.md](./CHANGELOG.md) for complete details and migration guide.
|
|
221
|
+
|
|
222
|
+
## 🤝 Contributing
|
|
223
|
+
|
|
224
|
+
We welcome contributions! Please:
|
|
225
|
+
|
|
226
|
+
1. Fork the repository
|
|
227
|
+
2. Create a feature branch: `git checkout -b feature/amazing-feature`
|
|
228
|
+
3. Make your changes with tests
|
|
229
|
+
4. Ensure all tests pass: `npm test`
|
|
230
|
+
5. Submit a pull request
|
|
231
|
+
|
|
232
|
+
## 🆘 Support
|
|
233
|
+
|
|
234
|
+
- 📖 **Documentation**: See [API Reference](#🎛️-api-reference) above
|
|
235
|
+
- 🐛 **Issues**: [GitHub Issues](https://github.com/valencevibrations/valence-sdk-js/issues)
|
|
236
|
+
- 💬 **Questions**: Contact [Valence Vibrations](https://valencevibrations.com)
|
|
237
|
+
|
|
238
|
+
## 📜 License
|
|
239
|
+
|
|
240
|
+
ISC License © 2025 [Valence Vibrations](https://valencevibrations.com)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ValenceClient } from 'valenceai';
|
|
2
|
+
|
|
3
|
+
(async () => {
|
|
4
|
+
const client = new ValenceClient();
|
|
5
|
+
const requestId = await client.asynch.upload('long_audio.wav');
|
|
6
|
+
console.log('Request ID:', requestId);
|
|
7
|
+
|
|
8
|
+
const result = await client.asynch.emotions(requestId);
|
|
9
|
+
console.log('Emotion:', result);
|
|
10
|
+
})();
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
testEnvironment: 'node',
|
|
3
|
+
transform: {},
|
|
4
|
+
moduleNameMapper: {
|
|
5
|
+
'^(\\.{1,2}/.*)\\.js$': '$1'
|
|
6
|
+
},
|
|
7
|
+
collectCoverageFrom: [
|
|
8
|
+
'src/**/*.js',
|
|
9
|
+
'!src/index.js',
|
|
10
|
+
'!**/node_modules/**'
|
|
11
|
+
],
|
|
12
|
+
coverageDirectory: 'coverage',
|
|
13
|
+
coverageReporters: ['text', 'lcov', 'html'],
|
|
14
|
+
coverageThreshold: {
|
|
15
|
+
global: {
|
|
16
|
+
branches: 90,
|
|
17
|
+
functions: 95,
|
|
18
|
+
lines: 95,
|
|
19
|
+
statements: 95
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
testMatch: [
|
|
23
|
+
'**/tests/**/*.test.js'
|
|
24
|
+
],
|
|
25
|
+
setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
|
|
26
|
+
verbose: true
|
|
27
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "valenceai",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node examples/uploadShort.js",
|
|
8
|
+
"example:discrete": "node examples/uploadShort.js",
|
|
9
|
+
"example:async": "node examples/uploadLong.js",
|
|
10
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
11
|
+
"test:coverage": "c8 jest",
|
|
12
|
+
"test:watch": "jest --watch"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"axios": "^1.6.0",
|
|
16
|
+
"dotenv": "^16.3.1",
|
|
17
|
+
"form-data": "^4.0.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"jest": "^29.7.0",
|
|
21
|
+
"nock": "^13.5.0",
|
|
22
|
+
"c8": "^8.0.1"
|
|
23
|
+
},
|
|
24
|
+
"description": "**valenceai** is a Node.js SDK for interacting with the [Valence Vibrations](https://valencevibrations.com) Emotion Detection API. It provides full support for uploading short and long audio files to retrieve their emotional signatures.",
|
|
25
|
+
"directories": {
|
|
26
|
+
"example": "examples"
|
|
27
|
+
},
|
|
28
|
+
"author": "julian.olarte",
|
|
29
|
+
"license": "ISC"
|
|
30
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { config } from './config.js';
|
|
4
|
+
import { getHeaders } from './client.js';
|
|
5
|
+
import { log } from './utils/logger.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Uploads async (long) audio files using multipart upload
|
|
9
|
+
* @param {string} filePath - Path to the audio file
|
|
10
|
+
* @param {number} partSize - Size of each part in bytes (default: 5MB)
|
|
11
|
+
* @param {number} maxRetries - Maximum retry attempts (default: 3)
|
|
12
|
+
* @returns {Promise<string>} Request ID for tracking the upload
|
|
13
|
+
* @throws {Error} If file doesn't exist, API key missing, or upload fails
|
|
14
|
+
*/
|
|
15
|
+
export async function uploadAsyncAudio(filePath, partSize = 5 * 1024 * 1024, maxRetries = 3) {
|
|
16
|
+
// Validation
|
|
17
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
18
|
+
throw new Error('filePath is required and must be a string');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!config.apiKey) {
|
|
22
|
+
throw new Error('VALENCE_API_KEY is required');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(filePath)) {
|
|
26
|
+
throw new Error(`File not found: ${filePath}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (partSize < 1024 * 1024 || partSize > 100 * 1024 * 1024) {
|
|
30
|
+
throw new Error('partSize must be between 1MB and 100MB');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
log(`Starting async audio upload for ${filePath}`, 'info');
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const fileSize = fs.statSync(filePath).size;
|
|
37
|
+
const partCount = Math.ceil(fileSize / partSize);
|
|
38
|
+
const initiateUrl = `${config.asyncAudioUrl}/upload/initiate`;
|
|
39
|
+
|
|
40
|
+
log(`File size: ${fileSize} bytes, parts: ${partCount}`, 'debug');
|
|
41
|
+
|
|
42
|
+
const { data } = await axios.get(initiateUrl, {
|
|
43
|
+
headers: getHeaders(),
|
|
44
|
+
params: { file_name: filePath.split('/').pop(), part_count: partCount },
|
|
45
|
+
timeout: 30000
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const fileStream = fs.createReadStream(filePath, { highWaterMark: partSize });
|
|
49
|
+
const parts = [];
|
|
50
|
+
let index = 0;
|
|
51
|
+
|
|
52
|
+
for await (const chunk of fileStream) {
|
|
53
|
+
if (!data.presigned_urls || !data.presigned_urls[index]) {
|
|
54
|
+
throw new Error(`Missing presigned URL for part ${index + 1}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const part = data.presigned_urls[index];
|
|
58
|
+
const partNumber = part.part_number;
|
|
59
|
+
const url = part.url;
|
|
60
|
+
|
|
61
|
+
log(`Uploading part ${index + 1}/${partCount}`, 'debug');
|
|
62
|
+
|
|
63
|
+
const res = await axios.put(url, chunk, {
|
|
64
|
+
headers: { 'Content-Length': chunk.length },
|
|
65
|
+
timeout: 60000 // 1 minute timeout for each part
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!res.headers.etag) {
|
|
69
|
+
throw new Error(`Failed to get ETag for part ${partNumber}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
parts.push({ ETag: res.headers.etag, PartNumber: partNumber });
|
|
73
|
+
index++;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
log('All parts uploaded, completing multipart upload', 'info');
|
|
77
|
+
|
|
78
|
+
const completeUrl = `${config.asyncAudioUrl}/upload/complete`;
|
|
79
|
+
await axios.post(completeUrl, {
|
|
80
|
+
request_id: data.request_id,
|
|
81
|
+
upload_id: data.upload_id,
|
|
82
|
+
parts: parts.sort((a, b) => a.PartNumber - b.PartNumber)
|
|
83
|
+
}, {
|
|
84
|
+
headers: getHeaders(),
|
|
85
|
+
timeout: 30000
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
log(`Async audio upload completed. Request ID: ${data.request_id}`, 'info');
|
|
89
|
+
return data.request_id;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
log(`Error uploading async audio: ${error.message}`, 'error');
|
|
92
|
+
|
|
93
|
+
if (error.response) {
|
|
94
|
+
throw new Error(`API error (${error.response.status}): ${error.response.data?.message || error.response.statusText}`);
|
|
95
|
+
} else if (error.request) {
|
|
96
|
+
throw new Error('Network error: Unable to reach the API');
|
|
97
|
+
} else {
|
|
98
|
+
throw new Error(`Upload error: ${error.message}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Retrieves emotion prediction results for async audio processing
|
|
105
|
+
* @param {string} requestId - Request ID from uploadAsyncAudio
|
|
106
|
+
* @param {number} maxTries - Maximum polling attempts (default: 20)
|
|
107
|
+
* @param {number} intervalMs - Polling interval in milliseconds (default: 5000)
|
|
108
|
+
* @returns {Promise<Object>} Emotion prediction results
|
|
109
|
+
* @throws {Error} If requestId is invalid or prediction times out
|
|
110
|
+
*/
|
|
111
|
+
export async function getEmotions(requestId, maxTries = 20, intervalMs = 5000) {
|
|
112
|
+
// Validation
|
|
113
|
+
if (!requestId || typeof requestId !== 'string') {
|
|
114
|
+
throw new Error('requestId is required and must be a string');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!config.apiKey) {
|
|
118
|
+
throw new Error('VALENCE_API_KEY is required');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (maxTries < 1 || maxTries > 100) {
|
|
122
|
+
throw new Error('maxTries must be between 1 and 100');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (intervalMs < 1000 || intervalMs > 60000) {
|
|
126
|
+
throw new Error('intervalMs must be between 1000 and 60000');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
log(`Starting emotion prediction polling for request ${requestId}`, 'info');
|
|
130
|
+
|
|
131
|
+
const url = `${config.asyncAudioUrl}/prediction`;
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < maxTries; i++) {
|
|
134
|
+
try {
|
|
135
|
+
log(`Polling attempt ${i + 1}/${maxTries}`, 'debug');
|
|
136
|
+
|
|
137
|
+
const res = await axios.get(url, {
|
|
138
|
+
headers: getHeaders(),
|
|
139
|
+
params: { request_id: requestId },
|
|
140
|
+
timeout: 15000
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (res.status === 200 && res.data) {
|
|
144
|
+
log('Emotion prediction completed successfully', 'info');
|
|
145
|
+
return res.data;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (res.status === 202) {
|
|
149
|
+
log('Prediction still processing, waiting...', 'debug');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (error.response?.status === 404) {
|
|
154
|
+
throw new Error(`Request ID not found: ${requestId}`);
|
|
155
|
+
} else if (error.response?.status >= 400 && error.response?.status < 500) {
|
|
156
|
+
throw new Error(`Client error (${error.response.status}): ${error.response.data?.message || error.response.statusText}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
log(`Polling error (attempt ${i + 1}): ${error.message}`, 'warn');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (i < maxTries - 1) {
|
|
163
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
throw new Error(`Prediction not available after ${maxTries} attempts. The request may still be processing.`);
|
|
168
|
+
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { config } from './config.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns headers required for API authentication
|
|
6
|
+
* @returns {Object} Headers object with API key
|
|
7
|
+
* @throws {Error} If API key is not configured
|
|
8
|
+
*/
|
|
9
|
+
export const getHeaders = () => {
|
|
10
|
+
if (!config.apiKey) {
|
|
11
|
+
throw new Error('VALENCE_API_KEY is required but not configured');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
'x-api-key': config.apiKey,
|
|
16
|
+
'User-Agent': 'valenceai/0.4.0'
|
|
17
|
+
};
|
|
18
|
+
};
|