162 lines
5.1 KiB
JavaScript
162 lines
5.1 KiB
JavaScript
|
// ====================================
|
||
|
// Voice Bot Utilities
|
||
|
// Helper functions for text processing, metrics calculation,
|
||
|
// and pricing calculations for the voice bot system
|
||
|
// ====================================
|
||
|
|
||
|
const { encoding_for_model } = require('tiktoken');
|
||
|
const prices = require('./prices.json');
|
||
|
require('dotenv').config();
|
||
|
|
||
|
// ====================================
|
||
|
// Text Processing Functions
|
||
|
// ====================================
|
||
|
|
||
|
/**
|
||
|
* Checks if a string ends with a sentence terminator (., !, ?, or :)
|
||
|
* Used to determine when to send text for speech synthesis
|
||
|
* @param {string} text - The text to check
|
||
|
* @returns {boolean} - True if the text ends with a sentence terminator
|
||
|
*/
|
||
|
function matchesSentenceEnding(text) {
|
||
|
return /([.!?:]([\s]|$|\n))/.test(text);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Removes special characters and formatting markers from text
|
||
|
* Cleans up text before processing or displaying
|
||
|
* @param {string} text - The text to clean
|
||
|
* @returns {string} - Cleaned text
|
||
|
*/
|
||
|
function removeSpecialCharacters(text) {
|
||
|
return text
|
||
|
.replace(/[*#\n]/g, '') // Remove asterisks, hashtags, and newlines
|
||
|
.replace(/【\d+:\d+†[^】]+】/g, '') // Remove timestamp-like markers
|
||
|
.trim();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Calculates the duration of an audio buffer
|
||
|
* @param {Buffer} buffer - The audio buffer
|
||
|
* @param {number} sampleRate - The sample rate of the audio
|
||
|
* @param {number} channels - Number of audio channels
|
||
|
* @returns {number} - Duration in seconds
|
||
|
*/
|
||
|
function getAudioDuration(buffer, sampleRate, channels) {
|
||
|
const bytesPerSample = 2; // Assuming 16-bit audio
|
||
|
const totalBytes = buffer.length;
|
||
|
const duration = totalBytes / (sampleRate * channels * bytesPerSample);
|
||
|
return Math.ceil(duration);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts a hex string to a UUID format string
|
||
|
* @param {string} hex - The hex string to convert to UUID
|
||
|
* @returns {string} - Formatted UUID string
|
||
|
*/
|
||
|
function toUUID(hex) {
|
||
|
return hex.replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, '$1-$2-$3-$4-$5');
|
||
|
}
|
||
|
|
||
|
// ====================================
|
||
|
// Metrics and Pricing Calculator
|
||
|
// ====================================
|
||
|
|
||
|
/**
|
||
|
* Calculates various metrics and costs for processing text
|
||
|
* Handles different roles (system, user, assistant) with different pricing models
|
||
|
*
|
||
|
* @param {string} role - The role of the message (system, user, or assistant)
|
||
|
* @param {string} text - The text content to analyze
|
||
|
* @param {number} [wordsPerMinute=130] - Words per minute rate for duration calculation
|
||
|
* @param {number} [costPerWord=0.00001] - Cost per word
|
||
|
* @param {number} [costPerChar=0.0000075] - Cost per character
|
||
|
* @param {number} [costPerSecond=0.00025] - Cost per second of audio
|
||
|
* @returns {Object} - Object containing various metrics and costs
|
||
|
*/
|
||
|
function calculateMetricsAndPricing(
|
||
|
role,
|
||
|
text,
|
||
|
wordsPerMinute = 130,
|
||
|
costPerWord = 0.00001,
|
||
|
costPerChar = 0.0000075,
|
||
|
costPerSecond = 0.00025
|
||
|
) {
|
||
|
// Determine which model to use, fallback to gpt-3.5-turbo if specified model not in prices
|
||
|
const model = prices[process.env.OPENAI_MODEL || 'gpt-3.5-turbo']
|
||
|
? process.env.OPENAI_MODEL || 'gpt-3.5-turbo'
|
||
|
: 'gpt-4';
|
||
|
|
||
|
// Initialize tokenizer for the selected model
|
||
|
const encoder = encoding_for_model(model);
|
||
|
|
||
|
// Calculate basic metrics
|
||
|
const charCount = text.length;
|
||
|
const wordCount = text.trim().split(/\s+/).length;
|
||
|
const durationInSeconds = Math.ceil((wordCount / wordsPerMinute) * 60);
|
||
|
const tokenCount = encoder.encode(text).length;
|
||
|
|
||
|
// Calculate costs with precision
|
||
|
const costByWord = parseFloat((wordCount * costPerWord).toFixed(7));
|
||
|
const costByCharacter = parseFloat((charCount * costPerChar).toFixed(7));
|
||
|
const costBySecond = parseFloat((durationInSeconds * costPerSecond).toFixed(7));
|
||
|
const costByToken = parseFloat(
|
||
|
(tokenCount * prices[model][role === 'assistant' ? 'output' : 'input'] / 1000)
|
||
|
.toFixed(7)
|
||
|
);
|
||
|
|
||
|
// Free up encoder resources
|
||
|
encoder.free();
|
||
|
|
||
|
// Return appropriate metrics based on role
|
||
|
switch (role) {
|
||
|
case 'system':
|
||
|
return {
|
||
|
tokenCount,
|
||
|
costByToken,
|
||
|
model
|
||
|
};
|
||
|
|
||
|
case 'user':
|
||
|
return {
|
||
|
durationInSeconds,
|
||
|
tokenCount,
|
||
|
costBySecond,
|
||
|
costByToken,
|
||
|
model
|
||
|
};
|
||
|
|
||
|
case 'assistant':
|
||
|
return {
|
||
|
charCount,
|
||
|
tokenCount,
|
||
|
costByCharacter,
|
||
|
costByToken,
|
||
|
model
|
||
|
};
|
||
|
|
||
|
default:
|
||
|
// Return all metrics if role is not specified
|
||
|
return {
|
||
|
charCount,
|
||
|
wordCount,
|
||
|
tokenCount,
|
||
|
durationInSeconds,
|
||
|
costByWord,
|
||
|
costByCharacter,
|
||
|
costBySecond,
|
||
|
costByToken,
|
||
|
model
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Export utility functions
|
||
|
module.exports = {
|
||
|
removeSpecialCharacters,
|
||
|
matchesSentenceEnding,
|
||
|
calculateMetricsAndPricing,
|
||
|
getAudioDuration,
|
||
|
toUUID
|
||
|
};
|