{ "kind": "open-flow-workflow", "version": 1, "name": "OpenAI Text to Speech", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_7567d8226b9ae371": { "name": "Speech text and voice", "kind": "start", "inputDefs": [ { "jsonSchema": { "type": "string", "minLength": 1, "maxLength": 4096 }, "nullable": false, "handle": "input_text" }, { "jsonSchema": { "type": "string" }, "nullable": true, "handle": "voice" } ] }, "n_984162e9021686b2": { "inputs": { "input_text": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7567d8226b9ae371", "output": "input_text" } ] }, "voice": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7567d8226b9ae371", "output": "voice" } ] } }, "name": "Validate speech input", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "input_text" }, { "jsonSchema": { "type": "string" }, "nullable": true, "handle": "voice" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "input_text" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "voice" } ], "name": "Validate speech input", "moduleId": "n_1dfcc0a72c29193a", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_a9b1c51c6e87c00a": { "inputs": { "model": { "kind": "value", "value": "tts-1" }, "input": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_984162e9021686b2", "output": "input_text" } ] }, "voice": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_984162e9021686b2", "output": "voice" } ] }, "response_format": { "kind": "value", "value": "mp3" }, "stream_format": { "kind": "value", "value": "audio" } }, "name": "Generate MP3 speech", "kind": "task", "task": { "inputs": [ { "description": "The text-to-speech model to use.", "jsonSchema": { "type": "string", "description": "The text-to-speech model to use." }, "nullable": false, "handle": "model" }, { "description": "The text to synthesize into speech.", "jsonSchema": { "type": "string", "description": "The text to synthesize into speech." }, "nullable": false, "handle": "input" }, { "description": "The voice configuration used for synthesis.", "jsonSchema": { "anyOf": [ { "type": "string", "description": "A built-in voice name." }, { "type": "object", "additionalProperties": { "description": "Any JSON value accepted by the upstream API." }, "description": "A structured voice object accepted by the API." } ], "description": "The voice configuration used for synthesis." }, "nullable": false, "handle": "voice" }, { "description": "Optional voice instructions that guide the synthesis style.", "jsonSchema": { "type": "string", "description": "Optional voice instructions that guide the synthesis style." }, "nullable": true, "handle": "instructions", "value": null }, { "description": "The audio format to return.", "jsonSchema": { "type": "string", "enum": [ "mp3", "opus", "aac", "flac", "wav", "pcm" ], "description": "The audio format to return." }, "nullable": true, "handle": "response_format", "value": null }, { "description": "The playback speed multiplier.", "jsonSchema": { "type": "number", "minimum": 0.25, "maximum": 4, "description": "The playback speed multiplier." }, "nullable": true, "handle": "speed", "value": null }, { "description": "The speech response delivery format. This connector only accepts audio or an omitted value.", "jsonSchema": { "type": "string", "enum": [ "audio", "sse" ], "description": "The speech response delivery format. This connector only accepts audio or an omitted value." }, "nullable": true, "handle": "stream_format", "value": null } ], "outputs": [ { "description": "The synthesized audio encoded as base64.", "jsonSchema": { "type": "string", "description": "The synthesized audio encoded as base64." }, "nullable": false, "handle": "content_base64" }, { "description": "The MIME type of the synthesized audio.", "jsonSchema": { "type": "string", "description": "The MIME type of the synthesized audio." }, "nullable": false, "handle": "content_type" } ], "name": "Generate MP3 speech", "executor": { "kind": "connector", "action": "openai.create_speech" } } }, "n_93e5f554aa8c8c31": { "inputs": { "content_base64": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_a9b1c51c6e87c00a", "output": "content_base64" } ] }, "content_type": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_a9b1c51c6e87c00a", "output": "content_type" } ] } }, "name": "Save audio artifact", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string", "description": "The synthesized audio encoded as base64." }, "nullable": false, "handle": "content_base64" }, { "jsonSchema": { "type": "string", "description": "The MIME type of the synthesized audio." }, "nullable": false, "handle": "content_type" } ], "outputs": [ { "jsonSchema": { "type": "object", "properties": { "kind": { "const": "artifact" }, "id": { "type": "string" }, "name": { "type": "string" }, "mediaType": { "type": "string" }, "size": { "type": "integer", "minimum": 1 }, "digest": { "type": "string" } }, "required": [ "kind", "id", "name", "size", "digest" ] }, "nullable": false, "handle": "audio" } ], "name": "Save audio artifact", "moduleId": "n_d4aa114b1fbfda6b", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_6b061d0175e9795f": { "name": "Download speech audio", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "object", "properties": { "kind": { "const": "artifact" }, "id": { "type": "string" }, "name": { "type": "string" }, "mediaType": { "type": "string" }, "size": { "type": "integer", "minimum": 1 }, "digest": { "type": "string" } }, "required": [ "kind", "id", "name", "size", "digest" ] }, "nullable": false, "handle": "audio" } ], "outputMappings": { "audio": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_93e5f554aa8c8c31", "output": "audio" } ] } } } }, "edges": [ { "source": "n_7567d8226b9ae371", "target": "n_984162e9021686b2" }, { "source": "n_984162e9021686b2", "target": "n_a9b1c51c6e87c00a" }, { "source": "n_a9b1c51c6e87c00a", "target": "n_93e5f554aa8c8c31" }, { "source": "n_93e5f554aa8c8c31", "target": "n_6b061d0175e9795f" } ] } }, "modules": { "n_1dfcc0a72c29193a": { "name": "Validate speech input", "imports": [], "source": "export default function prepareSpeech({ input_text, voice }) {\n if (typeof input_text !== 'string' || !input_text.trim()) throw new Error('Input text is required.');\n if (Array.from(input_text).length > 4096) throw new Error('Input text must contain at most 4096 characters.');\n const selected = voice == null || voice === '' ? 'alloy' : voice;\n if (!['alloy', 'ash', 'coral', 'echo', 'fable', 'onyx', 'nova', 'sage', 'shimmer'].includes(selected)) throw new Error('Choose a supported tts-1 voice.');\n return { input_text, voice: selected };\n}\n" }, "n_d4aa114b1fbfda6b": { "name": "Save audio artifact", "imports": [], "source": "import { Buffer } from 'node:buffer';\n\nexport default async function saveSpeech({ content_base64, content_type }, context) {\n if (typeof content_base64 !== 'string' || !content_base64 || content_base64.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(content_base64)) throw new Error('The speech response does not contain valid base64 audio.');\n const mediaType = typeof content_type === 'string' ? content_type.split(';')[0].trim().toLowerCase() : '';\n if (!['audio/mpeg', 'audio/mp3', 'application/octet-stream'].includes(mediaType)) throw new Error('The speech response is not MP3 audio.');\n const bytes = new Uint8Array(Buffer.from(content_base64, 'base64'));\n if (!bytes.length || Buffer.from(bytes).toString('base64') !== content_base64) throw new Error('The speech response is empty or has invalid base64 padding.');\n return { audio: await context.artifact.put(bytes, { name: 'speech.mp3', mediaType: 'audio/mpeg' }) };\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }