[{"data":1,"prerenderedAt":1293},["ShallowReactive",2],{"navigation_docs":3,"-guides-speech":412,"-guides-speech-surround":1288},[4,38,84,326,386],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":37},"Getting started","i-lucide-rocket","\u002Fgetting-started","1.getting-started",[10,15,20,24,28,32],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction","i-lucide-book-open",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":6},"Quick start: Nuxt","\u002Fgetting-started\u002Fquick-start-nuxt","1.getting-started\u002F3.quick-start-nuxt",{"title":25,"path":26,"stem":27,"icon":6},"Quick start: plain Vue","\u002Fgetting-started\u002Fquick-start-vue","1.getting-started\u002F4.quick-start-vue",{"title":29,"path":30,"stem":31,"icon":6},"Quick start: React","\u002Fgetting-started\u002Fquick-start-react","1.getting-started\u002F5.quick-start-react",{"title":33,"path":34,"stem":35,"icon":36},"Glossary","\u002Fgetting-started\u002Fglossary","1.getting-started\u002F6.glossary","i-lucide-book-a",false,{"title":39,"icon":40,"path":41,"stem":42,"children":43,"page":37},"Tutorial","i-lucide-graduation-cap","\u002Ftutorial","2.tutorial",[44,49,54,59,64,69,74,79],{"title":45,"path":46,"stem":47,"icon":48},"Tutorial: overview","\u002Ftutorial\u002Foverview","2.tutorial\u002F1.overview","i-lucide-map",{"title":50,"path":51,"stem":52,"icon":53},"Chapter 2: A simple editor","\u002Ftutorial\u002Fsimple-editor","2.tutorial\u002F2.simple-editor","i-lucide-pencil",{"title":55,"path":56,"stem":57,"icon":58},"Chapter 3: Compose features","\u002Ftutorial\u002Fcompose-features","2.tutorial\u002F3.compose-features","i-lucide-blocks",{"title":60,"path":61,"stem":62,"icon":63},"Chapter 4: Your own block, path A","\u002Ftutorial\u002Fown-block-path-a","2.tutorial\u002F4.own-block-path-a","i-lucide-square-plus",{"title":65,"path":66,"stem":67,"icon":68},"Chapter 5: Your own feature, path B","\u002Ftutorial\u002Fown-feature-path-b","2.tutorial\u002F5.own-feature-path-b","i-lucide-puzzle",{"title":70,"path":71,"stem":72,"icon":73},"Chapter 6: Backend and collaboration","\u002Ftutorial\u002Fbackend-and-collaboration","2.tutorial\u002F6.backend-and-collaboration","i-lucide-users",{"title":75,"path":76,"stem":77,"icon":78},"Chapter 7: AI","\u002Ftutorial\u002Fai","2.tutorial\u002F7.ai","i-lucide-sparkles",{"title":80,"path":81,"stem":82,"icon":83},"Chapter 8: The React surface","\u002Ftutorial\u002Freact-surface","2.tutorial\u002F8.react-surface","i-lucide-atom",{"title":85,"icon":14,"path":86,"stem":87,"children":88,"page":37},"Guides","\u002Fguides","3.guides",[89,93,98,103,108,112,116,121,126,130,134,139,144,149,154,159,164,169,174,179,184,189,194,199,204,209,213,218,223,228,233,237,242,247,251,256,261,266,271,276,281,286,291,295,302,307,311,316,321],{"title":90,"path":91,"stem":92,"icon":58},"Feature composition and entries","\u002Fguides\u002Fcomposition","3.guides\u002F1.composition",{"title":94,"path":95,"stem":96,"icon":97},"Media, mentions and transcript features","\u002Fguides\u002Fmedia-mentions-transcript","3.guides\u002F10.media-mentions-transcript","i-lucide-image",{"title":99,"path":100,"stem":101,"icon":102},"Authors and comments","\u002Fguides\u002Fauthors-and-comments","3.guides\u002F11.authors-and-comments","i-lucide-message-square-text",{"title":104,"path":105,"stem":106,"icon":107},"Themes (Obsidian)","\u002Fguides\u002Fthemes","3.guides\u002F12.themes","i-lucide-palette",{"title":109,"path":110,"stem":111,"icon":58},"Composites and the settings UI","\u002Fguides\u002Fcomposites","3.guides\u002F13.composites",{"title":113,"path":114,"stem":115,"icon":78},"AI","\u002Fguides\u002Fai","3.guides\u002F14.ai",{"title":117,"path":118,"stem":119,"icon":120},"Document tree","\u002Fguides\u002Fdocument-tree","3.guides\u002F15.document-tree","i-lucide-folder-tree",{"title":122,"path":123,"stem":124,"icon":125},"Markdown and code","\u002Fguides\u002Fmarkdown-and-code","3.guides\u002F16.markdown-and-code","i-lucide-file-code",{"title":127,"path":128,"stem":129,"icon":83},"Advanced editor in React","\u002Fguides\u002Fadvanced-react","3.guides\u002F17.advanced-react",{"title":131,"path":132,"stem":133,"icon":6},"Kitchen sink: learning path","\u002Fguides\u002Fadvanced-nuxt-example","3.guides\u002F18.advanced-nuxt-example",{"title":135,"path":136,"stem":137,"icon":138},"Production","\u002Fguides\u002Fproduction","3.guides\u002F19.production","i-lucide-server",{"title":140,"path":141,"stem":142,"icon":143},"Register the Nuxt module","\u002Fguides\u002Fnuxt-module","3.guides\u002F2.nuxt-module","i-lucide-package",{"title":145,"path":146,"stem":147,"icon":148},"Storage adapters, comment stores, Nitro host","\u002Fguides\u002Fstorage-adapters","3.guides\u002F20.storage-adapters","i-lucide-database",{"title":150,"path":151,"stem":152,"icon":153},"Migrating from #tiptap-editor","\u002Fguides\u002Fmigration","3.guides\u002F21.migration","i-lucide-arrow-right-left",{"title":155,"path":156,"stem":157,"icon":158},"Content tree mechanics","\u002Fguides\u002Fcontent-tree","3.guides\u002F22.content-tree","i-lucide-git-fork",{"title":160,"path":161,"stem":162,"icon":163},"Block capabilities, abilities and actions","\u002Fguides\u002Fblock-capabilities","3.guides\u002F23.block-capabilities","i-lucide-shield-check",{"title":165,"path":166,"stem":167,"icon":168},"Block types in code and JSON","\u002Fguides\u002Fblock-types","3.guides\u002F24.block-types","i-lucide-shapes",{"title":170,"path":171,"stem":172,"icon":173},"Derive methods and the Derivation index","\u002Fguides\u002Fderive-methods","3.guides\u002F25.derive-methods","i-lucide-git-branch-plus",{"title":175,"path":176,"stem":177,"icon":178},"Block properties and Property definitions","\u002Fguides\u002Fblock-properties","3.guides\u002F26.block-properties","i-lucide-sliders-horizontal",{"title":180,"path":181,"stem":182,"icon":183},"Image studio","\u002Fguides\u002Fimage-studio","3.guides\u002F27.image-studio","i-lucide-image-plus",{"title":185,"path":186,"stem":187,"icon":188},"Prüfung and Freigabestatus","\u002Fguides\u002Fpruefung","3.guides\u002F28.pruefung","i-lucide-stamp",{"title":190,"path":191,"stem":192,"icon":193},"Workspace: find, read, reference and embed Blocks across Documents","\u002Fguides\u002Fworkspace","3.guides\u002F29.workspace","i-lucide-network",{"title":195,"path":196,"stem":197,"icon":198},"EditorPorts: auth and storage","\u002Fguides\u002Feditor-ports","3.guides\u002F3.editor-ports","i-lucide-plug",{"title":200,"path":201,"stem":202,"icon":203},"Collections: an ordered list of Blocks","\u002Fguides\u002Fcollections","3.guides\u002F30.collections","i-lucide-list-checks",{"title":205,"path":206,"stem":207,"icon":208},"Data views: a query shown as table, list or board","\u002Fguides\u002Fdata-views","3.guides\u002F31.data-views","i-lucide-table-properties",{"title":210,"path":211,"stem":212,"icon":193},"Explore: backlinks, related Blocks and the trail","\u002Fguides\u002Fexplore","3.guides\u002F32.explore",{"title":214,"path":215,"stem":216,"icon":217},"Render: export a Block or a Document to a file","\u002Fguides\u002Frender","3.guides\u002F33.render","i-lucide-file-output",{"title":219,"path":220,"stem":221,"icon":222},"Importers: what a dropped file becomes","\u002Fguides\u002Fimporters","3.guides\u002F34.importers","i-lucide-file-input",{"title":224,"path":225,"stem":226,"icon":227},"Templates: start a Document from a Dokumentvorlage","\u002Fguides\u002Ftemplates","3.guides\u002F35.templates","i-lucide-file-stack",{"title":229,"path":230,"stem":231,"icon":232},"Citations","\u002Fguides\u002Fcitations","3.guides\u002F36.citations","i-lucide-quote",{"title":234,"path":235,"stem":236,"icon":163},"AI proposals and the export check","\u002Fguides\u002Fexport-check","3.guides\u002F37.export-check",{"title":238,"path":239,"stem":240,"icon":241},"Query: filter, sort, group and total Blocks","\u002Fguides\u002Fquery","3.guides\u002F38.query","i-lucide-list-filter",{"title":243,"path":244,"stem":245,"icon":246},"Edges: backlinks and authored connections","\u002Fguides\u002Fedges","3.guides\u002F39.edges","i-lucide-link-2",{"title":248,"path":249,"stem":250,"icon":163},"Server routes and authentication","\u002Fguides\u002Froutes-and-auth","3.guides\u002F4.routes-and-auth",{"title":252,"path":253,"stem":254,"icon":255},"Speech: text to audio, and subtitle files","\u002Fguides\u002Fspeech","3.guides\u002F41.speech","i-lucide-audio-lines",{"title":257,"path":258,"stem":259,"icon":260},"Graph: the Edges around a Block","\u002Fguides\u002Fgraph","3.guides\u002F42.graph","i-lucide-waypoints",{"title":262,"path":263,"stem":264,"icon":265},"PDF viewer: passages, anchors and quotes","\u002Fguides\u002Fpdf-viewer","3.guides\u002F43.pdf-viewer","i-lucide-file-search",{"title":267,"path":268,"stem":269,"icon":270},"Obsidian: import and export a vault","\u002Fguides\u002Fobsidian","3.guides\u002F44.obsidian","i-lucide-folder-input",{"title":272,"path":273,"stem":274,"icon":275},"Typst: render a Document as a PDF with citations","\u002Fguides\u002Ftypst","3.guides\u002F45.typst","i-lucide-file-text",{"title":277,"path":278,"stem":279,"icon":280},"Audio: read aloud, mixdown and ElevenLabs","\u002Fguides\u002Faudio","3.guides\u002F46.audio","i-lucide-audio-waveform",{"title":282,"path":283,"stem":284,"icon":285},"Slides: decks, slide layouts and exports","\u002Fguides\u002Fslides","3.guides\u002F47.slides","i-lucide-presentation",{"title":287,"path":288,"stem":289,"icon":290},"Knowledge graph: sources and concepts beside the Documents","\u002Fguides\u002Fknowledge-graph","3.guides\u002F49.knowledge-graph","i-lucide-share-2",{"title":292,"path":293,"stem":294,"icon":78},"AI configuration and secrets","\u002Fguides\u002Fai-configuration","3.guides\u002F5.ai-configuration",{"title":296,"path":297,"stem":298,"children":299,"icon":301},"Index on Postgres: typos, meaning and SQL pushdown","\u002Fguides\u002Findex-pg","3.guides\u002F50.index-pg",[300],{"title":296,"path":297,"stem":298,"icon":301},"i-lucide-database-zap",{"title":303,"path":304,"stem":305,"icon":306},"Charts: bar, line, pie and scatter over a query","\u002Fguides\u002Fcharts","3.guides\u002F51.charts","i-lucide-chart-column",{"title":308,"path":309,"stem":310,"icon":73},"Collaboration on the client","\u002Fguides\u002Fcollaboration","3.guides\u002F6.collaboration",{"title":312,"path":313,"stem":314,"icon":315},"Block UI (drag handle, hover actions)","\u002Fguides\u002Fblock-ui","3.guides\u002F7.block-ui","i-lucide-grip-vertical",{"title":317,"path":318,"stem":319,"icon":320},"Settings and app lifetime","\u002Fguides\u002Fsettings","3.guides\u002F8.settings","i-lucide-settings",{"title":322,"path":323,"stem":324,"icon":325},"Language","\u002Fguides\u002Flanguage","3.guides\u002F9.language","i-lucide-languages",{"title":327,"icon":14,"path":328,"stem":329,"children":330,"page":37},"Reference","\u002Freference","4.reference",[331,335,339,344,348,352,356,360,364,368,373,378,382],{"title":332,"path":333,"stem":334,"icon":143},"Public exports","\u002Freference\u002Fexports","4.reference\u002F1.exports",{"title":336,"path":337,"stem":338,"icon":173},"Derive method contract","\u002Freference\u002Fderive-methods","4.reference\u002F10.derive-methods",{"title":340,"path":341,"stem":342,"icon":343},"Changelog","\u002Freference\u002Fchangelog","4.reference\u002F11.changelog","i-lucide-history",{"title":345,"path":346,"stem":347,"icon":217},"Renderer port","\u002Freference\u002Frenderer-port","4.reference\u002F12.renderer-port",{"title":349,"path":350,"stem":351,"icon":255},"Speech port","\u002Freference\u002Fspeech-port","4.reference\u002F13.speech-port",{"title":353,"path":354,"stem":355,"icon":58},"Presets and features","\u002Freference\u002Fpresets-and-features","4.reference\u002F2.presets-and-features",{"title":357,"path":358,"stem":359,"icon":68},"Feature contract","\u002Freference\u002Ffeature-contract","4.reference\u002F3.feature-contract",{"title":361,"path":362,"stem":363,"icon":107},"CSS and UI-provider requirements","\u002Freference\u002Fcss-and-ui","4.reference\u002F4.css-and-ui",{"title":365,"path":366,"stem":367,"icon":148},"Vue-free advanced schema","\u002Freference\u002Fadvanced-schema","4.reference\u002F5.advanced-schema",{"title":369,"path":370,"stem":371,"icon":372},"Dependency rules","\u002Freference\u002Fdependency-rules","4.reference\u002F6.dependency-rules","i-lucide-git-branch",{"title":374,"path":375,"stem":376,"icon":377},"Commands","\u002Freference\u002Fcommands","4.reference\u002F7.commands","i-lucide-terminal",{"title":379,"path":380,"stem":381,"icon":163},"Block capability contract","\u002Freference\u002Fblock-capabilities","4.reference\u002F8.block-capabilities",{"title":383,"path":384,"stem":385,"icon":168},"Block type contract","\u002Freference\u002Fblock-types","4.reference\u002F9.block-types",{"title":387,"path":388,"stem":389,"children":390,"icon":393},"AI agents","\u002Fai-agents","5.ai-agents\u002F1.index",[391,394,398,402,407],{"title":392,"path":388,"stem":389,"icon":393},"Overview","i-lucide-bot",{"title":395,"path":396,"stem":397,"icon":198},"Integrate the editor","\u002Fai-agents\u002Fintegrate","5.ai-agents\u002F2.integrate",{"title":399,"path":400,"stem":401,"icon":68},"Add a block or feature","\u002Fai-agents\u002Fextend","5.ai-agents\u002F3.extend",{"title":403,"path":404,"stem":405,"icon":406},"Pitfalls and invariants","\u002Fai-agents\u002Finvariants","5.ai-agents\u002F4.invariants","i-lucide-shield-alert",{"title":408,"path":409,"stem":410,"icon":411},"Verify","\u002Fai-agents\u002Fverify","5.ai-agents\u002F5.verify","i-lucide-check-check",{"id":413,"title":252,"body":414,"description":1281,"extension":1282,"links":1283,"meta":1284,"navigation":1285,"path":253,"seo":1286,"stem":254,"__hash__":1287},"docs\u002F3.guides\u002F41.speech.md",{"type":415,"value":416,"toc":1274},"minimark",[417,430,538,549,554,788,838,852,856,876,960,1004,1008,1026,1032,1115,1119,1158,1162,1190,1193,1256,1270],[418,419,420,421,425,426,429],"p",{},"The ",[422,423,424],"code",{},"speech"," capability turns text into audio. The host passes one ",[422,427,428],{},"SpeechProvider","; the editor adds the routes, the daily budget and the client call. The audio is a file like any render result: it is stored as an asset of the Document.",[431,432,433,449],"table",{},[434,435,436],"thead",{},[437,438,439,443,446],"tr",{},[440,441,442],"th",{},"Piece",[440,444,445],{},"What it does",[440,447,448],{},"Port (capability)",[450,451,452,466,480,498,517],"tbody",{},[437,453,454,459,462],{},[455,456,457],"td",{},[422,458,428],{},[455,460,461],{},"lists engines with their voices, synthesizes text, optionally transcribes audio",[455,463,464],{},[422,465,424],{},[437,467,468,473,476],{},[455,469,470],{},[422,471,472],{},"GET \u002Fapi\u002Feditor\u002Fspeech\u002Fengines",[455,474,475],{},"the engines and voices of the user",[455,477,478],{},[422,479,424],{},[437,481,482,487,490],{},[455,483,484],{},[422,485,486],{},"POST \u002Fapi\u002Feditor\u002Fspeech",[455,488,489],{},"one synthesis, streamed as server-sent events; the audio is stored as an asset of the Document",[455,491,492,494,495],{},[422,493,424],{}," and ",[422,496,497],{},"assets",[437,499,500,505,513],{},[455,501,502],{},[422,503,504],{},"EditorBackend['speech']",[455,506,507,494,510],{},[422,508,509],{},"engines()",[422,511,512],{},"synthesize(request, { onProgress })",[455,514,515],{},[422,516,424],{},[437,518,519,525,535],{},[455,520,521,522],{},"Block action ",[422,523,524],{},"transcript-export",[455,526,527,528,531,532],{},"downloads the timed segments of a transcript as ",[422,529,530],{},".vtt"," or ",[422,533,534],{},".srt",[455,536,537],{},"none",[418,539,540,541,544,545,548],{},"A synthesis lives as long as its request. There is no job store: the server streams progress, stores the file and ends the stream. A client that disconnects aborts the engine through the ",[422,542,543],{},"AbortSignal"," that ",[422,546,547],{},"SpeechProvider.synthesize"," receives.",[550,551,553],"h2",{"id":552},"provide-a-speech-port","Provide a speech port",[555,556,562],"pre",{"className":557,"code":558,"filename":559,"language":560,"meta":561,"style":561},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { createMemorySpeech, createOpenAiSpeech } from '@skaile\u002Fforge-editor\u002Fadapters\u002Fspeech'\nimport type { EditorPorts } from '@skaile\u002Fforge-editor\u002Fserver'\n\n\u002F\u002F The speech port is one provider. `createOpenAiSpeech` calls an OpenAI-compatible `\u002Faudio\u002Fspeech` endpoint from the\n\u002F\u002F server, so the key never reaches a page; `createMemorySpeech` answers a second of silence, for tests and demos.\nexport function speechPort(apiKey: string | undefined): NonNullable\u003CEditorPorts['speech']> {\n  return apiKey\n    ? createOpenAiSpeech({ endpoint: 'https:\u002F\u002Fapi.openai.com\u002Fv1', model: 'gpt-4o-mini-tts', apiKey })\n    : createMemorySpeech()\n}\n","docs\u002Fsnippets\u002Fspeech-provider.ts","ts","",[422,563,564,603,627,634,641,647,712,721,771,782],{"__ignoreMap":561},[565,566,569,573,577,581,584,587,590,593,596,600],"span",{"class":567,"line":568},"line",1,[565,570,572],{"class":571},"s7zQu","import",[565,574,576],{"class":575},"sMK4o"," {",[565,578,580],{"class":579},"sTEyZ"," createMemorySpeech",[565,582,583],{"class":575},",",[565,585,586],{"class":579}," createOpenAiSpeech",[565,588,589],{"class":575}," }",[565,591,592],{"class":571}," from",[565,594,595],{"class":575}," '",[565,597,599],{"class":598},"sfazB","@skaile\u002Fforge-editor\u002Fadapters\u002Fspeech",[565,601,602],{"class":575},"'\n",[565,604,606,608,611,613,616,618,620,622,625],{"class":567,"line":605},2,[565,607,572],{"class":571},[565,609,610],{"class":571}," type",[565,612,576],{"class":575},[565,614,615],{"class":579}," EditorPorts",[565,617,589],{"class":575},[565,619,592],{"class":571},[565,621,595],{"class":575},[565,623,624],{"class":598},"@skaile\u002Fforge-editor\u002Fserver",[565,626,602],{"class":575},[565,628,630],{"class":567,"line":629},3,[565,631,633],{"emptyLinePlaceholder":632},true,"\n",[565,635,637],{"class":567,"line":636},4,[565,638,640],{"class":639},"sHwdD","\u002F\u002F The speech port is one provider. `createOpenAiSpeech` calls an OpenAI-compatible `\u002Faudio\u002Fspeech` endpoint from the\n",[565,642,644],{"class":567,"line":643},5,[565,645,646],{"class":639},"\u002F\u002F server, so the key never reaches a page; `createMemorySpeech` answers a second of silence, for tests and demos.\n",[565,648,650,653,657,661,664,668,671,675,678,681,684,687,690,693,696,699,701,703,706,709],{"class":567,"line":649},6,[565,651,652],{"class":571},"export",[565,654,656],{"class":655},"spNyl"," function",[565,658,660],{"class":659},"s2Zo4"," speechPort",[565,662,663],{"class":575},"(",[565,665,667],{"class":666},"sHdIc","apiKey",[565,669,670],{"class":575},":",[565,672,674],{"class":673},"sBMFI"," string",[565,676,677],{"class":575}," |",[565,679,680],{"class":673}," undefined",[565,682,683],{"class":575},"):",[565,685,686],{"class":673}," NonNullable",[565,688,689],{"class":575},"\u003C",[565,691,692],{"class":673},"EditorPorts",[565,694,695],{"class":579},"[",[565,697,698],{"class":575},"'",[565,700,424],{"class":598},[565,702,698],{"class":575},[565,704,705],{"class":579},"]",[565,707,708],{"class":575},">",[565,710,711],{"class":575}," {\n",[565,713,715,718],{"class":567,"line":714},7,[565,716,717],{"class":571},"  return",[565,719,720],{"class":579}," apiKey\n",[565,722,724,727,729,732,735,738,740,742,745,747,749,752,754,756,759,761,763,766,768],{"class":567,"line":723},8,[565,725,726],{"class":575},"    ?",[565,728,586],{"class":659},[565,730,663],{"class":731},"swJcz",[565,733,734],{"class":575},"{",[565,736,737],{"class":731}," endpoint",[565,739,670],{"class":575},[565,741,595],{"class":575},[565,743,744],{"class":598},"https:\u002F\u002Fapi.openai.com\u002Fv1",[565,746,698],{"class":575},[565,748,583],{"class":575},[565,750,751],{"class":731}," model",[565,753,670],{"class":575},[565,755,595],{"class":575},[565,757,758],{"class":598},"gpt-4o-mini-tts",[565,760,698],{"class":575},[565,762,583],{"class":575},[565,764,765],{"class":579}," apiKey",[565,767,589],{"class":575},[565,769,770],{"class":731},")\n",[565,772,774,777,779],{"class":567,"line":773},9,[565,775,776],{"class":575},"    :",[565,778,580],{"class":659},[565,780,781],{"class":731},"()\n",[565,783,785],{"class":567,"line":784},10,[565,786,787],{"class":575},"}\n",[418,789,790,793,794,797,798,801,802,805,806,809,810,813,814,817,818,821,822,825,826,829,830,833,834,837],{},[422,791,792],{},"createOpenAiSpeech({ endpoint, model, apiKey?, id?, label?, voices?, fetch? })"," offers one engine (id ",[422,795,796],{},"openai",") and posts ",[422,799,800],{},"{ model, input, voice, response_format }"," to ",[422,803,804],{},"\u003Cendpoint>\u002Faudio\u002Fspeech",". Its voices default to ",[422,807,808],{},"OPENAI_VOICES","; pass ",[422,811,812],{},"voices"," for a compatible server with other voices. ",[422,815,816],{},"createMemorySpeech({ engines? })"," is one engine ",[422,819,820],{},"memory"," with the voice ",[422,823,824],{},"silent",": every synthesis answers one second of silence as WAV (",[422,827,828],{},"silentWav(seconds?)"," builds it), and ",[422,831,832],{},"mp3"," is refused with ",[422,835,836],{},"422",".",[418,839,840,841,843,844,847,848,851],{},"A host with several engines, for example a second vendor, implements ",[422,842,428],{}," itself and lists each engine in ",[422,845,846],{},"engines",". A provider that can also transcribe adds ",[422,849,850],{},"transcribe","; the routes do not use it yet.",[550,853,855],{"id":854},"the-route","The route",[418,857,858,860,861,864,865,868,869,531,872,875],{},[422,859,486],{}," takes ",[422,862,863],{},"{ documentId, engine, voice, text, format }"," with ",[422,866,867],{},"format"," ",[422,870,871],{},"'mp3'",[422,873,874],{},"'wav'",". The server checks the request before the stream starts:",[431,877,878,888],{},[434,879,880],{},[437,881,882,885],{},[440,883,884],{},"Status",[440,886,887],{},"Cause",[450,889,890,900,910,920,930,944],{},[437,891,892,897],{},[455,893,894],{},[422,895,896],{},"400",[455,898,899],{},"malformed body, empty text, text longer than the daily budget, or a Document ID that is not valid",[437,901,902,907],{},[455,903,904],{},[422,905,906],{},"401",[455,908,909],{},"no user",[437,911,912,917],{},[455,913,914],{},[422,915,916],{},"403",[455,918,919],{},"the user may not read the Document",[437,921,922,927],{},[455,923,924],{},[422,925,926],{},"404",[455,928,929],{},"unknown engine, or a voice the engine does not have",[437,931,932,937],{},[455,933,934],{},[422,935,936],{},"429",[455,938,939,940,943],{},"the text does not fit the characters the user has left today; ",[422,941,942],{},"Retry-After"," is the seconds until UTC midnight",[437,945,946,951],{},[455,947,948],{},[422,949,950],{},"501",[455,952,953,954,956,957,959],{},"the host has no ",[422,955,424],{}," or no ",[422,958,497],{}," port",[418,961,962,963,966,967,970,971,974,975,970,978,981,982,970,985,988,989,992,993,996,997,1000,1001,1003],{},"After that the answer is a ",[422,964,965],{},"text\u002Fevent-stream"," with the events ",[422,968,969],{},"progress"," (",[422,972,973],{},"{ phase, percent? }","), then ",[422,976,977],{},"done",[422,979,980],{},"{ asset, fileName, mime }",") or ",[422,983,984],{},"error",[422,986,987],{},"{ message }","). The audio is stored through ",[422,990,991],{},"ports.assets.storeAsset"," as an asset of ",[422,994,995],{},"documentId","; ",[422,998,999],{},"asset"," is its name, read with the asset route like any upload. An engine that fails ends the stream with ",[422,1002,984],{},", stores nothing and gives the characters back.",[550,1005,1007],{"id":1006},"daily-budget","Daily budget",[418,1009,1010,1011,1014,1015,1018,1019,1022,1023,1025],{},"Each user may synthesize ",[422,1012,1013],{},"SPEECH_CHARS_PER_DAY"," (50 000) characters per UTC day. The count is kept in memory per server process and starts over at UTC midnight, so a restart or a second process starts a fresh count. The key is the user's ",[422,1016,1017],{},"id"," (see ",[422,1020,1021],{},"userKey",", the same key the AI rate limit uses). A call that would pass the limit is refused with ",[422,1024,936],{}," and counts nothing.",[418,1027,1028,1029,670],{},"A test or a host with another limit builds its own budget and hands it to the handlers through ",[422,1030,1031],{},"HandlerContext.speechBudget",[555,1033,1035],{"className":557,"code":1034,"language":560,"meta":561,"style":561},"import { createSpeechBudget } from '@skaile\u002Fforge-editor\u002Fserver'\n\nconst speechBudget = createSpeechBudget({ limit: 1000, now: () => Date.now() })\n",[422,1036,1037,1056,1060],{"__ignoreMap":561},[565,1038,1039,1041,1043,1046,1048,1050,1052,1054],{"class":567,"line":568},[565,1040,572],{"class":571},[565,1042,576],{"class":575},[565,1044,1045],{"class":579}," createSpeechBudget",[565,1047,589],{"class":575},[565,1049,592],{"class":571},[565,1051,595],{"class":575},[565,1053,624],{"class":598},[565,1055,602],{"class":575},[565,1057,1058],{"class":567,"line":605},[565,1059,633],{"emptyLinePlaceholder":632},[565,1061,1062,1065,1068,1071,1073,1075,1077,1080,1082,1086,1088,1091,1093,1096,1099,1102,1104,1107,1110,1113],{"class":567,"line":629},[565,1063,1064],{"class":655},"const",[565,1066,1067],{"class":579}," speechBudget ",[565,1069,1070],{"class":575},"=",[565,1072,1045],{"class":659},[565,1074,663],{"class":579},[565,1076,734],{"class":575},[565,1078,1079],{"class":731}," limit",[565,1081,670],{"class":575},[565,1083,1085],{"class":1084},"sbssI"," 1000",[565,1087,583],{"class":575},[565,1089,1090],{"class":659}," now",[565,1092,670],{"class":575},[565,1094,1095],{"class":575}," ()",[565,1097,1098],{"class":655}," =>",[565,1100,1101],{"class":579}," Date",[565,1103,837],{"class":575},[565,1105,1106],{"class":659},"now",[565,1108,1109],{"class":579},"() ",[565,1111,1112],{"class":575},"}",[565,1114,770],{"class":579},[550,1116,1118],{"id":1117},"the-client","The client",[418,1120,1121,1124,1125,1127,1128,1131,1132,1135,1136,1139,1140,1143,1144,1146,1147,970,1150,1153,1154,1157],{},[422,1122,1123],{},"backend.speech"," is present when the server serves the ",[422,1126,424],{}," capability. ",[422,1129,1130],{},"backend.speech.engines()"," lists the engines. ",[422,1133,1134],{},"backend.speech.synthesize(request, { onProgress, signal })"," resolves with ",[422,1137,1138],{},"{ asset, fileName, mime, url }"," and rejects with a ",[422,1141,1142],{},"BackendError"," that carries the server's message and status (",[422,1145,936],{}," over the budget). The loop over the stream is ",[422,1148,1149],{},"postSse",[422,1151,1152],{},"core\u002Fsse.ts","), the same helper ",[422,1155,1156],{},"render"," uses.",[550,1159,1161],{"id":1160},"subtitle-files-from-a-transcript","Subtitle files from a transcript",[418,1163,1164,1165,494,1168,1171,1172,1176,1177,970,1181,1183,1184,1186,1187,1189],{},"A transcript is a run of speaker segments with ",[422,1166,1167],{},"start",[422,1169,1170],{},"end"," seconds (see ",[1173,1174,1175],"a",{"href":95},"Media, mentions and transcripts","). The transcript feature adds the block action ",[1178,1179,1180],"strong",{},"„Untertitel exportieren“",[422,1182,524],{},") to a Typed block that holds timed segments. It is also offered in a read-only editor, because it does not change the Document. Its menu downloads the segments as WebVTT (",[422,1185,530],{},") or SubRip (",[422,1188,534],{},"), named after the block's title.",[418,1191,1192],{},"The functions are pure and DOM-free, so a server can write the same files:",[431,1194,1195,1205],{},[434,1196,1197],{},[437,1198,1199,1202],{},[440,1200,1201],{},"Function",[440,1203,1204],{},"Result",[450,1206,1207,1228,1246],{},[437,1208,1209,1214],{},[455,1210,1211],{},[422,1212,1213],{},"vttOf(cues)",[455,1215,1216,1219,1220,1223,1224,1227],{},[422,1217,1218],{},"WEBVTT",", then one ",[422,1221,1222],{},"hh:mm:ss.mmm --> hh:mm:ss.mmm"," block per cue; the speaker is a ",[422,1225,1226],{},"\u003Cv Name>"," voice tag",[437,1229,1230,1235],{},[455,1231,1232],{},[422,1233,1234],{},"srtOf(cues)",[455,1236,1237,1238,1241,1242,1245],{},"numbered ",[422,1239,1240],{},"hh:mm:ss,mmm --> hh:mm:ss,mmm"," blocks; the speaker is a ",[422,1243,1244],{},"Name: "," prefix",[437,1247,1248,1253],{},[455,1249,1250],{},[422,1251,1252],{},"transcriptCuesOf(node)",[455,1254,1255],{},"the cues of the timed segments in a node, in order",[418,1257,1258,1259,1262,1263,1266,1267,837],{},"A cue is ",[422,1260,1261],{},"{ start, end, text, speaker? }",". A cue without text is left out, an end before the start is raised to the start, and a segment without a start time has no cue. A segment without an end time ends where the next cue starts, the last one two seconds after its start. The functions come from ",[422,1264,1265],{},"@skaile\u002Fforge-editor\u002Ftranscript",". For the port types see the ",[1173,1268,1269],{"href":350},"Speech port reference",[1271,1272,1273],"style",{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":561,"searchDepth":605,"depth":605,"links":1275},[1276,1277,1278,1279,1280],{"id":552,"depth":605,"text":553},{"id":854,"depth":605,"text":855},{"id":1006,"depth":605,"text":1007},{"id":1117,"depth":605,"text":1118},{"id":1160,"depth":605,"text":1161},"The SpeechProvider port with its engines and voices, the SSE route that streams progress and stores the audio as an asset, the daily character budget, the client call, and the block action that exports a transcript as WebVTT or SubRip.","md",null,{},{"icon":255},{"title":252,"description":1281},"gy1TjgNWBi6vLzfboiRxGzmYcyZqANDjyJ-wkMRAhWc",[1289,1291],{"title":248,"path":249,"stem":250,"description":1290,"icon":163,"children":-1},"How every editor route authenticates through the identity port, the full route table with status codes, and websocket authentication.",{"title":257,"path":258,"stem":259,"description":1292,"icon":260,"children":-1},"The graph route that walks the Edges of the Block index in both directions, the walk's limits, the client call, and the @skaile\u002Fforge-editor-graph add-on that draws the answer with sigma in Vue and React.",1793213906730]